Как правильно заполнить реквизиты табличной части в управляемых формах 1С 8.3

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Разработка в среде управляемого приложения 1С:Предприятие 8.3 требует от программиста четкого понимания клиент-серверной архитектуры. Одной из наиболее частых задач является заполнение реквизитов строки табличной части при изменении ключевого значения (например, при выборе номенклатуры или составляющего элемента). Начинающие разработчики часто сталкиваются с техническими трудностями и ошибками производительности, пытаясь выполнить эти операции «напрямую».

В этой статье мы подробно проанализируем типичные ошибки, которые допускают программисты при работе с табличными частями на управляемых формах, выясним причины их возникновения и разберем два правильных, наиболее производительных подхода к решению этой задачи.

Рассмотрим исходную проблему. У нас есть табличная часть Составляющие. При изменении реквизита Наименование (который имеет ссылочный тип, например, справочник) нам необходимо автоматически заполнить реквизит Производитель этой же строки, а также добавить в табличную часть подчиненные элементы из состава этого наименования.

Анализ типичных ошибок разработчика

Прежде чем перейти к правильным алгоритмам, давайте разберем, какие архитектурные ошибки часто совершаются на практике, и почему код из обсуждения на форуме считается неоптимальным.

Ошибка 1. Обращение «через точку» на клиенте. Попытка написать на клиенте конструкцию вида ТекДанные.Наименование.Производитель приводит к ошибке выполнения. В управляемом приложении на стороне клиента (в тонком или веб-клиенте) ссылочные данные представляют собой лишь легковесные идентификаторы (ссылки). Они не содержат внутри себя значений реквизитов объекта БД. Чтобы получить значение реквизита по ссылке, необходимо обратиться к серверу.

Ошибка 2. Передача клиентского объекта строки на сервер. Переменная ТекущиеДанные (получаемая через Элементы.ИмяТаблицы.ТекущиеДанные) на клиенте имеет тип ДанныеФормыСтруктура. Передавать ее напрямую в процедуры с директивой &НаСервереБезКонтекста нельзя. При попытке передать ее в контекстный вызов &НаСервере платформа будет выполнять избыточную сериализацию, нагружая сеть.

Ошибка 3. Множественные контекстные серверные вызовы. В промежуточном решении автора (из Сообщения 4) при изменении одного реквизита происходят сразу два последовательных серверных вызова подряд: сначала функция ПолучитьПроизводителя(), а затем процедура ЗаполнитьНаСервере(). При каждом контекстном вызове (&НаСервере) вся форма целиком (все ее реквизиты, настройки, элементы и строки табличных частей) преобразуется в XML/JSON, передается по сети на сервер, там распаковывается, обрабатывается, снова упаковывается и возвращается обратно на клиент. На реальной базе в многопользовательском режиме это неизбежно приведет к ощутимым задержкам интерфейса («зависаниям» формы при вводе данных).


Способ 1. Получение данных без контекста формы и заполнение на клиенте (Рекомендуемый)

Платформа 1С позволяет полноценно работать с коллекциями формы на клиенте. Мы можем добавлять новые строки в табличную часть (имеющую тип ДанныеФормыКоллекция) непосредственно в клиентском коде с помощью метода Добавить(). Это дает нам возможность реализовать максимально быстрый алгоритм:

  1. На клиенте мы получаем ссылку на выбранный элемент.
  2. Одним легким неконтекстным вызовом &НаСервереБезКонтекста передаем эту ссылку на сервер.
  3. На сервере без лишней нагрузки на контекст формы считываем нужные данные из базы данных (значение производителя и составные части) и упаковываем их в структуру или массив структур.
  4. Возвращаем эту структуру на клиент и заполняем строки табличной части прямо на клиенте.

Разберем этот метод по шагам на примере кода:


&НаКлиенте
Процедура СоставляющиеНаименованиеПриИзменении(Элемент)
    
    // Получаем текущую строку табличной части
    ТекДанные = Элементы.Составляющие.ТекущиеДанные;
    
    // Проверяем, заполнена ли строка и само наименование
    Если ТекДанные = Неопределено Или Не ЗначениеЗаполнено(ТекДанные.Наименование) Тогда
        Возврат;
    КонецЕсли;
    
    // Выполняем ОДИН легкий неконтекстный вызов сервера
    ДанныеЗаполнения = ПолучитьДанныеДляЗаполнения(ТекДанные.Наименование);
    
    // Заполняем реквизиты текущей строки на клиенте
    ТекДанные.Производитель = ДанныеЗаполнения.Производитель;
    
    // Очищать или не очищать ТЧ перед добавлением новых строк - зависит от бизнес-логики.
    // Если нужно просто добавить новые элементы состава:
    Для Каждого СтрокаСостава Из ДанныеЗаполнения.МассивСостава Цикл
        
        НоваяСтрока = Объект.Составляющие.Добавить();
        НоваяСтрока.Наименование  = СтрокаСостава.Наименование;
        НоваяСтрока.Основное      = СтрокаСостава.Основное;
        НоваяСтрока.Производитель = ДанныеЗаполнения.Производитель;
        
    КонецЦикла;
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьДанныеДляЗаполнения(СсылкаНаименование)
    
    // Создаем структуру для возврата данных на клиент
    Результат = Новый Структура;
    Результат.Вставить("Производитель",  СсылкаНаименование.Производитель);
    
    МассивСостава = Новый Массив;
    
    // Обходим табличную часть справочника на сервере
    Для Каждого Стр Из СсылкаНаименование.СоставныеЧасти Цикл
        
        СтрокаСтруктуры = Новый Структура;
        СтрокаСтруктуры.Вставить("Наименование", Стр.Наименование);
        // Получаем реквизит "Основное" составной части (тоже через точку, так как мы на сервере)
        СтрокаСтруктуры.Вставить("Основное",     Стр.Наименование.Основное); 
        
        МассивСостава.Добавить(СтрокаСтруктуры);
        
    КонецЦикла;
    
    Результат.Вставить("МассивСостава", МассивСостава);
    
    Возврат Результат;
    
