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