/*
* Copyright 2016 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.
*/
/**
* This class provides a widget to select one or more sites.<br>
* This embeds a drop down list with querying on title of sites and type-ahead support.<br>
* A dialog box also allow to select sites in the site tree.<br>
* See {@link Ametys.web.helper.ChooseSite}<br>
*
* This widget is registered for fields of type Ametys.form.WidgetManager#TYPE_STRING
*/
Ext.define('Ametys.web.form.widget.SelectSite', {
extend: 'Ametys.form.AbstractQueryableComboBox',
statics: {
/**
* @property {String} SITE_CONTEXT_CURRENT values for the current site
* @private
* @readonly
*/
SITE_CONTEXT_CURRENT: 'CURRENT_SITE',
/**
* @property {String} SITE_CONTEXT_ALL values for all sites
* @private
* @readonly
*/
SITE_CONTEXT_ALL: 'SITES',
/**
* @property {String} SITE_CONTEXT_OTHERS values for others sites
* @private
* @readonly
*/
SITE_CONTEXT_OTHERS: 'OTHER_SITES',
/**
* @property {String} SITE_CONTEXT_SITES_LIST values for list of sites
* @private
* @readonly
*/
SITE_CONTEXT_SITES_LIST: 'SITES_LIST'
},
/**
* @cfg {Boolean/String} [allowSelectContext=false] True display the select context combobox. If false, allowSelectSite must be true.
*/
/**
* @cfg {Boolean/String} [allowContextAll=true] Set to false to not display the all option in the select context combobox
*/
/**
* @cfg {Boolean/String} [allowContextOthers=true] Set to false to not display the other option in the select context combobox
*/
/**
* @cfg {Boolean/String} [allowSelectSite=true] Set to false to not display the site combobox. If false, allowSelectContext must be true.
*/
/**
* @cfg {Boolean/String} [readAccessOnly=false] true to list sites on read access only. Defaults to false.
*/
/**
* @cfg {Boolean/String} [sharedSitesOnly=false] true to list sites with shared contents only. Defaults to false.
*/
/**
* @cfg {Boolean} [multiple=false] True Use #multipleSites
* @private
*/
multiple: false,
/**
* @cfg {Boolean/String} multipleSites To allow to select multiple sites. Do not use #cfg-multiple.
*/
/**
* @cfg {Object} buttonConfig The main button configuration. Some fields like handler cannot be set, and take care of overwwrite by #cfg-buttonText, #cfg-buttonIcon or #cfg-buttonIconCls.
*/
/**
* @cfg {String} [boxTitle] The title of the dialog box.
*/
/**
* @cfg {String} [boxIcon] The title of the dialog box.
*/
/**
* @cfg {String} [helpMsg] The message displayed at the top of dialog box
*/
/**
* @cfg {String} [buttonText=""] The text of the select site button.
* Note that if you supply a value for {@link #buttonConfig}, the buttonConfig.text
* value will be used instead if available.
*/
buttonText: '',
/**
* @cfg {String} buttonIcon The button icon path for the select button.
* Note that if you supply a value for {@link #buttonConfig}, the buttonConfig.icon
* value will be used instead if available.
*/
buttonIcon: null,
/**
* @cfg {String} buttonIconCls The CSS class to apply to the select button
* Note that if you supply a value for {@link #buttonConfig}, the buttonConfig.icon
* value will be used instead if available.
*/
buttonIconCls: 'ametysicon-world91',
/**
* @cfg {String} buttonTooltip The button icon tooltip for the select button.
*/
buttonTooltip: "{{i18n PLUGINS_WEB_WIDGET_SITESTREEWIDGET_SELECTSITE}}",
/**
* @cfg {Number} [minChars=3] The minimum number of characters the user must type before autocomplete activates.
*/
minChars: 3,
/**
* @cfg {Number} [growMax=300] If not set to `false`, the max height in pixels of the box select
*/
growMax: 300,
/**
* @property {Ext.button.Button} selectButton The select sites button, if any.
* @protected
*/
valueField: 'name',
displayField: 'title',
maxResult: 50,
simpleValue: false,
/**
* @property {Ext.form.field.ComboBox} _contextCombobox The site context combobox
* @private
*/
/**
* @property {String} _contextValue The currently selected context, even if the context combobox is not rendered yet.
* @private
*/
/**
* @property {String[]} _siteValues An array containing the currently selected site (even if the site are not yet rendered in the combobox)
* @private
*/
initComponent: function()
{
this.cls = 'x-form-selectsite-widget';
// allowSelectContext is false by default
this.allowSelectContext = this.allowSelectContext == true || this.allowSelectContext == 'true';
// allowContextAll is true by default
this.allowContextAll = this.allowContextAll != false && this.allowContextAll != 'false';
// allowContextOthers is true by default
this.allowContextOthers = this.allowContextOthers != false && this.allowContextOthers != 'false';
// allowSelectSite is true by default
this.allowSelectSite = this.allowSelectSite != false && this.allowSelectSite != 'false';
// readAccessOnly is false by default
this.readAccessOnly = this.readAccessOnly == true || this.readAccessOnly == 'true';
// sharedSitesOnly is false by default
this.sharedSitesOnly = this.sharedSitesOnly == true || this.sharedSitesOnly == 'true';
// workaround to handle multiple site on the widget side (but not on the storage side because the stored value is a JSON string)
// false by default
this.multiple = this.multipleSites == true || this.multipleSites == 'true';
this.callParent(arguments);
// initialize fields status
if (this.allowSelectContext && this.allowSelectSite)
{
var siteContext = this.getSiteContext();
if (siteContext != Ametys.web.form.widget.SelectSite.SITE_CONTEXT_SITES_LIST)
{
this._disableSelectSite();
}
}
},
getItems: function()
{
var otherItems = [];
this._contextValue = this._contextValue || Ametys.web.form.widget.SelectSite.SITE_CONTEXT_CURRENT;
var containerItems = [];
if (this.allowSelectContext)
{
var contextComboBox = {
xtype: 'combobox',
itemId: 'select-site-context',
editable: false,
forceSelection: true,
allowBlank: this.allowBlank,
width: 120,
valueField: this.valueField,
displayField: this.displayField,
queryMode: 'local',
value: this._contextValue,
store: {
type: 'array', // Ext.data.ArrayStore
fields: [this.valueField, this.displayField],
data: [
[Ametys.web.form.widget.SelectSite.SITE_CONTEXT_CURRENT, "{{i18n PLUGINS_WEB_WIDGET_SELECTSITE_CURRENTSITE_OPTION}}"],
[Ametys.web.form.widget.SelectSite.SITE_CONTEXT_ALL, "{{i18n PLUGINS_WEB_WIDGET_SELECTSITE_ALL_OPTION}}"],
[Ametys.web.form.widget.SelectSite.SITE_CONTEXT_OTHERS, "{{i18n PLUGINS_WEB_WIDGET_SELECTSITE_OTHERS_OPTION}}"]
]
},
listeners: {
render: {fn: this._onContextRender, scope: this},
change: {fn: this._onContextChange, scope: this}
}
}
// adding site lists if allow select site
if (this.allowSelectSite)
{
contextComboBox.store.data.push([Ametys.web.form.widget.SelectSite.SITE_CONTEXT_SITES_LIST, this.multiple ? "{{i18n PLUGINS_WEB_WIDGET_SELECTSITE_SITES_OPTION}}" : "{{i18n PLUGINS_WEB_WIDGET_SELECTSITE_SITE_OPTION}}"]);
}
containerItems.push(contextComboBox);
}
if (this.allowSelectSite)
{
containerItems = containerItems.concat(this.callParent(arguments));
// Button that opens the search dialog box.
if (!this.readOnly)
{
var buttonConfig = this.buttonConfig || {};
Ext.applyIf(buttonConfig, {
text: this.buttonText,
icon: this.buttonIcon,
iconCls: this.buttonIcon ? null : this.buttonIconCls,
tooltip: this.buttonTooltip,
handler: this.chooseSite,
scope: this
});
this.selectButton = Ext.create('Ext.button.Button', buttonConfig);
otherItems.push(this.selectButton);
}
}
return [
{
xtype: 'container',
itemId: 'container',
layout: {
type: 'hbox',
align: 'stretch'
},
flex: 1,
items: containerItems
}
].concat(otherItems);
},
onResize: function(w, h)
{
this.callParent(arguments);
if (this.allowSelectContext && this.allowSelectSite && this.bodyEl.getWidth() < 300)
{
var labelHeight = 0;
if (this.labelAlign == "top" && this.labelEl)
{
labelHeight = this.labelEl.getHeight();
}
var minHeight = labelHeight + 2 * 24;
if (h < minHeight)
{
this.getComponent('container').getLayout().setVertical(false);
}
else
{
this.getComponent('container').getLayout().setVertical(true);
}
}
},
getComboBoxConfig: function()
{
var cfg = this.callParent(arguments);
cfg.listeners = cfg.listeners || {};
cfg.listeners.change = {
scope: this,
fn: function(combo, newValue, oldValue)
{
this._siteValues = Ext.Array.from(newValue);
}
}
return cfg;
},
getErrors: function ()
{
var errors = [];
if (this.allowSelectContext)
{
errors = errors.concat(this._getContextCombobox().getErrors());
}
if (this.allowSelectSite)
{
errors = errors.concat(this.combobox.getErrors());
}
return errors;
},
getLabelTpl: function()
{
return '{[values.title]}';
},
getTipTpl: function()
{
var tipTpl = ['<div class="site-tooltip">'];
tipTpl.push('<b>{[values.title]} ({[values.name]})</b><br/>');
tipTpl.push("<u>{{i18n PLUGINS_WEB_WIDGET_SITEMAPWIDGET_TOOLTIP_URL}}</u> : {[values.url]}<br/>");
tipTpl.push("<u>{{i18n PLUGINS_WEB_WIDGET_SITESTREEWIDGET_TOOLTIP_TYPE}}</u> : {[values.type]}<br/>");
tipTpl.push('</div>');
return tipTpl;
},
/**
* Open the dialog box to select sites in sitemap
*/
chooseSite: function()
{
var config = {
title: this.chooseSiteDialogTitle,
icon: this.chooseSiteDialogIcon,
helpMessage: this.chooseSiteDialogHint,
values: this.getSiteValues(),
readAccessOnly: this.readAccessOnly,
sharedSitesOnly: this.sharedSitesOnly,
multiple: this.multiple,
callback: Ext.bind(this._chooseSiteCb, this)
};
Ametys.web.helper.ChooseSite.open(config);
},
/**
* @private
* This function is called after selecting sites
* Sets the value of the field.
* @param {String/String[]} siteNames The names of selected sites
*/
_chooseSiteCb: function(siteNames)
{
this._setSiteValues(siteNames);
},
getStore: function()
{
return Ext.create('Ext.data.Store', {
proxy: {
type: 'ametys',
plugin: 'web',
url: 'search-sites',
reader: {
type: 'json',
rootProperty: 'sites'
}
},
fields: [
{name: 'id'},
{name: 'name'},
{name: 'title'},
{name: 'description'},
{name: 'path'},
{name: 'url'},
{name: 'type'}
],
pageSize: this.maxResult,
remoteSort: true,
sortOnLoad: true,
sorters: [{property: 'title', direction:'ASC'}],
listeners: {
beforeload: {fn: this._onStoreBeforeLoad, scope: this}
}
});
},
/**
* @private
* Set the request parameters before loading the store.
* @param {Ext.data.Store} store The store.
* @param {Ext.data.operation.Operation} operation The Ext.data.operation.Operation object that will be passed to the Proxy to load the Store.
*/
_onStoreBeforeLoad: function(store, operation)
{
var params = operation.getParams() || {};
operation.setParams(Ext.apply(params, {
names: params.name ? params.name.split(',') : null,
query: operation.getParams().query,
readAccessOnly: this.readAccessOnly,
sharedSitesOnly: this.sharedSitesOnly
}));
},
setValue: function (value)
{
// retrieves the value object
value = value || null;
if (value)
{
if (Ext.isString(value))
{
if (!this.simpleValue)
{
if (value.startsWith("{"))
{
value = Ext.JSON.decode(value);
}
else if (value == Ametys.web.form.widget.SelectSite.SITE_CONTEXT_CURRENT || value == Ametys.web.form.widget.SelectSite.SITE_CONTEXT_ALL || value == Ametys.web.form.widget.SelectSite.SITE_CONTEXT_OTHERS)
{
value = {context: value};
}
else
{
value = {context: Ametys.web.form.widget.SelectSite.SITE_CONTEXT_SITES_LIST, sites: value.split(',')}
}
}
else
{
if (value == Ametys.web.form.widget.SelectSite.SITE_CONTEXT_CURRENT)
{
value = {context: null, sites: [Ametys.getAppParameter("siteName")]};
}
else
{
value = {context: null, sites: value.split(',')}
}
}
}
// value should be an object at this point
if (!Ext.isObject(value))
{
value = null;
}
}
if (value)
{
this._setSiteContext(value.context || null);
this._setSiteValues(value.sites || null);
}
else
{
this._setSiteContext(null);
this._setSiteValues(null);
}
},
getValue: function()
{
if (this.simpleValue)
{
if (this.multiple)
{
return this.getSiteValues();
}
else
{
return this.getSiteValues() && this.getSiteValues().length > 0 ? this.getSiteValues()[0] : null;
}
}
else
{
var value = {
context: this.getSiteContext(),
sites: this.getSiteValues()
};
}
return Ext.encode(value);
},
/**
* Retrieves the site context
* @return {String} The site context.
*/
getSiteContext: function()
{
let contextCombobox = this._getContextCombobox();
return contextCombobox ? contextCombobox.getValue() : null;
},
/**
* @private
* Set the value of the site context field
* @param {String} context The site context to set. If null the default site context Ametys.web.form.widget.SelectSite.SITE_CONTEXT_CURRENT will be used.
*/
_setSiteContext: function(context)
{
// Remember the value, because when the combobox is loading (after a set value), a call to combobox.getValue do not return the desired value.
this._contextValue = context || Ametys.web.form.widget.SelectSite.SITE_CONTEXT_CURRENT;
let contextCombobox = this._getContextCombobox();
if (contextCombobox)
{
contextCombobox.setValue(this._contextValue);
}
},
/**
* @private
* Retrieves the site context field
*/
_getContextCombobox: function()
{
if (this.allowSelectContext && !this._contextCombobox)
{
this._contextCombobox = this.getComponent('container').getComponent('select-site-context');
}
return this._contextCombobox;
},
/**
* Retrieves the site values
* @return {String[]} Array of the site names or null if the field is disabled
*/
getSiteValues: function()
{
return this._siteValues || null;
},
/**
* @private
* Set the site values
* @param {String/String[]} values The names of the site to set. Can be null to empty the field.
*/
_setSiteValues: function(values)
{
values = Ext.Array.from(values);
// Remember the values, because when the combobox is loading (after a set value), a call to combobox.getValue do not return the desired value.
this._siteValues = values;
if (this.combobox)
{
values = this.multiple ? values : values[0];
this.combobox.setValue(values);
}
},
/**
* @private
* Called when the context combobox is rendered.
* @param {Ext.form.field.Field} combobox The context combobox
*/
_onContextRender: function(combobox)
{
// If the context value was initialized before the context combobox is rendered, set the value now.
if (this._contextValue != null)
{
combobox.setValue(this._contextValue);
}
},
/**
* @private
* Listener on the change event of the context combobox
* @param {Ext.form.field.Field} field the field
* @param {String} newValue The new context
* @param {String} oldValue The old context
*/
_onContextChange: function(field, newValue, oldValue)
{
this._contextValue = newValue;
if (newValue != Ametys.web.form.widget.SelectSite.SITE_CONTEXT_SITES_LIST)
{
this._disableSelectSite();
}
else
{
this._enableSelectSite();
}
},
/**
* @private
* Enable the select site field
*/
_enableSelectSite: function()
{
if (this.combobox)
{
this.combobox.enable();
this.combobox.allowBlank = false;
if (this.selectButton)
{
this.selectButton.enable();
}
}
},
/**
* @private
* Disable the select site field
*/
_disableSelectSite: function()
{
if (this.combobox)
{
this.combobox.disable();
this.combobox.allowBlank = true;
this.combobox.lastQuery = null;
this._setSiteValues(null);
this.combobox.clearInvalid();
if (this.selectButton)
{
this.selectButton.disable();
}
}
},
getReadableValue: function(separator)
{
separator = separator || ',';
var readableValue = '';
let contextCombobox = this._getContextCombobox();
if (contextCombobox)
{
var contextStore = contextCombobox.getStore(),
index = contextStore.find(this.valueField, this.getSiteContext()),
record = contextStore.getAt(index);
if (record)
{
readableValue += record.get(this.displayField);
}
}
if (this.combobox)
{
readableValue += this.callParent(arguments);
}
return readableValue;
}
});