Таблица значений на клиенте 1С: что делать, если её нельзя вернуть напрямую
Содержание
Таблицу значений нельзя передать с сервера на клиент как есть: в тонком и веб-клиенте типа ТаблицаЗначений просто нет. Поэтому на сервере таблицу превращают во что-то, что клиент понимает: в массив структур, в реквизит формы типа «Таблица значений» (на клиенте он виден как ДанныеФормыКоллекция), в строку JSON или оставляют её на сервере во временном хранилище и передают клиенту только адрес.
Какой способ выбрать, зависит от того, что клиенту нужно с данными делать: показать пользователю, обработать в цикле или просто дождаться следующего серверного вызова. Ниже каждый способ с кодом для управляемой формы на платформе 8.3.
Почему таблицу значений не вернуть на клиент и какие есть способы передачи
Клиентский код управляемого приложения работает в тонком клиенте, веб-клиенте или мобильном. Набор типов там урезан. ТаблицаЗначений, ДеревоЗначений, РезультатЗапроса, Запрос, объекты документов и справочников живут только на сервере.
Если написать Новый ТаблицаЗначений в процедуре с директивой &НаКлиенте, модуль не пройдёт проверку: платформа сообщит, что тип не определён. Если вернуть таблицу из серверной функции, вызов упадёт уже при выполнении.
С сервера на клиент свободно проходят:
- 🔹 примитивы:
Строка,Число,Дата,Булево,Неопределено; - 🔹 ссылки на объекты базы и
УникальныйИдентификатор; - 🔹 коллекции
Массив,Структура,Соответствиеи их фиксированные версии, если внутри тоже только передаваемые типы; - 🔹 данные формы: реквизиты формы, в том числе табличные.
Отсюда пять рабочих способов:
| Способ | Когда подходит | Минусы |
|---|---|---|
| Массив структур из функции | Клиенту нужно пройти по строкам в коде | Каждая строка тянет имена полей, тяжелее на больших объёмах |
| Реквизит формы «Таблица значений» | Данные надо показать пользователю в таблице | Утяжеляет каждый контекстный вызов сервера |
| Строка JSON | Данные уходят дальше: в веб-сервис, файл, внешний компонент | Ссылки и даты требуют преобразования |
XML или ЗначениеВСтрокуВнутр | Таблицу надо «заморозить» и восстановить на сервере | На клиенте прочитать как таблицу нельзя |
| Временное хранилище | Таблица большая, клиенту нужна по частям или потом | Адрес живёт ограниченное время |
Возврат таблицы из серверной функции в виде массива структур
Самый частый приём. Серверная функция выполняет запрос, раскладывает строки таблицы по структурам и возвращает массив. На клиенте с ним работают как с обычной коллекцией.
Серверная часть, универсальное преобразование:
&НаСервереБезКонтекста
Функция ТаблицаВМассивСтруктур(Таблица)
ИменаКолонок = Новый Массив;
Для Каждого Колонка Из Таблица.Колонки Цикл
ИменаКолонок.Добавить(Колонка.Имя);
КонецЦикла;
СтрокаКлючей = СтрСоединить(ИменаКолонок, ",");
Результат = Новый Массив;
Для Каждого СтрокаТаблицы Из Таблица Цикл
Элемент = Новый Структура(СтрокаКлючей);
ЗаполнитьЗначенияСвойств(Элемент, СтрокаТаблицы);
Результат.Добавить(Элемент);
КонецЦикла;
Возврат Результат;
КонецФункции
ЗаполнитьЗначенияСвойств копирует значения по совпадающим именам, поэтому вложенный цикл по колонкам не нужен. Структура с заранее заданными ключами создаётся быстрее, чем структура с вызовами Вставить на каждое поле.
В конфигурациях на БСП уже есть готовая функция ОбщегоНазначения.ТаблицаЗначенийВМассив. Свою имеет смысл писать, только если нужно отобрать часть колонок или переименовать поля.
Функция, которую вызывает клиент:
&НаСервереБезКонтекста
Функция КонтрагентыБезИНН()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка,
| Контрагенты.Наименование КАК Наименование
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| НЕ Контрагенты.ЭтоГруппа
| И Контрагенты.ИНН = """"";
Возврат ТаблицаВМассивСтруктур(Запрос.Выполнить().Выгрузить());
КонецФункции
Клиентская часть:
&НаКлиенте
Процедура ПроверитьКонтрагентов(Команда)
Список = КонтрагентыБезИНН();
Для Каждого Элемент Из Список Цикл
Сообщить(СтрШаблон("Не заполнен ИНН: %1", Элемент.Наименование));
КонецЦикла;
КонецПроцедуры
Наименование берём из отдельного поля, а не через Элемент.Ссылка.Наименование. В тонком клиенте обращаться через точку к реквизитам ссылки нельзя. Всё, что нужно клиенту, кладите в структуру заранее, ещё на сервере.
Если клиенту нужна одна колонка, структуры не нужны вовсе: Таблица.ВыгрузитьКолонку("Ссылка") сразу даёт массив.
Модификатор Знач в параметрах серверных процедур
Таблицу почти всегда получают по каким-то условиям, которые клиент отдаёт на сервер: период, склад, список ссылок. Тут легко потерять трафик на ровном месте.
Когда клиент вызывает серверную процедуру, параметр без Знач передаётся «по ссылке». Для клиент-серверного вызова это значит, что значение сериализуется на клиенте, уходит на сервер, а после вызова платформа везёт его обратно, чтобы клиент увидел возможные изменения. С Знач значение идёт только в одну сторону.
// Массив из нескольких тысяч ссылок уйдёт на сервер и не вернётся обратно
&НаСервереБезКонтекста
Функция ДанныеПоДокументам(Знач МассивСсылок, Знач ДатаОстатков)
// ...
КонецФункции
Правило простое:
- Параметр только читается на сервере, ставьте
Знач. - Сервер должен вернуть клиенту результат, верните его через
Возврат, а не через изменение параметра. - Возвращать несколько значений удобно одной структурой:
Новый Структура("Таблица, Итог, Ошибки", ...).
С Знач изменения параметра на сервере клиент не увидит. Если старый код рассчитывал, что сервер «допишет» строки в переданный массив, после добавления Знач он молча перестанет работать.
Директива тоже влияет на объём. &НаСервере везёт на сервер данные формы, &НаСервереБезКонтекста нет. Для функции, которая только читает базу и возвращает массив, контекст формы не нужен.
Таблица в реквизите формы: передача для показа пользователю
Если таблицу надо показать в форме, массив не нужен. Создайте реквизит формы с типом ТаблицаЗначений, добавьте ему колонки в редакторе реквизитов и перетащите в дерево элементов: получится элемент «Таблица формы».
На сервере реквизит заполняют двумя равноценными способами:
&НаСервере
Процедура ЗаполнитьНаСервере()
Таблица = ПолучитьТаблицуИзЗапроса(); // обычная ТаблицаЗначений
// Вариант 1: метод коллекции данных формы
ТаблицаДанных.Загрузить(Таблица);
// Вариант 2: метод формы
// ЗначениеВРеквизитФормы(Таблица, "ТаблицаДанных");
КонецПроцедуры
Колонки сопоставляются по именам, поэтому псевдонимы в запросе должны совпадать с именами колонок реквизита. Лишние колонки запроса просто не попадут в форму.
Обратное преобразование нужно, когда на сервере удобнее работать с настоящей таблицей значений: ТаблицаДанных.Выгрузить() или РеквизитФормыВЗначение("ТаблицаДанных").
На клиенте реквизит доступен как коллекция строк:
&НаКлиенте
Функция СуммаПоКонтрагенту(Контрагент)
Итого = 0;
Отбор = Новый Структура("Контрагент", Контрагент);
Для Каждого Строка Из ТаблицаДанных.НайтиСтроки(Отбор) Цикл
Итого = Итого + Строка.Сумма;
КонецЦикла;
Возврат Итого;
КонецФункции
Цена удобства: реквизит участвует в каждом контекстном вызове &НаСервере. Таблица на десятки тысяч строк в форме, где кнопки постоянно дёргают сервер с контекстом, заметно тормозит.
Сериализация таблицы в JSON и XML
JSON: когда данные идут дальше клиента
ЗаписатьJSON не принимает таблицу значений напрямую. Таблицу сначала превращают в массив структур, а ссылки заменяют строками: идентификатором или представлением.
&НаСервереБезКонтекста
Функция ДокументыВJSON(Знач Начало, Знач Конец)
Таблица = ВыбратьДокументы(Начало, Конец); // колонки: Номер, Дата, Сумма, Контрагент
Массив = Новый Массив;
Для Каждого Стр Из Таблица Цикл
Массив.Добавить(Новый Структура("Номер, Дата, Сумма, Контрагент",
Стр.Номер, Стр.Дата, Стр.Сумма, Строка(Стр.Контрагент)));
КонецЦикла;
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку();
ЗаписатьJSON(Запись, Массив);
Возврат Запись.Закрыть();
КонецФункции
На клиенте строку читают обратно. Даты в JSON хранятся текстом, поэтому имя поля с датой передаём третьим параметром, иначе получим строку вместо даты:
&НаКлиенте
Процедура ПрочитатьДокументы(ТекстJSON)
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(ТекстJSON);
Документы = ПрочитатьJSON(Чтение, Ложь, "Дата");
Чтение.Закрыть();
// Документы — массив структур с полями Номер, Дата, Сумма, Контрагент
КонецПроцедуры
Для обмена внутри одной формы JSON избыточен: массив структур передаётся и без него. JSON нужен, когда строку дальше отправляют HTTP-запросом, пишут в файл или отдают внешнему компоненту.
XML и внутренняя строка: «заморозить» таблицу
Таблица значений сериализуется в XDTO целиком, с колонками и типами:
&НаСервереБезКонтекста
Функция ТаблицаВXML(Таблица)
Запись = Новый ЗаписьXML;
Запись.УстановитьСтроку();
СериализаторXDTO.ЗаписатьXML(Запись, Таблица);
Возврат Запись.Закрыть();
КонецФункции
Восстанавливается она на сервере через СериализаторXDTO.ПрочитатьXML. Короче вариант с ЗначениеВСтрокуВнутр и ЗначениеИзСтрокиВнутр: оба метода вызывайте на сервере.
Клиент такую строку только хранит, например в строковом реквизите формы, и возвращает на сервер при следующем вызове. Работать с ней как с таблицей клиент не может.
Временное хранилище: таблица остаётся на сервере
Когда таблица большая, а клиенту она нужна не сразу или по частям, её вообще не стоит гонять туда-обратно. Таблицу кладут во временное хранилище, клиенту отдают строку-адрес.
&НаСервере
Процедура ПодготовитьДанныеНаСервере()
Таблица = ПолучитьТаблицуИзЗапроса();
АдресТаблицы = ПоместитьВоВременноеХранилище(Таблица, УникальныйИдентификатор);
ВсегоСтрок = Таблица.Количество();
КонецПроцедуры
АдресТаблицы и ВсегоСтрок здесь реквизиты формы. Второй параметр, уникальный идентификатор формы, привязывает значение к форме: после её закрытия платформа удалит его сама. Без этого параметра значение может исчезнуть раньше, чем вы за ним вернётесь.
Дальше клиент запрашивает данные порциями:
&НаСервереБезКонтекста
Функция СтраницаТаблицы(Знач Адрес, Знач НомерСтраницы, Знач РазмерСтраницы)
Таблица = ПолучитьИзВременногоХранилища(Адрес);
Если Таблица = Неопределено Тогда
Возврат Неопределено;
КонецЕсли;
Результат = Новый Массив;
Начало = (НомерСтраницы - 1) * РазмерСтраницы;
Конец = Мин(Начало + РазмерСтраницы, Таблица.Количество()) - 1;
Для Индекс = Начало По Конец Цикл
Стр = Таблица[Индекс];
Результат.Добавить(Новый Структура("Наименование, Сумма",
Стр.Наименование, Стр.Сумма));
КонецЦикла;
Возврат Результат;
КонецФункции
Проверка на числах: 250 строк, страница 100 строк. Первая страница берёт индексы 0–99, третья 200–249. Для четвёртой начало 300 больше конца 249, цикл не выполнится, вернётся пустой массив.
Ещё два случая для хранилища:
- 📦 результат фонового задания: задание кладёт таблицу по заранее выданному адресу, форма забирает её после завершения;
- 📦 файл от пользователя: клиент помещает двоичные данные в хранилище, сервер читает их оттуда и разбирает в таблицу.
Когда данные больше не нужны, освободите память вызовом УдалитьИзВременногоХранилища(Адрес).
Как ускорить передачу таблиц между сервером и клиентом
Медленная передача обычно складывается из лишних колонок, лишних строк и лишних вызовов. Что проверить:
- Запрос выбирает только нужные поля.
ВЫБРАТЬ *из документа тянет все реквизиты, а клиенту нужны три. - Строк столько, сколько человек увидит. Для просмотра хватит
ВЫБРАТЬ ПЕРВЫЕ 500или постраничной выдачи из хранилища. - Один вызов вместо цикла вызовов. Сто вызовов сервера по одной строке медленнее одного вызова на сто строк, даже если данных столько же.
&НаСервереБезКонтекста, где форма не нужна. ИЗначдля входных параметров.- Представления готовятся на сервере. Через
ПРЕДСТАВЛЕНИЕ()в запросе илиСтрока(Ссылка)при упаковке. - Повторно не запрашивать. Неизменный справочник для формы достаточно получить один раз при открытии и держать в реквизите.
Для списков из базы на тысячи строк вместо таблицы значений лучше подходит реквизит типа «Динамический список». Он сам подгружает видимую часть и не держит весь результат в данных формы.
Где теряется время, видно в замере производительности конфигуратора: запустите отладку, включите замер и выполните действие на форме. Строки с серверными вызовами и их доля времени покажут, что оптимизировать первым.
Пример: таблица документов реализации в форме обработки
Задача: в обработке для 1С:Бухгалтерии 3.0 показать проведённые реализации за период, посчитать итог и открыть выбранные документы.
Реквизиты формы:
ДатаНачала,ДатаОкончания, типДата;Документы, типТаблицаЗначенийс колонкамиСсылка(ДокументСсылка.РеализацияТоваровУслуг),Дата,Номер,Контрагент,Сумма;ИтогоСумма, типЧисло.
Заполнение на сервере:
&НаКлиенте
Процедура Заполнить(Команда)
ЗаполнитьНаСервере();
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка КАК Ссылка,
| Реализация.Дата КАК Дата,
| Реализация.Номер КАК Номер,
| Реализация.Контрагент КАК Контрагент,
| Реализация.СуммаДокумента КАК Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Проведен
| И Реализация.Дата МЕЖДУ &Начало И &Конец
|
|УПОРЯДОЧИТЬ ПО
| Реализация.Дата";
Запрос.УстановитьПараметр("Начало", НачалоДня(ДатаНачала));
Запрос.УстановитьПараметр("Конец", КонецДня(ДатаОкончания));
Таблица = Запрос.Выполнить().Выгрузить();
Документы.Загрузить(Таблица);
ИтогоСумма = Таблица.Итог("Сумма");
КонецПроцедуры
Здесь контекстный вызов оправдан: процедура читает период из реквизитов формы и заполняет табличный реквизит. Итог считаем на сервере методом Итог, пока таблица значений под рукой.
Открытие выделенных строк целиком на клиенте, без обращения к серверу за данными строк:
&НаКлиенте
Процедура ОткрытьВыделенные(Команда)
Для Каждого Идентификатор Из Элементы.Документы.ВыделенныеСтроки Цикл
Строка = Документы.НайтиПоИдентификатору(Идентификатор);
Если Строка <> Неопределено Тогда
ПоказатьЗначение(, Строка.Ссылка);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Для множественного выделения у элемента таблицы формы включите свойство «Режим выделения» в значение «Множественный».
Если те же документы нужны не для показа, а, например, для выгрузки во внешний сервис, вместо реквизита формы верните из &НаСервереБезКонтекста массив структур или строку JSON из разделов выше.
Типовые ошибки при передаче таблиц и ответы на вопросы
Ещё одна ловушка: изменение структуры из массива на клиенте не меняет таблицу на сервере. Клиент получил копию. Чтобы сохранить правки, отправьте изменённые данные на сервер отдельным вызовом.