При работе с платформой 1С:Предприятие 8.3 разработчики часто сталкиваются с необходимостью организовывать взаимодействие между клиентским и серверным кодом, особенно при работе с управляемыми формами. Одна из распространенных проблем, возникающих в этом контексте, – это ошибка "Отсутствует отображение для типа 'ДанныеФормыЭлементКоллекции'". Давайте разберем эту ошибку подробнее и выясним, как ее эффективно устранить.
Платформа 1С:Предприятие 8.3 функционирует в клиент-серверной архитектуре. Это означает, что часть кода выполняется на клиентской машине пользователя (в так называемом "клиентском контексте"), а другая часть – на сервере 1С (в "серверном контексте").
Когда вы работаете с управляемыми формами, данные, отображаемые на форме (например, строки табличной части документа), являются не напрямую объектами базы данных, а их клиентскими представлениями. Эти клиентские представления имеют специальные типы:
ДанныеФормыСтруктура: используется для реквизитов основного объекта формы (например, самого документа).ДанныеФормыКоллекция: используется для табличных частей формы.ДанныеФормыЭлементКоллекции: используется для отдельной строки табличной части формы.Ошибка "Отсутствует отображение для типа 'ДанныеФормыЭлементКоллекции'" возникает, когда клиентский код пытается передать объект одного из этих клиентских типов (в данном случае ДанныеФормыЭлементКоллекции) в серверную процедуру общего модуля, которая не была подготовлена для работы с такими типами данных напрямую. Серверные процедуры оперируют прикладными объектами базы данных (например, ДокументОбъект.ТабличнаяЧасть.Строка), а не их клиентскими представлениями. Платформа пытается автоматически преобразовать (сериализовать) клиентский объект для передачи на сервер, но для ДанныеФормыЭлементКоллекции в этом контексте такого прямого и автоматического отображения на сервере не найдено, что и приводит к ошибке преобразования данных.
Давайте рассмотрим исходный пример, который привел к ошибке. У нас есть обработчик события изменения количества в табличной части формы:
&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
ТекДанные = Элементы.Товары.ТекущиеДанные; // ТекДанные имеет тип ДанныеФормыЭлементКоллекции
АвтоматическийРассчет.ОбновитьСумму(ТекДанные);
КонецПроцедуры
И процедура в общем модуле АвтоматическийРассчет:
Процедура ОбновитьСумму(ТекДанные) Экспорт
ТекДанные.Сумма = ТекДанные.Цена * ТекДанные.Количество;
КонецПроцедуры
В этом сценарии, когда обработчик ТоварыКоличествоПриИзменении вызывается на клиенте, он получает текущую строку табличной части формы в виде объекта ДанныеФормыЭлементКоллекции. Затем этот объект передается в процедуру ОбновитьСумму общего модуля.
Проблема возникает, если общий модуль АвтоматическийРассчет был настроен для выполнения только на сервере (например, у него установлен флаг "Сервер", но не установлен флаг "Клиент"). В такой ситуации, платформа пытается передать объект ТекДанные с клиента на сервер, что вызывает упомянутую ошибку сериализации.
Почему же аналогичный код, как указал автор, работал в другом месте?
&НаКлиенте
Процедура ПереченьНоменклатурыЦенаПриИзменении(Элемент)
СтрокаТабличнойЧасти = Элементы.ПереченьНоменклатуры.ТекущиеДанные;
РаботаСДокументами.РассчитатьСумму(СтрокаТабличнойЧасти);
КонецПроцедуры
Процедура РассчитатьСумму(СтрокаТабличнойЧасти) Экспорт
СтрокаТабличнойЧасти.Сумма = СтрокаТабличнойЧасти.Количество * СтрокаТабличнойЧасти.Цена;
КонецПроцедуры
Вероятнее всего, общий модуль РаботаСДокументами, содержащий процедуру РассчитатьСумму, уже имел корректные настройки, позволяющие ему выполняться на клиенте. Таким образом, передача объекта СтрокаТабличнойЧасти (ДанныеФормыЭлементКоллекции) происходила внутри клиентского контекста, без попытки передачи на сервер, и все работало без ошибок.
Как указал автор темы, решение проблемы заключалось во включении флажка "Клиент" в палитре свойств общего модуля АвтоматическийРассчет. Давайте подробно разберем, что это означает и почему это работает.
Общий модуль в 1С:Предприятии 8.3 может быть скомпилирован для выполнения в различных контекстах. Это определяется флагами в его свойствах:
Когда вы для общего модуля АвтоматическийРассчет установили флажок "Клиент", платформа скомпилировала его процедуры (включая ОбновитьСумму) таким образом, чтобы они могли выполняться на клиентской стороне. В результате, когда обработчик ТоварыКоличествоПриИзменении вызывает АвтоматическийРассчет.ОбновитьСумму(ТекДанные), эта процедура теперь исполняется непосредственно на клиенте. Объект ТекДанные (ДанныеФормыЭлементКоллекции) остается клиентским объектом, и все операции с ним (ТекДанные.Сумма = ТекДанные.Цена * ТекДанные.Количество;) выполняются в том же клиентском контексте, без необходимости передачи данных на сервер и без проблем с сериализацией.
Таким образом, ошибка "Отсутствует отображение для типа 'ДанныеФормыЭлементКоллекции'" исчезает, потому что объект этого типа никогда не покидает клиентский контекст.
Это решение подходит, если процедура общего модуля (в нашем случае ОбновитьСумму) не выполняет никаких действий с базой данных (чтение, запись, запросы к БД). Если бы процедура ОбновитьСумму пыталась, например, записать документ или выполнить запрос к информационной базе, то выполнение ее только на клиенте привело бы к другой ошибке, поскольку клиентский код не имеет прямого доступа к базе данных. В данном случае, так как происходит только расчет суммы на основе уже имеющихся данных, выполнение на клиенте вполне допустимо и эффективно.
Для более полного понимания взаимодействия клиента и сервера, давайте рассмотрим также директивы компиляции, которые используются внутри модулей:
&НаКлиенте:
Эта директива указывает, что процедура или функция должна выполняться на клиенте. Код в такой процедуре имеет доступ к контексту формы (ее реквизитам, элементам управления) и предназначен для работы с пользовательским интерфейсом и локальными данными.
// В общем модуле с установленным флагом "Клиент"
&НаКлиенте
Процедура МояКлиентскаяПроцедура() Экспорт
// Здесь доступен клиентский контекст
КонецПроцедуры
&НаСервере:
Эта директива указывает, что процедура или функция должна выполняться на сервере. Когда клиентский код вызывает процедуру с директивой &НаСервере, платформа автоматически передает весь контекст формы на сервер. На сервере процедура имеет доступ к данным формы и может оперировать ими, а также выполнять операции с базой данных. После завершения выполнения, измененные данные формы могут быть возвращены на клиент.
// В общем модуле с установленными флагами "Сервер" и "Вызов сервера"
&НаСервере
Процедура МояСервернаяПроцедура() Экспорт
// Здесь доступен серверный контекст и данные формы (если вызывается из формы)
// Возможна работа с базой данных
КонецПроцедуры
&НаСервереБезКонтекста:
Эта директива также указывает на выполнение процедуры или функции на сервере, но без автоматической передачи контекста формы. Это очень важная директива для оптимизации производительности и снижения сетевого трафика. Когда клиентский код вызывает такую процедуру, на сервер передаются только явно указанные параметры. Это полезно, когда серверной процедуре требуется лишь несколько значений из формы, а не весь ее объем.
// В общем модуле с установленными флагами "Сервер" и "Вызов сервера"
&НаСервереБезКонтекста
Процедура МояСервернаяПроцедураБезКонтекста(Параметр1, Параметр2) Экспорт
// Здесь доступен только серверный контекст и переданные параметры.
// Нет прямого доступа к реквизитам формы вызывающего окна.
КонецПроцедуры
Важно: Флаги в свойствах общего модуля определяют, где код модуля может быть скомпилирован. Директивы &НаКлиенте, &НаСервере, &НаСервереБезКонтекста внутри модуля определяют, в каком контексте будет выполняться конкретная процедура/функция, при условии, что соответствующие флаги модуля разрешают этот контекст.
Например, процедура с &НаКлиенте в общем модуле, у которого нет флага "Клиент", вызовет ошибку компиляции. Аналогично, вызов клиентским кодом процедуры с &НаСервере из общего модуля, у которого нет флага "Сервер" и "Вызов сервера", также приведет к ошибке.
Хотя установка флажка "Клиент" для модуля АвтоматическийРассчет решила проблему автора, это не всегда является лучшим или единственным решением. Давайте рассмотрим более универсальные и правильные с точки зрения архитектуры подходы.
&НаСервереБезКонтекста)Этот подход является наиболее предпочтительным для повышения производительности и снижения сетевого трафика, особенно когда серверной процедуре требуются только отдельные значения из формы, а не весь ее контекст. Мы передаем на сервер только те данные, которые действительно необходимы для расчета.
На клиенте мы формируем структуру или соответствие с нужными данными и передаем ее на сервер.
// НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
ТекДанные = Элементы.Товары.ТекущиеДанные; // Это ДанныеФормыЭлементКоллекции
// Формируем структуру с необходимыми для расчета данными
ПередаваемыеПараметры = Новый Структура;
ПередаваемыеПараметры.Вставить("Цена", ТекДанные.Цена);
ПередаваемыеПараметры.Вставить("Количество", ТекДанные.Количество);
// Если нужно идентифицировать строку на сервере, можно передать УникальныйИдентификатор
// ПередаваемыеПараметры.Вставить("ИдентификаторСтроки", ТекДанные.УникальныйИдентификатор);
// Вызываем серверную процедуру
АвтоматическийРассчет.ОбновитьСуммуНаСервере(ПередаваемыеПараметры);
// Обновляем сумму на клиенте после получения результата
ТекДанные.Сумма = ПередаваемыеПараметры.Сумма;
КонецПроцедуры
Общий модуль АвтоматическийРассчет должен иметь установленные флаги "Сервер" и "Вызов сервера". Сама процедура должна быть помечена директивой &НаСервереБезКонтекста.
// Общий модуль "АвтоматическийРассчет"
// (свойства: флаги "Сервер" и "Вызов сервера" установлены)
&НаСервереБезКонтекста
Процедура ОбновитьСуммуНаСервере(ПередаваемыеПараметры) Экспорт
// На сервере работаем только с переданной структурой
ПередаваемыеПараметры.Сумма = ПередаваемыеПараметры.Цена * ПередаваемыеПараметры.Количество;
// Если бы потребовалось, здесь мы могли бы:
// - Найти строку в объекте по идентификатору и обновить ее в базе данных (если речь идет о записи)
// - Выполнить запрос к базе данных
// - Выполнить сложные расчеты, требующие серверных мощностей
КонецПроцедуры
Этот подход позволяет избежать проблем с сериализацией клиентских объектов формы и минимизировать объем передаваемых данных, что особенно важно для производительности в высоконагруженных системах.
&НаСервере)Если серверной процедуре нужно изменить данные формы, которые были введены или изменены на клиенте, и эти данные связаны с основным реквизитом формы (например, объектом документа), то можно использовать серверный вызов с передачей контекста. Платформа сама управляет передачей измененных данных формы на сервер и обратно.
Вызов остается почти таким же, но мы вызываем серверную процедуру, которая помечена &НаСервере.
// НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
// Здесь не передаем отдельную строку, а вызываем серверную процедуру
// которая работает с контекстом всей формы
АвтоматическийРассчет.ОбновитьСуммуНаСервереКонтекст();
// После возврата с сервера, данные формы уже обновлены
КонецПроцедуры
Общий модуль АвтоматическийРассчет должен иметь установленные флаги "Сервер" и "Вызов сервера". Сама процедура должна быть помечена директивой &НаСервере. Внутри такой процедуры мы можем получить доступ к данным формы через ее реквизиты.
// Общий модуль "АвтоматическийРассчет"
// (свойства: флаги "Сервер" и "Вызов сервера" установлены)
&НаСервере
Процедура ОбновитьСуммуНаСервереКонтекст() Экспорт
// Здесь мы имеем доступ ко всему контексту формы, включая ее реквизиты.
// Предположим, что основной реквизит формы называется "Объект"
// и это документ, содержащий табличную часть "Товары".
// Чтобы найти нужную строку, нам может потребоваться ее идентификатор
// или другой способ ее определения из текущей строки формы.
// В данном случае, так как мы изменили количество и вызвали сервер из обработчика,
// платформа уже передала измененные данные формы на сервер.
// Мы можем получить ссылку на текущий объект формы
// Если ОбновитьСуммуНаСервереКонтекст вызывается из модуля формы,
// то доступ к реквизитам формы будет прямым.
// Если из общего модуля, то понадобится передать объект формы.
// Пример: если у нас есть доступ к объекту формы как к параметру или через реквизиты формы,
// или если эта процедура вызывается из модуля формы и работает с "Объект" формы.
// Для упрощения, предположим, что мы работаем с реквизитом формы "Товары"
// и нам нужно обновить сумму в текущей строке.
// Для этого нам потребуется явно найти текущую строку на сервере или передать ее идентификатор.
// Если процедура находится в модуле формы:
// Для Каждого СтрокаТЧ Из Объект.Товары Цикл
// Если СтрокаТЧ.УникальныйИдентификатор = ИдентификаторТекущейСтроки Тогда
// СтрокаТЧ.Сумма = СтрокаТЧ.Цена * СтрокаТЧ.Количество;
// Прервать;
// КонецЕсли;
// КонецЦикла;
// Если процедура в общем модуле и вызывается из клиентского кода,
// то для работы с конкретной строкой формы, ее нужно будет передать
// или найти по идентификатору, предварительно передав идентификатор.
// Это возвращает нас к подходу с &НаСервереБезКонтекста.
// Таким образом, для операции с конкретной строкой, &НаСервереБезКонтекста чаще более уместен.
// Если же нам нужно обновить, например, итоговую сумму по всему документу,
// то &НаСервере может быть более удобен:
// ДокументОбъект = РеквизитФормыВЗначение("Объект"); // Преобразование данных формы в прикладной объект
// ДокументОбъект.РассчитатьСуммы(); // Метод прикладного объекта
// ЗначениеВРеквизитФормы(ДокументОбъект, "Объект"); // Обновление данных формы
КонецПроцедуры
Методы РеквизитФормыВЗначение() и ЗначениеВРеквизитФормы() используются для преобразования реквизитов формы (клиентских представлений) в прикладные объекты базы данных на сервере и обратно. Это позволяет работать с полноценными объектами, имеющими все методы и свойства, определенные в конфигурации.
Мы видим, что решение проблемы "Отсутствует отображение для типа 'ДанныеФормыЭлементКоллекции'" кроется в правильной организации клиент-серверного взаимодействия и настройки общих модулей. Установка флажка "Клиент" для модуля, содержащего процедуру расчета, позволила выполнять этот код непосредственно на клиенте, избегая попыток сериализации объекта ДанныеФормыЭлементКоллекции. Однако, для более сложных сценариев, требующих взаимодействия с базой данных или оптимизации трафика, следует использовать серверные вызовы с директивами &НаСервере или &НаСервереБезКонтекста, тщательно продумывая, какие данные и как передавать между клиентом и сервером.
Помните, что осознанный выбор контекста выполнения и методов взаимодействия между клиентом и сервером является ключевым для создания производительных и надежных решений на платформе 1С:Предприятие 8.3.