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