Таблица значений в управляемой форме: как доставить данные с сервера до клиента
Содержание
Таблицу значений нельзя напрямую вернуть с сервера на тонкий клиент 1С 8.3: на клиенте такого типа нет. Чтобы передать данные, их упаковывают. Есть три рабочих способа: положить таблицу в реквизит формы через ЗначениеВРеквизитФормы(), превратить её в массив структур или оставить на сервере во временном хранилище и передавать на клиент только адрес.
Типичная ситуация: разработчик пишет обработку, выполняет запрос в процедуре &НаСервере, делает Выгрузить() и пытается вернуть результат в клиентскую процедуру. В Конфигураторе код компилируется, а при нажатии кнопки 1С падает с ошибкой. Ниже разобраны причина, способы обойти ограничение и цена каждого способа по скорости.
Почему клиент и сервер 1С обмениваются данными не напрямую
В управляемом приложении код формы делится по директивам компиляции. Процедура с &НаКлиенте выполняется на компьютере пользователя, процедура с &НаСервере — на сервере 1С. Между ними — вызов сервера, и всё, что через него проходит, сериализуется: превращается в поток данных и собирается заново на другой стороне.
Собрать значение на клиенте можно только если его тип там существует. У типа ТаблицаЗначений в синтакс-помощнике указана доступность: сервер, толстый клиент, внешнее соединение. Тонкого и веб-клиента в этом списке нет. Поэтому:
- 🚫 вернуть таблицу значений из серверной функции в клиентский код нельзя;
- 🚫 создать её на клиенте через
Новый ТаблицаЗначенийнельзя — будет ошибкаТип не определен (ТаблицаЗначений); - ✅ можно передать данные таблицы в другом «контейнере», который клиент понимает.
В толстом клиенте тип существует, и прямой возврат там может сработать. Не стройте на этом логику: код управляемой формы обязан работать в тонком и веб-клиенте, иначе он сломается у первого пользователя, который зайдёт через браузер.
Есть и второй нюанс. Контекстный вызов &НаСервере из формы гонит на сервер и обратно все данные формы: реквизиты, табличные части, состояние. Вызов &НаСервереБезКонтекста передаёт только параметры и возвращаемое значение. От выбора директивы зависит, сколько килобайт уйдёт по сети при каждом нажатии кнопки.
Способы передать таблицу значений на клиент
| Способ | Когда подходит | Что получает клиент | Минусы |
|---|---|---|---|
Реквизит формы типа ТаблицаЗначений | Данные нужно показать пользователю в таблице формы | ДанныеФормыКоллекция | Колонки задаются заранее; передаются с каждым контекстным вызовом |
| Массив структур | Клиентскому коду нужно перебрать строки и что-то посчитать | Массив из Структура | Ручная упаковка, лишние ключи в каждой строке |
| Временное хранилище | Таблица большая и нужна только серверу в следующих вызовах | Строка-адрес | На клиенте таблицу не прочитать |
СписокЗначений | Нужна одна колонка значений | СписокЗначений | Только одна колонка (плюс представление и пометка) |
| Строка XML или внутренний формат | Таблицу надо провести через клиент и вернуть на сервер без изменений | Строка | На клиенте не разобрать обратно в таблицу |
Способ 1. Реквизит формы и ЗначениеВРеквизитФормы
Самый частый вариант. В редакторе формы на вкладке «Реквизиты» добавьте реквизит, например Товары, с типом ТаблицаЗначений, и опишите его колонки с типами. Перетащите реквизит на форму — получится элемент «Таблица».
На сервере заполните обычную таблицу значений и перенесите её в реквизит:
&НаКлиенте
Процедура Заполнить(Команда)
ЗаполнитьНаСервере();
// На клиенте реквизит Товары — это ДанныеФормыКоллекция
Для Каждого СтрокаТовара Из Товары Цикл
Сообщить(СтрШаблон("%1, артикул %2", СтрокаТовара.Номенклатура, СтрокаТовара.Артикул));
КонецЦикла;
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 100
| Номенклатура.Ссылка КАК Номенклатура,
| Номенклатура.Артикул КАК Артикул
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ЭтоГруппа";
ТЗ = Запрос.Выполнить().Выгрузить();
ЗначениеВРеквизитФормы(ТЗ, "Товары");
КонецПроцедуры
На клиенте с ДанныеФормыКоллекция работают почти как с таблицей: перебирают в цикле, обращаются к строке по индексу, вызывают Добавить(), Удалить(), Количество(), НайтиСтроки(). Методы Выгрузить() и Загрузить() у коллекции есть только на сервере.
Обратная дорога — РеквизитФормыВЗначение("Товары") в серверной процедуре. Она вернёт полноценную таблицу значений, с которой можно делать всё: группировать, сворачивать, передавать в запрос.
ЗначениеВРеквизитФормы() и РеквизитФормыВЗначение() — методы формы. В процедуре &НаСервереБезКонтекста формы нет, и вызвать их там нельзя. Для коллекций, которые не являются реквизитом формы целиком, есть глобальные ЗначениеВДанныеФормы() и ДанныеФормыВЗначение().
Способ 2. Массив структур
Когда таблицу не надо показывать, а клиентскому коду нужны значения, удобнее вернуть массив структур. Массив и Структура есть на клиенте, и если внутри лежат примитивы и ссылки, передача пройдёт без ошибок.
&НаСервереБезКонтекста
Функция ТаблицаВМассив(ТЗ)
ИменаКолонок = "";
Для Каждого Колонка Из ТЗ.Колонки Цикл
ИменаКолонок = ИменаКолонок + ?(ИменаКолонок = "", "", ",") + Колонка.Имя;
КонецЦикла;
Результат = Новый Массив;
Для Каждого СтрокаТЗ Из ТЗ Цикл
Элемент = Новый Структура(ИменаКолонок);
ЗаполнитьЗначенияСвойств(Элемент, СтрокаТЗ);
Результат.Добавить(Элемент);
КонецЦикла;
Возврат Результат;
КонецФункции
&НаСервереБезКонтекста
Функция ПолучитьНоменклатуру(Знач Родитель)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Родитель = &Родитель";
Запрос.УстановитьПараметр("Родитель", Родитель);
Возврат ТаблицаВМассив(Запрос.Выполнить().Выгрузить());
КонецФункции
На клиенте результат перебирают так: Для Каждого Элемент Из Данные Цикл ... Элемент.Наименование .... Функцию упаковки лучше вынести в серверный общий модуль, чтобы не копировать её по формам. В типовых конфигурациях на БСП похожая функция уже есть в общем модуле ОбщегоНазначения — поищите по слову «ТаблицаЗначенийВМассив».
Если колонка одна, не нужен ни массив структур, ни реквизит. Верните ТЗ.ВыгрузитьКолонку("Ссылка") — это обычный массив значений.
Способ 3. Временное хранилище
Бывает, что таблица в 50 тысяч строк нужна только серверу: на первом шаге её собирают, на втором — проверяют, на третьем — записывают документы. Гонять её через клиент незачем. Положите таблицу во временное хранилище и держите на клиенте только адрес:
&НаСервере
Процедура ПодготовитьДанные()
ТЗ = СобратьДанные(); // любая серверная функция, возвращающая таблицу значений
АдресДанных = ПоместитьВоВременноеХранилище(ТЗ, УникальныйИдентификатор);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПроверитьДанные(Знач Адрес)
ТЗ = ПолучитьИзВременногоХранилища(Адрес);
Возврат ТЗ.Количество();
КонецФункции
АдресДанных — реквизит формы типа «Строка». Второй параметр УникальныйИдентификатор привязывает значение к форме: оно живёт, пока форма открыта. Если идентификатор формы не передать, платформа может удалить значение уже после следующего вызова сервера, и на втором шаге вы получите Неопределено вместо таблицы.
Если таблица больше не нужна, а форма ещё открыта, освободите память явно: УдалитьИзВременногоХранилища(Адрес).
Способ 4. Сериализация в строку
Таблицу можно превратить в строку на сервере: через ЗначениеВСтрокуВнутр() или через СериализаторXDTO в XML. Строку клиент примет. Разобрать её обратно в таблицу можно только там, где тип существует, то есть снова на сервере.
Этот вариант нужен редко: например, когда таблицу надо сохранить в файл на компьютере пользователя или передать в другую базу. Для обычной работы формы временное хранилище проще и быстрее.
Какие типы можно передать на клиент, а какие нет
Ограничение касается не только самой таблицы, но и содержимого её ячеек. Структура со значением недопустимого типа тоже не пройдёт.
| Передаётся на клиент | Не передаётся |
|---|---|
Строка, Число, Дата, Булево, Неопределено, Null | ТаблицаЗначений, ДеревоЗначений |
Ссылки на объекты (СправочникСсылка, ДокументСсылка и т.д.) | Объекты (СправочникОбъект, ДокументОбъект) |
Массив, Структура, Соответствие и их фиксированные варианты | Запрос, РезультатЗапроса, выборки |
СписокЗначений | МенеджерВременныхТаблиц |
УникальныйИдентификатор, ДвоичныеДанные | Табличные части объекта, ОбъектМетаданных |
Тип, ОписаниеТипов | COMОбъект |
Правило проверки простое: откройте тип в синтакс-помощнике и посмотрите строку «Доступность». Если там нет тонкого клиента , значение такого типа на клиент не попадёт.
Отдельная ловушка — колонки без типа. Если в таблице, полученной кодом, колонка добавлена без описания типов, в ней может оказаться что угодно, в том числе объект. Ошибка всплывёт не на всех данных, а на той строке, где лежит неподходящее значение.
Как ускорить передачу таблицы значений между сервером и клиентом
Каждый серверный вызов — это задержка сети плюс объём данных. На локальной сети разница незаметна, а у пользователя на удалённом подключении или в веб-клиенте лишние сотни килобайт превращают кнопку в «зависание».
Передавайте только нужные колонки
Запрос часто возвращает больше полей, чем нужно форме. Перед упаковкой оставьте нужные:
ТЗДляКлиента = ТЗ.Скопировать(, "Номенклатура,Количество");
Первый параметр метода Скопировать() — отбор строк, второй — список колонок через запятую.
Выбирайте вызов без контекста
Если серверной процедуре не нужны данные формы, ставьте &НаСервереБезКонтекста. Контекстный вызов передаёт весь реквизит Товары туда и обратно, даже если процедура его не трогает. На форме с тремя таблицами по тысяче строк это заметно.
Объявляйте параметры как Знач
Параметр серверной процедуры без Знач после вызова возвращается на клиент: платформа сериализует его повторно, чтобы клиент увидел изменения. Если массив на 5 000 элементов уходит на сервер только для чтения, пишите Функция Обработать(Знач Данные) — обратно он не поедет.
Не дробите вызовы
Цикл на клиенте, который на каждой строке вызывает сервер, — главный враг скорости. 500 строк — 500 вызовов. Соберите всё, что нужно, за один вызов и верните одним пакетом.
Порции для больших объёмов
Если пользователю надо показать десятки тысяч строк, не грузите их в реквизит целиком. Либо выводите по 500–1000 строк с кнопкой «Показать ещё», либо используйте динамический список (о нём ниже).
Ошибки при передаче таблицы и как их найти
Ошибки сериализации проявляются только при выполнении: синтаксический контроль в Конфигураторе их не ловит. Поэтому проверяйте форму в режиме 1С:Предприятия, а лучше в тонком клиенте и в браузере.
Новый ТаблицаЗначений. Перенесите создание таблицы в серверную процедуру, а на клиент передайте результат в виде массива структур или реквизита формы.&НаСервере или функция общего модуля с флагом «Вызов сервера» возвращает таблицу значений, дерево или результат запроса. Упакуйте результат: ЗначениеВРеквизитФормы(), массив структур или адрес временного хранилища.Скопировать().КАК Номенклатура) с колонками реквизита в редакторе формы.УникальныйИдентификатор формы.&НаСервереБезКонтекста, где формы нет. Смените директиву на &НаСервере или верните данные функцией и запишите их в реквизит в контекстной процедуре.Как увидеть, сколько данных уходит на клиент
Запустите 1С:Предприятие с параметром командной строки /DisplayPerformance. В окне программы появятся показатели: число серверных вызовов и объём переданных данных. Нажмите кнопку, сравните цифры до и после правки — так сразу видно, помог ли переход на &НаСервереБезКонтекста или Знач.
В отладчике поставьте точку останова в клиентской процедуре сразу после серверного вызова и посмотрите, что пришло. Если вместо ожидаемой коллекции Неопределено или ошибка — проблема в упаковке на сервере, а не в клиентском коде.
Когда таблица значений вообще не нужна
Часто задача «передать таблицу на клиент» — следствие неудачной архитектуры. Прежде чем упаковывать данные, проверьте, нельзя ли обойтись без этого.
Динамический список. Если пользователю надо показать результат запроса, добавьте на форму реквизит типа ДинамическийСписок с произвольным запросом. Платформа сама подгружает строки порциями при прокрутке, и вы не передаёте на клиент ни одной лишней строки.
Табличный документ. Для отчётных выводов, которые не редактируют, подходит ТабличныйДокумент. Его формируют на сервере, а на клиенте он отображается целиком, с оформлением.
Обработка на сервере целиком. Если клиенту нужен итог — сумма, количество, флаг «есть ошибки», — посчитайте его на сервере и верните одно число или структуру. Таблица в 10 000 строк ради одной суммы на клиенте — лишняя нагрузка.
Колонки заранее неизвестны. Когда таблица строится по произвольному запросу, реквизит можно создать программно через ИзменитьРеквизиты():
&НаСервере
Процедура ВывестиРезультат(ТекстЗапроса)
ТЗ = Новый Запрос(ТекстЗапроса).Выполнить().Выгрузить();
Добавляемые = Новый Массив;
Добавляемые.Добавить(Новый РеквизитФормы("Результат", Новый ОписаниеТипов("ТаблицаЗначений")));
Для Каждого Колонка Из ТЗ.Колонки Цикл
ТипКолонки = Новый ОписаниеТипов(Колонка.ТипЗначения, , "Null");
Добавляемые.Добавить(Новый РеквизитФормы(Колонка.Имя, ТипКолонки, "Результат"));
КонецЦикла;
ИзменитьРеквизиты(Добавляемые);
ЭлементТаблица = Элементы.Добавить("Результат", Тип("ТаблицаФормы"));
ЭлементТаблица.ПутьКДанным = "Результат";
Для Каждого Колонка Из ТЗ.Колонки Цикл
Поле = Элементы.Добавить("Результат" + Колонка.Имя, Тип("ПолеФормы"), ЭлементТаблица);
Поле.Вид = ВидПоляФормы.ПолеВвода;
Поле.ПутьКДанным = "Результат." + Колонка.Имя;
КонецЦикла;
ЗначениеВРеквизитФормы(ТЗ, "Результат");
КонецПроцедуры
Из типов колонок убран Null: запрос с левым соединением добавляет его в описание типов, а реквизиту формы он не нужен. При повторном вызове реквизит Результат уже существует — перед новым построением удалите его вторым параметром ИзменитьРеквизиты() и уберите старые элементы формы через Элементы.Удалить().
Частые вопросы
Можно ли передать дерево значений на клиент?
ДеревоЗначений: на клиенте он станет ДанныеФормыДерево, а на сервере заполняется тем же методом ЗначениеВРеквизитФормы().Можно ли передать на клиент результат запроса?
РезультатЗапроса и выборка существуют только на сервере. Выгрузите результат в таблицу и упакуйте её одним из способов: реквизит, массив структур, временное хранилище.Чем ДанныеФормыКоллекция отличается от таблицы значений?
Свернуть(), и в запрос её не передать. Сложную обработку делайте на сервере после РеквизитФормыВЗначение().Как передать таблицу с клиента на сервер?
&НаСервере доставит его сам — на сервере вызовите РеквизитФормыВЗначение(). Из клиентского кода без формы передайте массив структур и соберите таблицу на сервере.