Таблица значений на клиенте: четыре рабочих способа и их цена

· 10 мин

Содержание

Таблицу значений нельзя передать с сервера на клиент напрямую: в тонком и веб-клиенте тип ТаблицаЗначений не существует. Рабочих вариантов четыре. Если данные нужно показать в форме — кладите их в реквизит формы через ЗначениеВРеквизитФормы(). Если нужно обработать в клиентском коде — превращайте таблицу в массив структур или в структуру массивов по колонкам.

Ещё два варианта решают другие задачи. Временное хранилище переносит таблицу между вызовами сервера и из фонового задания, клиенту достаётся только строка-адрес. ДвоичныеДанные нужны, когда таблицу надо отдать пользователю файлом. Выбор зависит от того, что клиент будет делать с данными.

Почему таблица значений не доходит до клиента

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

Каждый тип в синтаксис-помощнике имеет строку «Доступность». У ТаблицаЗначений там перечислены сервер, толстый клиент, внешнее соединение и сервер мобильного приложения. Тонкого и веб-клиента в списке нет. Объекта такого типа на клиенте просто не из чего создать.

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

  • 🔹 примитивы: Строка, Число, Дата, Булево, Неопределено;
  • 🔹 ссылки на объекты базы (СправочникСсылка, ДокументСсылка и т. д.);
  • 🔹 Массив, Структура, Соответствие и их фиксированные варианты — если внутри только допустимые значения;
  • 🔹 УникальныйИдентификатор, ХранилищеЗначения, ДвоичныеДанные;
  • 🔹 данные формы: ДанныеФормыКоллекция, ДанныеФормыСтруктура, ДанныеФормыДерево.

Таблица значений в этот список не входит. Функция с директивой &НаСервере, которая делает Возврат ТЗ;, на тонком клиенте завершится ошибкой передачи значения недопустимого типа.

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

Способ 1. Реквизит формы с типом «ТаблицаЗначений»

Самый частый случай: таблицу надо показать пользователю, дать отредактировать и потом снова обработать на сервере. Для этого в форме создают реквизит с типом ТаблицаЗначений и описывают его колонки.

На сервере реквизит выглядит как обычная таблица значений. На клиент он приходит как ДанныеФормыКоллекция: со строками можно работать, добавлять и удалять их, читать поля.

Пошагово

  1. В редакторе формы на вкладке «Реквизиты» добавьте реквизит, например ТаблицаТоваров, тип — ТаблицаЗначений.
  2. Добавьте ему колонки с теми же именами, что и в исходной таблице: Номенклатура, Количество, Сумма. Типы колонок задайте явно.
  3. Перетащите реквизит в дерево элементов формы — появится поле таблицы.
  4. В модуле формы заполните реквизит на сервере.
&НаСервере
Процедура ЗаполнитьТаблицуНаСервере()

    Запрос = Новый Запрос;
    Запрос.Текст =
    "ВЫБРАТЬ
    |    Остатки.Номенклатура КАК Номенклатура,
    |    Остатки.КоличествоОстаток КАК Количество,
    |    Остатки.СуммаОстаток КАК Сумма
    |ИЗ
    |    РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки";

    ТЗ = Запрос.Выполнить().Выгрузить();
    ЗначениеВРеквизитФормы(ТЗ, "ТаблицаТоваров");

КонецПроцедуры

&НаКлиенте
Процедура Заполнить(Команда)
    ЗаполнитьТаблицуНаСервере();
КонецПроцедуры

Имя регистра в примере условное — подставьте свой. Обратная операция на сервере — РеквизитФормыВЗначение("ТаблицаТоваров"): она вернёт полноценную таблицу значений со всеми методами (Свернуть, Найти, Сортировать).

Для табличной части объекта есть короче путь: Объект.Товары.Загрузить(ТЗ) на сервере. Метод Загрузить у ДанныеФормыКоллекция принимает таблицу значений, Выгрузить() — возвращает её. Оба работают только на сервере.

