Создание плагинов для элементов управления формой
Что такое элемент управления
Элемент управления формой – это элемент пользовательского интерфейса, предназначенный для помощи авторам контента в управлении и редактировании контента и метаданных. В 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() { }
});
```
Структура директории плагина
При разработке плагинов JavaScript файлы для плагинов должны соответствовать определенному руководству и размещаться в следующем месте: authoring/static-assets/plugins/{yourPluginId}/control/{yourPluginName}/JS_FILE.js
где:
{yourPluginName}
: название плагина элемента управления формойJS_FILE.js
: JavaScript файл, содержащий реализацию интерфейса элемента управления
Пример создания плагина элемента управления формой
В качестве примера добавим элемент управления с именем text-input
в проект под названием “My Editorial”.
Код для элемента управления
Сначала нужно настроить структуру папок для 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
В нашем примере <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);
Ниже вы найдете полный пример файла плагина для элемента управления вводом текста:
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);
Сохранение дополнительных параметров элементов управления в XML
Чтобы сохранить дополнительные параметры элемента управления формой в XML-контент, вызовите registerDynamicField
при инициализации элемента управления формой. Это гарантирует, что при вызове updateField
ваш элемент будет сохранен в XML.
Например:
this.form.registerDynamicField(this.timezoneId);
Настройка файла описания на автоподключение к плагину
Чтобы настроить файл описания таким образом, чтобы плагин автоматически подключался в соответствующем файле конфигурации 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
Подробнее о настройке автоматического подключения вашего плагина в CMS Studio можно узнать здесь.
Тестирование плагина
После размещения вашего 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
Во время установки плагина автоподключение настраивается в файле 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>