суббота, 8 декабря 2012 г.

Пример перевода конфигурации на управляемый интерфейс

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

Данными примерами я хочу показать как можно с наименьшим количеством трудозатрат и изменений кода реализовать управляемый интерфейс, для уже написаной конфигурации. Конечно эта технология не будет оптимальной, и в дальнейшем ее нужно переписать. Однако это позволит в короткие сроки создать управляемый интерфейс для уже существующих документовb справочников.

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


Приведу пример процедуры из конфигурации УТ 10.3:
Процедура ПриИзмененииКонтрагента() Экспорт

 // Выполняем общие действия для всех документов при изменении Контрагент.
 ЗаполнениеДокументов.ПриИзмененииЗначенияКонтрагента(ЭтотОбъект, мСтруктураПараметровДляПолученияДоговора);

 Если НЕ ЗначениеЗаполнено(КонтактноеЛицоКонтрагента) Тогда
  КонтактноеЛицоКонтрагента = Контрагент.ОсновноеКонтактноеЛицо;
 Иначе
  Если КонтактноеЛицоКонтрагента.Владелец  Контрагент Тогда
   КонтактноеЛицоКонтрагента = Контрагент.ОсновноеКонтактноеЛицо;
  КонецЕсли;
 КонецЕсли;КонецПроцедуры // ПриИзмененииКонтрагента()

Данная процедура  находится в модуле формы документов. Скопировав код данной процедуры в модуль управляемой формы. в начале процедуры необходимо добавить строчку:

 ЭтотОбъект = РеквизитФормыВЗначение("Объект");

Проще говоря этой функцией мы получаем привычный(или правильнее сказать обычный для работы в толстом клиенте) нам объект, а не доступный ДанныеФормыСтруктура, Да и еще вызов функции РеквизитФормыВЗначение доступен только на сервере поэтому перед объявлением нашей процедуры нужно поставить директиву компиляции &НаСервере, после этого у полученного объекта можно вызывать экспортные процедуры и функции расположенные в модуле объекта.
Например:

ЭтотОбъект = РеквизитФормыВЗначение("Объект");

мСуммаВсего    = ЭтотОбъект.ПолучитьСуммуСНДС();

В конце выше приведенной процедуры, нужно будет вернуть данный объект на форму сделать это можно процедурой ЗначениеВРеквизитФормы:

ЗначениеВРеквизитФормы(ЭтотОбъект, "Объект") 

И еще поскольку теперь нельзя обращаться на прямую к реквизитам формы или объекта, Нам перед каждым реквизитом нужно вставить слово ЭтотОбъект, в результате мы получили такую процедуру:

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

 Если НЕ ЗначениеЗаполнено(ЭтотОбъект.КонтактноеЛицоКонтрагента) Тогда
  ЭтотОбъект.КонтактноеЛицоКонтрагента = ЭтотОбъект.Контрагент.ОсновноеКонтактноеЛицо;
 Иначе
  Если ЭтотОбъект.КонтактноеЛицоКонтрагента.Владелец  ЭтотОбъект.Контрагент Тогда
   ЭтотОбъект.КонтактноеЛицоКонтрагента = ЭтотОбъект.Контрагент.ОсновноеКонтактноеЛицо;
  КонецЕсли;
 КонецЕсли;КонецПроцедуры // ПриИзмененииКонтрагента()

Согласитесь, что данная методика чем-то напоминает создание аналогичной внешней печатной формы, когда перед всеми рекизитами документа добавляется  ДокументСсылка.

К сожалению данный метод не панацея т.к. работать во всех случаях не будет, например если вызываемый модуль содержит директивы компиляции #Если Клиент Тогда, то они ни когда не будут отработаны, т.к. в данном случае эта процедура будет всегда выполняться на сервере. И как правило в таких местах располагаются интерактивное взаимодействие с клиентом, т.е вывод сообщений или задаются уточняющие  вопросы.
В таких случаях можно создать аналогичный модуль добавив в конце наименование слово “Клиент”, выполняемый только на клиенте и скопировав туда данную функцию,  необходимо внести соответвующие изменения.
Например поскольку данная процедура или функция будет вызываться на клиенте то работать с объектом  ДокументОбъект у нас нет возможности, у нас есть только объект типаДанныеФормыСтруктура, а у данного объекта мы можем обращаться только  к реквизитам “первого уровня”.
поэтому если в процедуре или функции встречается такой код:


Документ.ДоговорКонтрагента.ВидВзаиморасчетов, 

 его следует переписать на:

ОбщегоНазначения.ПолучитьРеквизитЭлемента(Документ.ДоговорКонтрагента,”ВидВзаиморасчетов”) 

Предварительно добавив данную функция в общий модуль(пример кода этой функции приводить не буду т.к. ее можно найти в БСП) Так же в процедурах попадается сравнение с типом, это конструкции вида