Колонки, которых нет в реквизите формы, при загрузке молча пропадут. Если на клиенте «пустая» колонка, первым делом сверьте имена колонок запроса и реквизита — регистр букв не важен, а вот лишняя буква важна.

Когда колонки заранее неизвестны

Если набор колонок формируется динамически (отчёт-конструктор, загрузка из файла), реквизит создают программно: на сервере через ИзменитьРеквизиты() с массивом объектов РеквизитФормы, затем добавляют элементы методом Элементы.Добавить(). Это отдельный приём, и он дороже: при каждом изменении структуры форма перестраивается.

Способ 2. Передача таблицы значений на клиент массивом структур

Иногда таблица в форме не нужна. Например, клиентский код должен пройти по строкам и показать вопрос, открыть формы или собрать параметры для внешней компоненты. Тогда сервер возвращает Массив, где каждый элемент — Структура с полями строки.

&НаСервереБезКонтекста
Функция ДанныеДляКлиента()

    ТЗ = ПолучитьТаблицуНаСервере(); // ваш запрос или расчёт

    Результат = Новый Массив;
    Для Каждого Строка Из ТЗ Цикл
        Элемент = Новый Структура("Номенклатура, Количество, Сумма");
        ЗаполнитьЗначенияСвойств(Элемент, Строка);
        Результат.Добавить(Элемент);
    КонецЦикла;

    Возврат Результат;

КонецФункции

ЗаполнитьЗначенияСвойств копирует только поля, которые объявлены в структуре. Это удобно: заодно отсекаются лишние колонки.

В конфигурациях на БСП есть готовая функция ОбщегоНазначения.ТаблицаЗначенийВМассив(Таблица). Она делает то же самое для всех колонок. Своя функция полезна, когда колонок много, а клиенту нужны две-три.

Директива &НаСервереБезКонтекста здесь не случайна. Она не тянет на сервер и обратно данные формы, поэтому вызов легче. Используйте её всегда, когда функции не нужны реквизиты формы.

Проверяйте, что в колонках только допустимые типы. Если в ячейке лежит вложенная таблица значений, ОписаниеТипов или объект (СправочникОбъект), массив структур тоже не пройдёт на клиент.

Как уменьшить объём: только нужные колонки и строки

Скорость передачи определяется размером сериализованных данных. Таблица на 20 000 строк и 15 колонок и та же таблица на 3 колонки различаются по объёму в разы. Отсюда три приёма.

Отбор на уровне запроса

Лучший способ сократить таблицу — не выбирать лишнего. В запросе перечисляйте поля явно, без ВЫБРАТЬ *. Ставьте условия в ГДЕ и при необходимости ПЕРВЫЕ N.

Копирование только нужных колонок

Если таблица уже получена и используется на сервере целиком, для клиента сделайте облегчённую копию:

// только две колонки
Облегченная = ТЗ.Скопировать(, "Номенклатура, Количество");

// только строки с признаком ошибки
Отбор = Новый Структура("Ошибка", Истина);
ТолькоОшибки = ТЗ.Скопировать(Отбор, "Номенклатура, Количество");

Первый параметр Скопировать — массив строк или структура отбора, второй — строка с именами колонок через запятую. Оба можно опустить.

Колоночный формат вместо построчного

Массив структур хранит имена полей в каждом элементе. На 10 000 строк это 10 000 повторов слова «Номенклатура». Компактнее передать структуру, где ключ — имя колонки, а значение — массив значений этой колонки:

&НаСервереБезКонтекста
Функция ТаблицаПоКолонкам()

    ТЗ = ПолучитьТаблицуНаСервере();

    Результат = Новый Структура;
    Результат.Вставить("ЧислоСтрок", ТЗ.Количество());
    Для Каждого Колонка Из ТЗ.Колонки Цикл
        Результат.Вставить(Колонка.Имя, ТЗ.ВыгрузитьКолонку(Колонка.Имя));
    КонецЦикла;

    Возврат Результат;

КонецФункции

