/*
* 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 allows to search contents.
*/
Ext.define('Ametys.plugins.cms.search.ContentSearchTool', {
extend: 'Ametys.plugins.cms.search.AbstractFacetedSearchTool',
/**
* @cfg {Boolean/String} [allowEdition=true] Set to false to disable grid edition
*/
/**
* @cfg {Boolean} [allowAdditionalExtensions=true] Set to false to disable additional extensions brought by {@link Ametys.plugins.cms.search.ContentSearchToolExtensions}
*/
/**
* @cfg {Boolean} [allowExportResults=true] Set to false to disallow the search results export
*/
/**
* @cfg {Boolean} [autoOpenSingleContent=false] Set to true to open the content when the search returns only one result.
*/
/**
* @cfg {Number/String} [workflowEditActionId=2] The workflow action to use when editing the grid
*/
/**
* @property {Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel} advancedSearchForm The advanced search form
* @private
*/
/**
* @property {String} _advancedLanguageCriterionId the id of the criterion language for advanced search
* @private
*/
/**
* @property {String} _modelId The id of search model
* @private
*/
/**
* @property {String} _facetModelName The unique name of the model used by the facet store
* @private
*/
/**
* @property {String} _exportCSVUrl The url for CSV export
* @private
*/
/**
* @property {String} _exportCSVUrlPlugin The plugin name for CSV export
* @private
*/
/**
* @property {String} _exportDOCUrl The url for DOC export
* @private
*/
/**
* @property {String} _exportDOCUrlPlugin The plugin name for DOC export
* @private
*/
/**
* @property {String} _exportXMLUrl The url for XML export
* @private
*/
/**
* @property {String} _exportXMLUrlPlugin The plugin name for XML export
* @private
*/
/**
* @property {String} _exportPDFUrl The url for PDF export
* @private
*/
/**
* @property {String} _exportPDFUrlPlugin The plugin name for PDF export
* @private
*/
/**
* @property {String} _printUrl The url for print
* @private
*/
/**
* @property {String} _printUrlPlugin The plugin name for print
* @private
*/
/**
* @property {String} _summaryView The name of the summary view. Not null to display a summary of the record content on a click on a '+' icon on the left of the row, null otherwise.
* @private
*/
/**
* @property {Object} _initialFormatting The initial formatting of the tool
* @private
*/
/**
* @cfg {Object} groupHeaderTpl The group header template search grid
*/
groupHeaderTpl: [
'{columnName}: {name:this.formatName}',
{
formatName: function(name) {
return Ext.String.trim(name.toString());
}
}
],
/**
* @cfg {Boolean} enableGroupingMenu True to enable the grouping control in search grid columns
*/
enableGroupingMenu: true,
statics: {
/**
* Add the default content field mappings to a field array (content type icon, workflow step icon, ...)
* @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', mapping: 'id'},
{name: 'name', mapping: 'name'},
{name: 'iconSmall', mapping: 'smallIcon'},
{name: 'iconGlyph', mapping: 'iconGlyph'},
{name: 'iconDecorator', mapping: 'iconDecorator'},
{name: 'languageIcon', mapping: 'properties.contentLanguage.icon'},
{name: 'workflowStepId', mapping: 'properties.workflowStep.stepId'},
{name: 'workflowStepIcon', mapping: 'properties.workflowStep.smallIcon'}
];
},
/**
* @property {Number}
* @readonly
* @static
* The default limit for the 'show column' menu of the result grid header, to display a filter on the columns
*/
HEADER_SHOW_COLUMN_MENU_DEFAULT_FILTERING_LIMIT: 10
},
/**
* @readonly
* @property {Boolean} contentSearchTabCompatible Specify this tool is compatible with the 'content search tab'.
*/
contentSearchTabCompatible: true,
/**
* @cfg {Number} [headerShowColumnMenuFilteringLimit=Ametys.plugins.cms.search.ContentSearchTool.HEADER_SHOW_COLUMN_MENU_DEFAULT_FILTERING_LIMIT] The limit for the 'show column' menu of the result grid header, to display a filter on the columns
*/
_getDefaultContentFields: function()
{
var defaultContentFields = this.statics()._getDefaultContentFields();
if (this._summaryView)
{
defaultContentFields.push(
{name:'rowBodyContent', defaultValue: ''},
{name:'rowBodyLoaded', defaultValue: false});
}
return defaultContentFields;
},
constructor: function(config)
{
this.callParent(arguments);
this.workflowEditActionId = parseInt(config.workflowEditActionId || 2);
this.allowAdditionalExtensions = config.allowAdditionalExtensions != false && config.allowAdditionalExtensions != "false"; // true by default
this.allowEdition = config.allowEdition != false && config.allowEdition != "false"; // true by default
this.allowExportResults = config.allowExportResults != false && config.allowExportResults != "false"; // true by default
this.autoOpenSingleContent = config.autoOpenSingleContent == true || config.autoOpenSingleContent == "true"; // false by default
if (!this.headerShowColumnMenuFilteringLimit)
{
this.headerShowColumnMenuFilteringLimit = this.statics().HEADER_SHOW_COLUMN_MENU_DEFAULT_FILTERING_LIMIT;
}
Ametys.message.MessageBus.on(Ametys.message.Message.ARCHIVED, this._onContentArchived, this);
Ametys.message.MessageBus.on(Ametys.message.Message.UNARCHIVED, this._onContentUnarchived, this);
},
/**
* @protected
* @template
* Get the role of the message target to use to listen and write to the bus.
* The default impl returns Ametys.message.MessageTarget#CONTENT
* @return {String} The role such as 'content'.
*/
getMessageTargetRole: function()
{
return Ametys.message.MessageTarget.CONTENT;
},
/**
* @protected
* Gets the configuration object for the the RowRender plugin
* @return {Object} the configuration object for the the RowRender plugin
*/
_getRowRenderPluginCfg: function()
{
return {
ptype: 'rowexpander',
id: 'rowexpander',
expandOnDblClick: false,
rowBodyTpl: new Ext.XTemplate('{rowBodyContent}')
};
},
/**
* @private
* Listener on the view of the result grid fired when the RowBody is expanded.
* Loads from server the content of the RowBody to set.
* @param {HTMLElement} rowNode The <tr> element which owns the expanded row.
* @param {Ext.data.Model} record The record providing the data.
* @param {HTMLElement} expandRow The <tr> element containing the expanded data.
*/
_onExpandBody: function(rowNode, record, expandRow)
{
if (record.get('rowBodyLoaded'))
{
return;
}
function updateRowBodyContent(data, arguments)
{
if (data == null)
{
// An error occured (such as the metadata set does not exist for the given content)
record.set('rowBodyContent', '', {dirty: false});
}
else
{
var result = Ext.dom.Query.selectValue(">", data);
record.set('rowBodyContent', result, {dirty: false});
}
record.set('rowBodyLoaded', true, {dirty: false});
};
Ametys.data.ServerComm.send({
workspace: 'cms',
url: "_content.html?contentId=" + record.get('id') + "&viewName=" + this._summaryView,
parameters: {},
priority: Ametys.data.ServerComm.PRIORITY_MAJOR,
callback: {
handler: updateRowBodyContent,
scope: this
},
waitMessage: {
msg: "{{i18n UITOOL_SEARCH_ROWBODY_WAITING_MESSAGE}}",
target: this.grid
},
errorMessage: false,
responseType: 'text'
});
},
/**
* @protected
* Get the config to be used to create the result grid.
* @param {Ext.data.Store} store The store to use for the grid
* @return {Object} The config object
*/
_getResultGridCfg: function(store)
{
return {
store: store,
region: 'center',
split: true,
border: false,
allowEdition: this.allowEdition,
workflowEditActionId: this.workflowEditActionId,
stateful: true,
stateId: this.getStateId() + "$grid",
columns: [],
showColumnMenuFilteringLimit: this.headerShowColumnMenuFilteringLimit,
showColumnMenuSortOption: true,
messageTarget: this.getMessageTargetRole(),
selModel : {
mode: 'MULTI'
},
plugins: [this._getRowRenderPluginCfg(), {
ptype: 'multisort',
maxNumberOfSortFields: 3
}],
features: [{
ftype: 'grouping',
enableGroupingMenu: this.enableGroupingMenu,
groupHeaderTpl: this.groupHeaderTpl
}],
viewConfig: {
loadingText: "{{i18n plugin.cms:UITOOL_SEARCH_WAITING_MESSAGE}}"
},
listeners: {
'itemdblclick': {fn: function (view, record, item, index, e) { this.openContent(record); }, scope: this},
'outofdate': Ext.bind(this.showOutOfDate, this, [false], false),
'dirtychange': Ext.bind(this.onDirtyChange, this)
}
};
},
_createResultGrid: function(store)
{
return Ext.create("Ametys.cms.content.EditContentsGrid", this._getResultGridCfg(store));
},
close: function (manual)
{
if (manual && this.grid.isModeEdition())
{
var me = this;
this.grid.discardChanges(true, function() {
Ametys.plugins.cms.search.ContentSearchTool.superclass.close.call(me, [manual])
});
}
else
{
this.callParent(arguments);
}
},
/**
* Open content (on double-click for example)
* @param {Ext.data.Model} record The content to open
*/
openContent: function (record)
{
Ametys.cms.content.EditContentsGrid.openContent (record);
},
/**
* @private
* Listener when the grid is dirty or not
* @param {Boolean} dirty True if the grid is dirty
*/
onDirtyChange: function(dirty)
{
this.setDirty(dirty);
// Hide the search form and facet
this._getFormPanel().setVisible(!dirty);
this.facetPanel.setVisible(this.facetPanel.shouldBeVisible && !dirty);
},
getSearchFormPanelSubItems: function()
{
var subitems = this.callParent(arguments); // array with one item: the simple search form. We will add the advanced search form
this.advancedSearchForm = this._createAdvancedSearchFormPanel();
subitems.push(this.advancedSearchForm);
return subitems;
},
/**
* Create the advanced form panel.
* @private
*/
_createAdvancedSearchFormPanel: function()
{
return Ext.create('Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel', {
itemId: 'advanced-search-form-panel',
border: false,
bodyPadding: 10,
cls: 'search-tool-advanced-form-panel',
scrollable: true
});
},
getMBSelectionInteraction: function()
{
return Ametys.tool.Tool.MB_TYPE_ACTIVE;
},
_getStoreCfg: function()
{
// The proxy of this store is automatically configured in the _configureProxy method
return {
remoteSort: true,
sortOnLoad: true,
listeners: {
'beforeload': {fn: this._onBeforeLoad, scope: this},
'load': {fn: this._onLoad, scope: this}
}
};
},
_getReaderCfg: function()
{
return {
type: 'json',
rootProperty: 'contents',
transform: {
fn: this._transformSearchData,
scope: this
}
// messageProperty: 'message'
};
},
_createModel: function(fields)
{
fields = fields || [
{name: 'id', mapping: 'id'},
{name: 'icon-small', mapping: 'smallIcon'},
{name: 'icon-glyph', mapping: 'glyphIcon'},
{name: 'icon-decorator', mapping: 'iconDecorator'},
{name: 'name', mapping: 'name'},
{name: 'title', mapping: 'title', type: 'string'},
{name: 'lastModified', type: 'date', mapping: 'properties.lastModified'},
{name: 'contributor', mapping: 'properties.contributor.login', type: 'string'},
{name: 'contributorDisplay', mapping: 'properties.contributor.fullname', type: 'string'},
{name: 'workflow-step', mapping: 'properties.workflowStep.name'},
{name: 'workflow-step-id', mapping: 'properties.workflowStep.stepId'},
{name: 'workflow-icon-small', mapping: 'properties.workflowStep.smallIcon'}
];
this.callParent(arguments);
},
/**
* Transform search data before loading it. Used to extract error and facet results.
* @param data {Object} The original data object.
* @return {Object} The transformed data object.
* @private
*/
_transformSearchData: function(data)
{
this._error = data.error;
this._facets = data.facets;
// Return the data untouched.
return data;
},
_getSearchFormPanelBBar: function()
{
var items = this.callParent(arguments);
if (this.allowAdditionalExtensions)
{
var me = this;
var leftAddOns = this._getAdditionalExtensions('l');
if (leftAddOns)
{
Ext.Array.each (leftAddOns, function (buttonConfig) {
items.push (Ext.apply(buttonConfig, {toolId: me.getId()}));
});
}
}
// Right items.
items.push ('->');
this._getSwitchModeButtons(items);
if (this.allowAdditionalExtensions)
{
var rightAddOns = this._getAdditionalExtensions('r');
if (rightAddOns && rightAddOns.length > 0)
{
Ext.Array.each (rightAddOns, function (buttonConfig) {
items.push (Ext.apply(buttonConfig, {toolId: me.getId()}));
});
}
}
return items;
},
/**
* @protected
* Get the additional buttons to switch to simple or advanced mode
* @param {Ext.button.Button} items the toolbar items
*/
_getSwitchModeButtons: function (items)
{
items.push(
{
// Switch to simple mode
itemId: 'toggle-simple-search-mode',
text: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_BUTTON_SIMPLE_SEARCH}}",
iconCls: 'ametysicon-magnifier12',
enableToggle: true,
toggleGroup: this.getId() + '-search-mode',
allowDepress: false,
toggleHandler: this._toggleSimpleSearchMode,
scope: this,
hidden: true,
tooltip: {
title: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_BUTTON_SIMPLE_SEARCH}}",
text: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_BUTTON_SIMPLE_SEARCH_TOOLTIP}}",
glyphIcon: 'ametysicon-magnifier12',
inribbon: false
}
},{
// Switch to advanced mode
itemId: 'toggle-advanced-search-mode',
text: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_BUTTON_ADVANCED_SEARCH}}",
iconCls: 'ametysicon-magnifier12', // FIXME decorator-ametysicon-three115
enableToggle: true,
allowDepress: false,
toggleGroup: this.getId() + '-search-mode',
toggleHandler: this._toggleAdvancedSearchMode,
hidden: true,
scope: this,
tooltip: {
title: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_BUTTON_ADVANCED_SEARCH}}",
text: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_BUTTON_ADVANCED_SEARCH_TOOLTIP}}",
iconDecorator: 'decorator-ametysicon-three115',
glyphIcon: 'ametysicon-magnifier12',
inribbon: false
}
});
},
/**
* Show or hide the "switch mode" buttons.
* @param {Boolean} visible `true` to display the buttons, `false` to hide them.
* @private
*/
_showHideSwitchModeButtons: function (visible)
{
var btn = this.searchPanel.getDockedItems('toolbar[dock="bottom"]')[0].getComponent('toggle-simple-search-mode');
if (btn)
{
btn.setVisible(visible);
}
btn = this.searchPanel.getDockedItems('toolbar[dock="bottom"]')[0].getComponent('toggle-advanced-search-mode');
if (btn)
{
btn.setVisible(visible);
}
},
/**
* @protected
* Get the additional extensions
* @param {String} location The button location ('l' for left or 'r' for right)
* @return {Array} The additional buttons
*/
_getAdditionalExtensions: function(location)
{
return Ametys.plugins.cms.search.ContentSearchToolExtensions.getAdditionalButtons(location);
},
/**
* Get the current search parameters
* @return {Object} The current search parameters
*/
getCurrentSearchParameters: function ()
{
var params = {
id: this._modelId,
values: this.getSearchValues(),
sort: this.getCurrentSorters()
};
if (this.isAdvancedMode())
{
params.searchMode = 'advanced';
params.language = this.advancedSearchForm.getLanguage();
}
return params;
},
/**
* Get the current search formatting (active columns, sorts, facets)
* @return {Object} The current formatting
*/
getCurrentFormatting: function()
{
var columns = [];
var columnsState = this.grid.getState().columns;
var gridColumns = this.grid.getColumns();
for (let i in gridColumns)
{
let column = gridColumns[i];
// Column without dataIndex are autogenerated by ExtJS
if (column.dataIndex)
{
let initialConfig = column.initialConfig;
let state = columnsState[i];
if (state.id == initialConfig.stateId)
{
// Copy the state without the id to update info like hidden, width etc…
delete state.id;
initialConfig = Ext.apply(initialConfig, state);
}
// formatting need to be serializable so remove the function.
delete initialConfig.renderer;
columns.push(initialConfig);
}
}
var params = {
id: this._modelId,
sort: this.getCurrentSorters(),
facets: this.getFacetValues(),
columns: columns,
state: this.grid.getState()
};
return params;
},
/**
* Apply the initial formatting for this search tool
*/
resetFormatting: function()
{
if (this.applyFormatting)
{
// Reset user prefs for the grid
var gridStateId = this.grid.getStateId();
Ext.state.Manager.getProvider("workspace").set(gridStateId, {});
// Aplly initial formatting
this.applyFormatting(this._getFormattingForReset());
// Grouping
this.grid.getStore().clearGrouping();
}
},
/**
* @protected
* Get the formatting to apply when resetting
* @return {Object} the formatting to apply when resetting
*/
_getFormattingForReset: function()
{
return this._initialFormatting;
},
/**
* Apply the given formatting
* @param {Object} formatting The formatting to apply
* @param {Object} [formatting.state] The state to apply to the grid
* @param {Object} [formatting.facets] The facets to select
* @param {Object} [formatting.sort] The sorters to apply
*/
applyFormatting: function(formatting)
{
this._updatingModel = true; // block search during formatting
if (formatting.sort)
{
this.store.sorters = null; // There is no other way to clean old sorters
this.store.setSorters(formatting.sort);
}
if (formatting.columns)
{
formatting.columns.forEach(column => {
column.renderer = Ametys.plugins.cms.search.SearchGridHelper.getRenderer(column);
// In previous version, the column formatting was the initial formatting
// without the state applied to it.
// Previous version stored the state id in the id field. So we use it to detect the case and apply the state for retrocompatibility
if (column.id != null && formatting.state)
{
delete column.id;
let columnsState = formatting.state.columns;
for (let i in columnsState)
{
if (columnsState[i].id == column.stateId)
{
let state = columnsState[i];
delete state.id;
column = Ext.apply(column, state);
}
}
}
});
// N.B: this.grid.applyState(formatting.state); is not sufficient (columns order is lost and does not work well)
this.grid.reconfigure(this.store, formatting.columns, null, false /* applyState=false: do not use current state, use columns/sort provided by formatting */);
}
this._applyFacets(formatting.facets, function(withFacets) {
this._updatingModel = false; // unblock search
this._launchSearch({faceting: withFacets, sort: formatting.sort}); // launch search with this formatting
}, this);
},
/**
* @private
* Apply the facets (select facets in the tree)
* @param {Object} facets the facets to apply
* @param {Function} callback the callback function to invoke after applying facets. Parameters are :
* @param {Boolean} callback.withFacets if facets are active (eg. at least one facet was selected)
* @param {Object} scope The scope for callback function
*/
_applyFacets: function(facets, callback, scope)
{
if (facets && this.facetPanel)
{
if (this.facetPanel.getRootNode().childNodes.length == 0)
{
// The facets are not visible because the search was never launch
// we need to launch the search first before applying the facets
this._updatingModel = false; // unblock search
// launch a search without facets
this._launchSearch({faceting: false}, function () {
this._updatingModel = true; // block search
this._applyFacets(facets, callback);
});
}
else
{
var facetActive = false;
Ext.Array.forEach(this.facetPanel.getRootNode().childNodes, function(node) {
var facetGroup = node.get('name');
if (facets[facetGroup])
{
// make sure the facets group is expanded
node.expand();
}
Ext.Array.forEach(node.childNodes, function (facet) {
if (facets[facetGroup] && Ext.Array.contains(facets[facetGroup], facet.get('value')))
{
facet.set('checked', true);
facetActive = true;
}
else
{
facet.set('checked', false);
}
});
});
// Invoked callback function
callback.call(scope || this, facetActive);
}
}
else
{
callback.call(scope || this, false); // no facets
}
},
_possiblyCallInternalSetParams: function()
{
// Overriden method, without calling parent because we do not want to always call #_internalSetParams (in case there is no model id for example)
// Was not already opened ?
if (!this._modelId)
{
// First render will be done by onActivate...
this.setTitle(this.getParams().title || this.getInitialConfig('title'));
this.setDescription(this.getParams().description || this.getInitialConfig('description') || this.getInitialConfig('default-description'));
this.setToolHelp(this.getParams()['help'] || this.getInitialConfig('help'));
this.setGlyphIcon(this.getParams()['icon-glyph'] || this.getInitialConfig('icon-glyph'));
this.setIconDecorator(this.getParams()['icon-decorator'] || this.getInitialConfig('icon-decorator'));
this.setIconDecoratorType(this.getParams()['icon-decorator-type'] || this.getInitialConfig('icon-decorator-type'));
this.setSmallIcon(this.getParams()['icon-small'] || this.getInitialConfig('icon-small'));
this.setMediumIcon(this.getParams()['icon-medium'] || this.getInitialConfig('icon-medium'));
this.setLargeIcon(this.getParams()['icon-large'] || this.getInitialConfig('icon-large'));
}
// Was not already opened ?
else
{
this._internalSetParams(false);
}
},
onActivate: function()
{
this.callParent(arguments);
this._internalSetParams(false);
},
_retrieveCriteriaAndColumns: function(force)
{
var params = this.getParams();
var modelId = params.modelId || params.id;
if (this._modelId != modelId || force)
{
// First render will be done by onActivate
this._modelId = modelId;
this._updatingModel = true;
Ametys.data.ServerComm.callMethod({
role: "org.ametys.cms.search.model.SearchModelHelper",
methodName: "getSearchModelConfiguration",
parameters: [this._modelId, this.getContextualParameters()],
callback: {
handler: this._getSearchModelCb,
scope: this,
arguments: {
toolParams: params
}
},
errorMessage: {
msg: "{{i18n plugin.cms:UITOOL_SEARCH_ERROR}}",
category: Ext.getClassName(this)
}
});
}
else
{
this._initSearchForm(params);
this._updatingModel = false;
if (this._startSearchAtOpening)
{
this.refresh();
}
}
},
/**
* @template
* Get the contextual parameters used to get search model and search results.
* @return {Object} the contextual parameters
*/
getContextualParameters: function()
{
var params = Ametys.getAppParameters();
// default language
params.language = Ametys.cms.language.LanguageDAO.getCurrentLanguage();
return params;
},
/**
* Get the contextual parameters for search.
* @return {Object} the contextual parameters.
*/
getSearchContextualParameters: function()
{
var params = this.getContextualParameters();
if (this.isAdvancedMode())
{
params.language = this.advancedSearchForm.getLanguage();
}
return params;
},
/**
* @private
* Callback function after getting model.
* Updates the model and grid columns
* @param {Object} result The server response
* @param {Object} params The callback arguments
*/
_getSearchModelCb: function (result, params)
{
this._exportCSVUrl = result.exportCSVUrl;
this._exportCSVUrlPlugin = result.exportCSVUrlPlugin;
this._exportDOCUrl = result.exportDOCUrl;
this._exportDOCUrlPlugin = result.exportDOCUrlPlugin;
this._exportXMLUrl = result.exportXMLUrl;
this._exportXMLUrlPlugin = result.exportXMLUrlPlugin;
this._exportPDFUrl = result.exportPDFUrl;
this._exportPDFUrlPlugin = result.exportPDFUrlPlugin;
this._printUrl = result.printUrl;
this._printUrlPlugin = result.printUrlPlugin;
this._summaryView = result.summaryView;
if (this._summaryView)
{
this.grid.getView().on('expandbody', this._onExpandBody, this);
}
else
{
// workaround for "disabling" the rowexpander plugin when no summary view is defined
this.grid.on('afterlayout', function(grid) {
Ext.suspendLayouts();
var rowExpanderPlugin = grid.getPlugin('rowexpander'),
rowRenderColumn = rowExpanderPlugin && rowExpanderPlugin.expanderColumn;
if (rowRenderColumn && !rowRenderColumn.isHidden())
{
rowRenderColumn.hide();
}
Ext.resumeLayouts(true);
});
}
var advancedCriteria = result['advanced-criteria'];
if (Ext.isObject(advancedCriteria) && !Ext.Object.isEmpty(advancedCriteria))
{
this._advancedSearchEnabled = true;
this._advancedLanguageCriterionId = advancedCriteria.language ? advancedCriteria.language.name : null;
this._showHideSwitchModeButtons (true);
// Initialize the advanced search form (from the advanced criteria).
this.advancedSearchForm.initialize(result);
}
else
{
// Disable the advanced form and the search mode buttons.
if (this.advancedSearchForm) { this.advancedSearchForm.hide() };
this._showHideSwitchModeButtons (false);
}
if (this.facetPanel)
{
// Hide or show the facet panel depending on if the model has facets.
this.facetPanel.setVisible(result.hasFacets);
this.facetPanel.shouldBeVisible = result.hasFacets;
}
var toolParams = params.toolParams;
this._configureSearchTool(result['simple-criteria'], result, toolParams);
// Set the 'initial' formatting
this._initialFormatting = this._getInitialFormatting(result.columns);
},
/**
* @private
* Gets the 'initial' formatting
* @param {Object[]} initialColumns The initial column configurations
* @return {Object} The 'initial' formatting
*/
_getInitialFormatting: function(initialColumns)
{
var columns = Ametys.plugins.cms.search.SearchGridHelper.getColumnsFromJson(initialColumns, true, null/* Do NOT pass this.grid because we do NOT want grid state information */);
Ext.Array.forEach(columns, function(column) {
delete column.renderer; // avoid storing renderer as function
});
var sort = Ametys.plugins.cms.search.SearchGridHelper.getSortersFromJson(columns, null/* Do NOT pass this.grid because we do NOT want grid state information */);
var initialFormatting = {
id: this._modelId,
sort: sort,
facets: {},
columns: columns
};
return initialFormatting;
},
_initSearchForm: function(params)
{
// TODO Compute the values from params.values AND the criterion default values
// and initialize the form with this.form.setValues (which fires the 'formready' event).
if (params.values)
{
var advancedMode = params.searchMode == 'advanced';
var btn = this.searchPanel.getDockedItems('toolbar[dock="bottom"]')[0].getComponent('toggle-advanced-search-mode');
if (btn)
{
btn.toggle(advancedMode);
}
if (advancedMode)
{
this.advancedSearchForm.initForm(params.values, params.language);
var btn = this.searchPanel.getDockedItems('toolbar[dock="bottom"]')[0].getComponent('toggle-advanced-search-mode');
if (btn)
{
btn.toggle(true);
}
}
else
{
this.callParent(arguments);
var btn = this.searchPanel.getDockedItems('toolbar[dock="bottom"]')[0].getComponent('toggle-simple-search-mode');
if (btn)
{
btn.toggle(true);
}
}
}
else
{
var btn = this.searchPanel.getDockedItems('toolbar[dock="bottom"]')[0].getComponent('toggle-simple-search-mode');
if (btn)
{
btn.toggle(true);
}
}
this._onFormInitialized();
},
refresh: function ()
{
if (this.grid.isModeEdition())
{
var me = this;
this.grid.discardChanges(true, function() {
me.refresh();
});
}
else
{
this.callParent(arguments);
}
},
/**
* Function called before loading the store
* @param {Ext.data.Store} store The store
* @param {Ext.data.operation.Operation} operation The object that will be passed to the Proxy to load the store
* @protected
*/
_onBeforeLoad: function(store, operation)
{
if (this.grid && !this._updatingModel && (!(this.form instanceof Ametys.form.ConfigurableFormPanel) || this.form.isFormReady()))
{
this.grid.getView().unmask();
if (!this.isAdvancedMode() && !this.form.isValid())
{
this._stopSearch();
return false;
}
operation.setParams( Ext.apply(operation.getParams() || {}, {
model: this._modelId,
values: this.getSearchValues()
}));
if (this.isAdvancedMode())
{
operation.setParams(Ext.apply(operation.getParams(), {
searchMode: 'advanced'
}));
}
// Facet handling
this.facetValues = this._faceting ? this.getFacetValues() : {};
operation.setParams(Ext.apply(operation.getParams(), {
facetValues: this.facetValues,
contextualParameters: this.getSearchContextualParameters()
}));
this._error = null;
this._facets = null;
}
else
{
// avoid use less requests at startup (applyState...)
return false;
}
},
/**
* Determines if the activated mode is the advanced mode
* @return true if the activated mode is the advanced mode
*/
isAdvancedMode: function()
{
if (this.searchPanel.getLayout().type == 'card')
{
var activeSearchFormId = this.searchPanel.getLayout().getActiveItem().getItemId();
return activeSearchFormId == 'advanced-search-form-panel';
}
else
{
return false;
}
},
/**
* Get the search values
* @return {Object} The search values
*/
getSearchValues: function()
{
if (this.isAdvancedMode())
{
return Ext.clone(this.advancedSearchForm.getValueTree());
}
else
{
return Ext.clone(this.form.getJsonValues());
}
},
/**
* Initialize the advanced search form criteria from the simple values.
*/
initAdvancedFromSimple: function()
{
var switchMask = Ext.create('Ext.LoadMask', {
target: this.mainPanel,
msg: "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_ADVANCED_SEARCH_LOADING}}",
msgCls: 'ametys-mask-unloading'
});
switchMask.show();
// Send the language value and those of other criteria separately
var simpleValues = Ext.clone(this.form.getJsonValues());
if (this._advancedLanguageCriterionId)
{
var language = simpleValues[this._advancedLanguageCriterionId];
delete simpleValues[this._advancedLanguageCriterionId];
}
this.advancedSearchForm.initForm({
simpleValues: simpleValues
}, language);
if (switchMask)
{
switchMask.hide();
}
},
/**
* Function called after loading results
* @param {Ext.data.Store} store The store
* @param {Ext.data.Model[]} records An array of records
* @param {Boolean} successful True if the operation was successful.
* @param {Ext.data.operation.Operation} operation Operation performed by the proxy
* @protected
*/
_onLoad: function (store, records, successful, operation)
{
if (operation.aborted)
{
// Load has been canceled. Do nothing.
return;
}
// Hack to process groups locally even if remoteSort is enabled.
store.getData().setAutoGroup(true);
this._setGridDisabled(false);
if (!successful)
{
Ametys.log.ErrorDialog.display({
title: "{{i18n plugin.cms:UITOOL_SEARCH_ERROR_TITLE}}",
text: "{{i18n plugin.cms:UITOOL_SEARCH_ERROR}}",
details: "",
category: "Ametys.plugins.cms.search.ContentSearchTool"
});
return;
}
// var rawData = store.getProxy().getReader().rawData;
if (this._error)
{
Ametys.log.ErrorDialog.display({
title: "{{i18n plugin.cms:UITOOL_SEARCH_ERROR_QUERY_TITLE}}",
text: this._error,
details: "",
category: "Ametys.plugins.cms.search.ContentSearchTool"
});
}
// Load facet store
if (this.facetPanel.isVisible() && this._facets)
{
if (!this._faceting)
{
// TODO Create a brand new memory proxy with the data and set it, instead of just changing the 'data' property?
this.facetPanel.getStore().getProxy().setData(this._facets);
this.facetPanel.getStore().load();
this.facetPanel.getRootNode().expand();
}
else
{
this._updateFacetPanel(this._facets);
}
}
if (records.length == 0)
{
this.grid.getView().mask("{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_NO_RESULT}}", 'ametys-mask-unloading');
}
if (this.autoOpenSingleContent && records.length == 1)
{
// Direct open the content
this.openContent(records[0]);
}
if (this._expanded || records.length == 0)
{
this.searchPanel.expand();
}
else
{
this.searchPanel.collapse();
}
/* var columns = this.grid.columnManager.columns;
Ext.Array.each (columns, function (column) {
column.autoSize();
});*/
var toolParams = this.getCurrentSearchParameters();
toolParams.title = this.getTitle();
toolParams['icon-small'] = this.getSmallIcon();
toolParams['icon-medium'] = this.getMediumIcon();
toolParams['icon-large'] = this.getLargeIcon();
toolParams.startSearchAtOpening = true;
var description = this.getDescription();
if (this._advancedSearchEnabled == true)
{
description += '<br/><u>' + "{{i18n PLUGINS_CMS_UITOOL_SEARCH_NAVHISTORY_DESCRIPTION_MODE_LABEL}}" + '</u> : ';
if (this.isAdvancedMode())
{
description += "{{i18n PLUGINS_CMS_UITOOL_SEARCH_NAVHISTORY_DESCRIPTION_MODE_ADVANCED}}";
}
else
{
description += "{{i18n PLUGINS_CMS_UITOOL_SEARCH_NAVHISTORY_DESCRIPTION_MODE_SIMPLE}}";
}
}
description += "<br/><a>{{i18n PLUGINS_CMS_UITOOL_SEARCH_NAVHISTORY_SEARCH_ACTION}}</a>";
var toolId = this.getFactory().getId();
var id = toolId + '$' + this._modelId;
// Save to history dao
Ametys.navhistory.HistoryDAO.addEntry({
id: id + "$" + Ext.JSON.encode(toolParams.values).replace(/"/g, "").replace(/\\/g, "").replace(/'/g, "").replace(/ /g, "_"), // Unique ID for this search
label: this.getTitle(),
description: description,
iconGlyph: this.getGlyphIcon(),
iconSmall: this.getSmallIcon(),
iconMedium: this.getMediumIcon(),
iconLarge: this.getLargeIcon(),
type: Ametys.navhistory.HistoryDAO.SEARCH_TYPE,
action: function () {Ametys.tool.ToolsManager.openTool(toolId, toolParams)}
});
},
sendCurrentSelection: function()
{
this.grid.sendCurrentSelection();
},
/**
* @inheritdoc
* @param {Object/Ext.button.Button} [params] Additional parameters, or the button which was clicked if this function was bound to a button
* @param {Boolean} [params.faceting] true if the search was launch from facet tree.
* @param {Object} [params.sort] the sorters to apply
* @param {Function} [callback] Callback function to execute after search
* @param {Object} [scope] The scope for callback function
*/
_launchSearch: function(params, callback, scope)
{
params = params || {};
// If search launched by the search button, and sorters have to be changed (i.e. one of the textual search criteria is not empty), change the sorters
var searchButton = params.isButton && params.itemId == 'search';
if (searchButton)
{
var sorters = Ametys.plugins.cms.search.SearchGridHelper.getDefaultSorters(this.form.getValues());
if (sorters !== undefined)
{
this.store.sorters = null; // clean old sorters
this.store.setSorters(sorters);
}
}
this.callParent([params, callback, scope]);
},
/**
* Change the search mode.
* @param {Ext.button.Button} button The pressed button
* @param {Boolean} state The state
* @private
*/
_toggleAdvancedSearchMode: function(button, state)
{
if (this.searchPanel.getLayout().type == 'card')
{
// Load the advanced search form from the simple search form values.
this.searchPanel.getLayout().setActiveItem('advanced-search-form-panel');
this.searchPanel.setTitle("{{i18n PLUGINS_CMS_UITOOL_ADVANCED_SEARCH_PANEL_TITLE}}");
this.initAdvancedFromSimple();
}
},
/**
* Change the search mode.
* @param {Ext.button.Button} button The pressed button
* @param {Boolean} state The state
* @private
*/
_toggleSimpleSearchMode: function(button, state)
{
if (this.searchPanel.getLayout().type == 'card')
{
// Active the simple search form
this.searchPanel.getLayout().setActiveItem('simple-search-form-panel');
this.searchPanel.setTitle("{{i18n plugin.cms:UITOOL_SEARCH_CRITERIA}}");
}
},
/**
* Get the plugin for XML export
* @return {String} The plugin name
*/
getExportXMLUrlPlugin: function ()
{
return this._exportXMLUrlPlugin;
},
/**
* Get the plugin for CSV export
* @return {String} The plugin name
*/
getExportCSVUrlPlugin: function ()
{
return this._exportCSVUrlPlugin;
},
/**
* Get the plugin for doc export
* @return {String} The plugin name
*/
getExportDOCUrlPlugin: function ()
{
return this._exportDOCUrlPlugin;
},
/**
* Get the plugin for PDF export
* @return {String} The plugin name
*/
getExportPDFUrlPlugin: function ()
{
return this._exportPDFUrlPlugin;
},
/**
* Get the plugin for print
* @return {String} The plugin name
*/
getPrintUrlPlugin: function ()
{
return this._printUrlPlugin;
},
/**
* Get the url for XML export
* @return {String} The url
*/
getExportXMLUrl: function ()
{
return this._exportXMLUrl;
},
/**
* Get the url for CSV export
* @return {String} The url
*/
getExportCSVUrl: function ()
{
return this._exportCSVUrl;
},
/**
* Get the url for doc export
* @return {String} The url
*/
getExportDOCUrl: function ()
{
return this._exportDOCUrl;
},
/**
* Get the url for PDF export
* @return {String} The url
*/
getExportPDFUrl: function ()
{
return this._exportPDFUrl;
},
/**
* Get the url for print
* @return {String} The url
*/
getPrintUrl: function ()
{
return this._printUrl;
},
/**
* Get the JS parameters for export
* @return {Object} the parameters
*/
getSearchParametersForExport: function ()
{
var params = {}
params.values = this.getSearchValues();
// If columns are not already set from searchValues, we set them to have only the displayed columns, as in the grid
if (params.values.columns == null)
{
params.values.columns = this.getVisibleColumns();
}
params.sort = this.getEncodedSortersForExport() || undefined;
params.model = this._modelId;
if (this.isAdvancedMode())
{
params.searchMode = 'advanced';
}
params.facetValues = this.getFacetValues();
params.contextualParameters = this.getExportContextualParameters();
params.limit = -1; // no limit
return params;
},
/**
* Get search contextual parameters for export.
* @return {Object} the contextual parameters.
*/
getExportContextualParameters: function()
{
return this.getContextualParameters();
},
/**
* Get the array of current active sorters.
* @return {String} The list of sorters. Entry of the list are object with keys 'property' and 'direction' (optionnal)
*/
getCurrentSorters: function()
{
var sorters = [];
this.store.getSorters().each (function (sorter) {
sorters.push({property: sorter.getProperty(), direction: sorter.getDirection()})
});
return sorters;
},
/**
* Get the JSON encoded array of sorters.
* @return {String} A JSON String representing the list of sorters. Entry of the list are object with keys 'property' and 'direction' (optionnal)
*/
getEncodedSortersForExport: function()
{
return Ext.encode(this.getCurrentSorters() || [{property: 'creationDate', direction: 'ASC'}]);
},
/**
* Get the available columns for grouping
* @return {Object[]} The grouping fields
*/
getAvailableGroupingFields: function()
{
var groupingFields = [];
Ext.Array.forEach(this.grid.getColumns(), function (column) {
if (column.dataIndex)
{
groupingFields.push([column.dataIndex, column.text]);
}
});
return groupingFields;
},
/**
* Get the list of columns which are visible in the tool
* @return {String[]} The id of columns in a Array
*/
getVisibleColumns: function()
{
var columns = [];
Ext.Array.forEach(this.grid.getVisibleColumns(), function (column) {
if (column.dataIndex)
{
columns.push(column.dataIndex);
}
});
return columns;
}
});