Разработка интерактивных интерфейсов в платформе «1С:Предприятие 8.3» на управляемых формах требует от разработчика понимания клиент-серверной архитектуры, принципов работы с динамическими списками и асинхронных вызовов. Одной из наиболее частых задач является создание удобной формы подбора товаров (номенклатуры) для документов резервирования, заказов или отгрузок. В этой статье мы подробно разберем, как спроектировать такую форму с нуля, вывести актуальные остатки товаров на складах в реальном времени, реализовать удобный интерактивный выбор (включая множественный выбор и drag-and-drop), а также безопасно передать данные в документ без использования устаревших модальных окон.
Процесс подбора строится на взаимодействии двух управляемых форм: вызывающей формы (например, формы документа БронированиеНоменклатуры) и вспомогательной формы (формы подбора). Платформа 1С предлагает стандартный механизм для возврата выбранных значений через событие выбора. Нам важно правильно определить владельца открываемой формы, чтобы система корректно перенаправила поток данных.
Существует два основных сценария возврата данных:
Элементы.Товары). В этом случае после вызова метода ОповеститьОВыборе управление передается в обработчик события таблицы ОбработкаВыбора.ЭтаФорма). Тогда событие выбора обрабатывается в процедуре модуля формы документа ОбработкаВыбора.Мы подробно рассмотрим первый сценарий как наиболее интуитивный и разделяемый на уровне интерфейса.
Для реализации подбора нам потребуется создать произвольную форму. Назовем ее ФормаПодбора. Нам необходимо разместить на ней два ключевых элемента:
ТаблицаЗначений (в контексте формы — ДанныеФормыКоллекция).Для таблицы доступных товаров обязательно включим свойство Множественный выбор в свойствах элемента формы таблицы. Это позволит пользователю выделять несколько строк удерживанием клавиш Shift или Ctrl и переносить их в корзину одним действием.
Чтобы пользователь видел актуальные остатки, настроим динамический список (назовем реквизит Товары) с произвольным запросом. В запросе мы соединим справочник номенклатуры с виртуальной таблицей остатков регистра накопления.
Рассмотрим пример запроса для динамического списка:
ВЫБРАТЬ
СправочникНоменклатура.Ссылка КАК Товар,
СправочникНоменклатура.Код КАК Код,
ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК ОстатокНаСкладе,
ЕСТЬNULL(ТоварыНаСкладахОстатки.Серия, ЗНАЧЕНИЕ(Справочник.СерииНоменклатуры.ПустаяСсылка)) КАК Серия
ИЗ
Справочник.Номенклатура КАК СправочникНоменклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК ТоварыНаСкладахОстатки
ПО ТоварыНаСкладахОстатки.Номенклатура = СправочникНоменклатура.Ссылка
ГДЕ
НЕ СправочникНоменклатура.ЭтоГруппа
Обратите внимание на параметр запроса &Склад. При открытии формы подбора мы должны передать склад из документа-владельца в параметры формы подбора и установить его значение в динамическом списке. Сделаем это в обработчике создания формы на сервере:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверяем наличие переданного склада в параметрах открытия формы
Если Параметры.Свойство("Склад") Тогда
Склад = Параметры.Склад;
// Устанавливаем параметр в динамическом списке "Товары"
Товары.Параметры.УстановитьЗначениеПараметра("Склад", Склад);
КонецЕсли;
КонецПроцедуры
Разберем, как обрабатывать действия пользователя при выборе товара из каталога. Для этого мы задействуем событие Выбор таблицы доступных товаров. При двойном клике на строку (или при нажатии Enter) выбранные товары должны добавиться в таблицу ВыбранныеТовары (нашу корзину).
В коде обработчика важно учитывать возможность множественного выбора (когда параметр ВыбраннаяСтрока содержит массив идентификаторов строк, а не одно число):
&НаКлиенте
Процедура ТоварыВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
// Отменяем стандартное открытие карточки номенклатуры по двойному клику
СтандартнаяОбработка = Ложь;
// Обрабатываем выбор строк
ДобавитьСтрокиВКорзину(ВыбраннаяСтрока);
КонецПроцедуры
Для поддержки интерфейсного механизма Drag-and-Drop (перетаскивание мышью из каталога в корзину) настроим свойства таблиц. У таблицы-источника (каталог) установим свойство РазрешитьНачалоПеретаскивания в значение Истина. У таблицы-приемника (корзина) установим свойство РазрешитьПеретаскивание в значение Истина и создадим обработчик события ПроверкаПеретаскивания и Перетаскивание:
&НаКлиенте
Процедура ВыбранныеТоварыПеретаскивание(Элемент, ПараметрыПеретаскивания, СтандартнаяОбработка, Строка, Поле)
СтандартнаяОбработка = Ложь;
// При перетаскивании значением является массив идентификаторов строк источника
Если ТипЗнч(ПараметрыПеретаскивания.Значение) = Тип("Массив") Тогда
ДобавитьСтрокиВКорзину(ПараметрыПеретаскивания.Значение);
КонецЕсли;
КонецПроцедуры
Использование синхронного метода ВвестиЧисло() внутри циклов — частая и грубая ошибка начинающих разработчиков. В современных версиях платформы 1С при отключенной модальности такой вызов приведет к аварийному завершению работы программы.
Существует два архитектурно верных решения этой задачи:
Вариант А (Лучший UI/UX): Пользователь быстро наполняет корзину (таблицу ВыбранныеТовары) двойным кликом. При этом товар добавляется в количестве 1 штуки. После наполнения корзины пользователь редактирует колонку Количество прямо в таблице корзины на форме подбора. Это избавляет от необходимости постоянно реагировать на всплывающие окна ввода.
Вариант Б (Асинхронный ввод): Если бизнес-логика требует обязательного ввода количества сразу при клике на каждую позицию, необходимо использовать асинхронные методы платформы, такие как ПоказатьВводЧисла(). Разберем реализацию этого паттерна подробнее.
Разработаем общий метод добавления строк с асинхронным запросом количества:
&НаКлиенте
Процедура ДобавитьСтрокиВКорзину(МассивСтрок)
Для Каждого ИдентификаторСтроки Из МассивСтрок Цикл
ТекСтрока = Элементы.Товары.ДанныеСтроки(ИдентификаторСтроки);
Если ТекСтрока = Неопределено Тогда
Продолжить;
КонецЕсли;
// Подготавливаем описание оповещения для обработки завершения ввода числа
КонтекстВызова = Новый Структура;
КонтекстВызова.Вставить("Товар", ТекСтрока.Товар);
КонтекстВызова.Вставить("Серия", ТекСтрока.Серия);
КонтекстВызова.Вставить("Остаток", ТекСтрока.ОстатокНаСкладе);
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВводаКоличества", ЭтотОбъект, КонтекстВызова);
// Показываем немодальное окно ввода числа
ПоказатьВводЧисла(ОписаниеОповещения, 1, "Укажите количество для: " + Строка(ТекСтрока.Товар), 10, 3);
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура ПослеВводаКоличества(РезультатЧисло, ДополнительныеПараметры) Экспорт
// Если пользователь закрыл окно или ввел некорректное число, прерываем процесс
Если РезультатЧисло = Неопределено Или РезультатЧисло <= 0 Тогда
Возврат;
КонецЕсли;
// Проверяем превышение остатка на складе
Если РезультатЧисло > ДополнительныеПараметры.Остаток Тогда
ПоказатьПредупреждение(, "Введенное количество превышает свободный остаток на складе!");
Возврат;
КонецЕсли;
// Выполняем поиск позиции в корзине
Поиск = Новый Структура("Товар, Серия", ДополнительныеПараметры.Товар, ДополнительныеПараметры.Серия);
МассивСтрокКорзины = ВыбранныеТовары.НайтиСтроки(Поиск);
Если МассивСтрокКорзины.Количество() = 0 Тогда
// Добавляем новую позицию в корзину
НоваяСтрока = ВыбранныеТовары.Добавить();
НоваяСтрока.Товар = ДополнительныеПараметры.Товар;
НоваяСтрока.Серия = ДополнительныеПараметры.Серия;
НоваяСтрока.Количество = РезультатЧисло;
Иначе
// Обновляем количество в существующей строке
МассивСтрокКорзины[0].Количество = РезультатЧисло;
КонецЕсли;
КонецПроцедуры
Передача клиентского объекта ДанныеФормыКоллекция (которым является наша корзина ВыбранныеТовары на клиенте) напрямую в метод ОповеститьОВыборе часто вызывает критические ошибки платформы, связанные с сериализацией мутабельных типов данных. Для стабильной работы нам необходимо преобразовать коллекцию формы в простой массив структур, содержащий только элементарные типы данных и ссылки.
Реализуем процедуру отправки данных при нажатии на кнопку «Перенести в документ»:
&НаКлиенте
Процедура ПеренестиВДокумент(Команда)
// Формируем массив сериализуемых структур из таблицы выбранных товаров
МассивДанныхВыбора = Новый Массив;
Для Каждого СтрокаКорзины Из ВыбранныеТовары Цикл
ДанныеСтроки = Новый Структура;
ДанныеСтроки.Вставить("Товар", СтрокаКорзины.Товар);
ДанныеСтроки.Вставить("Серия", СтрокаКорзины.Серия);
ДанныеСтроки.Вставить("Количество", СтрокаКорзины.Количество);
МассивДанныхВыбора.Добавить(ДанныеСтроки);
КонецЦикла;
// Оповещаем форму-владельца и передаем подготовленный массив
ОповеститьОВыборе(МассивДанныхВыбора);
КонецПроцедуры
Если предполагается перенос сотен или тысяч строк, выгрузка на клиенте может выполняться медленно. В таких случаях эффективнее свернуть данные на сервере формы подбора, поместить таблицу значений во временное хранилище и вернуть в вызывающую форму только уникальный текстовый адрес хранилища (АдресХранилища).
Теперь вернемся к форме нашего документа БронированиеНоменклатуры. Чтобы открыть форму подбора и передать туда контекст (склад), напишем обработчик для команды «Подбор» на форме документа:
&НаКлиенте
Процедура Подбор(Команда)
ПараметрыПодбора = Новый Структура;
ПараметрыПодбора.Вставить("Склад", Объект.Склад);
// Важно: третьим параметром указываем элемент-владелец (табличную часть формы документа)
ОткрытьФорму(
"Документ.БронированиеНоменклатуры.Форма.ФормаПодбора",
ПараметрыПодбора,
Элементы.Товары
);
КонецПроцедуры
Поскольку владельцем формы подбора мы указали элемент управления Элементы.Товары, платформа автоматически направит возвращаемые данные в событие ОбработкаВыбора этой таблицы. Создадим соответствующий обработчик в модуле формы документа:
&НаКлиенте
Процедура ТоварыОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
// Отменяем стандартное поведение платформы по обработке выбора
СтандартнаяОбработка = Ложь;
// Проверяем тип пришедших данных
Если ТипЗнч(ВыбранноеЗначение) = Тип("Массив") И ВыбранноеЗначение.Количество() > 0 Тогда
ЗаполнитьДокументПоРезультатамПодбора(ВыбранноеЗначение);
КонецЕсли;
КонецПроцедуры
Для непосредственного добавления строк в документ перейдем на серверный контекст формы, чтобы избежать циклических обращений к серверу при работе с данными объекта:
&НаСервере
Процедура ЗаполнитьДокументПоРезультатамПодбора(МассивВыбранныхТоваров)
Для Каждого СтрокаВыбора Из МассивВыбранныхТоваров Цикл
// Ищем, нет ли уже такого товара в табличной части документа
ПараметрыПоиска = Новый Структура("Номенклатура, Серия", СтрокаВыбора.Товар, СтрокаВыбора.Серия);
НайденныеСтроки = Объект.Товары.НайтиСтроки(ПараметрыПоиска);
Если НайденныеСтроки.Количество() = 0 Тогда
// Добавляем новую строку, если товар ранее не был добавлен
НоваяСтрока = Объект.Товары.Добавить();
НоваяСтрока.Номенклатура = СтрокаВыбора.Товар;
НоваяСтрока.Серия = СтрокаВыбора.Серия;
НоваяСтрока.Количество = СтрокаВыбора.Количество;
Иначе
// Если товар найден, увеличиваем количество
ТекущаяСтрока = НайденныеСтроки[0];
ТекущаяСтрока.Количество = ТекущаяСтрока.Количество + СтрокаВыбора.Количество;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Мы детально разобрали сквозной пример разработки формы подбора. Для того чтобы ваш код всегда соответствовал современным стандартам разработки 1С, рекомендуем придерживаться следующих правил:
ОповеститьОВыборе(), чтобы избежать ошибок сериализации клиентских типов.