/*
* 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 tool is an abstract class allowing to search some items, given some configurable criteria, and display the results in a grid (with configurable columns).
*/
Ext.define('Ametys.plugins.cms.search.AbstractSearchTool', {
extend: 'Ametys.tool.Tool',
statics: {
/**
* @property {Number}
* @readonly
* @static
* The number of records to display by 'page'
*/
PAGE_SIZE: 50,
/**
* @property {Number}
* @readonly
* @static
* The max height of search form
*/
FORM_DEFAULT_MAX_HEIGHT: 400,
/**
* @property {Number}
* @readonly
* @static
* The min height of search form
*/
FORM_DEFAULT_MIN_HEIGHT: 100,
/**
* @property {Number}
* @readonly
* @static
* The default ratio of form's height
*/
FORM_DEFAULT_HEIGHT_RATIO: 1/3,
/**
* @property {Number}
* @readonly
* @static
* The max ratio of form's height
*/
FORM_DEFAULT_MAX_HEIGHT_RATIO: 2/3,
/**
* @property {Number}
* @readonly
* @static
* The default width for label's fields
*/
FORM_DEFAULT_LABEL_WIDTH: 120,
/**
* @property {Number}
* @readonly
* @static
* The default width for of form's fields
*/
FORM_DEFAULT_FIELD_WIDTH: 300
},
/**
* @cfg {Number} [formMaxHeight=Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_MAX_HEIGHT] The max height of the form panel
*/
/**
* @cfg {Number} [formMinHeight=Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_MIN_HEIGHT] The min height of the form panel
*/
/**
* @cfg {Number} [formHeightRatio=Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_HEIGHT_RATIO] The ratio of form's height
*/
/**
* @cfg {Number} [formMaxHeightRatio=Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_MAX_HEIGHT_RATIO] The max ratio of form's height
*/
/**
* @cfg {Number} [formLabelWidth=Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_LABEL_WIDTH] The width for label's fields
*/
/**
* @cfg {Number} [formFieldWidth=Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_FIELD_WIDTH] The width for of form's fields
*/
/**
* @cfg {Boolean} [enablePagination=true] Enable the pagination for the grid.
*/
/**
* @property {Ext.panel.Panel} mainPanel The main panel of the tool
* @protected
*/
/**
* @property {Ext.panel.Panel} searchPanel The panel in the top of the tool, which displays the search criteria
* @protected
*/
/**
* @property {Ametys.form.ConfigurableFormPanel} form The simple search form
* @protected
*/
/**
* @property {Ext.data.Store} store The store with the searched items
* @protected
*/
/**
* @property {String} _modelName The unique name of model used by the store
* @private
*/
/**
* @property {Ext.grid.Panel} grid The grid panel displaying the items (results)
* @protected
*/
/**
* @property {Boolean} [_expanded=false] Set to true to always keep form expanded (default to false)
* @private
*/
/**
* @private
* @property {Boolean} _paramsHasChanged If set params was called but not applied. Will be applied on next focus.
*/
/**
* @property {Boolean} [_startSearchAtOpening=false] Set to true to start the search at the opening of the tool (default to false)
* @private
*/
/**
* @property {Boolean} _updatingModel True if the search form, model and columns are currently being updated. If true, all launch searches will be cancelled.
* @protected
*/
/**
* @cfg searchButtonText=plugin.cms:UITOOL_SEARCH_BUTTON_SEARCH The text for search button,
*/
searchButtonText: "{{i18n plugin.cms:UITOOL_SEARCH_BUTTON_SEARCH}}",
/**
* @cfg searchButtonTooltipText=plugin.cms:UITOOL_SEARCH_BUTTON_SEARCH_DESC The tooltip text for search button,
*/
searchButtonTooltipText: "{{i18n plugin.cms:UITOOL_SEARCH_BUTTON_SEARCH_DESC}}",
/**
* @cfg searchButtonIconCls=ametysicon-magnifier12 The CSS class for search button's icon,
*/
searchButtonIconCls: 'ametysicon-magnifier12',
/**
* @cfg cancelButtonText=plugin.cms:UITOOL_SEARCH_BUTTON_CANCEL The text for cancel button,
*/
cancelButtonText: "{{i18n plugin.cms:UITOOL_SEARCH_BUTTON_CANCEL}}",
/**
* @cfg cancelButtonTooltipText=plugin.cms:UITOOL_SEARCH_BUTTON_CANCEL_DESC The tooltip text for cancel button,
*/
cancelButtonTooltipText: "{{i18n plugin.cms:UITOOL_SEARCH_BUTTON_CANCEL_DESC}}",
/**
* @cfg cancelButtonIconCls=ametysicon-forbidden1 The CSS class for cancel search button's icon,
*/
cancelButtonIconCls: 'ametysicon-forbidden1',
constructor: function(config)
{
this.callParent(arguments);
if (!this.formMaxHeight)
{
this.formMaxHeight = Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_MAX_HEIGHT;
}
if (!this.formMinHeight)
{
this.formMinHeight = Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_MIN_HEIGHT;
}
if (!this.formHeightRatio)
{
this.formHeightRatio = Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_HEIGHT_RATIO;
}
if (!this.formMaxHeightRatio)
{
this.formMaxHeightRatio = Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_MAX_HEIGHT_RATIO;
}
if (!this.formLabelWidth)
{
this.formLabelWidth = Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_LABEL_WIDTH;
}
if (!this.formFieldWidth)
{
this.formFieldWidth = Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_FIELD_WIDTH;
}
this.enablePagination = config.enablePagination !== false;
},
/**
* The state id
* Should be overriden for tools using the basic tool factory.
* @return {String} The state id
*/
getStateId: function()
{
return this.getId();
},
createPanel: function()
{
this._updatingModel = true;
this.searchPanel = this._createSearchFormPanel();
var me = this;
this.mainPanel = Ext.create('Ext.panel.Panel', {
cls: 'search-tool',
layout: {
type: 'vbox',
align: 'stretch'
},
border: false,
items: [
this.searchPanel,
{
split: true,
xtype: 'panel',
flex: 1,
border: false,
layout: 'border',
stateful: true,
stateId: this.getStateId() + '$result-panel',
items: this.getBottomPanelItems(),
listeners: {
'resize': function(cmp, width, height)
{
if (!this.collapsed)
{
this._lastKnownFlex = this.flex
}
}
},
applyState: function (state)
{
this.flex = state.flex;
},
getState: function ()
{
// Save the height ratio
return {
flex: this.collapsed ? this._lastKnownFlex : this.flex
}
}
}
]
});
return this.mainPanel;
},
/**
* Creates the panel of search criteria
* @protected
*/
_createSearchFormPanel: function()
{
let me = this;
var cfg = this._getSearchFormPanelConfig(),
items = this.getSearchFormPanelSubItems(),
layout = items.length == 1 ? 'fit' : 'card';
var panelCfg = Ext.apply(cfg, {
split: false,
border: false,
cls: 'criteria',
ui: 'light',
header: {
titlePosition: 1
},
stateful: true,
stateId: this.getStateId() + '$search-form',
title: "{{i18n plugin.cms:UITOOL_SEARCH_CRITERIA}}",
collapsible: true,
titleCollapse: true,
animCollapse: false,
scrollable: 'vertical',
referenceHolder: true,
defaultButton: 'launch',
defaultButtonTarget: 'el',
minHeight: Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_MIN_HEIGHT,
maxHeight: Ametys.plugins.cms.search.AbstractSearchTool.FORM_DEFAULT_MAX_HEIGHT,
flex: 0.3,
layout: layout,
items: items,
listeners: {
'resize': function(cmp, width, height)
{
if (!this.collapsed)
{
this._lastKnownFlex = this.flex
}
}
},
applyState: function (state)
{
this.flex = state.flex;
},
getState: function ()
{
// Save the height ratio
return {
flex: this.collapsed ? this._lastKnownFlex : this.flex
}
}
});
return Ext.create('Ext.Panel', panelCfg);
},
/**
* @template
* @protected
* Get the configuration object of the search panel
* @return {Object} The configuration object
*/
_getSearchFormPanelConfig: function()
{
return {
bbar: this._getSearchFormPanelBBar()
};
},
/**
* @template
* @protected
* Get the items of the optional bottom bar of the criteria panel
* @return {Array} The bottom bar configuration array
*/
_getSearchFormPanelBBar: function()
{
var items = this._getDefaultButtons();
return items;
},
/**
* @private
* Get the default buttons
* @return {Array} The default buttons
*/
_getDefaultButtons: function()
{
var items = [{
// Search
itemId: 'search',
reference: 'launch',
text: this.searchButtonText,
iconCls: this.searchButtonIconCls,
handler: this._launchSearch,
scope: this,
tooltip: {
title: this.searchButtonText,
text: this.searchButtonTooltipText,
glyphIcon: this.searchButtonIconCls,
inribbon: false
}
},
{
// Cancel
itemId: 'cancel',
iconCls: this.cancelButtonIconCls,
handler: this._stopSearch,
scope: this,
disabled: true,
tooltip: {
title: this.cancelButtonText,
text: this.cancelButtonTooltipText,
glyphIcon: this.cancelButtonIconCls,
inribbon: false
}
}];
return items;
},
/**
* @template
* @protected
* Gets the sub-items of the search form panel. By default, only the simple search form panel.
* @return {Ext.Component[]} The sub-items of the search form panel to display (in a card layout)
*/
getSearchFormPanelSubItems: function()
{
this.form = this._createSimpleSearchFormPanel();
return [this.form];
},
/**
* Creates the simple form panel
* @private
*/
_createSimpleSearchFormPanel: function()
{
var formCfg = {
itemId: 'simple-search-form-panel',
cls: 'simple-search-form-panel',
scrollable: 'vertical',
fieldsetLayout: {
type: 'enhancedcolumns',
minColWidth: this.formFieldWidth,
ignoreItem: function (item) { return item.getInitialConfig('widget') == 'edition.hidden'; }
},
labelAlign: 'top',
additionalWidgetsConf: {
searchTool: true
},
additionalWidgetsConfFromParams: {
contentType: 'contentType' // some widgets requires the contentType configuration
},
withTitleOnLabels: true,
};
return Ext.create('Ametys.form.ConfigurableFormPanel', formCfg);
},
/**
* @template
* @protected
* Gets the items of the bottom panel. By default, only the result grid.
* @return {Ext.Component[]} The items to display in the bottom panel
*/
getBottomPanelItems: function()
{
this.store = this.createStore();
// Create interceptor to prevent loading while model, columns and form are not updated
this.store.load = Ext.Function.createInterceptor(this.store.load, function() { return !this._updatingModel; }, this);
this.grid = this._createResultGrid(this.store);
// The Stateful should wait for a reconfigure before operating
this.grid.applyState = Ext.Function.createInterceptor(this.grid.applyState, function() { return this._reconfigured === true; }, this);
this.grid.initState = Ext.Function.createInterceptor(this.grid.initState, function() { return this._reconfigured === true; }, this);
this.grid.getState = Ext.Function.createInterceptor(this.grid.getState, function() { return this._reconfigured === true; }, this);
this.grid.saveState = Ext.Function.createInterceptor(this.grid.saveState, function() { return this._reconfigured === true; }, this);
this.grid.on('reconfigure', Ext.bind(this._onFirstGridReconfigure, this));
return [this.grid];
},
/**
* @private
* Event the first time the grid is reconfigured
*/
_onFirstGridReconfigure: function()
{
this._reconfigured = true;
},
/**
* @protected
* Get the store the grid should use as its data source.
* @return {Ext.data.Store} The store
*/
createStore: function()
{
this._createModel();
var storeCfg = Ext.applyIf({
model: this._modelName,
autoDestroy: true,
pageSize: Ametys.plugins.cms.search.AbstractSearchTool.PAGE_SIZE // seems useless due to this.store.setPageSize done later
}, this._getStoreCfg());
return Ext.create('Ext.data.Store', storeCfg);
},
/**
* @template
* @protected
* Creates the model for the store.
* Override this method for defining your own default fields
* @param {Object[]} [fields] The fields for this model
*/
_createModel: function(fields)
{
fields = fields || [
{name: 'id', mapping: 'id'}
];
this._modelName = this.getId() + "-Ametys.plugins.cms.search.AbstractSearchTool.ContentEntry";
if (Ext.data.schema.Schema.get('default').hasEntity(this._modelName))
{
Ext.data.schema.Schema.get('default').getEntity(this._modelName).replaceFields(fields, true);
}
else
{
Ext.define(this._modelName, {
extend: 'Ext.data.Model',
schema: 'default',
fields: fields
});
}
},
/**
* @template
* @protected
* Get the config to be used to create the store.
* @return {Object} The config object
*/
_getStoreCfg: function()
{
throw new Error("This method is not implemented in " + this.self.getName());
},
/**
* @template
* @protected
* Get the config to be used to create the reader for the store.
* @return {Object} The config object
*/
_getReaderCfg: function()
{
throw new Error("This method is not implemented in " + this.self.getName());
},
/**
* @template
* @protected
* Create the grid panel for results.
* The default implementation creates a Ext.grid.Panel no columns, grouping enabled and pagination toolbar is #cfg-enablePagination is true.
* @param {Ext.data.Store} store The store to use for the grid. You MUST set this parameter as the 'store' configuration of the returned grid.
* @return {Ext.grid.Panel} The created grid
*/
_createResultGrid: function(store)
{
return Ext.create('Ext.grid.Panel', this._getResultGridCfg(store));
},
/**
* @template
* @protected
* Get the configuration object for result grid.
* The default implementation build a simple configuration with no columns, grouping enabled and pagination toolbar is #cfg-enablePagination is true.
* @param {Ext.data.Store} store The store to use for the grid. You MUST set this parameter as the 'store' configuration of the returned grid.
* @return {Ext.grid.Panel} The created grid
*/
_getResultGridCfg: function(store)
{
var dockedItems = [];
if (this.enablePagination)
{
dockedItems.push(this._getPaginationToolbarCfg(store));
}
return {
store: store,
region: 'center',
split: true,
border: false,
stateful: true,
stateId: this.getStateId() + "$grid",
columns: [], // columns will be set later
features: [{
ftype: 'grouping',
groupHeaderTpl: [
'{columnName}: {name:this.formatName}',
{
formatName: function(name) {
return Ext.String.trim(name.toString());
}
}
]
}],
viewConfig: {
loadingText: "{{i18n plugin.cms:UITOOL_SEARCH_WAITING_MESSAGE}}"
},
dockedItems: dockedItems,
listeners: {
'selectionchange': Ext.bind(this._onSelectionChanged, this)
}
};
},
/**
* @protected
* Get the configuration for pagination toolbar
* @param {Ext.data.Store} store The store of the grid
* @return {Object} the object configuration for pagination toolbar
*/
_getPaginationToolbarCfg: function(store)
{
return {
xtype: 'pagingtoolbar',
store: store,
dock: 'bottom',
displayInfo: true,
displayMsg: "{{i18n plugin.cms:UITOOL_SEARCH_PAGEBAR_RESULT_1}}{0}{{i18n plugin.cms:UITOOL_SEARCH_PAGEBAR_RESULT_2}}{1}{{i18n plugin.cms:UITOOL_SEARCH_PAGEBAR_RESULT_3}}{2}",
emptyMsg: "{{i18n plugin.cms:UITOOL_SEARCH_PAGEBAR_NO_RESULT}}"
};
},
/**
* Get the paging bar
* @return {Ext.toolbar.Paging} The paging toolbar
*/
getPaginationToolbar: function()
{
return this.enablePagination ? this.grid.getDockedItems('pagingtoolbar')[0] : null;
},
/**
* @template
* @protected
* Listener when the selection has changed in result grid.
* @param {Ext.selection.Model} sm the selection model
* @param { Ext.data.Model[]} selected the selected record
*/
_onSelectionChanged: function(sm, selected)
{
// nothing by default
},
/**
* Launch the search
* @param {Function} [callback] A callback function to execute after loading
* @param {Object} [scope] The scope for callback function
* @protected
*/
_launchSearch: function(callback, scope)
{
this.showUpToDate();
this._setGridDisabled(true);
var opts = {};
if (Ext.isFunction(callback))
{
opts = {
callback: callback,
scope: scope || this
}
}
// FIXME
// if (this.grid.enablePagination)
// {
this.store.loadPage(1, opts);
// }
// else
// {
// this.store.load();
// }
},
/**
* Stop the search in progress
* @protected
*/
_stopSearch: function()
{
var cancelCode = this.store.getProxy().getCancelCode();
if (cancelCode)
{
Ametys.data.ServerComm.cancel(cancelCode);
}
if (this.grid.getView().loadMask)
{
this.grid.getView().loadMask.hide();
}
this._setGridDisabled(false);
},
/**
* Enable or disable the grid columns and paging toolbar
* @param {Boolean} disabled true to disable.
* @protected
*/
_setGridDisabled: function(disabled)
{
this._getSearchToolbar().items.get('search').setDisabled(disabled);
this._getSearchToolbar().items.get('cancel').setDisabled(!disabled);
if (this.enablePagination)
{
this.getPaginationToolbar().setDisabled(disabled);
}
var columns = this.grid.columnManager.getColumns();
Ext.Array.each (columns, function(column) {
column.setDisabled(disabled);
})
},
/**
* Return the docked form
* @protected
*/
_getFormPanel: function()
{
return this.searchPanel;
},
/**
* Return the search toolbar.
* @protected
*/
_getSearchToolbar: function()
{
return this.searchPanel.getDockedItems('toolbar')[0];
},
setParams: function(params)
{
this.callParent(arguments);
this._paramsHasChanged = true;
this._expanded = params.expanded || false;
this._startSearchAtOpening = params.startSearchAtOpening || false;
this._possiblyCallInternalSetParams();
},
/**
* @template
* @protected
* You can override this method in order to check if you want to call {@link #_internalSetParams}
*/
_possiblyCallInternalSetParams: function()
{
this._internalSetParams(false);
},
/**
* @protected
* This method effectively change params IF params was changed since last call.
* @param {Boolean} force True to force the re-setting of the params (for updating the model and columns for instance)
*/
_internalSetParams: function(force)
{
if (!this._paramsHasChanged && !force)
{
return;
}
this._paramsHasChanged = false;
this._retrieveCriteriaAndColumns(force);
},
/**
* @template
* @protected
* Implement this method with your own logic for retrieving information about search criteria and columns.
* It should call {@link #_configureSearchTool}, either directly, or as a callback function after a server call, so as to finish the process of drawing and configure the search tool.
* @param {Boolean} force True to force the update of the model and columns even if not needed
*/
_retrieveCriteriaAndColumns: function(force)
{
throw new Error("This method is not implemented in " + this.self.getName());
},
/**
* @protected
* Call this method to configure the search tool
* @param {Object} criteria The search criteria
* @param {Object} data The data object where to retrieve information in order to configure the columns
* @param {Object[]} data.columns The columns definition
* @param {Object} toolParams The parameters of the tool
*/
_configureSearchTool: function(criteria, data, toolParams)
{
this._configureSearchForm(criteria);
this._configureSearchColumns(data, toolParams);
this._updatingModel = false;
if (this._startSearchAtOpening)
{
this.refresh();
}
},
/**
* @protected
* Configures the search criteria
* @param {Object} criteria The search criteria
*/
_configureSearchForm: function(criteria)
{
if (this.form instanceof Ametys.form.ConfigurableFormPanel)
{
this.form.configure(criteria);
}
if (this.form.items.get(0))
{
this.form.items.get(0).on('resize', this._initSearchFormHeight, this, {single: true});
}
},
/**
* @protected
* Configures the search columns
* @param {Object} data The data object where to retrieve information in order to configure the columns
* @param {Object[]} data.columns The columns definition
* @param {String} data.searchUrlPlugin The name of the plugin which will be set in the proxy of the store
* @param {String} data.searchUrl The URL which will be set in the proxy of the store
* @param {Number} [data.pageSize] The page size
* @param {Object} toolParams The parameters of the tool
*/
_configureSearchColumns: function(data, toolParams)
{
var fields = Ametys.plugins.cms.search.SearchGridHelper.getFieldsFromJson(data.columns);
this._addDefaultContentFields(fields);
var columns = this._getColumns(data);
var sorters = toolParams.sort || Ametys.plugins.cms.search.SearchGridHelper.getSortersFromJson(columns, this.grid);
this._configureProxy(data);
if (data.pageSize != null && data.pageSize > 0)
{
this.store.setPageSize(data.pageSize);
}
// Update model
this._createModel(fields);
this.grid.reconfigure(this.store, columns);
this.store.sorters = null; // There is no other way to clean old sorters...
this.store.setSorters(sorters);
// TODO Init search form from the default values.
this._initSearchForm(toolParams);
},
/**
* @protected
* Call this method to get the columns
* @param {Object[]} data.columns The columns definition
*/
_getColumns: function(data)
{
return Ametys.plugins.cms.search.SearchGridHelper.getColumnsFromJson(data.columns, true, this.grid, false);
},
/**
* @protected
* Configures the search store proxy
* @param {Object} data The data object where to retrieve information in order to configure the columns
* @param {String} [data.searchUrlPlugin] The name of the plugin which will be set in the proxy of the store
* @param {String} [data.searchUrl] The URL which will be set in the proxy of the store
* @param {String} [data.searchRole] The role which will be set in the proxy of the store
* @param {String} [data.searchMethodName] The method name which will be set in the proxy of the store
*/
_configureProxy: function(data)
{
this.store.setProxy({
type: 'ametys',
plugin: data.searchUrlPlugin,
url: data.searchUrl,
role: data.searchRole,
methodName: data.searchMethodName,
cancelOutdatedRequest: true,
reader: this._getReaderCfg()
});
},
/**
* @protected
* Add some default field mappings to a field array
* @param {Array} fields the field array to fill.
*/
_addDefaultContentFields: function(fields)
{
if (fields != null && Ext.isArray(fields))
{
Array.prototype.push.apply(fields, this._getDefaultContentFields());
}
},
/**
* @protected
* Gets the default field mappings
* @return {Object[]} the default field mappings
*/
_getDefaultContentFields: function()
{
return [{name: 'id'}];
},
refresh: function()
{
this.callParent(arguments);
this._launchSearch();
},
/**
* @protected
* Fills the search form with initial search parameters
* @param {Object} params The parameters of the tool
* @param {Object} params.values The initial search parameters for the form
*/
_initSearchForm: function(params)
{
var form = this.form.getForm();
Ext.Object.each(params.values, function(name, value) {
var fd = form.findField(name);
if (fd != null)
{
fd.setValue(value);
}
});
this._onFormInitialized();
},
/**
* @protected
* Called when the form is initialized
*/
_onFormInitialized: function()
{
if (this.form instanceof Ametys.form.ConfigurableFormPanel)
{
// Dummy "set values" call to send the formready event.
this.form.setValues();
}
},
/**
* Initialize the height of the search form panel
* @protected
*/
_initSearchFormHeight: function()
{
var form = this._getFormPanel();
var height = form.getSize().height;
var maxHeight = Math.min(this.formMaxHeight, parseInt(this.mainPanel.getHeight() * this.formMaxHeightRatio));
if (height > maxHeight)
{
height = maxHeight;
form.setHeight(height);
}
},
/**
* Set the #cfg-title with a concatenation of original title and an additional one
* @param {String} additionalTitle The prefix to add to the original title
*/
setAdditionalTitle: function(additionalTitle)
{
var toolParams = this.getParams();
toolParams.title = this.getInitialConfig('title') + (additionalTitle ? ' - ' + additionalTitle : '');
Ametys.tool.Tool.prototype.setParams.apply(this, [toolParams]); // avoid tool refreshing
this.setTitle(toolParams.title);
},
});