КонецФункции

Этот способ является наиболее производительным, так как объем данных, передаваемый между клиентом и сервером, минимален (передается только одна ссылка туда и одна структура обратно), а контекст формы не затрагивается вовсе.


Способ 2. Одиночный контекстный вызов с передачей идентификатора строки

Бывают ситуации, когда алгоритм заполнения очень сложен: он зависит от других реквизитов шапки документа, требует пересчета итогов формы или выполнения тяжелых процедур, жестко завязанных на контекст. В таких случаях оправдан контекстный вызов &НаСервере, но выполнять его нужно строго один раз за событие.

При этом важно правильно адресовать строку на сервере. Вместо передачи на сервер всей строки (что невозможно сделать напрямую в неконтекстные методы или неэффективно в контекстных) мы передаем числовой Идентификатор строки. На сервере мы находим нужную строку в коллекции формы при помощи метода НайтиПоИдентификатору().

Рассмотрим пример реализации такого подхода:


&НаКлиенте
Процедура СоставляющиеНаименованиеПриИзменении(Элемент)
    
    // Получаем уникальный идентификатор текущей строки на форме (это число)
    ИдентификаторСтроки = Элементы.Составляющие.ТекущаяСтрока;
    
    Если ИдентификаторСтроки = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Вызываем сервер ОДИН раз, передавая только числовой идентификатор
    ЗаполнитьНаСервере(ИдентификаторСтроки);
    
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьНаСервере(ИдентификаторСтроки)
    
    // Находим конкретную строку в контексте формы Объект.Составляющие по ее ID
    ТекСтрокаТЧ = Объект.Составляющие.НайтиПоИдентификатору(ИдентификаторСтроки);
    
    Если ТекСтрокаТЧ = Неопределено Или Не ЗначениеЗаполнено(ТекСтрокаТЧ.Наименование) Тогда
        Возврат;
    КонецЕсли;
    
    // На сервере мы можем безопасно обращаться к реквизитам через точку
    ТекСтрокаТЧ.Производитель = ТекСтрокаТЧ.Наименование.Производитель;
    
    // Добавляем новые строки состава непосредственно в контекст формы
    Для Каждого СтрТз Из ТекСтрокаТЧ.Наименование.СоставныеЧасти Цикл
        
        НоваяСтрока = Объект.Составляющие.Добавить();
        НоваяСтрока.Наименование  = СтрТз.Наименование;
        НоваяСтрока.Основное      = СтрТз.Наименование.Основное;        
        НоваяСтрока.Производитель = ТекСтрокаТЧ.Производитель;        
        
    КонецЦикла;
    
КонецПроцедуры

Этот способ уступает первому по скорости из-за передачи контекста всей формы, однако он значительно проще в написании, когда требуется задействовать другие реквизиты объекта (например, дату документа, склад, организацию) для расчета значений в строках.


Использование инструментов Библиотеки Стандартных Подсистем (БСП)

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

Например, для получения одного значения (производителя) на клиенте можно вызвать встроенный серверный метод БСП:


// Получаем одно значение реквизита "Производитель" для выбранной номенклатуры
Производитель = ОбщегоНазначенияВызовСервера.ЗначениеРеквизитаОбъекта(ТекДанные.Наименование, "Производитель");

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


// Запрашиваем несколько реквизитов одной ссылочной сущности за один вызов
Реквизиты = ОбщегоНазначенияВызовСервера.ЗначенияРеквизитовОбъекта(ТекДанные.Наименование, "Производитель, СтранаПроисхождения");

// Заполняем реквизиты строки
ТекДанные.Производитель = Реквизиты.Производитель;
ТекДанные.Страна = Реквизиты.СтранаПроисхождения;

Использование механизмов БСП позволяет существенно сократить объем кода в модуле формы, повышает читаемость кода и сводит к минимуму вероятность совершения ошибок при ручном написании неконтекстных запросов.

← На главную