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