При разработке функционала на управляемых формах в 1С:Предприятие 8.3 разработчики часто сталкиваются с ситуацией, когда серверная процедура или функция, вынесенная в Общий модуль, отказывается работать при вызове с клиента. При этом в модуле формы или модуля объекта код может выполняться без ошибок, а при переносе в общий модуль платформа выдает ошибки компиляции, сообщения вида Переменная не определена, Метод объекта не обнаружен или падает при передаче данных. Разберем по шагам все причины такого поведения и рассмотрим правильные варианты реализации клиент-серверного взаимодействия.
Проанализируем ключевые моменты, которые приводят к сбоям при обращении из клиентского контекста к общему модулю:
&НаКлиенте формы), у общего модуля должны быть установлены флаги Сервер и Вызов сервера. Если флаг «Вызов сервера» снят, клиентское приложение не увидит серверные методы модуля.ОбщийМодульКонтакты.МояФункция()) приведет к ошибке Переменная не определена: ОбщийМодульКонтакты. Для глобальных модулей вызов выполняется напрямую: МояФункция(). В прикладных решениях рекомендуется избегать глобальных модулей и использовать обращение по имени модуля без этого флага.&НаСервере, &НаКлиенте допустимы в тексте общего модуля только тогда, когда у него одновременно установлены флаги «Клиент» и «Сервер». Если модуль объявлен как чисто серверный (стоит только флаг «Сервер»), указание директивы &НаСервере перед методом вызывает синтаксическую ошибку.ТаблицаЗначений доступен исключительно на сервере, в толстом клиенте и во внешнем соединении. В тонком и веб-клиенте такого типа данных не существует. Если серверная функция возвращает ТаблицуЗначений напрямую в клиентский контекст тонкого клиента, платформа генерирует исключение времени выполнения.Рассмотрим правильную конфигурацию общего модуля для выполнения серверных расчетов и их вызова с формы:
&НаКлиенте).Экспорт без директив &НаСервере.Поскольку вернуть ТаблицуЗначений на клиент в тонком клиенте нельзя, наиболее удобным и стандартным решением является её трансформация в Массив, состоящий из объектов Структура. Каждая структура в таком массиве представляет собой отдельную строку таблицы, где ключами являются имена колонок.
Посмотрим на пример универсальной функции преобразования:
Функция ТаблицаЗначенийВМассив(ТаблицаЗначений) Экспорт
Массив = Новый Массив;
СтруктураСтрокой = "";
НужнаЗапятая = Ложь;
Для Каждого Колонка Из ТаблицаЗначений.Колонки Цикл
Если НужнаЗапятая Тогда
СтруктураСтрокой = СтруктураСтрокой + ",";
КонецЕсли;
СтруктураСтрокой = СтруктураСтрокой + Колонка.Имя;
НужнаЗапятая = Истина;
КонецЦикла;
Для Каждого Строка Из ТаблицаЗначений Цикл
НоваяСтрока = Новый Структура(СтруктураСтрокой);
ЗаполнитьЗначенияСвойств(НоваяСтрока, Строка);
Массив.Добавить(НоваяСтрока);
КонецЦикла;
Возврат Массив;
КонецФункции
Теперь разберем, как выглядит расчетная функция в общем модуле ОбщийМодульКонтакты, формирующая и возвращающая такой массив:
Функция КонтактыКлиентаНаСервереМодульОбщий(ВходнаяСтруктура) Экспорт
ВнешниеДанные = Новый ТаблицаЗначений;
ВнешниеДанные.Колонки.Добавить("Партнеры", Новый ОписаниеТипов("СправочникСсылка.Партнеры"));
ВнешниеДанные.Колонки.Добавить("Контрагенты", Новый ОписаниеТипов("СправочникСсылка.Контрагенты"));
Стр = ВнешниеДанные.Добавить();
Стр.Контрагенты = ВходнаяСтруктура.Контрагенты;
Стр.Партнеры = ВходнаяСтруктура.Партнеры;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВнешниеДанные.Контрагенты,
| ВнешниеДанные.Партнеры
|ПОМЕСТИТЬ ВнешниеДанные
|ИЗ
| &ВнешниеДанные КАК ВнешниеДанные
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| КонтрагентыКонтактнаяИнформация.Представление КАК ВидКонтактов,
| КонтрагентыКонтактнаяИнформация.Вид.Наименование КАК Контакты
|ИЗ
| ВнешниеДанные КАК ВнешниеДанные
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты.КонтактнаяИнформация КАК КонтрагентыКонтактнаяИнформация
| ПО ВнешниеДанные.Контрагенты = КонтрагентыКонтактнаяИнформация.Ссылка
|
|ОБЪЕДИНИТЬ
|
|ВЫБРАТЬ
| ПартнерыКонтактнаяИнформация.Представление,
| ПартнерыКонтактнаяИнформация.Вид.Наименование
|ИЗ
| ВнешниеДанные КАК ВнешниеДанные
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Партнеры.КонтактнаяИнформация КАК ПартнерыКонтактнаяИнформация
| ПО ВнешниеДанные.Партнеры = ПартнерыКонтактнаяИнформация.Ссылка";
Запрос.УстановитьПараметр("ВнешниеДанные", ВнешниеДанные);
ТаблицаРезультат = Запрос.Выполнить().Выгрузить();
// Преобразуем результат запроса в массив структур
МассивРезультат = Новый Массив;
Для Каждого СтрокаТаблицы Из ТаблицаРезультат Цикл
МассивРезультат.Добавить(Новый Структура("ВидКонтактов, Контакты", СтрокаТаблицы.ВидКонтактов, СтрокаТаблицы.Контакты));
КонецЦикла;
Возврат МассивРезультат;
КонецФункции
Вызовем подготовленную функцию напрямую из клиентской процедуры в модуле управляемой формы:
&НаКлиенте
Процедура КонтактыКлиента(Команда)
ПараметрыЗапроса = Новый Структура("Партнеры,Контрагенты", Объект.Партнер, Объект.Контрагент);
// Прямой вызов серверной функции общего модуля
МассивКонтактов = ОбщийМодульКонтакты.КонтактыКлиентаНаСервереМодульОбщий(ПараметрыЗапроса);
Для Каждого СтрокаКонтакта Из МассивКонтактов Цикл
Сообщить("Вид: " + СтрокаКонтакта.ВидКонтактов + ", Контакт: " + СтрокаКонтакта.Контакты);
КонецЦикла;
КонецПроцедуры
Если структура колонок простая и не требуется обращение по именам свойств через точку, данные можно упаковать в двумерный массив. Это снижает накладные расходы на создание структур для каждой строки.
Посмотрим на реализацию упаковки на сервере:
Функция ПолучитьКонтактыМассивом(ВходнаяСтруктура) Экспорт
// ... Выполнение запроса и получение ТаблицаРезультат ...
КоличествоСтрок = ТаблицаРезультат.Количество();
ДвумерныйМассив = Новый Массив(КоличествоСтрок, 2);
Индекс = 0;
Для Каждого СтрокаТаблицы Из ТаблицаРезультат Цикл
ДвумерныйМассив[Индекс][0] = СтрокаТаблицы.ВидКонтактов;
ДвумерныйМассив[Индекс][1] = СтрокаТаблицы.Контакты;
Индекс = Индекс + 1;
КонецЦикла;
Возврат ДвумерныйМассив;
КонецФункции
Обработка такого массива на клиенте выполняется с обращением по индексам:
&НаКлиенте
Процедура ОбработатьМассивКлиент(Команда)
ПараметрыЗапроса = Новый Структура("Партнеры,Контрагенты", Объект.Партнер, Объект.Контрагент);
ДвумерныйМассив = ОбщийМодульКонтакты.ПолучитьКонтактыМассивом(ПараметрыЗапроса);
Для Индекс = 0 По ДвумерныйМассив.ВГраница() Цикл
Вид = ДвумерныйМассив[Индекс][0];
Значение = ДвумерныйМассив[Индекс][1];
Сообщить("" + Вид + ": " + Значение);
КонецЦикла;
КонецПроцедуры
Если таблица содержит большой объем данных, который не требуется отображать в интерфейсе тонкого клиента, а необходимо передать в другой серверный контекст или в другую форму, оптимально использовать платформенный механизм временного хранилища. При этом на клиент возвращается только строковый адрес.
Разберем пример реализации:
// В общем модуле (Сервер, Вызов сервера):
Функция ПоместитьТаблицуВХранилище(ВходныеПараметры, УникальныйИдентификаторФормы) Экспорт
// ... Формирование ТаблицаРезультат ...
АдресХранилища = ПоместитьВоВременноеХранилище(ТаблицаРезультат, УникальныйИдентификаторФормы);
Возврат АдресХранилища;
КонецФункции
На клиенте получаем адрес и используем его при открытии отчета или другой формы:
&НаКлиенте
Процедура ОткрытьОтчетПоДанным(Команда)
ПараметрыЗапроса = Новый Структура("Партнеры,Контрагенты", Объект.Партнер, Объект.Контрагент);
АдресВХ = ОбщийМодульКонтакты.ПоместитьТаблицуВХранилище(ПараметрыЗапроса, УникальныйИдентификатор);
ПараметрыОткрытия = Новый Структура("АдресДанных", АдресВХ);
ОткрытьФорму("Отчет.КонтактыКлиентов.Форма", ПараметрыОткрытия);
КонецПроцедуры
Число, Строка, Дата, Булево, Структура, Массив, ссылки объектов). Избегайте передачи объектных типов (ДокументОбъект, СправочникОбъект) и несериализуемых коллекций (ТаблицаЗначений, ДеревоЗначений).&НаСервереБезКонтекста либо прямые вызовы функций общих модулей, чтобы снизить сетевой трафик.ВызовСервера для обращения с клиента.