Создание плагинов для элементов управления формой

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

Что такое элемент управления

Элемент управления формой – это элемент пользовательского интерфейса, предназначенный для помощи авторам контента в управлении и редактировании контента и метаданных. В CMS Studio элементы управления формой должны быть спроектированы таким образом, чтобы быть независимыми от конкретных данных, с которыми они работают, что позволяет их повторное использование с различными наборами данных.

Ниже расположен список встроенных элементов управления:

Элемент управления Описание
Раздел формы Добавление нового раздела на форму создания/редактирования типа контента. Например, разделы “Metadata” и “Content”.
Repeating Group (Повторяющиеся группы) Повторяющиеся группы используются, когда в форме есть один или несколько элементов управления, которые повторяются для сбора одинаковых данных. Например: список изображений в карусели или список виджетов на странице.
Ввод Простая текстовая строка ввода
Числовой ввод Простая строка числового ввода
Текстовая область Простой блок обычного текста
Редактор расширенного текста Блок HTML
Выпадающий список Выпадающий список элементов для выбора
Время Поле для выбора времени
Дата/время Поле для выбора даты и времени
Отметить поле Флажок True\False
Сгруппированные флажки Несколько флажков True\False
Селектор элементов Выбор элемента из списка
Изображение Выбор изображения из источника данных
Видео Выбор видео из источника данных
Перекодированное видео Выбор перекодированного видео из источника данных
Label Отображает текстовый лейбл на форме
Порядок страниц Изменение порядка страниц
Имя файла Простое текстовое имя файла
Автоимя файла Простое текстовое автоматическое имя файла
Внутреннее имя Простое текстовое внутреннее имя
Выбор локали Указание языка, выбранного для контента

Структура плагина элемента управления

Элемент управления формой включает в себя (как минимум):

  • Самостоятельный файл JavaScript, который реализует интерфейс элемента управления.
    • Название файла JavaScript и название элемента управления в конфигурации не обязательно должны совпадать. Файл JS может иметь любое соответствующее название, которое может отличаться от названия элемента управления, указанного в конфигурации.
  • Конфигурацию в проекте CMS Studio для включения элемента управления в использование.

Интерфейс элемента управления

```

/**

 * Constructor: Where .X is substituted with your class name

 * ID is the variable name

 * FORM is the form object

 * OWNER is the parent section/form

 * PROPERTIES is the collection of configured property values

 * CONSTRAINTS is the collection of configured constraint values

 * READONLY is a true/false flag indicating re-only mode

 */

CStudioForms.Controls.X = CStudioForms.Controls.X ||

function(id, form, owner, properties, constraints, readonly)  { }


YAHOO.extend(CStudioForms.Controls.X, CStudioForms.CStudioFormField, {


  /**

   * Return a user friendly name for the control (will show up in content type builder UX)

   */

  getLabel: function() { },


  /**

   * method is called by the engine when the value of the control is changed

   */

  _onChange: function(evt, obj) { },


  /**

   * method is called by the engine to invoke the control to render. The control is responsible for creating and managing its own HTML.

   * CONFIG is a structure containing the form definition and other control configuration

   * CONTAINER EL is the containing element the control is to render in to.

   */

  render: function(config, containerEl) { },


   /**

    * returns the current value of the control

    */

   getValue: function() { },


   /**

    * sets the value of the control

    */

   setValue: function(value) { },


   /**

    * return a string that represents the kind of control (this is the same as the file name)

    */

   getName: function() {  },


   /**

    * return a list of properties supported by the control.

    * properties is an array of objects with the following structure { label: "", name: "", type: "" }

    */

   getSupportedProperties: function() { },


   /**

    * return a list of constraints supported by the control.

    * constraints is an array of objects with the following structure { label: "", name: "", type: "" }

    */

   getSupportedConstraints: function() { }

});

```

Copy-icon

Структура директории плагина Copy-icon

При разработке плагинов JavaScript файлы для плагинов должны соответствовать определенному руководству и размещаться в следующем месте: authoring/static-assets/plugins/{yourPluginId}/control/{yourPluginName}/JS_FILE.js

где:

  • {yourPluginName}: название плагина элемента управления формой
  • JS_FILE.js: JavaScript файл, содержащий реализацию интерфейса элемента управления

Пример создания плагина элемента управления формой Copy-icon

В качестве примера добавим элемент управления с именем text-input в проект под названием “My Editorial”.

Код для элемента управления  Copy-icon

Сначала нужно настроить структуру папок для JS-файла нашего элемента управления. Мы будем следовать руководству о структуре директории плагинов.

В локальной директории создайте файл описания для вашего плагина, назвав его cms-plugin.yaml, и установите plugin.id на ru.dc.cms.plugin.excontrol. Далее создайте папку authoring. Внутри папки authoring создайте папку static-assets, а внутри неё — папку plugins.

