.. _form_scripting-label: Скриптинг в формах ------------------ Основные требования и некоторые детали задачи описаны в соответствующей постановке: `«Скриптинг в формах» `_ В данном же документе описываются все используемые для скриптинга модели, свойства и методы. Примеры использования скриптинга можно найти в разделе :ref:`extfp-usecases-label`. **Используемые технологии и библиотеки:** * `jQuery` * `Underscore` - утилиты * `Backbone` - UI компоненты * `Marionette` - UI компоненты * `jQuery ui` - UI компоненты * `math.js` - поддержка математики больших чисел * `XregExp.js` - поддержка более сложных регулярных выражений Схемы работы проигрывателя ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. figure:: _static/img/schema_1.png Общая схема работы проигрывателя .. figure:: _static/img/schema_2.png Процесс изменения модели .. figure:: _static/img/schema_3.png Процесс изменения значения компонента .. figure:: _static/img/schema_4.png Взаимодействие со средой Общедоступные объекты ~~~~~~~~~~~~~~~~~~~~~ * ``AS`` - общее пространство имен * ``AS.FORMS`` - проигрыватель, компоненты форм, утилиты * ``AS.SERVICES`` - сервисы * ``AS.LOGGER`` - логгер собщений и ошибок исполнения * ``AS.OPTIONS`` - параметры приложения .. * ``AS.COMPONENTS`` - компоненты, которые могут быть использованы отдельно от проигрывателя форм Особенности реализации ~~~~~~~~~~~~~~~~~~~~~~ В области видимости скрипта компонента имеются следующие переменные: * ``model`` - модель текущего компонента; * ``view`` - отображение текущего компонента; * ``editable`` - режим (просмотр / редактирование); * ``model.playerModel`` - модель проигрывателя; * ``view.playerView`` - отображение проигрывателя. Скрипт компонента выполняется каждый раз при смене режима проигрывателя (просмотр - редактирование). При этом *модель* компонента остается та же, а *отображение* компонента каждый раз пересоздается. Поэтому при написании скриптов следует учесть следующее: если добавляются или переписываются методы модели, либо происходит подписывание на события другой модели, то рекомендуется использовать следующее: .. code-block:: js if (!model.inited) { //манипулирование моделями model.inited = true; } Чтобы указать значение компонента при создании данных по форме, следует написать скрипт: .. code-block:: js /*подписаться на событие изменения значения модели*/ model.on(AS.FORMS.EVENT_TYPE.valueChange, function(_1, _2, value) { /*если значение меняется на null, то инициализируем значение*/ if (value == null) { console.log('setting init value value', model); model.setValue('2017-08-01 09:00:00'); } }) **"Строгий режим" JavaScript:** Начиная с версии Synergy 3.14, все пользовательские скрипты выполняются с добавлением директивы ``use strict``. Эта директива означает, что соответствующий ей код будет выполнятся в так называемом **"строгом режиме"**, поддерживающем стандарт `JavaScript` `ECMAScript5 `_ .. warning:: Если код скрипта содержит конструкции, не соответствующие стандарту ES5, то они не будут выполняться. Это не является ошибкой Synergy. Пользовательский компонент ~~~~~~~~~~~~~~~~~~~~~~~~~~ Пользовательский компонент (ПК) - это компонент, написанный разработчиком Synergy, который можно использовать на форме либо в ui Synergy. В данной главе речь пойдет о пользовательском компоненте, который будет использован на форме. Для настройки компонента необходимо в разделе Процессы конфигуратора выбрать пункт "Пользовательские компоненты". В области редактирования компонета можно ввести название, код, HTML код и JAVASCRIPT код (js код), а также указать будет ли использован компонент в формах. Как и любой компонент на форме, пользовательский компонент имеет модель ``CustomComponentModel`` и отображение ``CustomComponentView``. .. figure:: _static/img/forms/cmp/custom_cmp_creation.png Схема загрузки пользовательского компонента на форме При создании функции на основе js кода ПК, основной код начинается с объявления переменных ``var model = arguments[0], view = arguments[1], editable = arguments[2];`` Переменная ``model`` хранит значение модели, ``view`` - отображение компонента. Переменная ``editable`` определяет режим отображения: редактирование или чтение. Поскольку схема загрузки ПК на форме отрабатывает каждый раз при изменении режима отображения проигрывателя, то и значения переменных так же будут актуальными. Перед созданием нового компонента необходимо определиться со следующими вопросами: * Какие данные он будет хранить? * Какие ошибки валидации данного компонента существуют? * Как компонент должен выглядеть в режиме просмотра, редактирования, неправильно заполненным в режиме редактирования? Ответив на эти вопросы, можно приступить к написанию компонента. Предположим, нужно хранить в качестве значения компонента 3 поля: * text - введенный текст; * title - подсказка (будет состоять из текста с постфиксом); * info - дополнительная информация. Таким образом, в переменной value модели будет объект, содержащий эти 3 поля. Например: ``value = { text : 1, title : «Подсказка», info : «Дополнительная информация» }`` Данный объект необходимо передавать в метод модели ``setValue``, а получать в методе модели ``getValue``. Чтобы эти данные сохранялись в файл по форме и поднимались при последующем открытии, необходимо реализовать 2 метода модели: * getAsfData(blockNumber) * setAsfData(asfData) Необходимо учесть, что поля сохраняемого объекта asfData могут иметь лишь следующий перечень наименований: * value - обычно это текстовое значение компонента; * key - обычно это значение компонента; * valueID - дополнительный идентификатор; * username - имя пользователя; * userID - идентификатор пользователя; * values - массив строк; * keys - массив строк. Все эти поля необязательны, но объект, сохраняемый в файле по форме, может иметь только такие свойства. Пример реализации этих методов: .. code-block:: js model.getAsfData = function(blockNumber){ if(model.getValue()) { /*следующий метод сформирует правильную запись для сохранения в файле по форме при этом: model.getValue().title — запишется в поле value model.getValue().text — запишется в поле key*/ var result = AS.FORMS.ASFDataUtils.getBaseAsfData(model.asfProperty, blockNumber, model.getValue().title , model.getValue().text); /* дописываем необходимую информацию в поле valueID*/ result.valueID = model.getValue().info ; return result; } else { return AS.FORMS.ASFDataUtils.getBaseAsfData(model.asfProperty, blockNumber); } }; model.setAsfData = function(asfData){ if(!asfData || !asfData.value) { return; } /*читаем данные из объекта из файла по форме: дополнительная информация была сохранена в поле valueID и теперь читаем из него*/ var value = { text : asfData.key, title : asfData.value, info : asfData.valueID}; model.setValue(value); }; Далее необходимо определить список специальных ошибок. Для этого необходимо переопределить метод модели getSpecialErrors. .. code-block:: js model.getSpecialErrors = function() { if(model.getValue()) { if(model.getValue().text == '0') { return {id : model.asfProperty.id, errorCode : AS.FORMS.INPUT_ERROR_TYPE.wrongValue}; } } }; В данном примере проверяется, является ли значение равным 0. Если да, то это значит, что компонент неправильно заполнен и возвращается ошибка. Synergy при этом будет показывать, что данные заполнены некорректно. Работа с моделью теперь завершена. Далее будем работать с отображением. Предположим, что на вопрос №3 даны следующие ответы: * В режиме просмотра компонент должен представлять собой просто подпись. * В режиме редактирования - это поле ввода. * Необходимо отображать подсказку над полем ввода и подписью * Неправильно заполненное поле должно подсвечивать красным кнопку компонента. * Необходимо инициализировать отображение, в зависимости от режима (просмотр или редактирование). В области видимости есть переменная editable: * editable = false соответствует режиму просмотра; * editable = true соответствует режиму редактирования. HTML кодом для компонента будет следующим: .. code-block:: xml
Для режима просмотра берем div с innerId label, куда будет вставлено тестовое описание поля, и реализовать метод updateValueFromModel. Для режима редактирования берем компонент input и выполняем те же действия. Пример: .. code-block:: js var label = jQuery(view.container).children("[innerId='label']"); var input = jQuery(view.container).children("[innerId='input']"); if (editable) { label.hide(); input.show(); } else { label.show(); input.hide(); } // метод обновления отображения согласно изменившимся данным view.updateValueFromModel = function () { if (model.getValue()) { label.html(model.getValue().text); label.attr("title", model.getValue().title); input.val(model.getValue().text); input.attr("title", model.getValue().title); } else { label.html(""); input.val(""); } }; /** * при вводе в input изменяем значение модели */ input.on("input", function () { var value = {text : input.val(), title : input.val() + " " + "postfix title", info : "additional info"}; model.setValue(value); }); // подписываемся на событие модели об изменении, чтобы записать в label и input актуальные данные model.on(AS.FORMS.EVENT_TYPE.valueChange, function () { view.updateValueFromModel(); }); // подписываем на событие подгрузки для актуализации label и input model.on(AS.FORMS.EVENT_TYPE.dataLoad, function () { view.updateValueFromModel(); }); При любом изменении модели автоматически вызовется метод updateValueFromModel и значение изменится. Реализуем методы markInvalid, unmarkInvalid. Пример: .. code-block:: js /** * метод помечает поле как неправильно заполненное */ view.markInvalid = function(){ label.css("background-color", "#aa3344"); input.css("background-color", "#aa3344"); }; /** * метод убирает пометку неправильно заполненного поля */ view.unmarkInvalid = function(){ input.css("background-color", ""); label.css("background-color", ""); }; При сохранении данных по форме компонент будет хранить значение в следующем виде: .. code-block:: js { "id":"custom-ch8p9w", "type":"custom", "value":"11111 postfix title", "key":"11111", "valueID":"additional info" } Полный js-код компонента: .. code-block:: js model.getAsfData = function(blockNumber){ if(model.getValue()) { /*следующий метод сформирует правильную запись для сохранения в файле по форме при этом: model.getValue().title — запишется в поле value model.getValue().text — запишется в поле key*/ var result = AS.FORMS.ASFDataUtils.getBaseAsfData(model.asfProperty, blockNumber, model.getValue().title , model.getValue().text); /* дописываем необходимую информацию в поле valueID*/ result.valueID = model.getValue().info ; return result; } else { return AS.FORMS.ASFDataUtils.getBaseAsfData(model.asfProperty, blockNumber); } }; model.setAsfData = function(asfData){ if(!asfData || !asfData.value) { return; } /*читаем данные из объекта из файла по форме: дополнительная информация была сохранена в поле valueID и теперь читаем из него*/ var value = { text : asfData.key, title : asfData.value, info : asfData.valueID}; model.setValue(value); }; model.getSpecialErrors = function() { if(model.getValue()) { if(model.getValue().text == '0') { return {id : model.asfProperty.id, errorCode : AS.FORMS.INPUT_ERROR_TYPE.wrongValue}; } } }; var label = jQuery(view.container).children("[innerId='label']"); var input = jQuery(view.container).children("[innerId='input']"); if (editable) { label.hide(); input.show(); } else { label.show(); input.hide(); } // метод обновления отображения согласно изменившимся данным view.updateValueFromModel = function () { console.log(model.getValue()); if (model.getValue()) { label.html(model.getValue().text); label.attr("title", model.getValue().title); input.val(model.getValue().text); input.attr("title", model.getValue().title); } else { label.html(""); input.val(""); } }; // подписываемся на событие модели об изменении, чтобы записать в label и input актуальные данные model.on(AS.FORMS.EVENT_TYPE.valueChange, function () { view.updateValueFromModel(); }); // подписываем на событие подгрузки для актуализации label и input model.on(AS.FORMS.EVENT_TYPE.dataLoad, function () { view.updateValueFromModel(); }); /** * метод помечает поле как неправильно заполненное */ view.markInvalid = function(){ label.css("background-color", "#aa3344"); input.css("background-color", "#aa3344"); }; /** * метод убирает пометку неправильно заполненного поля */ view.unmarkInvalid = function(){ input.css("background-color", ""); label.css("background-color", ""); }; /** * при вводе в input изменяем значение модели */ input.on("input", function () { var value = {text : input.val(), title : input.val() + " postfix", info : "additional info"}; model.setValue(value); }); view.updateValueFromModel(); .. toctree:: :maxdepth: 2 :numbered: forms/user-cmp/button-cmp forms/user-cmp/registry-chooser forms/user-cmp/funnel Справочник API ~~~~~~~~~~~~~~~~ .. toctree:: :maxdepth: 2 :numbered: forms/options forms/player forms/cmp/index forms/services forms/utils forms/logger forms/examples