/*
* Copyright 2018 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Search context widget
* This widget is registered for fields of type Ametys.form.WidgetManager#TYPE_STRING
*/
Ext.define('Ametys.web.form.widget.SearchContext', {
extend: 'Ametys.form.AbstractFieldsWrapper',
/**
* @private
* @property {Ext.container.Container} _allContextCt The container of all single contexts
*/
/**
* @private
* @property {Ext.container.Container} _bottomContainer The bottom container
*/
/**
* @cfg {Number} initialSize The number of contexts to initially create
*/
initialSize: 0,
statics: {
/**
* @property {Number} LOGICAL_OPERATOR_COMPONENT_WIDTH The width of the components displaying logical operators
* @static
* @readonly
*/
LOGICAL_OPERATOR_COMPONENT_WIDTH: 35,
/**
* @property {String} CONNECTING_LOGICAL_OPERATOR_ITEM_ID The item id of the intermediate logical operator, to connect two contexts
* @static
* @readonly
*/
CONNECTING_LOGICAL_OPERATOR_ITEM_ID: 'logical-operator',
/**
* @property {String} CONNECTING_LOGICAL_OPERATOR_TEXT The text of the intermediate logical operator, to connect two contexts ('OR' keyword)
* @static
* @readonly
*/
CONNECTING_LOGICAL_OPERATOR_TEXT: '{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_LOGICAL_OPERATOR}}'
},
initComponent: function()
{
this.items = [{
xtype: 'panel',
flex: 1,
layout: {
type: 'vbox',
align: 'stretch'
},
bodyPadding: 10,
items: [this._createAllContextContainer(), this._createBottomContainer()]
}];
this.callParent(arguments);
},
/**
* @private
* Creates and returns the container of all single contexts
* @return {Ext.container.Container} the container of all single contexts
*/
_createAllContextContainer: function()
{
this._allContextCt = Ext.create('Ext.container.Container', {
layout: {
type: 'vbox',
align: 'stretch'
},
items: []
});
return this._allContextCt;
},
/**
* @private
* Creates and returns a component representing a single context
* @param {Number} position The position where the component is inserted
* @param {Object} [values] The values to set with {@link Ametys.form.ConfigurableFormPanel#setValues}.
* @return {Ext.container.Container} a component representing a single context
*/
_createSingleContextComponent: function(position, values)
{
var items = [];
if (position > 0)
{
items.push(this._logicalOperatorComponentCfg(position));
}
items.push({
xtype: 'container',
flex: 1,
layout: {
type: 'hbox',
align: 'stretch'
},
items: [this._formPanel(values), this._buttonContainerCfg()]
});
return Ext.create('Ext.container.Container', {
singleContext: true,
layout: {
type: 'vbox',
align: 'stretch'
},
items: items,
style: {
marginBottom: '10px'
}
});
},
/**
* @private
* Gets the configuration of the component displaying the logical operator
* @param {Number} position The position where the context component is inserted
* @return {Object} the configuration of the component displaying the logical operator
*/
_logicalOperatorComponentCfg: function(position)
{
return Ext.merge(this._logicalOperatorBaseCfg(), {
xtype: 'component',
itemId: this.statics().CONNECTING_LOGICAL_OPERATOR_ITEM_ID,
html: this.statics().CONNECTING_LOGICAL_OPERATOR_TEXT
});
},
/**
* @private
* Gets the base configuration of the logical operator component and the last-parenthesis component (width, style...)
* @return {Object} the base configuration of the logical operator component and the last-parenthesis component (width, style...)
*/
_logicalOperatorBaseCfg: function()
{
return {
width: this.statics().LOGICAL_OPERATOR_COMPONENT_WIDTH,
style: {
textAlign: 'left',
fontWeight: 'bold'
}
};
},
/**
* @private
* Creates and gets the form containing the items of a single context
* @param {Object} [values] The values to set with {@link Ametys.form.ConfigurableFormPanel#setValues}.
* @return {Object} the form containing the items of a single context
*/
_formPanel: function(values)
{
var sitesFieldName = 'sites',
sitemapContextFieldName = 'search-sitemap-context',
langFieldName = 'context-lang',
tagsFieldName = 'tags';
var data = {};
data[sitesFieldName] = {
label: '{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_SITES}}',
description: '{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_SITES_DESC}}',
type: 'STRING',
widget: 'edition.select-site',
'widget-params': {
allowSelectContext: true,
multipleSites: true
},
validation:{
mandatory: true
}
};
data[sitemapContextFieldName] = {
label: '{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_SITEMAP_CONTEXT}}',
description: '{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_SITEMAP_CONTEXT_DESC}}',
type: 'STRING',
widget: 'edition.search-sitemap-context',
'widget-params': {
naturalOrder: true,
sitesField: sitesFieldName
},
validation:{
mandatory: true
},
'default-value': {
context: 'CURRENT_SITE'
}
};
data[langFieldName] = {
label: '{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_LANGUAGE}}',
description: '{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_LANGUAGE_DESC}}',
type: 'STRING',
disableCondition: {
condition: [{
id: sitemapContextFieldName,
operator: 'neq',
value: Ext.encode({
context: 'CURRENT_SITE',
page: null
})
}]
},
widget: 'edition.combobox',
'widget-params': {
naturalOrder: true
},
enumeration: [
{
value: 'CURRENT',
label: "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_LANGUAGE_CURRENT}}"
},
{
value: 'ALL',
label: "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_LANGUAGE_ALL}}"
},
{
value: 'OTHERS',
label: "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_LANGUAGE_OTHERS}}"
}
],
validation:{
mandatory: true
},
'default-value': 'CURRENT'
};
data[tagsFieldName] = {
label: '{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_TAGS}}',
description: '{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_TAGS_DESC}}',
type: 'STRING',
multiple: true,
widget: 'edition.tag',
'widget-params': {
targetType: 'CONTENT',
url: 'tags.json',
sitesField: sitesFieldName,
allowToggleAutoposting: true
}
};
var cfp = Ext.create('Ametys.form.ConfigurableFormPanel', {
itemId: 'form',
flex: 1,
layout: {
type: 'vbox',
align: 'stretch'
},
defaultFieldConfig: {
labelWidth: 175,
flex: 1
}
});
cfp.configure(data);
cfp.setValues(values || {});
return cfp;
},
/**
* @private
* Gets the configuration of the container of the buttons placed on the right of a single context
* @return {Object} the configuration of the container of the buttons placed on the right of a single context
*/
_buttonContainerCfg: function()
{
return {
xtype: 'container',
width: '80px',
layout: {
type: 'hbox',
align: 'stretch'
},
defaults: {
xtype: 'button',
ui: 'default-toolbar-small'
},
items: [{
itemId: 'rem-btn',
iconCls: 'ametysicon-sign-raw-cross',
tooltip: '{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_REMOVE_BUTTON_TOOLTIP}}',
handler: function(remBtn) {
var singleContextCmp = this._getSingleContextCmp(remBtn);
this._removeContext(singleContextCmp);
// force computing of valid state
this.isValid();
},
scope: this
}]
}
},
/**
* @private
* Removes a context
* @param {Ext.container.Container} contextComponent The context component to remove
*/
_removeContext: function(contextComponent)
{
var position = this._allContextCt.items.indexOf(contextComponent);
// Context which is at first position will be removed. The one which is second needs to have its logical operator updated
if (position == 0)
{
var secondContext = this._allContextCt.items.getAt(1);
secondContext && secondContext.remove(this.statics().CONNECTING_LOGICAL_OPERATOR_ITEM_ID);
}
this._allContextCt.remove(contextComponent);
},
/**
* @private
* Gets the parent single context component of this button
* @param {Ext.button.Button} btn The button
* @return {Ext.container.Container} the parent single context component
*/
_getSingleContextCmp: function(btn)
{
return btn.up('container[@singleContext=true]');
},
/**
* @private
* Creates and returns the bottom container
* Gets the configuration of the bottom container
* @return {Object} the bottom container
*/
_createBottomContainer: function()
{
this._bottomContainer = Ext.create('Ext.container.Container', {
layout: {
type: 'vbox',
align: 'begin'
},
items: [{
xtype: 'button',
text: '{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_ADD_CONTEXT}}',
iconCls: 'ametysicon-sign-raw-add',
handler: function() {
this._allContextCt.add(this._createSingleContextComponent(this._allContextCt.items.getCount()));
// Avoid some weird horizontal scrollbar which can appear
this.updateLayout();
},
scope: this
}]
});
return this._bottomContainer;
},
setValue: function(value)
{
this._allContextCt.removeAll();
if (Ext.isArray(value))
{
Ext.Array.forEach(value, function(contextAsStr, index) {
var contextObj = Ext.decode(contextAsStr),
contextCmp = this._createSingleContextComponent(index, {values: contextObj});
this._allContextCt.add(contextCmp);
}, this);
}
else if (value == null)
{
for (var i = 0; i < parseInt(this.initialSize); i++)
{
this._allContextCt.add(this._createSingleContextComponent(i));
}
}
},
getValue: function()
{
return this._allContextCt.items.getRange() // get context components
.map(function(ct) { return ct.down('#form'); }) // get ConfigurableFormPanel of contexts
.map(function(cfp) { return cfp.getJsonValues();} ) // get values of form
.map(function(values) { return Ext.encode(values); }); // encode values
},
getErrors: function(value)
{
function getFields(configurableFormPanel)
{
return configurableFormPanel.getFieldNames()
.map(function(fieldName) { return configurableFormPanel.getField(fieldName) });
}
function concatErrors(existingErrors, subField)
{
if (subField.isDisabled())
{
return existingErrors;
}
else
{
return existingErrors.concat(subField.getErrors());
}
}
var errors = this.callParent(arguments);
var fieldsPerContext = this._allContextCt.items.getRange() // get context components
.map(function(ct) { return ct.down('#form'); }) // get ConfigurableFormPanel of contexts
.map(function(cfp) { return getFields(cfp); }); // get fields of form
var fields = Ext.Array.flatten(fieldsPerContext);
var subFieldErrors = [];
fields.forEach(function (field) {
subFieldErrors = concatErrors(subFieldErrors, field);
});
if (subFieldErrors.length)
{
errors.push("{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CONTEXTS_ERROR_CONTEXT_INVALID}}");
}
return errors;
}
});