ТаблицаЗначений недоступна на тонком клиенте: какие структуры данных брать вместо неё
Содержание
На тонком и веб-клиенте 1С 8.3 таблицу значений заменяют одним из четырёх способов. Нужна простая выборка строк для логики на клиенте — возвращайте с сервера массив структур. Данные показываются пользователю в таблице на форме — держите их в реквизите формы типа ТаблицаЗначений: на клиенте он превращается в ДанныеФормыКоллекция со строками, поиском и добавлением. Иерархия — реквизит типа ДеревоЗначений. Большой объём, который клиенту не нужен целиком, — оставьте на сервере во временном хранилище и передавайте только адрес.
Табличный документ тоже работает на клиенте, но как буфер для ввода и вставки из Excel, а не как контейнер для расчётов.
Почему ТаблицаЗначений не работает в клиентском коде
Тип ТаблицаЗначений доступен на сервере, в толстом клиенте и во внешнем соединении. В тонком клиенте и веб-клиенте его нет. Отсюда три типовых ситуации:
- ❌
Новый ТаблицаЗначенийвнутри процедуры с директивой&НаКлиенте— ошибка при выполнении. - ❌ Серверная функция возвращает таблицу значений в клиентскую процедуру — платформа не может передать значение типа, которого нет на клиенте.
- ❌ Код написан под обычное приложение и толстый клиент, а базу перевели на управляемые формы. Раньше всё работало, теперь падает.
Проверить, доступен ли тип в контексте, можно в синтакс-помощнике: у каждого типа и метода внизу описания есть строка «Доступность». Для ТаблицаЗначений там нет тонкого клиента
и веб-клиента. Для Массив, Структура, Соответствие, ТабличныйДокумент — есть.
Ограничение касается не только самой таблицы. На клиенте недоступны Запрос, ДеревоЗначений, объекты справочников и документов. Ссылку на клиент передать можно, а прочитать её реквизит через точку (Товар.Артикул) — уже нет: за реквизитом придётся сходить на сервер.
Из этого следует главное правило: расчёт по таблице выполняйте на сервере, на клиент отдавайте только результат. Альтернативы ниже — про то, в какой обёртке этот результат удобно держать.
Массив структур: лёгкая замена таблицы значений на клиенте
Самый частый вариант. Каждая строка — Структура с нужными полями, все строки — в Массив. Оба типа доступны в любом контексте и сериализуются при серверном вызове без проблем.
Как сформировать на сервере
&НаСервереБезКонтекста
Функция ОстаткиТоваров(Склад)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| Остатки.Номенклатура.Артикул КАК Артикул,
| Остатки.КоличествоОстаток КАК Количество
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК Остатки";
Запрос.УстановитьПараметр("Склад", Склад);
Результат = Новый Массив;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Строка = Новый Структура("Номенклатура, Артикул, Количество",
Выборка.Номенклатура, Выборка.Артикул, Выборка.Количество);
Результат.Добавить(Строка);
КонецЦикла;
Возврат Результат;
КонецФункции
Имя регистра и его измерения у вас будут свои — пример показывает схему. Артикул выбран в самом запросе, чтобы клиенту не пришлось обращаться к реквизиту ссылки.
Универсальное преобразование готовой таблицы
Если таблица значений уже получена (из запроса, из внешней обработки, из чужого кода), её переводят в массив структур по колонкам:
&НаСервереБезКонтекста
Функция ТаблицаВМассив(Таблица)
Результат = Новый Массив;
Для Каждого СтрокаТаблицы Из Таблица Цикл
Строка = Новый Структура;
Для Каждого Колонка Из Таблица.Колонки Цикл
Строка.Вставить(Колонка.Имя, СтрокаТаблицы[Колонка.Имя]);
КонецЦикла;
Результат.Добавить(Строка);
КонецЦикла;
Возврат Результат;
КонецФункции
В конфигурациях на БСП такая функция уже есть: ОбщегоНазначения.ТаблицаЗначенийВМассив. Свою писать не нужно.
Что теряется по сравнению с таблицей
- 🔍 Нет
НайтиСтрокииНайти. Поиск — циклом или черезСоответствие. - 📊 Нет
Свернуть,Итог,Сортировать. Группировку и сортировку делайте в запросе. - 🧩 Нет описания колонок. Набор полей в каждой структуре держится только на дисциплине кода.
Если по массиву нужно часто искать по ключу, соберите рядом Соответствие: ключ — ссылка или код, значение — структура строки. Поиск по соответствию идёт по ключу напрямую, а цикл по массиву просматривает строки по очереди.
&НаКлиенте
Функция ИндексПоНоменклатуре(Строки)
Индекс = Новый Соответствие;
Для Каждого Строка Из Строки Цикл
Индекс.Вставить(Строка.Номенклатура, Строка);
КонецЦикла;
Возврат Индекс;
КонецФункции
Для данных, которые не меняются за сеанс (справочные коэффициенты, настройки), используйте общий модуль с повторным использованием возвращаемых значений. Возвращайте ФиксированныйМассив и ФиксированнаяСтруктура: изменяемые коллекции из такого модуля клиентский код может случайно испортить для всех последующих вызовов.
Реквизит формы: коллекция строк, которая живёт на клиенте
Когда данные нужно показать пользователю, массив структур не подходит — его нельзя вывести в табличную часть формы. Здесь работает реквизит формы с типом ТаблицаЗначений.
На сервере с ним работают как с обычной таблицей, а на клиенте он доступен как ДанныеФормыКоллекция. У коллекции есть почти всё, что нужно в интерфейсе:
| Действие на клиенте | Метод ДанныеФормыКоллекция |
|---|---|
| Добавить строку | Добавить(), Вставить(Индекс) |
| Удалить строку | Удалить(Строка), Очистить() |
| Найти строки по отбору | НайтиСтроки(Новый Структура("Товар", Товар)) |
| Число строк | Количество() |
| Строка по идентификатору | НайтиПоИдентификатору(Ид) |
| Перестановка | Сдвинуть(Строка, Смещение) |
Перевод между полноценной таблицей и реквизитом выполняется только на сервере: РеквизитФормыВЗначение("Товары") и ЗначениеВРеквизитФормы(Таблица, "Товары"). Для табличной части объекта на форме — Объект.Товары.Выгрузить() и Загрузить() в серверной процедуре.
&НаСервере
Процедура ЗаполнитьТоварыНаСервере()
Таблица = РеквизитФормыВЗначение("Товары");
Таблица.Очистить();
// ... заполнение запросом, свёртка, сортировка
Таблица.Сортировать("Номенклатура");
ЗначениеВРеквизитФормы(Таблица, "Товары");
КонецПроцедуры
Реквизит формы — самый удобный и самый «тяжёлый» вариант. При каждом вызове процедуры с директивой &НаСервере платформа передаёт на сервер данные формы и возвращает изменения. Таблица на 20 000 строк в реквизите превращает каждый такой вызов в заметную паузу.
Дерево значений на клиенте: иерархия без серверного объекта
ДеревоЗначений тоже серверный тип. Для иерархических данных — групп номенклатуры, структуры подразделений, вложенных статей затрат — есть два пути.
Реквизит формы типа ДеревоЗначений
На клиенте он становится ДанныеФормыДерево. Обход идёт через ПолучитьЭлементы(): у корня — коллекция верхнего уровня, у каждого элемента — его подчинённые. Родителя возвращает ПолучитьРодителя().
&НаКлиенте
Процедура СнятьОтметкиРекурсивно(Элементы)
Для Каждого Элемент Из Элементы Цикл
Элемент.Пометка = Ложь;
СнятьОтметкиРекурсивно(Элемент.ПолучитьЭлементы());
КонецЦикла;
КонецПроцедуры
// Вызов: СнятьОтметкиРекурсивно(Дерево.ПолучитьЭлементы());
Типичная задача — пометить группу и всех её потомков, пересчитать сумму по родителю — решается так без единого серверного вызова. Пользователь кликает флажки, а ответ формы мгновенный.
Вложенные массивы структур
Если дерево не показывается на форме, хватит структуры с полем Строки, где лежит массив таких же структур. Сформировать её на сервере можно рекурсивным обходом ДеревоЗначений или выборкой запроса с ИТОГИ ПО ... ИЕРАРХИЯ и Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией).
Глубокая вложенность в массиве структур неудобна для поиска. Если нужен быстрый доступ к узлу по ссылке, держите рядом плоское Соответствие «ссылка → структура узла».
Табличный документ как буфер данных на клиенте
ТабличныйДокумент доступен в тонком и веб-клиенте. Хранить в нём рабочие данные для расчётов неудобно: значения лежат в ячейках, обращение идёт по координатам, типы приходится проверять самим. Но у него есть своя ниша.
Где табличный документ на форме действительно выручает:
- 📥 Вставка из Excel. Пользователь копирует диапазон из таблицы и вставляет в поле табличного документа на форме. Колонки и строки сохраняются.
- 📝 Свободный ввод большого количества строк без открытия формы каждой строки.
- 👁 Предпросмотр результата загрузки до записи в базу.
Чтение ячеек на клиенте — через Область с адресом R1C1. Размеры заполненной части дают ВысотаТаблицы и ШиринаТаблицы.
&НаКлиенте
Функция ПрочитатьБуфер(ТабДок)
Результат = Новый Массив;
Для НомерСтроки = 2 По ТабДок.ВысотаТаблицы Цикл
Адрес = "R" + Формат(НомерСтроки, "ЧГ=0");
Артикул = СокрЛП(ТабДок.Область(Адрес + "C1").Текст);
Количество = СокрЛП(ТабДок.Область(Адрес + "C2").Текст);
Если ПустаяСтрока(Артикул) Тогда
Продолжить;
КонецЕсли;
Результат.Добавить(Новый Структура("Артикул, Количество", Артикул, Количество));
КонецЦикла;
Возврат Результат;
КонецФункции
Первая строка пропущена — в ней шапка. Количество приходит строкой: преобразуйте в число на сервере вместе с поиском номенклатуры по артикулу, там же проверьте ошибки ввода.
Не передавайте сам табличный документ на сервер ради пары колонок. Он передаётся целиком, с оформлением и параметрами ячеек. Соберите из него компактный массив структур на клиенте и отправьте одним вызовом.
Временное хранилище: данные остаются на сервере
Пятый способ, которого часто не хватает в обзорах: не возить таблицу на клиент вообще. Сервер кладёт её во временное хранилище и возвращает адрес — строку. Клиент держит адрес и передаёт его обратно при следующем серверном вызове.
&НаСервере
Процедура ПодготовитьДанныеНаСервере()
Таблица = ПолучитьБольшуюТаблицу(); // ваша серверная функция
АдресДанных = ПоместитьВоВременноеХранилище(Таблица, УникальныйИдентификатор);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ИтогПоТовару(Адрес, Товар)
Таблица = ПолучитьИзВременногоХранилища(Адрес);
Строки = Таблица.НайтиСтроки(Новый Структура("Номенклатура", Товар));
Сумма = 0;
Для Каждого Строка Из Строки Цикл
Сумма = Сумма + Строка.Сумма;
КонецЦикла;
Возврат Сумма;
КонецФункции
АдресДанных — строковый реквизит формы. Второй параметр УникальныйИдентификатор привязывает данные к форме: после её закрытия хранилище очищается. Без этого параметра данные живут до конца серверного вызова, и следующий запрос по адресу вернёт Неопределено.
Подходит, когда таблица большая, на клиенте нужны точечные ответы по ней, а заново выполнять запрос дорого.
Сравнение способов: скорость и сценарии
Точных миллисекунд здесь не будет: они зависят от числа строк, колонок, канала до сервера и типа клиента. Порядок затрат при этом стабилен. Почти всегда больше всего времени уходит на серверные вызовы и объём передаваемых данных, а не на работу с коллекцией в памяти клиента.
| Способ | Когда брать | Объём передачи | Поиск на клиенте | Риски |
|---|---|---|---|---|
| Массив структур | Логика на клиенте, небольшие выборки | Только нужные поля | Цикл или Соответствие | Нет контроля колонок |
Реквизит ТаблицаЗначений | Таблица на экране, ввод пользователем | Весь реквизит при &НаСервере | НайтиСтроки | Тормозит при тысячах строк |
Реквизит ДеревоЗначений | Иерархия на экране | Весь реквизит при &НаСервере | Обход ПолучитьЭлементы() | Медленная рекурсия на больших деревьях |
ТабличныйДокумент | Вставка из Excel, свободный ввод | Тяжёлый: с оформлением | По координатам ячеек | Типы данных не контролируются |
| Временное хранилище | Большие объёмы, точечные запросы | Только адрес и ответ | Нет, поиск на сервере | Адрес теряется без привязки к форме |
Как замерить на своей базе. Для одного участка кода хватит пары строк:
Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
// проверяемый код
Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
Сообщить("Заняло, мс: " + Формат(Длительность, "ЧГ=0"));
Для общей картины запустите отладку и включите замер производительности в меню отладки конфигуратора. Он покажет, какие строки съедают время и сколько раз вызывались серверные процедуры.
Как ускорить клиентский код: практические приёмы
Приёмы, которые дают выигрыш чаще всего. Первые три обычно важнее выбора коллекции.
- Директива
&НаСервереБезКонтекставместо&НаСерверевезде, где процедуре не нужны реквизиты формы. Данные формы тогда не передаются вообще. Параметры и результат передайте явно. - Один серверный вызов вместо десяти. Не дёргайте сервер в цикле по строкам ради одного реквизита. Соберите все ссылки в массив, отправьте один раз, получите
Соответствие«ссылка → данные». - Выбирайте поля в запросе. Артикул, единица, цена — сразу колонками выборки. Тогда клиенту не нужно обращаться к реквизитам ссылок.
- Не держите в реквизите формы то, что не видит пользователь. Служебную таблицу на 50 000 строк — во временное хранилище или в серверную переменную модуля объекта, а не в реквизит.
- Свёртка и сортировка — в запросе.
СГРУППИРОВАТЬ ПОиУПОРЯДОЧИТЬ ПОна стороне СУБД выполняются быстрее, чем то же самое кодом. - Для длинных списков — динамический список. Если нужно показать журнал или большой справочник, реквизит
ДинамическийСписокчитает данные порциями по мере прокрутки, а не грузит всё в форму. - Длительные расчёты — в фоне. Механизм длительных операций БСП запускает обработку фоновым заданием, а результат возвращает через временное хранилище. Клиент при этом не висит.
Частые вопросы
Можно ли просто включить таблицу значений на тонком клиенте?
Почему таблица значений работает в обработке у меня, но не у пользователя?
Почему на клиенте нельзя прочитать реквизит ссылки из массива?
Что быстрее на клиенте: массив структур или ДанныеФормыКоллекция?
&НаСервере, а массив передаётся только там, где вы его указали параметром.Подходит ли JSON вместо массива структур?
ЗаписьJSON и ЧтениеJSON доступны на клиенте. Для обмена между клиентом и сервером своей конфигурации это лишняя сериализация — массив структур передаётся платформой без вашего участия.