На клиенте строка с номером Инд собирается так: Данные.Номенклатура[Инд], Данные.Сумма[Инд]. Индексы идут от 0 до Данные.ЧислоСтрок - 1.

Имена колонок должны быть допустимыми ключами структуры. Псевдонимы из запроса этому правилу соответствуют. Следите только, чтобы ни одна колонка не называлась «ЧислоСтрок» — этот ключ занят служебным полем.

Сравнение способов

СпособКогда выбиратьЧто на клиентеОграничения
Реквизит формыпоказать и редактировать в формеДанныеФормыКоллекцияколонки описаны заранее; ходит при каждом контекстном вызове
Массив структурпострочная обработка в клиентском кодеМассив из Структураобъём растёт из-за имён полей
Структура массивовмного строк, мало колонокСтруктура из Массивдоступ по индексу, код менее читаемый
Временное хранилищетранзит между вызовами, фонстрока-адресклиент не может разобрать таблицу

Временное хранилище, ХранилищеЗначения и двоичные данные

Иногда клиенту таблица вообще не нужна. Её надо сохранить между двумя вызовами сервера: пользователь подтверждает действие, а повторно выполнять тяжёлый запрос не хочется. Для этого есть временное хранилище.

&НаСервере
Процедура ПодготовитьДанные()
    ТЗ = ПолучитьТаблицуНаСервере();
    АдресДанных = ПоместитьВоВременноеХранилище(ТЗ, УникальныйИдентификатор);
КонецПроцедуры

&НаСервере
Процедура ОбработатьДанные()
    ТЗ = ПолучитьИзВременногоХранилища(АдресДанных);
    // ... работа с таблицей
КонецПроцедуры

АдресДанных — строковый реквизит формы. На клиент уходит только эта строка, а таблица остаётся на сервере. Если вызвать ПолучитьИзВременногоХранилища на тонком клиенте для такого адреса, получите ту же ошибку недопустимого типа: хранилище не превращает таблицу в клиентский тип.

Передавайте во втором параметре УникальныйИдентификатор формы. Тогда данные живут, пока открыта форма. Без него значение может удалиться сразу после окончания текущего вызова сервера, и следующий вызов получит Неопределено. Когда данные больше не нужны, освобождайте память: УдалитьИзВременногоХранилища(АдресДанных).

ХранилищеЗначения как упаковка

Объект ХранилищеЗначения можно создать на сервере: Новый ХранилищеЗначения(ТЗ, Новый СжатиеДанных(9)). Он сам ходит между клиентом и сервером и умеет сжимать содержимое. Клиент держит его как непрозрачный контейнер и может вернуть на сервер, где метод Получить() отдаст исходную таблицу.

Это удобно, когда таблицу надо передать в другую форму через параметры открытия: форма-получатель распакует её у себя на сервере.

Двоичные данные: выгрузка таблицы файлом

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

  1. На сервере выведите таблицу в ТабличныйДокумент или запишите в CSV.
  2. Запишите документ во временный файл (ПолучитьИмяВременногоФайла("xlsx")), прочитайте как Новый ДвоичныеДанные(ИмяФайла), удалите файл.
  3. Положите двоичные данные во временное хранилище и верните адрес.
  4. На клиенте отдайте файл пользователю: в конфигурациях на БСП — ФайловаяСистемаКлиент.СохранитьФайл(), на чистой платформе — асинхронным методом получения файла с сервера.

Для обмена «таблица целиком туда и обратно» встречается и сериализация: ЗначениеВСтрокуВнутр(ТЗ) или СериализаторXDTO. Но разобрать такую строку обратно в таблицу всё равно можно только на сервере, так что выигрыша перед хранилищем нет.

Большие выборки: фоновое задание и асинхронная загрузка

Если запрос строит таблицу 30 секунд, синхронный серверный вызов «подвешивает» форму. Пользователь видит песочные часы, а при долгом ожидании веб-клиент может оборвать соединение. Решение — посчитать таблицу в фоне, а в форму забрать готовый результат.

