Как реализовать пошаговый обход списка с интерактивным вводом данных в управляемых формах 1С без модальности?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Переход платформы «1С:Предприятие 8» на управляемый интерфейс принес множество преимуществ, однако он также потребовал от разработчиков полного отказа от модальности. В старых «обычных» формах задача последовательного обхода списка с интерактивным запросом данных решалась тривиально: мы запускали цикл, и если обнаруживали незаполненный элемент, то просто открывали модальное окно. Код останавливал свое выполнение на строке открытия формы, пользователь вносил данные, закрывал окно, и цикл продолжался. В управляемом интерфейсе такой линейный подход больше не работает, поскольку модальные окна заблокированы по соображениям производительности, кроссплатформенности и веб-стандартов.

В этой статье мы подробно разберем, как решить задачу эмуляции модальности при обходе коллекций данных в управляемых формах 1С. Мы изучим три мощных подхода: современный стандарт платформы с использованием асинхронных вызовов, классический паттерн «Конечный автомат» на описаниях оповещений для старых версий конфигураций, а также разберем, как оптимизировать интерфейс с точки зрения современного UX.

Способ №1. Использование современных асинхронных конструкций платформы (версии 8.3.18 и выше)

Если ваша конфигурация работает на современной версии платформы «1С:Предприятие» (начиная с релиза 8.3.18), то вам доступен наиболее элегантный и лаконичный механизм — конструкции Асинх и Ждать. Этот подход позволяет сохранить привычную линейную структуру кода и классический цикл обхода, не блокируя при этом интерфейс приложения.

Рассмотрим по шагам, как работает этот механизм. Нам не требуется создавать сложные цепочки вызовов. Мы просто помечаем нашу процедуру ключевым словом Асинх, а перед вызовом немодального диалога пишем оператор Ждать. Программа приостановит выполнение цикла ровно в этой точке, дождется ответа пользователя и продолжит работу со следующей итерации.

Проанализируем пример кода, реализующий данную логику:


&НаКлиенте
Асинх Процедура ОбработатьСписокЭлементовАсинх()
	
	// Предположим, что СписокЭлементов — это ТаблицаФормы или СписокЗначений
	Для Каждого СтрокаДанных Из СписокЭлементов Цикл
		
		// Проверяем, заполнены ли ключевые данные в элементе
		Если Не ЗначениеЗаполнено(СтрокаДанных.НеобходимыеДанные) Тогда
			
			// Нам нужно запросить подтверждение и открыть форму ввода
			ПараметрыВопроса = Новый Структура("КлючТекущегоЭлемента", СтрокаДанных.Ссылка);
			
			// Ждем ответа пользователя на вопрос
			РезультатВыбора = Ждать ПоказатьВопросАсинх(
				"Для элемента """ + СтрокаДанных.Представление + """ не заполнены данные. Заполнить сейчас?", 
				РежимДиалогаВопрос.ДаНет
			);
			
			Если РезультатВыбора = КодВозвратаДиалога.Да Тогда
				
				// Открываем форму ввода данных и ждем ее закрытия
				ПараметрыФормы = Новый Структура;
				ПараметрыФормы.Вставить("Ключ", СтрокаДанных.Ссылка);
				
				// Платформа приостанавливает выполнение цикла до закрытия формы
				РезультатЗаполнения = Ждать ОткрытьФормуАсинх(
					"Справочник.Номенклатура.Форма.ФормаРедактирования", 
					ПараметрыФормы, 
					ЭтотОбъект
				);
				
				// После закрытия формы управление возвращается сюда, и мы обновляем данные в нашей строке
				Если РезультатЗаполнения <> Неопределено Тогда
					СтрокаДанных.НеобходимыеДанные = РезультатЗаполнения.ВведенноеЗначение;
					СтрокаДанных.СтатусОбработки = "Заполнено";
				КонецЕсли;
				
			Иначе
				// Пользователь отказался заполнять — помечаем элемент как пропущенный
				СтрокаДанных.СтатусОбработки = "Пропущено";
			КонецЕсли;
			
		КонецЕсли;
		
	КонецЦикла;
	
	ПоказатьПредупреждениеАсинх(Неопределено, "Обработка списка полностью завершена!");
	
КонецПроцедуры

Преимущества данного подхода: код остается линейным, легко читается и поддерживается. Нам не нужно дробить алгоритм на множество мелких процедур-обработчиков. При этом интерфейс системы не зависает, а ожидает действий пользователя в штатном режиме.

Способ №2. Паттерн «Конечный автомат» через ОписаниеОповещения (для старых версий платформ)

Если вы вынуждены поддерживать конфигурации, работающие на старых версиях платформы (ниже 8.3.18), или в режиме совместимости со старыми релизами, использовать конструкции Асинх и Ждать не получится. В этом случае мы должны применить асинхронную модель на базе объекта ОписаниеОповещения.

Главная проблема здесь заключается в том, что обычный цикл Для Каждого выполнить не удастся — он прокрутится мгновенно, не дожидаясь открытия форм. Нам необходимо сымитировать работу итератора цикла с помощью рекурсивных вызовов и передачи контекста выполнения. Чтобы не перегружать стек вызовов и сохранять состояние, мы будем передавать текущий индекс обхода в структуре параметров оповещения.

Давайте разберем по шагам, как написать такой код:

  1. Создаем стартовую процедуру, которая инициализирует структуру параметров обхода (наш контекст).
  2. Пишем процедуру выполнения одного шага обхода, которая анализирует текущий элемент по индексу.
  3. При необходимости ввода данных мы открываем немодальное окно, передавая в него ОписаниеОповещения, содержащее наш контекст, и завершаем текущую процедуру.
  4. В процедуре-обработчике оповещения считываем контекст, фиксируем изменения, увеличиваем индекс на единицу и повторно вызываем процедуру шага обхода.

Проанализируем программный код, реализующий эту концепцию:


&НаКлиенте
Процедура ЗапуститьПошаговыйОбход()
	
	// Шаг 1. Инициализируем контекст для обхода коллекции
	ПараметрыОбхода = Новый Структура;
	ПараметрыОбхода.Вставить("Список", СписокЭлементов); // Исходная коллекция данных
	ПараметрыОбхода.Вставить("ТекущийИндекс", 0);        // Начинаем с первого элемента
	
	// Запускаем первый шаг обработки
	ВыполнитьОчереднойШагОбхода(ПараметрыОбхода);
	
КонецПроцедуры

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

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

Важная деталь реализации: Обратите внимание, что рекурсивный вызов процедуры ВыполнитьОчереднойШагОбхода происходит из процедуры оповещения ПослеРедактированияСтроки. Поскольку предыдущий вызов этой процедуры уже полностью завершился и стек очистился, мы гарантированно избегаем ошибки переполнения стека (Stack Overflow) даже при обработке очень длинных списков.

Способ №3. Изменение UX-парадигмы: Пакетный ввод данных через форму «Мастера»

С точки зрения юзабилити и проектирования пользовательских интерфейсов (UX/UI), последовательное открытие множества всплывающих окон подряд — это плохая практика («UI-спам»). Если пользователю придется поочередно открывать и закрывать 20-30 диалоговых окон для дозаполнения номенклатуры, это вызовет у него быстрое утомление и повысит вероятность совершения ошибок.

Гораздо более правильным и современным подходом является изменение самой логики работы. Вместо пошагового допроса пользователя мы можем предварительно проанализировать весь список на сервере, собрать все проблемные строки и вывести их в единую форму пакетной обработки (форму «Мастера»).

Рассмотрим алгоритм реализации этого паттерна:

  1. При запуске обработки мы выполняем быстрый серверный анализ коллекции и отбираем только те элементы, в которых не заполнены требуемые данные.
  2. Если такие элементы найдены, мы открываем одну специальную вспомогательную форму.
  3. В этой форме пользователю показывается удобная таблица, содержащая исключительно незаполненные элементы. Прямо в этой таблице пользователь может быстро ввести все недостающие значения.
  4. По нажатию одной кнопки «Готово» система пачкой обрабатывает весь список и сохраняет изменения.

Проанализируем пример серверной подготовки данных для формы пакетного ввода:


&НаСервере
Функция ПолучитьТаблицуНезаполненныхЭлементов(МассивСсылок)
	
	// Формируем запрос для быстрого анализа данных на сервере
	Запрос = Новый Запрос;
	Запрос.Текст = 
		"ВЫБРАТЬ
		|	Номенклатура.Ссылка КАК Ссылка,
		|	Номенклатура.Представление КАК Представление,
		|	Номенклатура.СтавкаНДС КАК СтавкаНДС
		|ИЗ
		|	Справочник.Номенклатура КАК Номенклатура
		|ГДЕ
		|	Номенклатура.Ссылка В(&МассивСсылок)
		|	И Номенклатура.СтавкаНДС = ЗНАЧЕНИЕ(Перечисление.СтавкиНДС.ПустаяСсылка)";
		
	Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
	
	Возврат Запрос.Выполнить().Выгрузить();
	
КонецФункции

После получения этой таблицы на клиенте мы проверяем: если количество строк больше нуля, мы открываем специально созданную обработку или общую форму, передавая туда эту таблицу. Пользователь видит один лаконичный список, заполняет пустые колонки СтавкаНДС в табличной части и нажимает кнопку сохранения. Это экономит время пользователя и исключает необходимость эмуляции сложного интерактивного цикла.

Подводя итог, можно утверждать, что запрет модальности в 1С — это не ограничение, а стимул к написанию более качественного, понятного и производительного кода. В зависимости от версии платформы и требований к интерфейсу, вы можете выбрать элегантный асинхронный цикл с оператором Ждать, надежный паттерн на базе ОписаниеОповещения или пойти по пути улучшения юзабилити, реализовав форму пакетного заполнения данных.

← На главную