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