В конфигурациях на БСП для этого есть подсистема «Длительные операции»:

&НаСервере
Функция ЗапуститьРасчет()
    Параметры = ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
    Параметры.НаименованиеФоновогоЗадания = "Расчёт таблицы остатков";
    Возврат ДлительныеОперации.ВыполнитьВФоне(
        "МойОбщийМодуль.РассчитатьТаблицу", Новый Структура, Параметры);
КонецФункции

&НаКлиенте
Процедура Рассчитать(Команда)
    Операция = ЗапуститьРасчет();
    Оповещение = Новый ОписаниеОповещения("ПослеРасчета", ЭтотОбъект);
    ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
    ДлительныеОперацииКлиент.ОжидатьЗавершение(Операция, Оповещение, ПараметрыОжидания);
КонецПроцедуры

&НаКлиенте
Процедура ПослеРасчета(Результат, ДополнительныеПараметры) Экспорт
    Если Результат = Неопределено Тогда
        Возврат; // пользователь отменил
    КонецЕсли;
    Если Результат.Статус = "Выполнено" Тогда
        ЗагрузитьРезультат(Результат.АдресРезультата);
    КонецЕсли;
КонецПроцедуры

&НаСервере
Процедура ЗагрузитьРезультат(Адрес)
    ЗначениеВРеквизитФормы(ПолучитьИзВременногоХранилища(Адрес), "ТаблицаТоваров");
КонецПроцедуры

Процедура в общем модуле принимает два параметра — (Параметры, АдресРезультата) — и в конце делает ПоместитьВоВременноеХранилище(ТЗ, АдресРезультата). Модуль должен быть серверным, а процедура — экспортной.

Без БСП схема та же, только вручную. Создайте адрес заранее: ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор). Запустите ФоновыеЗадания.Выполнить() и передайте адрес в параметрах. Затем опрашивайте состояние задания обработчиком ожидания с интервалом 1–2 секунды.

Фоновое задание — отдельный сеанс. Оно не видит реквизиты формы и параметры сеанса, если их не передали явно. Всё нужное для расчёта кладите в структуру параметров.

Если таблица нужна только для просмотра и в ней сотни тысяч строк, подумайте о динамическом списке. Он подгружает данные порциями по мере прокрутки, и тогда передавать на клиент всю таблицу не нужно вовсе.

Ошибки при передаче таблицы с сервера на клиент

Диагностика: выберите симптом

Ещё одна частая ловушка — серверный вызов в цикле. Код вида Для Каждого Строка Из Таблица Цикл ПолучитьЦенуНаСервере(Строка.Номенклатура) делает столько обращений к серверу, сколько строк в таблице. На 500 строках это 500 сетевых вызовов. Соберите все ссылки в массив, передайте одним вызовом и получите обратно соответствие «номенклатура → цена».

Частые вопросы

Можно ли передать на клиент одну строку таблицы значений?
Нет, СтрокаТаблицыЗначений тоже серверный тип. Скопируйте поля строки в структуру через ЗаполнитьЗначенияСвойств и верните структуру.
А дерево значений?
Те же правила. Для формы — реквизит с типом ДеревоЗначений, на клиенте он будет ДанныеФормыДерево. Для кода — вложенные массивы структур с полем для подчинённых строк.
Чем ФиксированныйМассив лучше обычного массива при передаче?
По объёму почти ничем. Он защищает данные от случайного изменения на клиенте и годится как значение параметра формы, которое не должно меняться.
Можно ли свернуть или посчитать итоги прямо на клиенте?
Методов Свернуть и Итог на клиенте нет — придётся писать цикл. Агрегацию выполняйте на сервере до передачи: это быстрее, и по сети уйдёт меньше данных.
Сколько строк можно безопасно держать в реквизите формы?
Жёсткого лимита платформа не задаёт, всё упирается в сеть и память клиента. Если форма с таблицей начинает заметно тормозить, это сигнал перейти на динамический список или постраничную подгрузку.