Теперь создадим папки, следуя пути plugin ID: ru.dc.cms.plugin.excontrol. Для этого внутри папки plugins создайте папку с именем ru. Внутри ru создайте папку dc. Внутри dc создайте папку cms. Внутри cms создайте папку plugin. В папке plugin создайте папку excontrol. После этого создайте папку control внутри excontrol. Наконец, создайте папку text-input в папке control, которая будет содержать разрабатываемый нами элемент управления. JS-файл, реализующий интерфейс элемента управления, названный в этом примере main.js, будет размещён внутри папки text-input.

<plugin-folder>/
 cms-plugin.yaml
  authoring/
    static-assets/
      plugins/
        ru/
          dc/
cms/ plugin/ excontrol/ control/ text-input/ main.js
Copy-icon

В нашем примере <plugin-folder> находится по адресу /users/myuser/myplugins/form-control-plugin.

В JS файле не забудьте, что CStudioAuthoring.Module является обязательным, и префикс для CStudioAuthoring.Module.moduleLoaded должен соответствовать названию элемента управления. В нашем случае префикс - text-input, как показано в примере.

CStudioForms.Controls.textInput = CStudioForms.Controls.textInput ||

function(id, form, owner, properties, constraints, readonly)  {

    this.owner = owner;

    this.owner.registerField(this);

    this.errors = [];

    this.properties = properties;

    this.constraints = constraints;

    this.inputEl = null;

    this.patternErrEl = null;

    this.countEl = null;

    this.required = false;

    this.value = "_not-set";

    this.form = form;

    this.id = id;

    this.readonly = readonly;


    return this;

}


YAHOO.extend(CStudioForms.Controls.textInput, CStudioForms.CStudioFormField, {


    getLabel: function() {

        return CMgs.format(langBundle, "Text Input");

    },

    .

    .

    .


    getName: function() {

            return "text-input";

    },


    getSupportedProperties: function() {

        return [

                { label: CMgs.format(langBundle, "displaySize"), name: "size", type: "int", defaultValue: "50" },

                { label: CMgs.format(langBundle, "maxLength"), name: "maxlength", type: "int",  defaultValue: "50" },

                { label: CMgs.format(langBundle, "readonly"), name: "readonly", type: "boolean" },

                { label: "Tokenize for Indexing", name: "tokenize", type: "boolean",  defaultValue: "false" }

        ];

    },


    getSupportedConstraints: function() {

        return [

                { label: CMgs.format(langBundle, "required"), name: "required", type: "boolean" },

                { label: CMgs.format(langBundle, "matchPattern"), name: "pattern", type: "string" },

        ];

    }


});


CStudioAuthoring.Module.moduleLoaded("text-input", CStudioForms.Controls.textInput);

Copy-icon

Ниже вы найдете полный пример файла плагина для элемента управления вводом текста:

