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