Создание плагинов для источников данных формы

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

Что такое источник данных

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

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

Ниже расположен список встроенных источников данных:

Источник данных Описание
Компоненты Настройка областей на странице, которые могут принимать компоненты
Общий контент Выбор или создание контента для использования в форме
Встроенный контент Создание встроенных компонентов
Изображение загружено с рабочего стола Загрузка изображений с десктопа
Изображение из репозитория Выбор изображений из репозитория
Файл загружен с рабочего стола Загрузка файлов с десктопа
Обзор файлов Выбор файлов из репозитория
Файл из репозитория CMIS Выбор файлов из CMIS репозитория
Изображение из репозитория CMIS Выбор изображений из CMIS репозитория
Видео из репозитория CMIS Выбор видео из CMIS репозитория
Файл загружен в репозиторий CMIS Загрузка файлов в CMIS репозиторий
Изображение загружено в репозиторий CMIS Загрузка изображений в CMIS репозиторий
Видео загружено в репозиторий CMIS Загрузка видео в CMIS репозиторий
Файл из репозитория WebDav Выбор файлов из WebDav репозитория
Изображение из репозитория WebDav Выбор изображений из WebDav репозитория
Видео из репозитория WebDav Выбор видео из WebDav репозитория
Файл загружен в репозиторий WebDav Загрузка файлов в WebDav репозиторий
Изображение загружено в репозиторий WebDav Загрузка изображений в WebDav репозиторий
Видео загружено в репозиторий WebDav Загрузка видео в WebDav репозиторий
Файл из репозитория S3 Выбор файлов из S3 репозитория
Изображение из репозитория S3 Выбор изображений из S3 репозитория
Видео из репозитория S3 Выбор видео из S3 репозитория
Файл загружен в репозиторий S3 Загрузка файлов в S3 репозиторий
Изображение загружено в репозиторий S3 Загрузка изображений в S3 репозиторий
Видео загружено в репозиторий S3 Загрузка видео в S3 репозиторий
Перекодирование видео из репозитория S3 Загрузка видео в репозиторий AWS MediaConvert
Видео загружено с рабочего стола Загрузка видео с десктопа
Видео из репозитория Выбор видео из репозитория
Статические пары значений ключа Источник данных, в который могут быть добавлены пары ключ/значение для использования в элементе управления
Простая классификация Выбор или создание контента для использования в форме


Подробнее о источниках данных можно узнать в статье "Моделирование контента в DC CMS".

Структура плагина источника данных

Источник данных включает в себя (как минимум):

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

Интерфейс источника данных

/**

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

 */

CStudioForms.Datasources.ConfiguredList = CStudioForms.Datasources.X ||

function(id, form, properties, constraints)  {

}


/**

 * Extension of the base class

 */

YAHOO.extend(CStudioForms.Datasources.X, CStudioForms.CStudioFormDatasource, {


    /**

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

     */

    getLabel: function() {  },


    /**

     * return a string that represents the type of data returned by the data source

     * This is often of type "item"

     */

    getInterface: function() { },


    /**

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

     */

    getName: function() { },


    /**

     * return a list of properties supported by the data source.

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

     */

    getSupportedProperties: function() { },


    /**

     * method responsible for getting the actual values. Caller must pass callback which meets interface:

     * { success: function(list) {}, failure: function(exception) }

     */

    getList: function(cb) { }

});

Copy-icon

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

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

где:

  • {yourPluginName}: название плагина источника данных
  • JS_FILE.js: JavaScript файл, содержащий реализацию интерфейса источника данных

Пример создания плагина источника данных

В качестве примера добавим источник данных с именем parent-content.

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

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

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

Теперь создадим папки, следуя пути plugin ID: ru.dc.cms.plugin.examples. Для этого внутри папки plugins создайте папку с именем ru. Внутри ru создайте папку dc. Внутри dc создайте папку cms. Внутри cms создайте папку plugin. В папке plugin создайте папку datasource. После этого внутри datasource создайте папку parent-content (parent-content - имя нашего будущего источника данных). JS-файл, реализующий интерфейс источника данных, названный в этом примере main.js, будет размещён внутри папки parent-content.