CStudioForms.Controls.textInput = CStudioForms.Controls.textInput ||

    function(id, form, owner, properties, constraints, readonly)  {

        this.owner = owner;

        this.owner.registerField(this);

        this.errors = [];

        this.properties = properties;

        this.constraints = constraints;

        this.inputEl = null;

        this.patternErrEl = null;

        this.countEl = null;

        this.required = false;

        this.value = "_not-set";

        this.form = form;

        this.id = id;

        this.readonly = readonly;


        return this;

    }


    YAHOO.extend(CStudioForms.Controls.textInput, CStudioForms.CStudioFormField, {


        getLabel: function() {

            return "Text Input";

        },


        _onChange: function(evt, obj) {

            obj.value = obj.inputEl.value;

        

            // Empty error state before new validation (for a clean state)

            YAHOO.util.Dom.removeClass(obj.patternErrEl, 'on');

            obj.clearError('pattern');

        

            var validationExist = false;

            var validationResult = true;

            if (obj.required) {

              if (obj.inputEl.value == '') {

                obj.setError('required', 'Field is Required');

                validationExist = true;

                validationResult = false;

              } else {

                obj.clearError('required');

                validationExist = true;

              }

            }

        

            if ((!validationExist && obj.inputEl.value != '') || (validationExist && validationResult)) {

              for (var i = 0; i < obj.constraints.length; i++) {

                var constraint = obj.constraints[i];

                if (constraint.name == 'pattern') {

                  var regex = constraint.value;

                  if (regex != '') {

                    if (obj.inputEl.value.match(regex)) {

                      // only when there is no other validation mark it as passed

                      obj.clearError('pattern');

                      YAHOO.util.Dom.removeClass(obj.patternErrEl, 'on');

                      validationExist = true;

                    } else {

                      if (obj.inputEl.value != '') {

                        YAHOO.util.Dom.addClass(obj.patternErrEl, 'on');

                      }

                      obj.setError('pattern', 'The value entered is not allowed in this field.');

                      validationExist = true;

                      validationResult = false;

                    }

                  }

        

                  break;

                }

              }

            }

            // actual validation is checked by # of errors

            // renderValidation does not require the result being passed

            obj.renderValidation(validationExist, validationResult);

            obj.owner.notifyValidation();

            const valueToSet = obj.escapeContent ? CStudioForms.Util.escapeXml(obj.getValue()) : obj.getValue();

            obj.form.updateModel(obj.id, valueToSet);

          },

        

          _onChangeVal: function(evt, obj) {

            obj.edited = true;

            if (this._onChange) {

              this._onChange(evt, obj);

            }

          },

        

          /**

           * perform count calculation on keypress

           * @param evt event

           * @param el element

           */

          count: function(evt, countEl, el) {

            // 'this' is the input box

            el = el ? el : this;

            var text = el.value;

        

            var charCount = text.length ? text.length : el.textLength ? el.textLength : 0;

            var maxlength = el.maxlength && el.maxlength != '' ? el.maxlength : -1;

        

            if (maxlength != -1) {

              if (charCount > el.maxlength) {

                // truncate if exceeds max chars

                if (charCount > el.maxlength) {

                  this.value = text.substr(0, el.maxlength);

                  charCount = el.maxlength;

                }

        

                if (

                  evt &&

                  evt != null &&

                  evt.keyCode != 8 &&

                  evt.keyCode != 46 &&

                  evt.keyCode != 37 &&

                  evt.keyCode != 38 &&

                  evt.keyCode != 39 &&

                  evt.keyCode != 40 && // arrow keys

                  evt.keyCode != 88 &&

                  evt.keyCode != 86

                ) {

                  // allow backspace and

                  // delete key and arrow keys (37-40)

                  // 86 -ctrl-v, 90-ctrl-z,

                  if (evt) YAHOO.util.Event.stopEvent(evt);

                }

              }

            }

        

            if (maxlength != -1) {

              countEl.innerHTML = charCount + ' / ' + el.maxlength;

            } else {

              countEl.innerHTML = charCount;

            }

          },

        

          render: function(config, containerEl) {

            // we need to make the general layout of a control inherit from common

            // you should be able to override it -- but most of the time it wil be the same

            containerEl.id = this.id;

        

            var titleEl = document.createElement('span');

        

            YAHOO.util.Dom.addClass(titleEl, 'cstudio-form-field-title');

            titleEl.textContent = config.title;

        

            var controlWidgetContainerEl = document.createElement('div');

            YAHOO.util.Dom.addClass(controlWidgetContainerEl, 'cstudio-form-control-input-container');

        

            var validEl = document.createElement('span');

            YAHOO.util.Dom.addClass(validEl, 'validation-hint');

            YAHOO.util.Dom.addClass(validEl, 'cstudio-form-control-validation fa fa-check');

        

            var inputEl = document.createElement('input');

            this.inputEl = inputEl;

            YAHOO.util.Dom.addClass(inputEl, 'datum');

            YAHOO.util.Dom.addClass(inputEl, 'cstudio-form-control-input');

        

            const valueToSet = this.escapeContent ? CStudioForms.Util.unEscapeXml(this.value) : this.value;

            inputEl.value = this.value === '_not-set' ? config.defaultValue : valueToSet;

            controlWidgetContainerEl.appendChild(inputEl);

        

            YAHOO.util.Event.on(

              inputEl,

              'focus',

              function(evt, context) {

                context.form.setFocusedField(context);

              },

              this

            );

        

            YAHOO.util.Event.on(inputEl, 'change', this._onChangeVal, this);

            YAHOO.util.Event.on(inputEl, 'blur', this._onChange, this);

        

            for (var i = 0; i < config.properties.length; i++) {

              var prop = config.properties[i];

        

              if (prop.name == 'size') {

                inputEl.size = prop.value;

              }

        

              if (prop.name == 'maxlength') {

                inputEl.maxlength = prop.value;

              }

        

              if (prop.name == 'readonly' && prop.value == 'true') {

                this.readonly = true;

              }

        

              if (prop.name === 'escapeContent' && prop.value === 'true') {

                this.escapeContent = true;

              }

            }

        

            if (this.readonly == true) {

              inputEl.disabled = true;

            }

        

            var countEl = document.createElement('div');

            YAHOO.util.Dom.addClass(countEl, 'char-count');

            YAHOO.util.Dom.addClass(countEl, 'cstudio-form-control-input-count');

            controlWidgetContainerEl.appendChild(countEl);

            this.countEl = countEl;

        

            var patternErrEl = document.createElement('div');

            patternErrEl.innerHTML = 'The value entered is not allowed in this field.';

            YAHOO.util.Dom.addClass(patternErrEl, 'cstudio-form-control-input-url-err');

            controlWidgetContainerEl.appendChild(patternErrEl);

            this.patternErrEl = patternErrEl;

        

            YAHOO.util.Event.on(inputEl, 'keyup', this.count, countEl);

            YAHOO.util.Event.on(inputEl, 'keypress', this.count, countEl);

            YAHOO.util.Event.on(inputEl, 'mouseup', this.count, countEl);

        

            this.renderHelp(config, controlWidgetContainerEl);

        

            var descriptionEl = document.createElement('span');

            YAHOO.util.Dom.addClass(descriptionEl, 'description');

            YAHOO.util.Dom.addClass(descriptionEl, 'cstudio-form-field-description');

            descriptionEl.textContent = config.description;

        

            containerEl.appendChild(titleEl);

            containerEl.appendChild(validEl);

            containerEl.appendChild(controlWidgetContainerEl);

            containerEl.appendChild(descriptionEl);

          },

        

          getValue: function() {

            return this.value;

          },

        

          setValue: function(value) {

            const valueToSet = this.escapeContent ? CStudioForms.Util.unEscapeXml(value) : value;

        

            this.value = valueToSet;

            this.inputEl.value = valueToSet;

            this.count(null, this.countEl, this.inputEl);

            this._onChange(null, this);

            this.edited = false;

          },


          getName: function() {

            return "text-input";

    },


    getSupportedProperties: function() {

        return [

            { label: CMgs.format(langBundle, "displaySize"), name: "size", type: "int", defaultValue: "50" },

            { label: CMgs.format(langBundle, "maxLength"), name: "maxlength", type: "int",  defaultValue: "50" },

            { label: CMgs.format(langBundle, "readonly"), name: "readonly", type: "boolean" },

            { label: "Tokenize for Indexing", name: "tokenize", type: "boolean",  defaultValue: "false" }

        ];

    },


    getSupportedConstraints: function() {

        return [

            { label: CMgs.format(langBundle, "required"), name: "required", type: "boolean" },

            { label: CMgs.format(langBundle, "matchPattern"), name: "pattern", type: "string" },

        ];

    }


});


