3. Скриптинг в формах

Основные требования и некоторые детали задачи описаны в соответствующей постановке: «Скриптинг в формах»

В данном же документе описываются все используемые для скриптинга модели, свойства и методы.

Примеры использования скриптинга можно найти в разделе Варианты использования внешнего проигрывателя форм.

Используемые технологии и библиотеки:

  • jQuery
  • Underscore - утилиты
  • Backbone - UI компоненты
  • Marionette - UI компоненты
  • jQuery ui - UI компоненты
  • math.js - поддержка математики больших чисел
  • XregExp.js - поддержка более сложных регулярных выражений

3.1. Схемы работы проигрывателя

_images/schema_1.png

Общая схема работы проигрывателя

_images/schema_2.png

Процесс изменения модели

_images/schema_3.png

Процесс изменения значения компонента

_images/schema_4.png

Взаимодействие со средой

3.2. Общедоступные объекты

  • AS - общее пространство имен
  • AS.FORMS - проигрыватель, компоненты форм, утилиты
  • AS.SERVICES - сервисы
  • AS.LOGGER - логгер собщений и ошибок исполнения
  • AS.OPTIONS - параметры приложения

3.3. Особенности реализации

В области видимости скрипта компонента имеются следующие переменные:

  • model - модель текущего компонента;
  • view - отображение текущего компонента;
  • editable - режим (просмотр / редактирование);
  • model.playerModel - модель проигрывателя;
  • view.playerView - отображение проигрывателя.

Скрипт компонента выполняется каждый раз при смене режима проигрывателя (просмотр - редактирование). При этом модель компонента остается та же, а отображение компонента каждый раз пересоздается. Поэтому при написании скриптов следует учесть следующее: если добавляются или переписываются методы модели, либо происходит подписывание на события другой модели, то рекомендуется использовать следующее:

if (!model.inited) {
    //манипулирование моделями
    model.inited = true;
}

Чтобы указать значение компонента при создании данных по форме, следует написать скрипт:

/*подписаться на событие изменения значения модели*/
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

Предупреждение

Если код скрипта содержит конструкции, не соответствующие стандарту ES5, то они не будут выполняться. Это не является ошибкой Synergy.

3.4. Пользовательский компонент

Пользовательский компонент (ПК) - это компонент, написанный разработчиком Synergy, который можно использовать на форме либо в ui Synergy. В данной главе речь пойдет о пользовательском компоненте, который будет использован на форме. Для настройки компонента необходимо в разделе Процессы конфигуратора выбрать пункт «Пользовательские компоненты».

В области редактирования компонета можно ввести название, код, HTML код и JAVASCRIPT код (js код), а также указать будет ли использован компонент в формах.

Как и любой компонент на форме, пользовательский компонент имеет модель CustomComponentModel и отображение CustomComponentView.

_images/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 - массив строк.

Все эти поля необязательны, но объект, сохраняемый в файле по форме, может иметь только такие свойства.

Пример реализации этих методов:

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.

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 кодом для компонента будет следующим:

<div innerId = 'label'></div>
<input innerId = 'input' type="text" class="asf-textBox"
       style="text-align: left; font-family: Arial, sans-serif; font-size: 12px;">

Для режима просмотра берем div с innerId label, куда будет вставлено тестовое описание поля, и реализовать метод updateValueFromModel. Для режима редактирования берем компонент input и выполняем те же действия.

Пример:

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.

Пример:

/**
 * метод помечает поле как неправильно заполненное
 */
view.markInvalid = function(){
   label.css("background-color", "#aa3344");
   input.css("background-color", "#aa3344");
};
/**
 * метод убирает пометку неправильно заполненного поля
 */
view.unmarkInvalid = function(){
    input.css("background-color", "");
    label.css("background-color", "");
};

При сохранении данных по форме компонент будет хранить значение в следующем виде:

{
    "id":"custom-ch8p9w",
    "type":"custom",
    "value":"11111 postfix title",
    "key":"11111",
    "valueID":"additional info"
}

Полный 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();