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