Таблица значений на клиенте: четыре рабочих способа и их цена
Содержание
Таблицу значений нельзя передать с сервера на клиент напрямую: в тонком и веб-клиенте тип ТаблицаЗначений не существует. Рабочих вариантов четыре. Если данные нужно показать в форме — кладите их в реквизит формы через ЗначениеВРеквизитФормы(). Если нужно обработать в клиентском коде — превращайте таблицу в массив структур или в структуру массивов по колонкам.
Ещё два варианта решают другие задачи. Временное хранилище переносит таблицу между вызовами сервера и из фонового задания, клиенту достаётся только строка-адрес. ДвоичныеДанные нужны, когда таблицу надо отдать пользователю файлом. Выбор зависит от того, что клиент будет делать с данными.
Почему таблица значений не доходит до клиента
В управляемом приложении код формы выполняется в двух местах. Клиент — это тонкий клиент, веб-клиент или мобильный клиент на компьютере пользователя. Сервер — рабочий процесс кластера 1С (в файловом варианте его роль играет сам клиентский процесс, но правила доступности типов те же).
Каждый тип в синтаксис-помощнике имеет строку «Доступность». У ТаблицаЗначений там перечислены сервер, толстый клиент, внешнее соединение и сервер мобильного приложения. Тонкого и веб-клиента в списке нет. Объекта такого типа на клиенте просто не из чего создать.
При вызове сервера платформа сериализует передаваемые значения. Без ограничений между клиентом и сервером ходят:
- 🔹 примитивы:
Строка,Число,Дата,Булево,Неопределено; - 🔹 ссылки на объекты базы (
СправочникСсылка,ДокументСсылкаи т. д.); - 🔹
Массив,Структура,Соответствиеи их фиксированные варианты — если внутри только допустимые значения; - 🔹
УникальныйИдентификатор,ХранилищеЗначения,ДвоичныеДанные; - 🔹 данные формы:
ДанныеФормыКоллекция,ДанныеФормыСтруктура,ДанныеФормыДерево.
Таблица значений в этот список не входит. Функция с директивой &НаСервере, которая делает Возврат ТЗ;, на тонком клиенте завершится ошибкой передачи значения недопустимого типа.
В толстом клиенте (обычное приложение) тип ТаблицаЗначений доступен. Но если форма должна работать в тонком и веб-клиенте, рассчитывать на это нельзя: пишите код по правилам управляемого приложения.
Способ 1. Реквизит формы с типом «ТаблицаЗначений»
Самый частый случай: таблицу надо показать пользователю, дать отредактировать и потом снова обработать на сервере. Для этого в форме создают реквизит с типом ТаблицаЗначений и описывают его колонки.
На сервере реквизит выглядит как обычная таблица значений. На клиент он приходит как ДанныеФормыКоллекция: со строками можно работать, добавлять и удалять их, читать поля.
Пошагово
- В редакторе формы на вкладке «Реквизиты» добавьте реквизит, например
ТаблицаТоваров, тип —ТаблицаЗначений. - Добавьте ему колонки с теми же именами, что и в исходной таблице:
Номенклатура,Количество,Сумма. Типы колонок задайте явно. - Перетащите реквизит в дерево элементов формы — появится поле таблицы.
- В модуле формы заполните реквизит на сервере.
&НаСервере
Процедура ЗаполнитьТаблицуНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| Остатки.КоличествоОстаток КАК Количество,
| Остатки.СуммаОстаток КАК Сумма
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки";
ТЗ = Запрос.Выполнить().Выгрузить();
ЗначениеВРеквизитФормы(ТЗ, "ТаблицаТоваров");
КонецПроцедуры
&НаКлиенте
Процедура Заполнить(Команда)
ЗаполнитьТаблицуНаСервере();
КонецПроцедуры
Имя регистра в примере условное — подставьте свой. Обратная операция на сервере — РеквизитФормыВЗначение("ТаблицаТоваров"): она вернёт полноценную таблицу значений со всеми методами (Свернуть, Найти, Сортировать).
Для табличной части объекта есть короче путь: Объект.Товары.Загрузить(ТЗ) на сервере. Метод Загрузить у ДанныеФормыКоллекция принимает таблицу значений, Выгрузить() — возвращает её. Оба работают только на сервере.
Колонки, которых нет в реквизите формы, при загрузке молча пропадут. Если на клиенте «пустая» колонка, первым делом сверьте имена колонок запроса и реквизита — регистр букв не важен, а вот лишняя буква важна.
Когда колонки заранее неизвестны
Если набор колонок формируется динамически (отчёт-конструктор, загрузка из файла), реквизит создают программно: на сервере через ИзменитьРеквизиты() с массивом объектов РеквизитФормы, затем добавляют элементы методом Элементы.Добавить(). Это отдельный приём, и он дороже: при каждом изменении структуры форма перестраивается.
Способ 2. Передача таблицы значений на клиент массивом структур
Иногда таблица в форме не нужна. Например, клиентский код должен пройти по строкам и показать вопрос, открыть формы или собрать параметры для внешней компоненты. Тогда сервер возвращает Массив, где каждый элемент — Структура с полями строки.
&НаСервереБезКонтекста
Функция ДанныеДляКлиента()
ТЗ = ПолучитьТаблицуНаСервере(); // ваш запрос или расчёт
Результат = Новый Массив;
Для Каждого Строка Из ТЗ Цикл
Элемент = Новый Структура("Номенклатура, Количество, Сумма");
ЗаполнитьЗначенияСвойств(Элемент, Строка);
Результат.Добавить(Элемент);
КонецЦикла;
Возврат Результат;
КонецФункции
ЗаполнитьЗначенияСвойств копирует только поля, которые объявлены в структуре. Это удобно: заодно отсекаются лишние колонки.
В конфигурациях на БСП есть готовая функция ОбщегоНазначения.ТаблицаЗначенийВМассив(Таблица). Она делает то же самое для всех колонок. Своя функция полезна, когда колонок много, а клиенту нужны две-три.
Директива &НаСервереБезКонтекста здесь не случайна. Она не тянет на сервер и обратно данные формы, поэтому вызов легче. Используйте её всегда, когда функции не нужны реквизиты формы.
Проверяйте, что в колонках только допустимые типы. Если в ячейке лежит вложенная таблица значений, ОписаниеТипов или объект (СправочникОбъект), массив структур тоже не пройдёт на клиент.
Как уменьшить объём: только нужные колонки и строки
Скорость передачи определяется размером сериализованных данных. Таблица на 20 000 строк и 15 колонок и та же таблица на 3 колонки различаются по объёму в разы. Отсюда три приёма.
Отбор на уровне запроса
Лучший способ сократить таблицу — не выбирать лишнего. В запросе перечисляйте поля явно, без ВЫБРАТЬ *. Ставьте условия в ГДЕ и при необходимости ПЕРВЫЕ N.
Копирование только нужных колонок
Если таблица уже получена и используется на сервере целиком, для клиента сделайте облегчённую копию:
// только две колонки
Облегченная = ТЗ.Скопировать(, "Номенклатура, Количество");
// только строки с признаком ошибки
Отбор = Новый Структура("Ошибка", Истина);
ТолькоОшибки = ТЗ.Скопировать(Отбор, "Номенклатура, Количество");
Первый параметр Скопировать — массив строк или структура отбора, второй — строка с именами колонок через запятую. Оба можно опустить.
Колоночный формат вместо построчного
Массив структур хранит имена полей в каждом элементе. На 10 000 строк это 10 000 повторов слова «Номенклатура». Компактнее передать структуру, где ключ — имя колонки, а значение — массив значений этой колонки:
&НаСервереБезКонтекста
Функция ТаблицаПоКолонкам()
ТЗ = ПолучитьТаблицуНаСервере();
Результат = Новый Структура;
Результат.Вставить("ЧислоСтрок", ТЗ.Количество());
Для Каждого Колонка Из ТЗ.Колонки Цикл
Результат.Вставить(Колонка.Имя, ТЗ.ВыгрузитьКолонку(Колонка.Имя));
КонецЦикла;
Возврат Результат;
КонецФункции
На клиенте строка с номером Инд собирается так: Данные.Номенклатура[Инд], Данные.Сумма[Инд]. Индексы идут от 0 до Данные.ЧислоСтрок - 1.
Имена колонок должны быть допустимыми ключами структуры. Псевдонимы из запроса этому правилу соответствуют. Следите только, чтобы ни одна колонка не называлась «ЧислоСтрок» — этот ключ занят служебным полем.
Сравнение способов
| Способ | Когда выбирать | Что на клиенте | Ограничения |
|---|---|---|---|
| Реквизит формы | показать и редактировать в форме | ДанныеФормыКоллекция | колонки описаны заранее; ходит при каждом контекстном вызове |
| Массив структур | построчная обработка в клиентском коде | Массив из Структура | объём растёт из-за имён полей |
| Структура массивов | много строк, мало колонок | Структура из Массив | доступ по индексу, код менее читаемый |
| Временное хранилище | транзит между вызовами, фон | строка-адрес | клиент не может разобрать таблицу |
Временное хранилище, ХранилищеЗначения и двоичные данные
Иногда клиенту таблица вообще не нужна. Её надо сохранить между двумя вызовами сервера: пользователь подтверждает действие, а повторно выполнять тяжёлый запрос не хочется. Для этого есть временное хранилище.
&НаСервере
Процедура ПодготовитьДанные()
ТЗ = ПолучитьТаблицуНаСервере();
АдресДанных = ПоместитьВоВременноеХранилище(ТЗ, УникальныйИдентификатор);
КонецПроцедуры
&НаСервере
Процедура ОбработатьДанные()
ТЗ = ПолучитьИзВременногоХранилища(АдресДанных);
// ... работа с таблицей
КонецПроцедуры
АдресДанных — строковый реквизит формы. На клиент уходит только эта строка, а таблица остаётся на сервере. Если вызвать ПолучитьИзВременногоХранилища на тонком клиенте для такого адреса, получите ту же ошибку недопустимого типа: хранилище не превращает таблицу в клиентский тип.
Передавайте во втором параметре УникальныйИдентификатор формы. Тогда данные живут, пока открыта форма. Без него значение может удалиться сразу после окончания текущего вызова сервера, и следующий вызов получит Неопределено. Когда данные больше не нужны, освобождайте память: УдалитьИзВременногоХранилища(АдресДанных).
ХранилищеЗначения как упаковка
Объект ХранилищеЗначения можно создать на сервере: Новый ХранилищеЗначения(ТЗ, Новый СжатиеДанных(9)). Он сам ходит между клиентом и сервером и умеет сжимать содержимое. Клиент держит его как непрозрачный контейнер и может вернуть на сервер, где метод Получить() отдаст исходную таблицу.
Это удобно, когда таблицу надо передать в другую форму через параметры открытия: форма-получатель распакует её у себя на сервере.
Двоичные данные: выгрузка таблицы файлом
ДвоичныеДанные нужны, когда пользователь хочет получить таблицу файлом. Клиенту не надо знать структуру — он только сохраняет файл. Схема такая:
- На сервере выведите таблицу в
ТабличныйДокументили запишите в CSV. - Запишите документ во временный файл (
ПолучитьИмяВременногоФайла("xlsx")), прочитайте какНовый ДвоичныеДанные(ИмяФайла), удалите файл. - Положите двоичные данные во временное хранилище и верните адрес.
- На клиенте отдайте файл пользователю: в конфигурациях на БСП —
ФайловаяСистемаКлиент.СохранитьФайл(), на чистой платформе — асинхронным методом получения файла с сервера.
Для обмена «таблица целиком туда и обратно» встречается и сериализация: ЗначениеВСтрокуВнутр(ТЗ) или СериализаторXDTO. Но разобрать такую строку обратно в таблицу всё равно можно только на сервере, так что выигрыша перед хранилищем нет.
Большие выборки: фоновое задание и асинхронная загрузка
Если запрос строит таблицу 30 секунд, синхронный серверный вызов «подвешивает» форму. Пользователь видит песочные часы, а при долгом ожидании веб-клиент может оборвать соединение. Решение — посчитать таблицу в фоне, а в форму забрать готовый результат.
В конфигурациях на БСП для этого есть подсистема «Длительные операции»:
&НаСервере
Функция ЗапуститьРасчет()
Параметры = ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
Параметры.НаименованиеФоновогоЗадания = "Расчёт таблицы остатков";
Возврат ДлительныеОперации.ВыполнитьВФоне(
"МойОбщийМодуль.РассчитатьТаблицу", Новый Структура, Параметры);
КонецФункции
&НаКлиенте
Процедура Рассчитать(Команда)
Операция = ЗапуститьРасчет();
Оповещение = Новый ОписаниеОповещения("ПослеРасчета", ЭтотОбъект);
ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
ДлительныеОперацииКлиент.ОжидатьЗавершение(Операция, Оповещение, ПараметрыОжидания);
КонецПроцедуры
&НаКлиенте
Процедура ПослеРасчета(Результат, ДополнительныеПараметры) Экспорт
Если Результат = Неопределено Тогда
Возврат; // пользователь отменил
КонецЕсли;
Если Результат.Статус = "Выполнено" Тогда
ЗагрузитьРезультат(Результат.АдресРезультата);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ЗагрузитьРезультат(Адрес)
ЗначениеВРеквизитФормы(ПолучитьИзВременногоХранилища(Адрес), "ТаблицаТоваров");
КонецПроцедуры
Процедура в общем модуле принимает два параметра — (Параметры, АдресРезультата) — и в конце делает ПоместитьВоВременноеХранилище(ТЗ, АдресРезультата). Модуль должен быть серверным, а процедура — экспортной.
Без БСП схема та же, только вручную. Создайте адрес заранее: ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор). Запустите ФоновыеЗадания.Выполнить() и передайте адрес в параметрах. Затем опрашивайте состояние задания обработчиком ожидания с интервалом 1–2 секунды.
Фоновое задание — отдельный сеанс. Оно не видит реквизиты формы и параметры сеанса, если их не передали явно. Всё нужное для расчёта кладите в структуру параметров.
Если таблица нужна только для просмотра и в ней сотни тысяч строк, подумайте о динамическом списке. Он подгружает данные порциями по мере прокрутки, и тогда передавать на клиент всю таблицу не нужно вовсе.
Ошибки при передаче таблицы с сервера на клиент
Ещё одна частая ловушка — серверный вызов в цикле. Код вида Для Каждого Строка Из Таблица Цикл ПолучитьЦенуНаСервере(Строка.Номенклатура) делает столько обращений к серверу, сколько строк в таблице. На 500 строках это 500 сетевых вызовов. Соберите все ссылки в массив, передайте одним вызовом и получите обратно соответствие «номенклатура → цена».
Частые вопросы
Можно ли передать на клиент одну строку таблицы значений?
СтрокаТаблицыЗначений тоже серверный тип. Скопируйте поля строки в структуру через ЗаполнитьЗначенияСвойств и верните структуру.А дерево значений?
ДеревоЗначений, на клиенте он будет ДанныеФормыДерево. Для кода — вложенные массивы структур с полем для подчинённых строк.Чем ФиксированныйМассив лучше обычного массива при передаче?
Можно ли свернуть или посчитать итоги прямо на клиенте?
Свернуть и Итог на клиенте нет — придётся писать цикл. Агрегацию выполняйте на сервере до передачи: это быстрее, и по сети уйдёт меньше данных.