При разработке внешних отчетов и обработок программисты часто сталкиваются с необходимостью поддержки сразу нескольких режимов работы: обычного приложения (толстый клиент) и управляемого приложения (тонкий и толстый клиенты). Основная сложность заключается в том, что обычные формы не работают в тонком клиенте, а управляемые формы во внешних объектах не всегда корректно ведут себя в обычном приложении без предварительного встраивания в конфигурацию.
Особую остроту проблеме придает невозможность создания клиентских процедур в МодулеОбъекта. Весь код модуля объекта всегда исполняется на сервере, а взаимодействие с локальными ресурсами пользователя (например, работа с COM-объектами MS Word или Excel) требует исполнения кода именно на стороне клиента. В этом материале мы разберем, как избежать дублирования кода и организовать «единое ядро» для клиентской логики во внешнем отчете.
Разберем ситуацию: у нас есть внешний отчет, в котором реализовано формирование печатной формы через COM-объект Word. Этот код должен выполняться на клиенте. Если мы создаем две формы (Обычную и Управляемую), нам приходится копировать один и тот же объемный блок кода в модули обеих форм. Это нарушает принцип DRY (Don't Repeat Yourself) и крайне усложняет поддержку: любое изменение в алгоритме формирования документа придется вносить дважды.
Стандартные механизмы (общие модули) нам не подходят, так как отчет должен оставаться внешним и автономным. МодульМенеджера и МодульОбъекта работают только на сервере. Рассмотрим способы решения этой задачи.
Проанализируем наиболее гибкий, хотя и специфический способ — хранение клиентского кода в виде строки в модуле объекта с последующим его исполнением на клиенте через метод Выполнить().
Суть метода заключается в создании экспортной функции в МодулеОбъекта, которая возвращает текст процедуры. Поскольку текст возвращается на клиент, мы можем запустить его в контексте текущей формы.
Рассмотрим пример реализации. В МодулеОбъекта создадим функцию:
Функция ПолучитьТекстПроцедурыПечати() Экспорт
// Формируем текст кода. Важно удвоить внутренние кавычки
// и использовать вертикальную черту для многострочных строк.
Текст = "
|Отказ = Ложь;
|Попытка
| MSWord = Новый COMОбъект(""Word.Application"");
|Исключение
| Сообщить(""Ошибка запуска Word!"");
| Отказ = Истина;
|КонецПопытки;
|
|Если Не Отказ Тогда
| Док = MSWord.Documents.Add();
| MSWord.Visible = Истина;
| // Дополнительная логика формирования документа...
| Состояние(""Документ сформирован успешно"");
|КонецЕсли;
|";
Возврат Текст;
КонецФункции
Теперь выясним, как вызвать этот код из разных типов форм. В обычной форме (толстый клиент) вызов будет выглядеть максимально просто:
Процедура ДействиеСформировать(Кнопка)
ТекстКода = ЭтотОбъект.ПолучитьТекстПроцедурыПечати();
Выполнить(ТекстКода);
КонецПроцедуры
В управляемой форме нам потребуется сначала получить объект отчета на сервере, а затем выполнить полученную строку на клиенте:
&НаСервере
Функция ПолучитьКодССервера()
// Преобразуем данные формы в объект для доступа к модулю
ОбъектОтчет = РеквизитФормыВЗначение("Объект");
Возврат ОбъектОтчет.ПолучитьТекстПроцедурыПечати();
КонецФункции
&НаКлиенте
Процедура СформироватьДокумент(Команда)
ТекстКода = ПолучитьКодССервера();
Выполнить(ТекстКода);
КонецПроцедуры
Важный нюанс: метод Выполнить() не поддерживает оператор Возврат. Если в вашем исходном коде были выходы из процедуры по условию, их необходимо заменить на структуру Если...Тогда...КонецЕсли или использовать флаги (как показано в примере с переменной Отказ).
Посмотрим на более удобный вариант хранения кода. Если писать код внутри строковой переменной в модуле, мы теряем подсветку синтаксиса и проверку ошибок. Чтобы этого избежать, можно использовать Макет типа «Текстовый документ».
БиблиотекаКлиентскогоКода.
Функция ПолучитьТекстИзМакета() Экспорт
Макет = ЭтотОбъект.ПолучитьМакет("БиблиотекаКлиентскогоКода");
Возврат Макет.ПолучитьТекст();
КонецФункции
Теперь при вызове Выполнить() мы будем получать структурированный и легко читаемый код. Это значительно упрощает отладку, так как вы можете просто скопировать текст из макета в любой модуль, проверить его на наличие ошибок и вернуть обратно.
Проанализируем ситуацию глубже. Если основной причиной использования клиентского кода является работа с MS Word через COM, то в современных конфигурациях от этого стоит отказаться в пользу серверной генерации документов.
Формат .docx представляет собой ZIP-архив с XML-файлами внутри. Платформа 1С позволяет работать с ZIP-архивами (ЗаписьZipФайла, ЧтениеZipФайла) и XML. Рассмотрим преимущества такого подхода:
МодульОбъекта на сервер.ПоместитьВоВременноеХранилище.На клиенте (в любой форме) останется лишь одна строка для открытия файла:
ПолучитьФайл(АдресХранилища, "Отчет.docx", Истина);
При использовании метода Выполнить() крайне важно понимать контекст исполнения. Если код внутри строки обращается к переменным или реквизитам, они должны быть доступны в той процедуре, где вызван Выполнить().
Чтобы сделать код универсальным, рекомендуем использовать структуру параметров. Рассмотрим пример:
// В вызывающей форме
ПараметрыСкрипта = Новый Структура;
ПараметрыСкрипта.Вставить("ДатаОтчета", Объект.Дата);
ПараметрыСкрипта.Вставить("СписокТоваров", Объект.Товары.Выгрузить());
ТекстКода = "
|Для Каждого Строка Из ПараметрыСкрипта.СписокТоваров Цикл
| Сообщить(Строка.Наименование + "" на дату "" + ПараметрыСкрипта.ДатаОтчета);
|КонецЦикла;
|";
Выполнить(ТекстКода);
Таким образом, мы обеспечиваем чистоту кода и минимизируем риск возникновения ошибок из-за отсутствия необходимых переменных в контексте формы.
Мы рассмотрели несколько способов организации общего клиентского кода во внешнем отчете. Выбор конкретного метода зависит от сложности задачи:
Выполнить().Попытка...Исключение), так как отладка такого кода затруднена.Используя данные подходы, вы сможете создавать профессиональные и отказоустойчивые внешние отчеты, работающие в любой среде 1С:Предприятия.