/*
* Copyright 2014 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 a tool that display a tree of the current selected content.
* Sub nodes are configurable and may be metadata of the content (subcontent or references).
*/
Ext.define('Ametys.plugins.contentstree.ContentsTreeTool', {
extend: "Ametys.tool.SelectionTool",
/**
* @property {Ext.tree.Panel} _treePanel The tree panel
* @private
*/
/**
* @cfg {Object} selection-target-parameter Use this configuration in addition to #cfg-selection-target-id in order to be more specific. This allow to check a target parameter.
* @cfg {String} selection-target-parameter.name The name of the parameter to check. The string is a regexp that have to match the current selection type. A leading '!' will reverse the regexp condition.
* @cfg {String} selection-target-parameter.value The value of the parameter to check. The string is a regexp that have to match the current selection type. A leading '!' will reverse the regexp condition. If the parameter is an array, it will check if the value is part of the array (using Ext.Array.contains)
*/
/**
* @property {RegExp} _selectionTargetParameterName See #selection-target-parameter.name converted as a regexp. The leading '!' is transmitted to {@link #_reversedSelectionTargetParameterName}
* @private
*/
/**
* @property {Boolean} _reversedSelectionTargetParameterName The leading '!' from {@link #cfg-selection-target-parameter}.name converted to true.
* @private
*/
/**
* @property {RegExp} _selectionTargetParameterValue See #selection-target-parameter.value converted as a regexp. The leading '!' is transmitted to {@link #_reversedSelectionTargetParameterValue}
* @private
*/
/**
* @property {Boolean} _reversedSelectionTargetParameterValue The leading '!' from {@link #cfg-selection-target-parameter}.value converted to true.
* @private
*/
/** @cfg {Boolean/String} [limit-drag-and-drop=false] When true or "true" the drag and drop of contents are limited to the metadata chose as children and the associated content types. Extra tree dragged objects will not be limited */
/**
* @property {Boolean} _limitDragAndDrop See #cfg-limit-drag-and-drop
* @private
*/
/**
* @cfg {String} model The class name of the model for the store. Default to {@link Ametys.plugins.contentstree.ContentsTreePanel.ContentsTreeModel}
*/
/**
* @property {String} _model The model class name. See {@link #cfg-model}.
* @private
*/
/**
* @cfg {String} url The URL for retrieving the nodes for the proxy. Default to 'tree-node.json'
*/
/**
* @property {String} _url See {@link #cfg-url}.
* @private
*/
/**
* @cfg {String} pluginUrl The plugin name for the proxy. See {@link #cfg-url}. Default to 'contentstree'
*/
/**
* @property {String} _pluginUrl See {@link #cfg-pluginUrl}.
* @private
*/
/**
* @cfg {String} [serverRole=org.ametys.plugins.contentstree.ContentsTreeHelper] The role of server component which handle tree filtering
*/
/**
* @property {String} _serverRole See {@link #cfg-serverRole}.
* @private
*/
/**
* @cfg {String} [filterMethodName="filterChildrenContentByRegExp"] The server method name of filter tree
*/
/**
* @property {String} _filterMethodName See {@link #cfg-filterMethodName}.
* @private
*/
/**
* @cfg {String} [rootId] The id of root content to initialize the tree at opening
*/
/**
* @cfg {Boolean|String} [displayToolbar=true] When true or 'true' a toolbar will be available at top of the tree with some help tools. Set to false or 'false' to disable toolbar.
*/
/**
* @property {Boolean} _displayToolbar See {@link #cfg-displayToolbar}.
* @private
*/
/**
* @cfg {Boolean/String} [dropAppendOnly=false] True if the tree should only allow append drops (use for trees which are sorted). See {@link #cfg-dropAppendOnly}.
*/
/**
* @property {Boolean} _dropAppendOnly See {@link #cfg-dropAppendOnly}.
* @private
*/
/**
* @private
* @property {Ext.data.NodeInterface[]} _lastOutOfDateNodes The nodes explaininng why the tree is out of date
*/
/**
* @private
* @property {Number} _ignoreNextModified Should we ignore the next "modified" bus message (> 0)? this happens when drag'n'dropping in the same tree or other in subclasses
*/
_ignoreNextModified: 0,
/**
* @private
* @property {String[]} _supportedContentTypes The list of supported content types
*/
/**
* @private
* @property {String} _selecting The id of the content beeing selected
*/
constructor: function(config)
{
this._lastOutOfDateNodes = [];
var targetParameter = config["selection-target-parameter"];
if (targetParameter)
{
var i = targetParameter.name.indexOf('!');
if (i == 0)
{
this._selectionTargetParameterName = new RegExp(targetParameter.name.substring(1));
this._reversedSelectionTargetParameterName = true;
}
else
{
this._selectionTargetParameterName = new RegExp(targetParameter.name);
this._reversedSelectionTargetParameterName = false;
}
i = targetParameter.value.indexOf('!');
if (i == 0)
{
this._selectionTargetParameterValue = new RegExp(targetParameter.value.substring(1));
this._reversedSelectionTargetParameterValue = true;
}
else
{
this._selectionTargetParameterValue = new RegExp(targetParameter.value);
this._reversedSelectionTargetParameterValue = false;
}
}
Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onModified, this);
Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onDeleted, this);
this._model = config["model"];
this._url = config["url"];
this._pluginUrl = config["pluginUrl"];
this._serverRole = config["serverRole"];
this._filterMethodName = config["filterMethodName"];
this._treeId = config["treeId"];
this._dropAppendOnly = config["dropAppendOnly"] === true || config["dropAppendOnly"] === "true"; // false by default
this._limitDragAndDrop = config["limit-drag-and-drop"] === true || config["limit-drag-and-drop"] === "true"; // false by default
this._supportedContentTypes = Ext.Object.getKeys(config['messagebustype-by-contenttype']);
this._displayToolbar = config["displayToolbar"] !== false && config["displayToolbar"] !== "false"; // true by default
this.callParent(arguments);
},
setParams: function(params)
{
this.callParent(arguments);
if (params.rootId)
{
this.showRefreshing();
this._setContentRootNode(params.rootId);
}
},
createPanel: function()
{
var me = this;
this._treePanel = this._createTree();
return Ext.create('Ext.Container', {
layout: 'card',
activeItem: 0,
items: [
{
xtype: 'component',
cls: 'a-panel-text-empty',
border: false,
html: ''
},
this._treePanel
]
});
},
/**
* @protected
* Create the tree panel
* @return {Ext.tree.Panel} the tree panel
*/
_createTree: function()
{
return Ext.create('Ametys.plugins.contentstree.ContentsTreePanel', this._getTreeConfig());
},
/**
* @protected
* Get configuration for tree
* @return {Object} the tree configuration object
*/
_getTreeConfig: function()
{
return {
model: this._model,
url: this._url,
pluginUrl: this._pluginUrl,
serverRole: this._serverRole,
filterMethodName: this._filterMethodName,
treeId: this._treeId,
displayToolbar: this._displayToolbar,
border: false,
scrollable: true,
animate: true,
multiSelect: false,
root: null,
rootVisible: true,
viewConfig: this._getTreeViewConfig(),
plugins: this._getPlugins(),
listeners: {
'selectionchange': {
fn: this.sendCurrentSelection,
scope: this
},
'load': {
fn: function(store, node, records, successful) {
if (node == this._treePanel.getRootNode())
{
this._treePanel.selectPath(node.getPath());
}
},
scope: this
},
'itemdblclick' : {
fn: this._onDoubleClick,
scope: this
}
}
}
},
/**
* @protected
* Get configuration for tree view
* @return {Object} the tree configuration object
*/
_getTreeViewConfig: function()
{
return {
plugins: this._getViewPlugins(),
listeners: {
'drop': {
fn: this._onDrop,
scope: this
}
}
}
},
/**
* @protected
* Get plugins for the tree view
*/
_getViewPlugins: function()
{
return [
this._getDragNDropPlugin()
]
},
/**
* @protected
* Get plugins for the tree grid
*/
_getPlugins: function()
{
return [
]
},
/**
* @protected
* Get drag N drop plugin
*/
_getDragNDropPlugin: function()
{
return {
ptype: 'ametystreeviewdragdrop',
containerScroll: true,
appendOnly: this._dropAppendOnly,
defaultRelation: Ametys.relation.Relation.MOVE,
sortOnDrop: true,
expandDelay: 500,
allowContainerDrops: true,
setAmetysDragInfos: Ext.bind(this.getDragInfo, this),
setAmetysDropZoneInfos: Ext.bind(this.getDropInfo, this)
};
},
_onSelectionChanged: function(message)
{
message = message || Ametys.message.MessageBus.getCurrentSelectionMessage();
var contentTargets = message.getTargets(Ametys.message.MessageTarget.CONTENT);
if (contentTargets.length > 0)
{
var contentId = contentTargets[0].getParameters().id;
this._selecting = contentId;
var selection = this._treePanel.getSelectionModel().getSelection();
if (selection.length > 0 && selection[0].get('contentId') == contentId && !(this.isRefreshing() || this.isOutOfDate())) // Refreshing tool is due to concurrency ODF-2084
{
// The content is already selected, so just show the selected node in the tree
this._treePanel.ensureVisible(selection[0].getPath());
}
else
{
var index = (this.isRefreshing() || this.isOutOfDate()) ? -1 : this._treePanel.getStore().findExact('contentId', contentId); // Refreshing tool is due to concurrent change ODF-2084
if (index != -1)
{
// Find content's in tree, select it
var node = this._treePanel.getStore().getAt(index);
this._treePanel.setSelection(node);
}
else
{
// is the contenttype one of the supported?
if (this._currentSelectionTargets && this._treePanel.getRootNode().get('contentId') && Ext.Array.intersect(contentTargets[0].getParameters().types, this._supportedContentTypes).length > 0)
{
// Try to get the path of current selection in tree if there is currently a root content
this._getPathInTree(contentId, this._treePanel.getRootNode().get('contentId'), function(pathInTree, args) {
if (contentId != this._selecting)
{
// Abort, another selection was required in the meantime ODF-2084
return;
}
if (pathInTree)
{
// The selected content is part of tree, select it
this.selectByPath(pathInTree)
}
else
{
this._refreshTree(message);
}
}, this);
}
else
{
this.callParent(arguments);
}
}
}
}
},
/** @private */
_refreshTree: function(message)
{
// Content was not found in the tree, let the parent do the job
Ametys.plugins.contentstree.ContentsTreeTool.superclass._onSelectionChanged.call(this, message);
// As we are asynchronous, the message bus has called Ametys.tool.ToolsManager.refreshTools too soon
Ametys.tool.ToolsManager.refreshTool(this);
},
/**
* @template
* Get the path of current selected content into the current tree
* @param {String} contentId The id of content
* @param {String} rootId The id of root content
* @param {Function} callback The callback function to call after getting the path in tree. Parameters have to be:
* @param {String} callback.path The path in tree or null if the content is not part of tree
* @param {Object} callback.args The callback arguments
* @param {Object} [scope] The scope for callback
* @param {Object} [args] The callback arguments.
*/
_getPathInTree: function(contentId, rootId, callback, scope, args)
{
// Nothing by default, invoked callback with a null path
Ext.defer(callback, 1, scope || this, [null, args]);
},
/**
* @protected
* @template
* When the selection has changed to a content, which does not currenlty exists in the tree
* @param {Ametys.message.MessageTarget} content The selection has changed, but we could not select this unknown content
*/
_onSelectionChangedOnUnknownId: function(content)
{
// Nothing by default
},
/**
* When droped
* @private
* @param {HTMLElement} node The {@link Ext.tree.View tree view} node **if any** over which the cursor was positioned.
* @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method. It contains the following properties:
* @param {Boolean} data.copy The value of #copy. Or `true` if #allowCopy is true **and** the control key was pressed as the drag operation began.
* @param {Ext.tree.View} data.view The source tree view from which the drag originated
* @param {HTMLElement} data.ddel The drag proxy element which moves with the cursor
* @param {HTMLElement} data.item The tree view node upon which the mousedown event was registered
* @param {Ext.data.TreeModel[]} data.records An Array of Models representing the selected data being dragged from the source tree view
* @param {Ext.data.TreeModel} overModel The Model over which the drop gesture took place.
* @param {String} dropPosition `"before"` or `"after"` depending on whether the cursor is above or below the mid-line of the node.
*/
_onDrop: function(node, data, overModel, dropPosition)
{
var treeStore = Ext.isFunction(data.records[0].getTreeStore) ? data.records[0].getTreeStore() : null;
if (treeStore && treeStore.getModel().getName() == this._treePanel.getStore().getModel().getName())
{
// Drag and drop in the same tree
this._ignoreNextModified++;
}
},
/**
* @protected
* Listener when the tree is double clicked
* @param {Ext.tree.Panel} tree The tree panel
* @param {Ext.data.Model} record The record that belongs to the item
* @param {HTMLElement} item The item element
* @param {Number} index The item's index
* @param {Ext.event.Event} e The raw event object
* @param {Object} eOpts The options object passed to Ext.util.Observable.addListener.
*/
_onDoubleClick: function(tree, record, item, index, e, eOpts)
{
var contentId = record.get('contentId');
if (contentId)
{
Ametys.tool.ToolsManager.openTool('uitool-content', {id: contentId, mode: 'view'});
}
},
/**
* @protected
* This event is thrown by the getDragData to add the 'source' of the drag.
* @param {Object} item The default drag data that will be transmitted. You have to add a 'source' item in it:
* @param {Ametys.relation.RelationPoint} item.source The source (in the relation way) of the drag operation.
*/
getDragInfo: function(item)
{
var targets = [];
for (var i = 0; i < item.records.length; i++)
{
var cfg = this.getMessageTargetConfiguration(item.records[i]);
if (cfg != null)
{
targets.push(cfg);
}
}
if (targets.length > 0)
{
item.source = {
relationTypes: [Ametys.relation.Relation.MOVE, Ametys.relation.Relation.REFERENCE],
targets: targets
};
}
},
/**
* @protected
* This event is thrown before the beforeDrop event and create the target of the drop operation relation.
* @param {Ext.data.Model[]} targetRecords The target records of the drop operation.
* @param {Object} item The default drag data that will be transmitted. You have to add a 'target' item in it:
* @param {Object} item.target The target (in the relation way) of the drop operation. A Ametys.relation.RelationPoint config.
* @param {"append"/"before"/"after"} dropPosition The drop mode
*/
getDropInfo: function(targetRecords, item, dropPosition)
{
var targets = [],
positionInTargets = -1,
dragTypes = [],
allowedDropTypes = [];
if (this._limitDragAndDrop)
{
dragTypes = item.source && Ext.Array.from(item.source.targets)[0].parameters.types || [];
allowedDropTypes = this._getAllowedDropTypes(dragTypes);
}
Ext.Array.forEach(targetRecords, function(targetRecord) {
var cfg = this.getMessageTargetConfiguration(targetRecord);
if (cfg != null && (targetRecord.isRoot() || dropPosition == 'append'))
{
if (!this._limitDragAndDrop || dragTypes.length == 0 || Ext.Array.intersect(targetRecord.get('contenttypesIds'), allowedDropTypes).length > 0)
{
targets.push(cfg);
}
}
else
{
if (!this._limitDragAndDrop || dragTypes.length == 0 || Ext.Array.intersect(targetRecord.parentNode.get('contenttypesIds'), allowedDropTypes).length > 0)
{
// dropPosition == 'before' or 'after'
positionInTargets = targetRecord.get('index') + (dropPosition == 'after' ? 1 : 0);
targets.push(this.getMessageTargetConfiguration(targetRecord.parentNode));
}
}
}, this);
if (targets.length > 0)
{
item.target = {
relationTypes: [Ametys.relation.Relation.MOVE, Ametys.relation.Relation.REFERENCE],
targets: targets,
positionInTargets: positionInTargets,
preferedMetadata: null
};
}
},
/**
* @protected
* Returns the content types allowed for dropping depending on the content types of the dragged object
* @param {String[]} dragTypes The content types of the dragged object
* @return {String[]} The content types allowed for dropping
*/
_getAllowedDropTypes: function(dragTypes)
{
var me = this, matchedContentTypes = [];
Ext.Object.each(this.getInitialConfig()["metadatapaths-by-contenttype"], function(finalContentTypeId, metadataAndContentTypes) {
Ext.Object.each(metadataAndContentTypes, function(metadataPath, contentTypes) {
if (Ext.Array.intersect(contentTypes, dragTypes).length > 0)
{
matchedContentTypes.push(finalContentTypeId);
}
});
});
return matchedContentTypes;
},
/**
* @protected
* Get the MessageTarget configuration for a given record.
* @param {Ext.data.Record} record The record to transform to message target.
* @return {Object} The configuration of a Ametys.message.MessageTarget corresponding to the record.
*/
getMessageTargetConfiguration: function(record)
{
var selectionId = record.get('contentId');
var contenttypeIds = record.get('contenttypesIds');
var tryContenttypeId;
var messagebustype = "content";
for (var i = 0; i < contenttypeIds.length; i++)
{
tryContenttypeId = contenttypeIds[i];
var tryMessagebustype = this.getInitialConfig("messagebustype-by-contenttype")[tryContenttypeId];
if (tryMessagebustype != null)
{
messagebustype == tryMessagebustype;
break;
}
}
var value = {
id: messagebustype,
parameters: {
ids: [selectionId],
types: record.get('contenttypesIds'),
parentNode: record.parentNode != null ? record.parentNode.get('contentId') : null,
parentMetadataPath: record.get('metadataPath')
}
};
if (this._limitDragAndDrop)
{
value.parameters.childrenMetadataPaths = Ext.Object.getKeys(this.getInitialConfig()["metadatapaths-by-contenttype"][tryContenttypeId]);
}
return value;
},
getMBSelectionInteraction: function()
{
return Ametys.tool.Tool.MB_TYPE_ACTIVE;
},
/**
* Get the currently selected content in the tool
* @return {String} The content identifier
*/
getCurrentSelectedContentId: function()
{
var selectionId;
if (!this.isOutOfDate() && this.getContentPanel().items.get(1).isVisible())
{
var selections = this._treePanel.getSelectionModel().getSelection();
if (selections.length > 0)
{
selectionId = selections[0].get('contentId');
}
}
if (selectionId == null && this.getCurrentSelectionTargets().length > 0)
{
selectionId = this.getCurrentSelectionTargets()[0].getParameters().id;
}
return selectionId;
},
/**
* Get the list of parents ids of the current selection content
*/
getCurrentSelectedContentPathIds: function()
{
var pathIds = [];
if (!this.isOutOfDate() && this.getContentPanel().items.get(1).isVisible())
{
var selections = this._treePanel.getSelectionModel().getSelection();
if (selections.length > 0)
{
var selection = selections[0];
while (selection != null)
{
pathIds.push(selection.get('contentId'));
selection = selection.parentNode;
}
}
}
return pathIds;
},
/**
* Get informations about the parent node of the current selection
* @return {Object} The informations
* @return {Object} return.parentNode The content identifier of the parent node
* @return {String} return.metadataPath The metadata path in the parent node that contains the link to the current selection
*/
getCurrentSelectedParentContentId: function()
{
var selectionId;
var metadataPath;
if (!this.isOutOfDate() && this.getContentPanel().items.get(1).isVisible())
{
var selections = this._treePanel.getSelectionModel().getSelection();
if (selections.length > 0)
{
selectionId = selections[0].parentNode != null ? selections[0].parentNode.get('contentId') : null
metadataPath = selections[0].parentNode != null ? selections[0].get('metadataPath') : null
}
}
if (selectionId == null && this.getCurrentSelectionTargets().length > 0)
{
selectionId = this.getCurrentSelectionTargets()[0].getParameters().parentNode;
metadataPath = this.getCurrentSelectionTargets()[0].getParameters().metadataPath;
}
return { parentNode : selectionId, metadataPath: metadataPath};
},
/**
* Get the content types of the current selection
* @return {String[]} The content types or null
*/
getCurrentSelectedContentTypesIds: function()
{
var selectionId;
if (!this.isOutOfDate() && this.getContentPanel().items.get(1).isVisible())
{
var selections = this._treePanel.getSelectionModel().getSelection();
if (selections.length > 0)
{
selectionId = selections[0].get('contenttypesIds');
}
}
if (selectionId == null && this.getCurrentSelectionTargets().length > 0)
{
selectionId = this.getCurrentSelectionTargets()[0].getParameters()['types'];
}
return selectionId;
},
sendCurrentSelection: function()
{
var targets = [];
var selectionId = this.getCurrentSelectedContentId();
var parentNodeObject = this.getCurrentSelectedParentContentId();
var parentNode = parentNodeObject.parentNode;
var parentPath = parentNodeObject.metadataPath;
var contentPathIds = this.getCurrentSelectedContentPathIds();
if (selectionId)
{
var contenttypeIds = this.getCurrentSelectedContentTypesIds();
var messagebustype = Ametys.message.MessageTarget.CONTENT;
for (var i = 0; i < contenttypeIds.length; i++)
{
var tryContenttypeId = contenttypeIds[i];
var tryMessagebustype = this.getInitialConfig("messagebustype-by-contenttype")[tryContenttypeId];
if (tryMessagebustype != null)
{
messagebustype == tryMessagebustype;
break;
}
}
targets.push({
id: messagebustype,
parameters: { ids: [selectionId], parentNode: parentNode, parentMetadataPath: parentPath, contentPathIds: contentPathIds }
});
}
Ext.create('Ametys.message.Message', {
type: Ametys.message.Message.SELECTION_CHANGED,
targets: targets
});
},
refresh: function()
{
if (this._destroyed || this._currentSelectionTargets == null)
{
this.showUpToDate();
return;
}
var pathToSelect = null;
var currentSelectedRecords = this._treePanel.getSelectionModel().getSelection();
if (currentSelectedRecords.length > 0)
{
pathToSelect = currentSelectedRecords[0].getPath('name');
}
if (this._lastOutOfDateNodes.length > 0)
{
this.showRefreshing();
this._reloadNodes(this._lastOutOfDateNodes, pathToSelect);
}
else
{
var contentTarget = this.getCurrentSelectionTargets()[0];
this.showRefreshing();
// Clear search filter if present
this._treePanel.clearSearchFilter();
this._setContentRootNode(contentTarget.getParameters()['id'], pathToSelect);
}
this._lastOutOfDateNodes = [];
},
/**
* @protected
* Set the content root node
* @param {String} contentId The id of root content
* @param {String} pathToSelect The path to select
*/
_setContentRootNode: function(contentId, pathToSelect)
{
let me = this;
this._treePanel.setContentRootNode(
contentId,
this._getRootNodeInfo(),
Ext.bind(me._refreshed, this, [pathToSelect])
);
},
/**
* @protected
* Get the specific root node configuration
*/
_getRootNodeInfo: function()
{
return {
allowDrag: true,
allowDrop: true
};
},
/**
* @private
* Callback of the #refresh
* @param {String} pathToSelect The path to select after the refresh
* @param {Boolean} [autofocus=true] true to focus to last expanded node
* @param {Boolean} [expand=true] true to expand to last expanded node
*/
_refreshed: function(pathToSelect, autofocus, expand)
{
this.showRefreshed();
var callback = pathToSelect != null ? Ext.bind(this.selectByPath, this, [pathToSelect, autofocus !== false, expand !== false]) : Ext.emptyFn;
function internalCallback()
{
this._treePanel.updateNodeUI(node);
callback();
}
var node = this._treePanel.getRootNode();
if (!node.isLoaded())
{
this._treePanel.getStore().load({
node: node,
callback: function () {
node.expand(false, internalCallback, this);
},
scope: this
});
}
else
{
node.expand(false, callback, this);
}
this.getContentPanel().getLayout().setActiveItem(1);
this.showUpToDate();
},
/**
* Expand the tree to the given path
* @param {String} path The path to expand
* @param {Boolean} autofocus true to focus to last expanded node
* @param {Boolean} expand true to expand to last expanded node
*/
selectByPath: function (path, autofocus, expand)
{
if (path)
{
this._treePanel.selectPath(path, 'name', '/', function(success, lastNode) {
if (success)
{
if (autofocus)
{
(this._treePanel.lockedGrid ? this._treePanel.lockedGrid : this._treePanel).getView().focusNode(lastNode);
}
if (expand)
{
lastNode.expand();
}
}
}, this);
}
},
setNoSelectionMatchState: function (message)
{
if (this._currentSelectionTargets == null)
{
// There is no selection at all => go back to no state
this.callParent(arguments);
var panel = this.getContentPanel().items.get(0);
panel.update(message);
this.getContentPanel().getLayout().setActiveItem(0);
}
// else keep the current selection
},
_isCurrentSelectionChanged: function (targets)
{
var matched;
if (!this.isOutOfDate() && this._currentSelectionTargets != null && targets != null && (matched = this.getMatchingNodes(targets)).length > 0)
{
if (matched && matched.length > 0)
{
this._treePanel.getSelectionModel().select(matched[0]);
}
return false;
}
return this.callParent(arguments);
},
_getMatchingSelectionTargets: function(message)
{
var me = this;
return message.getTargets(Ext.bind(this._testTarget, this, [this._selectionTargetType, this._reversedSelectionTargetType, this._selectionTargetParameterName, this._reversedSelectionTargetParameterName, this._selectionTargetParameterValue, this._reversedSelectionTargetParameterValue], true));
},
/**
* @private
* The function to find matching contents
* @param {Ametys.message.MessageTarget} target The target to test
* @param {RegExp} selectionTypeRegexp The regexp to match the selection type
* @param {boolean} selectionTypeReverseRegexp Is the preceding regexp to be considered as reversed?
* @param {RegExp} selectionParameterNameRegexp The regexp to match a parameter name
* @param {boolean} selectionParameterNameReverseRegexp Is the preceding regexp to be considered as reversed?
* @param {RegExp} selectionParameterValueRegexp The regexp to match the parameter value corresponding to the previously match parameter name
* @param {boolean} selectionParameterValueReverseRegexp Is the preceding regexp to be considered as reversed?
* @return {boolean} true if the target marches
*/
_testTarget: function (target, selectionTypeRegexp, selectionTypeReverseRegexp, selectionParameterNameRegexp, selectionParameterNameReverseRegexp, selectionParameterValueRegexp, selectionParameterValueReverseRegexp)
{
function checkParameters()
{
if (!selectionParameterNameRegexp)
{
return true;
}
function checkValue(value)
{
if (Ext.isArray(value))
{
var gotOne = false;
Ext.each(value, function(v, index, array) {
if (checkValue(v))
{
gotOne = true;
return false; // stop the iteration
}
});
return gotOne;
}
else
{
return ((!selectionParameterValueReverseRegexp && selectionParameterValueRegexp.test(value)
|| selectionParameterValueReverseRegexp && !selectionParameterValueRegexp.test(value)));
}
}
var gotOne = false;
Ext.Object.each(target.getParameters(), function(key, value, parameters) {
if ((!selectionParameterNameReverseRegexp && selectionParameterNameRegexp.test(key)
|| selectionParameterNameReverseRegexp && !selectionParameterNameRegexp.test(key))
&& checkValue(value))
{
gotOne = true;
return false; // stop the iteration
}
});
return gotOne;
}
return (!selectionTypeReverseRegexp && selectionTypeRegexp.test(target.getId())
|| selectionTypeReverseRegexp && !selectionTypeRegexp.test(target.getId()))
&& checkParameters();
},
/**
* Listener when something has been modified
* @param {Ametys.message.Message} [message] The bus message. Can be null to get the last selection message
* @private
*/
_onModified: function(message)
{
if (this._ignoreNextModified > 0)
{
this._ignoreNextModified--;
return;
}
var targets = message.getTargets(function() {return true;});
var nodes = this.getMatchingNodes(targets);
if (nodes.length > 0)
{
this._lastOutOfDateNodes = Ext.Array.merge(this._lastOutOfDateNodes, nodes);
this.showOutOfDate(false);
}
},
/**
* Listener when something has been deleted
* @param {Ametys.message.Message} [message] The bus message. Can be null to get the last selection message
* @private
*/
_onDeleted: function(message)
{
var me = this;
var targets = message.getTargets(function() {return true;});
var nodes = this.getMatchingNodes(targets);
Ext.Array.each(nodes, function(node) {
if (node.data.id == "root")
{
me._currentSelectionTargets = null
me.setNoSelectionMatchState(me.getInitialConfig("selection-description-empty"));
}
node.remove();
});
},
/**
* @private
* Reload nodes, one by one
* @param {Ext.data.NodeInterface[]} nodes The nodes to be refreshed
* @param {String} pathToSelect At the end, the node with this path will be selected
*/
_reloadNodes: function(nodes, pathToSelect)
{
let me = this;
this._treePanel._reloadNodes(nodes, function() {
me._refreshed(pathToSelect, false, true);
});
},
/**
* @private
* Filter nodes to keeps those which are in the tree
* @param {Ametys.message.MessageTarget[]} targets The messages to filter
* @return {Ext.data.NodeInterface[]} The matching nodes
*/
getMatchingNodes: function(targets)
{
var treeTool = this;
var store = treeTool._treePanel.getStore();
var found = [];
Ext.Array.each(targets, function(target) {
var idModified = target.getParameters().id;
if (idModified)
{
var index = store.findExact('contentId', idModified);
while (index != -1)
{
found.push(store.getAt(index));
index = store.findExact('contentId', idModified, index + 1);
}
}
});
return found;
}
});