Как работать с табличными данными в клиентском коде управляемой формы
Содержание
В тонком и веб-клиенте 1С 8.3 объект ТаблицаЗначений не существует: строка Новый ТаблицаЗначений в процедуре с директивой &НаКлиенте даёт ошибку Тип не определен (ТаблицаЗначений). Таблица значений доступна на сервере, во внешнем соединении и в толстом клиенте. На клиенте с табличными данными работают через реквизит формы типа «Таблица значений» — в клиентском коде он виден как ДанныеФормыКоллекция.
Типичная ситуация: разработчик переносит код из обычного приложения в управляемую форму, а там таблица значений собиралась прямо в обработчике кнопки. В управляемой форме так нельзя. Клиентский код работает с упрощённой копией данных, а вся «тяжёлая» обработка уходит на сервер.
Где таблица значений есть, а где её нет
Доступность типа зависит от того, где выполняется код. Это первое, что нужно держать в голове.
| Где выполняется код | ТаблицаЗначений | Чем работать с таблицей |
|---|---|---|
Сервер (&НаСервере, &НаСервереБезКонтекста, серверный общий модуль) | ✅ есть | Сама таблица значений |
| Тонкий клиент, веб-клиент | ❌ нет | ДанныеФормыКоллекция, массив структур |
| Толстый клиент, обычное приложение | ✅ есть | Таблица значений, табличное поле |
| Толстый клиент, управляемое приложение | ✅ формально есть | Лучше как в тонком клиенте |
| Внешнее соединение (COM) | ✅ есть | Таблица значений |
Последний пункт важен. В толстом клиенте управляемого приложения код с Новый ТаблицаЗначений на клиенте скомпилируется и сработает. Но тот же модуль откроют в тонком клиенте или в браузере — и форма сломается.
Если клиентский код всё же нужен только для толстого клиента, оберните его инструкцией препроцессора #Если ТолстыйКлиентОбычноеПриложение Тогда … #КонецЕсли. Без неё модуль не пройдёт синтаксический контроль для тонкого клиента.
Почему так сделано. Тонкий клиент — лёгкое приложение без прикладных объектов. Он умеет показывать форму и выполнять простые операции с её данными. Свёртка, итоги, индексы и прочая работа с таблицами — задача сервера.
Создание таблицы на форме: реквизит и колонки
Чтобы на клиенте появилась таблица, создают реквизит формы. Есть два пути: в конфигураторе и программно.
В конфигураторе
- Откройте форму в конфигураторе, вкладка Реквизиты.
- Добавьте реквизит, например
Товары, тип — ТаблицаЗначений. - Раскройте реквизит и добавьте колонки:
Наименование(Строка),Количество(Число 15.3),Цена(Число 15.2),Сумма(Число 15.2). - Перетащите реквизит на вкладку Элементы. Появится таблица формы со всеми колонками.
Колонки задаются только здесь или на сервере. На клиенте добавить колонку в реквизит нельзя: у ДанныеФормыКоллекция нет свойства Колонки.
💡 Табличная часть объекта (Объект.Товары в форме документа) на клиенте ведёт себя так же, как реквизит типа «Таблица значений». Всё, что написано ниже, относится и к ней.
Программно, когда состав колонок заранее неизвестен
Колонки по данным (склады, периоды, виды цен) создают на сервере методом ИзменитьРеквизиты(). После этого реквизит нужно вывести на форму элементами.
&НаСервере
Процедура СоздатьТаблицуОстатков()
Добавляемые = Новый Массив;
Добавляемые.Добавить(Новый РеквизитФормы("Остатки",
Новый ОписаниеТипов("ТаблицаЗначений"), , "Остатки"));
Добавляемые.Добавить(Новый РеквизитФормы("Товар",
Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(150)), "Остатки", "Товар"));
Добавляемые.Добавить(Новый РеквизитФормы("Количество",
Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)), "Остатки", "Количество"));
ИзменитьРеквизиты(Добавляемые);
ТаблицаФормы = Элементы.Добавить("Остатки", Тип("ТаблицаФормы"));
ТаблицаФормы.ПутьКДанным = "Остатки";
Для Каждого ИмяКолонки Из СтрРазделить("Товар,Количество", ",") Цикл
Поле = Элементы.Добавить("Остатки" + ИмяКолонки, Тип("ПолеФормы"), ТаблицаФормы);
Поле.Вид = ВидПоляФормы.ПолеВвода;
Поле.ПутьКДанным = "Остатки." + ИмяКолонки;
КонецЦикла;
КонецПроцедуры
Вызывайте такую процедуру из ПриСозданииНаСервере. Повторный вызов с тем же именем реквизита даст ошибку — проверяйте, создан ли он уже.
Заполнение таблицы данными на клиенте
На клиенте строки добавляют, меняют и удаляют напрямую. Синтаксис почти как у таблицы значений.
Добавление строки по кнопке
&НаКлиенте
Процедура ДобавитьПозицию(Команда)
НоваяСтрока = Товары.Добавить();
НоваяСтрока.Наименование = "Бумага А4";
НоваяСтрока.Количество = 5;
НоваяСтрока.Цена = 350;
НоваяСтрока.Сумма = НоваяСтрока.Количество * НоваяСтрока.Цена; // 1750
Элементы.Товары.ТекущаяСтрока = НоваяСтрока.ПолучитьИдентификатор();
КонецПроцедуры
Для позиционирования используйте идентификатор строки, а не индекс. Индекс меняется при удалении и вставке строк, идентификатор — нет.
Пересчёт при изменении поля
Обработчик ПриИзменении колонки работает с текущими данными таблицы формы:
&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
ТекущиеДанные.Сумма = ТекущиеДанные.Количество * ТекущиеДанные.Цена;
КонецПроцедуры
Такой пересчёт не требует обращения к серверу. Форма отвечает мгновенно даже в веб-клиенте.
Заполнение из запроса
Данные из базы на клиенте не получить: запросы выполняются только на сервере. Схема такая: клиентская команда вызывает серверную процедуру, та выгружает результат запроса в реквизит.
&НаКлиенте
Процедура Заполнить(Команда)
ЗаполнитьНаСервере();
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьНаСервере()
Запрос = Новый Запрос("ВЫБРАТЬ ... ");
ЗначениеВРеквизитФормы(Запрос.Выполнить().Выгрузить(), "Товары");
КонецПроцедуры
Колонки результата запроса должны называться так же, как колонки реквизита. Поля с другими именами в форму не попадут, а лишние колонки реквизита останутся пустыми.
Методы обработки строк в клиентском коде
У ДанныеФормыКоллекция набор методов меньше, чем у таблицы значений. На клиенте уверенно работают:
- ➕
Добавить(),Вставить(Индекс)— новая строка в конец или в позицию; - ➖
Удалить(Строка или Индекс),Очистить(); - 🔢
Количество(),Индекс(Строка), обращение по индексуТовары[0]; - 🔍
НайтиСтроки(Структура)— возвращает массив строк по отбору; - 🆔
НайтиПоИдентификатору(Ид)и у строкиПолучитьИдентификатор(); - ↕️
Сдвинуть(Строка, Смещение)— перемещение строки вверх или вниз.
Поиск строк по отбору
Отбор = Новый Структура("Наименование", "Бумага А4");
Найденные = Товары.НайтиСтроки(Отбор);
Если Найденные.Количество() > 0 Тогда
Найденные[0].Количество = Найденные[0].Количество + 1;
КонецЕсли;
Удаление строк по условию
Удаляют с конца, иначе после каждого удаления индексы съезжают и часть строк пропускается. Границу цикла считают один раз до начала:
Всего = Товары.Количество();
Для Сч = 1 По Всего Цикл
Строка = Товары[Всего - Сч];
Если Строка.Количество = 0 Тогда
Товары.Удалить(Строка);
КонецЕсли;
КонецЦикла;
При пяти строках индексы пойдут 4, 3, 2, 1, 0. Удаление строки с индексом 3 не трогает строки 0–2, которые ещё впереди.
Итог по колонке
Метода Итог() на клиенте нет. Для подвала таблицы или проверки перед записью сумму считают циклом:
ВсегоСумма = 0;
Для Каждого Строка Из Товары Цикл
ВсегоСумма = ВсегоСумма + Строка.Сумма;
КонецЦикла;
💡 Чтобы показать итог под таблицей, включите у таблицы формы свойство Подвал и укажите у колонки путь к данным подвала — числовой реквизит формы, в который этот цикл кладёт сумму.
Что делать только на сервере
Свернуть(), Итог(), ВыгрузитьКолонку(), Скопировать(), Колонки.Добавить(), Индексы.Добавить() — методы таблицы значений, на клиенте их нет. Сортировку и группировку больших объёмов тоже надёжнее делать на сервере: там доступна полная таблица значений и запрос с СГРУППИРОВАТЬ ПО.
Передача табличных данных на сервер и обратно
Есть четыре рабочих способа. Выбор зависит от объёма данных и от того, нужен ли серверу контекст формы.
| Способ | Когда подходит | Минус |
|---|---|---|
Контекстный вызов &НаСервере | Данные уже в реквизите формы | На сервер уходят все данные формы |
&НаСервереБезКонтекста + массив структур | Нужна часть строк, форма тяжёлая | Код преобразования туда и обратно |
| Временное хранилище | Большая таблица живёт между вызовами | Нужно следить за адресом |
ХранилищеЗначения | Сохранить таблицу в реквизите объекта | На клиенте содержимое не прочитать |
Контекстный вызов
В процедуре &НаСервере реквизит превращается в настоящую таблицу значений:
&НаСервере
Процедура СвернутьНаСервере()
ТЗ = РеквизитФормыВЗначение("Товары");
ТЗ.Свернуть("Наименование, Цена", "Количество, Сумма");
ЗначениеВРеквизитФормы(ТЗ, "Товары");
КонецПроцедуры
Для табличной части объекта то же делают через Объект.Товары.Выгрузить() и Объект.Товары.Загрузить(ТЗ). Оба метода работают только на сервере.
Без контекста: массив структур
Если в форме тысячи строк, а серверу нужна сотня, контекстный вызов гоняет лишнее. Передайте только нужное — массив структур. Массив, структура, строки, числа и ссылки передаются между клиентом и сервером без ограничений.
&НаКлиенте
Процедура Проверить(Команда)
Строки = Новый Массив;
Для Каждого Строка Из Товары Цикл
Если Строка.Сумма > 10000 Тогда
Строки.Добавить(Новый Структура("Наименование, Сумма",
Строка.Наименование, Строка.Сумма));
КонецЕсли;
КонецЦикла;
Результат = ПроверитьНаСервере(Строки);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПроверитьНаСервере(Знач Строки)
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка"));
ТЗ.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));
Для Каждого Элемент Из Строки Цикл
ЗаполнитьЗначенияСвойств(ТЗ.Добавить(), Элемент);
КонецЦикла;
// обработка таблицы
Возврат ТЗ.Итог("Сумма");
КонецФункции
Обратное преобразование — таблицу значений в массив структур — делают на сервере перед возвратом результата:
Функция ТаблицаВМассив(ТЗ)
Результат = Новый Массив;
Для Каждого Строка Из ТЗ Цикл
Элемент = Новый Структура;
Для Каждого Колонка Из ТЗ.Колонки Цикл
Элемент.Вставить(Колонка.Имя, Строка[Колонка.Имя]);
КонецЦикла;
Результат.Добавить(Элемент);
КонецЦикла;
Возврат Результат;
КонецФункции
Эту функцию размещают в серверном общем модуле или вызывают из серверной процедуры формы. С клиента вызвать её нельзя: параметр — таблица значений.
Временное хранилище
Когда таблица большая и нужна в нескольких серверных вызовах, её кладут во временное хранилище. Клиент хранит только адрес — обычную строку.
&НаСервере
Процедура ПодготовитьДанные()
ТЗ = ПолучитьТаблицуИзЗапроса(); // ваша функция
АдресТаблицы = ПоместитьВоВременноеХранилище(ТЗ, УникальныйИдентификатор);
КонецПроцедуры
&НаСервереБезКонтекста
Функция КоличествоСтрок(Адрес)
ТЗ = ПолучитьИзВременногоХранилища(Адрес);
Возврат ТЗ.Количество();
КонецФункции
АдресТаблицы — строковый реквизит формы. Второй параметр УникальныйИдентификатор привязывает данные к форме: хранилище очистится, когда форму закроют.
Сериализация в строку
ЗначениеВСтрокуВнутр() и СериализаторXDTO работают на сервере. Сериализовать таблицу на клиенте нечем, потому что на клиенте нет самой таблицы. Строку из ЗначениеВСтрокуВнутр() можно передать на клиент и вернуть обратно, но для формы проще временное хранилище: адрес короче, а данные не гоняются по сети.
Чем клиентская коллекция отличается от таблицы значений
Сводка ограничений, на которые чаще всего натыкаются:
- 🚫 нельзя создать коллекцию в коде клиента — только реквизит формы;
- 🚫 нельзя добавить или удалить колонку на клиенте;
- 🚫 нет
Свернуть,Итог,Скопировать,ВыгрузитьКолонку, индексов; - 🚫 функция с директивой
&НаСерверене может вернуть на тонкий клиент таблицу значений; - ⚠️ каждый контекстный серверный вызов передаёт все данные формы, включая большие таблицы;
- ✅ значения строк — те же типы, что у колонок реквизита, включая ссылки;
- ✅ изменения, сделанные на клиенте, сервер увидит при следующем контекстном вызове.
Из-за третьего пункта работает простое правило. Логику, которой нужны методы таблицы значений, пишите в серверной процедуре сразу. Не пытайтесь эмулировать Свернуть() на клиенте вложенными циклами: это медленно и плохо читается.
Ошибки при работе с таблицей на клиенте и как их найти
Как отлаживать
- Поставьте точку останова в клиентской процедуре и посмотрите переменную в окне Вычислить выражение (
Shift+F9). Тип покажетДанныеФормыКоллекция— это нормально. - Чтобы увидеть таблицу целиком, остановитесь в серверной процедуре после
РеквизитФормыВЗначение(). Отладчик покажет полноценную таблицу значений со всеми колонками. - Запустите
Конфигурация → Проверка конфигурациис флажками для тонкого клиента и веб-клиента. Обращения к серверным типам в клиентском коде найдутся ещё до запуска. - Если форма тормозит, включите замер производительности и посмотрите, сколько времени уходит на серверные вызовы формы. Объём передаваемых данных обычно виден по их числу и длительности.
Частые вопросы
Можно ли использовать таблицу значений в клиентском общем модуле?
Как передать таблицу в другую форму при открытии?
ПриСозданииНаСервере новой формы восстановите таблицу значений.Почему изменения, сделанные на клиенте, не видны в процедуре &НаСервереБезКонтекста?
&НаСервере.Как сохранить таблицу значений в реквизите справочника или документа?
Новый ХранилищеЗначения(ТЗ) и запишите в реквизит типа «Хранилище значения». Получить таблицу обратно — метод Получить(), тоже на сервере.