<plugin-folder>/
 cms-plugin.yaml
  authoring/
    static-assets/
      plugins/
        ru/
          dc/
cms/ plugin/ examples/ datasource/ parent-content/ main.js
Copy-icon

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

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

CStudioForms.Datasources.ParentContent= CStudioForms.Datasources.ParentContent ||

function(id, form, properties, constraints)  {

    this.id = id;

    this.form = form;

    this.properties = properties;

    this.constraints = constraints;

    this.selectItemsCount = -1;

    this.type = "";

    this.defaultEnableCreateNew = true;

    this.defaultEnableBrowseExisting = true;

    this.countOptions = 0;


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

            if(properties[i].name == "repoPath") {

                    this.repoPath = properties[i].value;

            }

            if(properties[i].name == "browsePath") {

                    this.browsePath = properties[i].value;

            }


            if(properties[i].name == "type"){

                    this.type = (Array.isArray(properties[i].value))?"":properties[i].value;

            }


        if(properties[i].name === "enableCreateNew"){

            this.enableCreateNew = properties[i].value === "true" ? true : false;

            this.defaultEnableCreateNew = false;

            properties[i].value === "true" ? this.countOptions ++ : null;

        }


        if(properties[i].name === "enableBrowseExisting"){

            this.enableBrowseExisting = properties[i].value === "true" ? true : false;

            this.defaultEnableBrowseExisting = false;

            properties[i].value === "true" ? this.countOptions ++ : null;

        }

    }


    if(this.defaultEnableCreateNew){

        this.countOptions ++;

    }

    if(this.defaultEnableBrowseExisting){

        this.countOptions ++;

    }


    return this;

}


YAHOO.extend(CStudioForms.Datasources.ParentContent, CStudioForms.CStudioFormDatasource, {

    .

    .

    .

    getName: function() {

            return "parent-content";

    },


    getSupportedProperties: function() {

            return [

            { label: CMgs.format(langBundle, "Enable Create New"), name: "enableCreateNew", type: "boolean", defaultValue: "true"  },

            { label: CMgs.format(langBundle, "Enable Browse Existing"), name: "enableBrowseExisting", type: "boolean", defaultValue: "true" },

                    { label: CMgs.format(langBundle, "repositoryPath"), name: "repoPath", type: "string" },

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

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

            ];

    },


    getSupportedConstraints: function() {

            return [

            ];

    }


});


CStudioAuthoring.Module.moduleLoaded("parent-content", CStudioForms.Datasources.ParentContent);

Copy-icon

Ниже вы найдете полный пример файла плагина для источника данных parent-content:

CStudioForms.Datasources.ParentContent = function(id, form, properties, constraints) {

    this.id = id;

    this.form = form;

    this.properties = properties;

    this.constraints = constraints;

    this.selectItemsCount = -1;

    this.type = '';

    this.defaultEnableCreateNew = true;

    this.defaultEnableBrowseExisting = true;

    this.countOptions = 0;

    //const i18n = DCCMSNext.i18n;

    //this.formatMessage = i18n.intl.formatMessage;

    //this.parentContentDSMessages = i18n.messages.parentContentDSMessages;

  

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

      if (properties[i].name == 'repoPath') {

        this.repoPath = properties[i].value;

      }

      if (properties[i].name == 'browsePath') {

        this.browsePath = properties[i].value;

      }

  

      if (properties[i].name == 'type') {

        this.type = Array.isArray(properties[i].value) ? '' : properties[i].value;

      }

  

      if (properties[i].name === 'enableCreateNew') {

        this.enableCreateNew = properties[i].value === 'true' ? true : false;

        this.defaultEnableCreateNew = false;

        properties[i].value === 'true' ? this.countOptions++ : null;

      }

  

      if (properties[i].name === 'enableBrowseExisting') {

        this.enableBrowseExisting = properties[i].value === 'true' ? true : false;

        this.defaultEnableBrowseExisting = false;

        properties[i].value === 'true' ? this.countOptions++ : null;

      }

    }

  

    if (this.defaultEnableCreateNew) {

      this.countOptions++;

    }

    if (this.defaultEnableBrowseExisting) {

      this.countOptions++;

    }

  

    return this;

  };

  

  YAHOO.extend(CStudioForms.Datasources.ParentContent, CStudioForms.CStudioFormDatasource, {

    itemsAreContentReferences: true,

  

    createElementAction: function(control, _self, addContainerEl) {

      if (this.countOptions > 1) {

        control.addContainerEl = null;

        control.containerEl.removeChild(addContainerEl);

      }

      if (_self.type === '') {

        CStudioAuthoring.Operations.createNewContent(

          CStudioAuthoringContext.site,

          _self.processPathsForMacros(_self.repoPath),

          false,

          {

            success: function(formName, name, value) {

              control.insertItem(value, formName.item.internalName, null, null, _self.id);

              control._renderItems();

            },

            failure: function() {}

          },

          true

        );

      } else {

        CStudioAuthoring.Operations.openContentWebForm(

          _self.type,

          null,

          null,

          _self.processPathsForMacros(_self.repoPath),

          false,

          false,

          {

            success: function(contentTO, editorId, name, value) {

              control.insertItem(name, value, null, null, _self.id);

              control._renderItems();

              CStudioAuthoring.InContextEdit.unstackDialog(editorId);

            },

            failure: function() {}

          },

          [{ name: 'childForm', value: 'true' }]

        );

      }

    },

  

    browseExistingElementAction: function(control, _self, addContainerEl) {

      if (this.countOptions > 1) {

        control.addContainerEl = null;

        control.containerEl.removeChild(addContainerEl);

      }

      // if the browsePath property is set, use the property instead of the repoPath property

      // otherwise continue to use the repoPath for both cases for backward compatibility

      var browsePath = _self.repoPath;

      if (_self.browsePath != undefined && _self.browsePath != '') {

        browsePath = _self.browsePath;

      }

      CStudioAuthoring.Operations.openBrowse(

        '',

        _self.processPathsForMacros(browsePath),

        _self.selectItemsCount,

        'select',

        true,

        {

          success: function(searchId, selectedTOs) {

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

              var item = selectedTOs[i];

              var value = item.internalName && item.internalName != '' ? item.internalName : item.uri;

              control.insertItem(item.uri, value, null, null, _self.id);

              control._renderItems();

            }

          },

          failure: function() {}

        }

      );

    },

  

    add: function(control, onlyAppend) {

      var CMgs = CStudioAuthoring.Messages;

      var langBundle = CMgs.getBundle('contentTypes', CStudioAuthoringContext.lang);

  

      var _self = this;

  

      var addContainerEl = control.addContainerEl ? control.addContainerEl : null;

  

      var datasourceDef = this.form.definition.datasources,

        newElTitle = '';

  

      for (var x = 0; x < datasourceDef.length; x++) {

        if (datasourceDef[x].id === this.id) {

          newElTitle = datasourceDef[x].title;

        }

      }

  

      if (!addContainerEl && (this.countOptions > 1 || onlyAppend)) {

        addContainerEl = document.createElement('div');

        control.containerEl.appendChild(addContainerEl);

        YAHOO.util.Dom.addClass(addContainerEl, 'cstudio-form-control-node-selector-add-container');

        control.addContainerEl = addContainerEl;

        control.addContainerEl.style.left = control.addButtonEl.offsetLeft + 'px';

        control.addContainerEl.style.top = control.addButtonEl.offsetTop + 22 + 'px';

      }

  

      if (this.enableCreateNew || this.defaultEnableCreateNew) {

        if (this.countOptions > 1 || onlyAppend) {

          addContainerEl.create = document.createElement('div');

          addContainerEl.appendChild(addContainerEl.create);

          YAHOO.util.Dom.addClass(addContainerEl.create, 'cstudio-form-controls-create-element');

  

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

          YAHOO.util.Dom.addClass(createEl, 'cstudio-form-control-node-selector-add-container-item');

          createEl.textContent = CMgs.format(langBundle, 'createNew') + ' - ' + newElTitle;

          control.addContainerEl.create.appendChild(createEl);

          var addContainerEl = control.addContainerEl;

          YAHOO.util.Event.on(

            createEl,

            'click',

            function() {

              _self.createElementAction(control, _self, addContainerEl);

            },

            createEl

          );

        } else {

          _self.createElementAction(control, _self);

        }

      }

  

      if (this.enableBrowseExisting || this.defaultEnableBrowseExisting) {

        if (this.countOptions > 1 || onlyAppend) {

          addContainerEl.browse = document.createElement('div');

          addContainerEl.appendChild(addContainerEl.browse);

          YAHOO.util.Dom.addClass(addContainerEl.browse, 'cstudio-form-controls-browse-element');

  

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

          browseEl.textContent = CMgs.format(langBundle, 'browseExisting') + ' - ' + newElTitle;

          YAHOO.util.Dom.addClass(browseEl, 'cstudio-form-control-node-selector-add-container-item');

          control.addContainerEl.browse.appendChild(browseEl);

          var addContainerEl = control.addContainerEl;

          YAHOO.util.Event.on(

            browseEl,

            'click',

            function() {

              _self.browseExistingElementAction(control, _self, addContainerEl);

            },

            browseEl

          );

        } else {

          _self.browseExistingElementAction(control, _self);

        }

      }

    },

  

    edit: function(key, control) {

      var _self = this;

      CStudioAuthoring.Service.lookupContentItem(CStudioAuthoringContext.site, key, {

        success: function(contentTO) {

          CStudioAuthoring.Operations.editContent(

            contentTO.item.contentType,

            CStudioAuthoringContext.siteId,

            contentTO.item.mimeType,

            contentTO.item.nodeRef,

            contentTO.item.uri,

            false,

            {

              success: function(contentTO, editorId, name, value) {

                if (control) {

                  control.updateEditedItem(value, _self.id);

                  CStudioAuthoring.InContextEdit.unstackDialog(editorId);

                }

              }

            }

          );

        },

        failure: function() {}

      });

    },

  

    updateItem: function(item, control) {

      if (item.key && item.key.match(/\.xml$/)) {

        var getContentItemCb = {

          success: function(contentTO) {

            item.value = contentTO.item.internalName || item.value;

            control._renderItems();

          },

          failure: function() {}

        };

  

        CStudioAuthoring.Service.lookupContentItem(CStudioAuthoringContext.site, item.key, getContentItemCb);

      }

    },

  

    getLabel: function() {

      //return this.formatMessage(this.parentContentDSMessages.parentContent);

      return "Parent Content";

    },

  

    getInterface: function() {

      return 'item';

    },

  

    getName: function() {

      return 'parent-content';

    },

  

    getSupportedProperties: function() {

      return [

        {

          label: CMgs.format(langBundle, 'Enable Create New'),

          name: 'enableCreateNew',

          type: 'boolean',

          defaultValue: 'true'

        },

        {

          label: CMgs.format(langBundle, 'Enable Browse Existing'),

          name: 'enableBrowseExisting',

          type: 'boolean',

          defaultValue: 'true'

        },

        { label: CMgs.format(langBundle, 'repositoryPath'), name: 'repoPath', type: 'string' },

        { label: CMgs.format(langBundle, 'browsePath'), name: 'browsePath', type: 'string' },

        { label: CMgs.format(langBundle, 'defaultType'), name: 'type', type: 'string' }

      ];

    },

  

    getSupportedConstraints: function() {

      return [];

    }

  });

  

  CStudioAuthoring.Module.moduleLoaded('parent-content', CStudioForms.Datasources.ParentContent);

Copy-icon

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

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

installation:

 - type: form-datasource

   elementXpath: //datasource/plugin[pluginId='ru.dc.cms.plugin.examples']

   element:

     name: datasource

     children:

       - name: plugin

         children:

           - name: pluginId

             value: ru.dc.cms.plugin.examples

           - name: type

             value: datasource

           - name: name

             value: parent-content

           - 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 следующей командой:

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

Copy-icon

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

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

<datasources>

    <datasource>

        <name>img-desktop-upload</name>

        .

        .

    </datasource>

    .

    .

    <datasource>

        <plugin>

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

            <type>datasource</type>

            <name>parent-content</name>

            <filename>main.js</filename>

        </plugin>

        <icon>

            <class>fa-users</class>

        </icon>

    </datasource>

</datasources>

Copy-icon