CStudioAuthoring.Module.moduleLoaded("text-input", CStudioForms.Controls.textInput);

Copy-icon
Сохранение дополнительных параметров элементов управления в XML

Чтобы сохранить дополнительные параметры элемента управления формой в XML-контент, вызовите registerDynamicField при инициализации элемента управления формой. Это гарантирует, что при вызове updateField  ваш элемент будет сохранен в XML.

Например:

this.form.registerDynamicField(this.timezoneId);

Copy-icon

Настройка файла описания на автоподключение к плагину

Чтобы настроить файл описания таким образом, чтобы плагин автоматически подключался в соответствующем файле конфигурации CMS Studio (который для элемента управления формой является файлом конфигурации проекта), добавьте следующее в ваш файл описания cms-plugin.yaml:

installation:

 - type: form-control

   elementXpath: //control/plugin[pluginId='ru.dc.cms.plugin.excontrol']

   element:

     name: control

     children:

       - name: plugin

         children:

           - name: pluginId

             value: ru.dc.cms.plugin.excontrol

           - name: type

             value: control

           - name: name

             value: text-input

           - name: filename

             value: main.js

       - name: icon

         children:

           - name: class

             value: fa-pencil-square-o

Copy-icon

Подробнее о настройке автоматического подключения вашего плагина в CMS Studio можно узнать здесь.

Тестирование плагина Copy-icon

После размещения вашего JS файла вы можете установить плагин для тестирования или отладки, используя команду cms-cli copy-plugin.

Чтобы выполнить команду cms-cli, сначала установите соединение с DC CMS с помощью команды add-environment. После установки соединения вы можете установить плагин в проект my-editorial следующей командой:

./cmsr-cli copy-plugin -e local -s my-editorial --path /users/myuser/myplugins/form-control-plugin

Copy-icon

Во время установки плагина автоподключение настраивается в файле site-config-tools.xml.

Настройки, указанные в файле описания для автоподключения, теперь должны отображаться в конфигурационном файле проекта. Вы можете открыть этот файл, открыв боковую панель и перейдя в Инструменты сайта > Конфигурация > Конфигурация проекта.

<controls>

    <control>

        <name>auto-filename</name>

        .

        .

    </control>

    .

    .

    <control>

        <plugin>

            <pluginId>ru.dc.cms.plugin.excontrol</pluginId>

            <type>control</type>

            <name>text-input</name>

            <filename>main.js</filename>

        </plugin>

        <icon>

            <class>fa-pencil-square-o</class>

        </icon>

    </control>

</controls>

Copy-icon