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