ТипЗнч(ДокументОбъект) = Тип("ДокументОбъект.ОтчетОРозничныхПродажах") 

данный код можно переписать на:

ТипЗнч(ДокументОбъект.Ссылка) = Тип("ДокументСсылка.ОтчетОРозничныхПродажах") И ТипЗнч(ДокументОбъект) = Тип("ДанныеФормыСтруктура") 

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

Если ВидСклада = Перечисления.ВидыСкладов.Оптовый Тогда 

 можно переписать так

ВидыСкладов = ОбщегоНазначения.ПолучитьСтруктуруПеречислений("ВидыСкладов")....Если ВидСклада = ВидыСкладов.Оптовый Тогда
 

А в общий модуль вставим процедуру:

Функция ПолучитьСтруктуруПеречислений(ИмяПеречисления) Экспорт
 СписокЗначений = Новый Структура();
 Для Каждого СтрокаЗначения Из Метаданные.Перечисления[ИмяПеречисления].ЗначенияПеречисления Цикл
  СписокЗначений.Вставить(СтрокаЗначения.Имя, Перечисления[ИмяПеречисления][СтрокаЗначения.Имя]);
 КонецЦикла;
 Возврат СписокЗначений;КонецФункции // ПолучитьСтруктуруПеречислений()

Если же обращение происходит всего один раз, тогда можно использовать и другой вариант, это обращение у предопределенной функции ПредопределенноеЗначение - пример использования приведен ниже.

Если в коде процедуры или функции происходит обращение к пустой ссылке или к предопределенному значению, а так-же к значениям перечислений, например

Если Номенклатура = Справочники.Номенклатура.ПустаяСсылка Тогда 

то можно переписать код на

Если Номенклатура =  ПредопределенноеЗначение("Справочник.Номенклатура.ПустаяСсылка") Тогда 

подробнее об этой Функции можно почитать в описании синтакс-помошника.

Так же в типовых конфигурациях  очень часто вызывается обращение к Метаданным что на клиенте впринципе невозможно, как правило эти обращения связаны чтобы проверить есть ли в переданном объекте тот или иной реквизит, т.е если встречается такой код:

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

одним из решений будет получить структуру реквитов объекта. Для этого например в общем модуле вставляем функцию:

 //Возвращает структуру переданного объекта//Функция ПолучитьРеквизитыДокумента(Объект) Экспорт

 МетаданныеОбъекта = Новый Структура;

 Для Каждого РеквизитОбъекта Из Объект.Метаданные().Реквизиты Цикл

  МетаданныеОбъекта.Вставить(РеквизитОбъекта.Имя,РеквизитОбъекта.Имя);

 КонецЦикла;

 ТабличныеЧасти = Новый Структура;

 Для Каждого РеквизитОбъекта Из Объект.Метаданные().ТабличныеЧасти Цикл

  МетаданныеТЧ = Новый Структура;

  Для Каждого РеквизитОбъектаТЧ Из Объект.Метаданные().ТабличныеЧасти[РеквизитОбъекта.Имя].Реквизиты Цикл
   МетаданныеТЧ.Вставить(РеквизитОбъектаТЧ.Имя,РеквизитОбъектаТЧ.Имя);
  КонецЦикла;

  ТабличныеЧасти.Вставить(РеквизитОбъекта.Имя,МетаданныеТЧ);

 КонецЦикла;

 МетаданныеОбъекта.Вставить("ТабличныеЧасти",ТабличныеЧасти);

 Возврат МетаданныеОбъекта;КонецФункции

В результате нам необходимо предварительно получить структуру данного объекта и переписать код на:

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

для конструкций вида:

Метаданные.НайтиПоТипу(ТипЗнч(СсылкаНаОбъект)).Имя 

можно опять написать функцию в общем модуле:

Функция НайтиПоТипу(Значение) Экспорт
 Возврат Метаданные.НайтиПоТипу(ТипЗнч(Значение)).Имя;КонецФункции
 

и изменить код на

ОбщегоНазначения.НайтиПоТипу(СсылкаНаОбъект) 

Так же очень часто в коде выполняются запросы, поскольку выполнение запросов возможно только на сервере, то данный кусок кода необходимо вынести например в новый  модуль выполняемый на сервере а для простоты запоминания можно назвать его аналогично поставив в конце наименования слово “Сервер”

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

&НаСервере
Функция ПолучитьСтруктуруРеквизитовДокумента();

 СтруктураПараметров = новый Структура();
 СтруктураПараметров.Вставить("СпособЗаполненияЦен",Перечисления.СпособыЗаполненияЦен.ПоЦенамНоменклатуры);
 СтруктураПараметров.Вставить("МетаданныеДокумента",ОбщегоНазначения.ПолучитьРеквизитыДокумента(ЭтотОбъект));..
 Возврат СтруктураПараметров;КонецФункции