Создание плагинов для источников данных формы
Что такое источник данных
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) { }
});
Структура директории плагина
При разработке плагинов 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
В нашем примере <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);
Ниже вы найдете полный пример файла плагина для источника данных 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);
Настройка файла описания на автоподключение к плагину
Чтобы настроить файл описания таким образом, чтобы плагин автоматически подключался в соответствующем файле конфигурации 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
Подробнее о настройке автоматического подключения вашего плагина в CMS Studio можно узнать здесь.
Тестирование плагина
После размещения вашего 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
Во время установки плагина автоподключение настраивается в файле 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>