В разработке на платформе «1С:Предприятие 8.3» часто возникает задача динамического обновления данных в строке табличной части. Классический пример: пользователь выбирает товар, материал или услугу, и программа должна мгновенно подставить актуальную цену, рассчитать сумму или заполнить другие связанные реквизиты.
Начинающие разработчики часто сталкиваются с трудностями при реализации этого механизма в управляемом интерфейсе. Проблемы обычно связаны с путаницей между идентификаторами строк и самими данными, а также с непониманием тонкостей клиент-серверного взаимодействия. В этой статье мы подробно разберем, как правильно реализовать автоматическое изменение ячеек, проанализируем частые ошибки и рассмотрим архитектурно верный подход к решению этой задачи.
Представим сценарий: в нашей конфигурации есть документ покупки драгоценных металлов. В его табличной части СписокЗалога присутствует колонка Металл (тип значения — ссылка на перечисление ПеречислениеСсылка.Металлы, содержащее значения Золото и Серебро) и колонка ЦенаЗаГрамм (числовой тип).
Наша цель — сделать так, чтобы при выборе металла в строке автоматически устанавливалась базовая цена: 1500 рублей для золота и 1000 рублей для серебра.
Типичная попытка реализации обработчика события изменения выглядит следующим образом:
&НаКлиенте
Процедура СписокЗалогаМеталлПриИзменении(Элемент)
СтрокаТабЧасти = Элементы.СписокЗалога.ТекущиеДанные;
Если СтрокаТабЧасти.Металл = Перечисления.Металлы.Золото Тогда
СтрокаТабЧасти.ЦенаЗаГрамм = 1500;
Иначе
СтрокаТабЧасти.ЦенаЗаГрамм = 1000;
КонецЕсли;
КонецПроцедуры
При запуске такого кода разработчик получает ошибку компиляции на клиенте: «Переменная не определена (Перечисления)». Давайте разберем по шагам, почему этот код не работает и как устроена работа с данными в управляемых формах.
Прежде чем исправлять синтаксические ошибки работы с перечислением, важно четко понимать, как обращаться к строке таблицы на форме. У элемента формы типа «Таблица» (в нашем случае Элементы.СписокЗалога) есть два похожих по названию, но принципиально разных свойства:
ТекущиеДанные — возвращает объект типа ДанныеФормыЭлементКоллекции. Это фактический контейнер, содержащий данные текущей строки, отображаемой на клиенте. Через него мы можем свободно читать и записывать значения колонок этой строки: например, СтрокаТабЧасти.ЦенаЗаГрамм = 1500. Данное свойство доступно только на клиенте.ТекущаяСтрока — возвращает идентификатор строки (как правило, числовой индекс или уникальный внутренний ключ строки в коллекции). Это не сам объект с данными, а лишь указатель на него! Если вы попытаетесь написать Элементы.СписокЗалога.ТекущаяСтрока.Металл, то платформа выдаст критическую ошибку: «Значение не является значением объектного типа», так как у обычного числа или идентификатора не может быть реквизита «Металл».Для нашей задачи обновления значений в интерактивном режиме на клиенте идеально подходит свойство ТекущиеДанные. Запомните: всегда используйте именно его, когда вам нужно быстро прочитать или изменить соседние ячейки в той строке, которую пользователь редактирует в данный момент.
Вернемся к ошибке «Переменная не определена (Перечисления)». Почему она возникает?
В управляемом приложении 1С («Тонкий клиент») в целях безопасности и экономии сетевого трафика разработчику недоступны глобальные менеджеры объектов. Мы не можем напрямую обратиться к базе данных или метаданным через конструкции Перечисления..., Справочники... или Документы... непосредственно в клиентских процедурах с директивой &НаКлиенте.
Для получения ссылок на предопределенные элементы, включая значения перечислений, на стороне клиента существует специальная встроенная функция платформы — ПредопределенноеЗначение().
При работе с этой функцией важно соблюдать строгий синтаксис. Имя типа метаданных в строковом параметре должно указываться в единственном числе:
"Перечисление.Металлы.Золото""Перечисления.Металлы.Золото"Платформа оптимизирует работу этой функции. При первом вызове она обращается к серверу за получением ссылки, а затем кэширует ее на клиенте, поэтому повторные обращения происходят мгновенно.
Давайте перепишем наш обработчик события с учетом полученных знаний:
&НаКлиенте
Процедура СписокЗалогаМеталлПриИзменении(Элемент)
// Получаем текущую строку таблицы
СтрокаТабЧасти = Элементы.СписокЗалога.ТекущиеДанные;
// Обязательно проверяем, что строка действительно выделена и существует
Если СтрокаТабЧасти <> Неопределено Тогда
// Сравниваем значение с предопределенным значением перечисления на клиенте
Если СтрокаТабЧасти.Металл = ПредопределенноеЗначение("Перечисление.Металлы.Золото") Тогда
СтрокаТабЧасти.ЦенаЗаГрамм = 1500;
ИначеЕсли СтрокаТабЧасти.Металл = ПредопределенноеЗначение("Перечисление.Металлы.Серебро") Тогда
СтрокаТабЧасти.ЦенаЗаГрамм = 1000;
Иначе
СтрокаТабЧасти.ЦенаЗаГрамм = 0;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Этот код написан абсолютно верно с точки зрения синтаксиса платформы, он будет быстро и без ошибок выполняться на клиенте, обеспечивая мгновенное обновление интерфейса формы при изменении металла.
Прописывание конкретных цен (например, 1500 и 1000 рублей) прямо в исходном коде программы — это грубейшая архитектурная ошибка в реальной коммерческой разработке. Цены на драгоценные металлы, товары или услуги меняются регулярно. Если зашить их в код, то при каждом изменении цен пользователям придется обращаться к программисту, обновлять конфигурацию базы данных и прерывать работу пользователей.
Правильный подход предполагает хранение цен в специальном объекте базы данных — Регистре сведений (например, периодический регистр «ЦеныНоменклатуры» или «ЦеныМеталлов»). При изменении металла в строке код должен обращаться к серверу, считывать актуальную цену из регистра на дату документа и возвращать её обратно на клиент.
Проанализируем, как реализовать такую схему правильно, используя контекстно-независимый серверный вызов &НаСервереБезКонтекста, чтобы минимизировать нагрузку на сеть.
&НаКлиенте
Процедура СписокЗалогаМеталлПриИзменении(Элемент)
СтрокаТабЧасти = Элементы.СписокЗалога.ТекущиеДанные;
Если СтрокаТабЧасти <> Неопределено Тогда
// Если металл не выбран, сразу обнуляем цену и завершаем работу
Если Не ЗначениеЗаполнено(СтрокаТабЧасти.Металл) Тогда
СтрокаТабЧасти.ЦенаЗаГрамм = 0;
Возврат;
КонецЕсли;
// Получаем актуальную цену с сервера, передавая металл и дату документа
АктуальнаяЦена = ПолучитьЦенуМеталлаНаСервере(СтрокаТабЧасти.Металл, Объект.Дата);
// Заполняем цену в строке табличной части
СтрокаТабЧасти.ЦенаЗаГрамм = АктуальнаяЦена;
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьЦенуМеталлаНаСервере(Знач ВыбранныйМеталл, Знач ДатаДокумента)
// Задаем базовое значение на случай отсутствия цены в базе данных
ВозвращаемаяЦена = 0;
// В реальной системе здесь выполняется запрос к регистру сведений.
// Пример запроса к периодическому регистру сведений "ЦеныМеталлов" (СрезПоследних):
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЦеныМеталловСрезПоследних.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныМеталлов.СрезПоследних(&Период, Металл = &Металл) КАК ЦеныМеталловСрезПоследних";
Запрос.УстановитьПараметр("Период", ?(ЗначениеЗаполнено(ДатаДокумента), ДатаДокумента, ТекущаяДата()));
Запрос.УстановитьПараметр("Металл", ВыбранныйМеталл);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
ВозвращаемаяЦена = Выборка.Цена;
КонецЕсли;
Возврат ВозвращаемаяЦена;
КонецФункции
Реализованная нами схема с вызовом функции &НаСервереБезКонтекста предоставляет ряд важнейших преимуществ:
БезКонтекста означает, что при вызове сервера платформа не передает туда тяжелую форму со всеми ее элементами и данными. Передаются только два простых параметра — ссылка на металл и дата, а возвращается одно число.Объект.Дата), система правильно определит цену, которая действовала именно на момент проведения документа, даже если вы решите перепровести старые документы за прошлый месяц.Используйте эти шаблоны проектирования в своих прикладных решениях, всегда разделяйте клиентскую логику отображения данных и серверную бизнес-логику работы с базой данных!