/*
* Copyright 2015 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.
*/
/**
* Explorer tool.
* Tool mainly composed of a tree used to allow the
* user to browse the explorer and its resources.
* @private
*/
Ext.define('Ametys.plugins.explorer.tool.ExplorerTool', {
extend: 'Ametys.tool.Tool',
/**
* @property {Ametys.explorer.tree.ExplorerTree} _tree The explorer tree
* @private
*/
/**
* @cfg {Boolean/String} [tree-ignore-files=false] True to ignore the files in the tree (ie. leaf nodes). Defaults to false.
*/
/**
* @property {Boolean} _treeIgnoreFiles See #cfg-tree-ignore-files
* @private
*/
/**
* @cfg {String} rootnodes-callmethod-role (required)
* The role of the component to be used for the call method in order to retrieve the root nodes of the tree.
*/
/**
* @cfg {String} rootnodes-callmethod-name (required)
* The name of the method to be used for the call method in order to retrieve the root nodes of the tree.
*/
/**
* @cfg {String} rootnodes-callmethod-parameters
* The optional JSON parameters to be used during the call method for the root nodes retrieval.
*/
constructor: function(config)
{
this.callParent(arguments);
// Listening to some bus messages.
Ametys.message.MessageBus.on(Ametys.message.Message.CREATED, this._onResourceCreated, this);
Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onResourceDeleted, this);
Ametys.message.MessageBus.on(Ametys.message.Message.MOVED, this._onResourceMoved, this);
Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onResourceModified, this);
Ametys.message.MessageBus.on(Ametys.message.Message.SELECTION_CHANGED, this._onSelectionChanged, this);
this._treeIgnoreFiles = String(config['tree-ignore-files']) == 'true';
// set root nodes params
var jsonCallMethodParams = config['rootnodes-callmethod-parameters'],
jsonCallMethodParams = jsonCallMethodParams ? [Ext.JSON.decode(jsonCallMethodParams)] : null;
this._rootNodesCallMethodParams = {
role: config['rootnodes-callmethod-role'],
name: config['rootnodes-callmethod-name'],
parameters: jsonCallMethodParams
};
},
getMBSelectionInteraction: function()
{
return Ametys.tool.Tool.MB_TYPE_ACTIVE;
},
createPanel: function()
{
this._tree = this._createTree();
this._tree.on('selectionchange', this._onSelectNode, this);
this._tree.on('itemmove', this._onItemMove, this);
this._tree.on('rootnodeschanged', this._onRootNodesChanged, this);
if (this._treeIgnoreFiles)
{
this._tree.on('rowdblclick', this._onRowDblClick, this);
}
this._tree.on('beforerender', this._onBeforeRender, this);
return this._tree;
},
sendCurrentSelection: function()
{
Ext.create("Ametys.message.Message", {
type: Ametys.message.Message.SELECTION_CHANGED,
targets: this._tree.getMessageTargetConfiguration(this._currentNode)
});
},
/**
* @private
* Set the root nodes for the explorer tree
*/
_onBeforeRender: function()
{
this.showRefreshing()
Ametys.explorer.ExplorerNodeDAO.getRootNodesInfo(
this._setRootNodesCb,
this,
this._rootNodesCallMethodParams.role,
this._rootNodesCallMethodParams.name,
this._rootNodesCallMethodParams.parameters
);
},
/**
* @private
* Callback of #_setRootNodes
* @param {Object[]} response the server response
*/
_setRootNodesCb: function(response)
{
if (response)
{
this._tree.setRootNodes(response);
}
this.showRefreshed();
},
/**
* @protected
* Create the explorer tree
* @return {Ametys.explorer.tree.ExplorerTree} The created explorer tree
*/
_createTree: function()
{
return Ext.create('Ametys.explorer.tree.ExplorerTree', {
inlineEditionEnable: true,
border: false,
ignoreFiles: this._treeIgnoreFiles
});
},
/**
* Get the explorer tree
* @return {Ametys.explorer.tree.ExplorerTree} The tree
*/
getTree: function ()
{
return this._tree;
},
refresh: function(manual)
{
this._tree.refreshRootNodes(this.showUpToDate, this);
},
/**
* On row double click listener. Opens a tool displaying the contents of the clicked folder.
* @param {Ext.tree.View} view the tree view
* @param {Ametys.explorer.tree.ExplorerTree.NodeEntry} node the tree node
* @param {HTMLElement} tr The TR element for the cell.
* @param {Number} index The cell index
* @param {Ext.event.Event} evt The event object
* @private
*/
_onRowDblClick: function(view, node, tr, index, evt)
{
if (node)
{
Ametys.tool.ToolsManager.openTool("uitool-explorer-resource-collection",
{
id: node.getId(),
applicationId: node.get('applicationId')
});
}
},
/**
* Expand the tree and select a node by its id
* @param {String} id The node identifier
*/
selectNodeById: function (id)
{
var node = this._tree.getStore().getNodeById(id);
this.selectNode(node);
},
/**
* Expand the tree and select a node
* @param {Ext.data.NodeInterface} node The node
*/
selectNode: function(node)
{
if (node)
{
this._tree.selectPath(node.getPath('name'), 'name');
}
},
/**
* Expand the tree and select a node by its path
* @param {String} path The path of the node
*/
selectNodeByPath: function (path)
{
this.getTree().selectByPath(path);
},
/**
* @private
* Listener when root nodes are loaded in the tree
* @param {Ametys.explorer.tree.ExplorerTree} tree The explorer tree
*/
_onRootNodesChanged: function(tree)
{
// Expand first nodes
this._tree.getRootNode().expandChildren(false, this._onRootNodesChangedAndExpanded, this);
},
/**
* @private
* When the newly loaded root nodes are expanded
*/
_onRootNodesChangedAndExpanded: function()
{
// Select first node
this._tree.getSelectionModel().select(this._tree.getRootNode().firstChild);
},
/**
* This function is called when a node is selected.
* @param {Ext.selection.Model} sm The selection model
* @param {Ext.data.NodeInterface[]} selected The selected nodes
* @private
*/
_onSelectNode: function (sm, selected)
{
var node = selected[0];
if (selected.length == 0 || !node)
{
if (this._currentNode != null)
{
this._currentNode = null;
this.sendCurrentSelection();
}
}
else
{
if (!this._currentNode || this._currentNode.getId() != node.getId())
{
this._currentNode = node;
this.sendCurrentSelection();
}
}
},
/**
* Reload a node
* @param {String} id The id of the node to reload
* @param {Function} callback The call back function. Can be null.
* @param {Array} args The arguments to the callback function
* @private
*/
_refreshNode: function(id, callback, args)
{
this._tree.refreshNode(id, callback, args);
this._tree.getStore().sort();
},
/**
* @private
* Call this function to rename a node
* @param {Ext.data.Model} parentNode The parent node
* @param {String} id The id of the node to rename
*/
_renameNode: function (parentNode, id)
{
if (parentNode != null && id != null)
{
var node = this.getTree().getStore().getNodeById(id)
this.getTree().selectNode(node);
Ext.defer (this._deferredEdit, 300, this, [node]);
}
},
/**
* @private
* Deferred function to edit a node
* @param {Ext.data.Model} node The node to start edit
*/
_deferredEdit: function (node)
{
this._tree.editingPlugin.startEdit(node, 0);
},
/**
* Deletes a node from the resources tree and selects the parent node.
* @param {String} id The id of the node to delete.
* @private
*/
_deleteNode: function(id)
{
var node = this._tree.getStore().getNodeById(id);
if (node != null)
{
var parentNode = node.parentNode;
this._tree.getSelectionModel().select([parentNode]);
node.remove();
}
},
/**
* Update node UI
* @param {String} id the id of node to updated
* @private
*/
_updateNodeUI: function (id)
{
var node = this._tree.getStore().getNodeById(id);
if (node)
{
Ametys.explorer.ExplorerNodeDAO.getExplorerNode(node.getId(), Ext.bind(this._updateNodeUICb, this, [node], true));
}
},
/**
* Function called after #_updateNodeUI is processed
* @param {Ametys.explorer.ExplorerNode/Ametys.explorer.Resource} explorerNode The retrieved explorer node
* @param {Ext.data.Model} treeNode The node to update
* @private
*/
_updateNodeUICb: function (explorerNode, treeNode)
{
var name = explorerNode.getProperties().name;
if (treeNode)
{
treeNode.beginEdit();
treeNode.set('text', name);
treeNode.set('name', name);
// reset the iconCls to update it if needed
treeNode.set('iconCls', explorerNode.cls);
treeNode.endEdit();
// Marks this Record as NOT dirty.
treeNode.commit();
this._tree.getStore().sort();
}
},
/**
* Determines if the root ids belong to the tree
* @param {String} rootId The root id to check
* @return True if at least one id belongs to the tree
* @private
*/
_isOnTree : function (rootId)
{
return Ext.Array.contains(this._tree.getRootIds(), rootId);
},
// ----------------------- Listeners -------------------------------//
/**
* This function is called when a node is moved to a new location in the tree.
* Fires the {@link Ametys.message.Message#MOVED} message on bus.
* @param {Ext.data.Model} node The moved node
* @param {Ext.data.Model} oldParent The old parent of this node
* @param {Ext.data.Model} newParent The new parent of this node
* @param {Number} index The index the node was moved to
* @private
*/
_onItemMove: function (node, oldParent, newParent, index)
{
Ext.create("Ametys.message.Message", {
type: Ametys.message.Message.MOVED,
targets: this._tree.getMessageTargetConfiguration(node)
});
},
/**
* Listener on {@link Ametys.message.Message#CREATED} message.
* If the tool is concerned by the created object, the parent node will be refreshed.
* @param {Ametys.message.Message} message The created message.
* @private
*/
_onResourceCreated: function (message)
{
var explorerNodeTarget = message.getTarget(Ametys.message.MessageTarget.EXPLORER_COLLECTION);
if (explorerNodeTarget)
{
// Ensure this explorer tool is concerned by the message.
if (!this._isOnTree(explorerNodeTarget.getParameters().rootId))
{
return;
}
if (!this.hasFocus())
{
this.showOutOfDate();
}
else
{
// Refresh parent node and rename new created node
this._refreshNode(explorerNodeTarget.getParameters().parentId, Ext.bind(this._renameNode, this, [explorerNodeTarget.getParameters().id], true));
return;
}
}
var resourceTarget = message.getTarget(Ametys.message.MessageTarget.RESOURCE);
if (resourceTarget)
{
// Ensure this explorer tool is concerned by the message.
if (!this._isOnTree (resourceTarget.getParameters().rootId))
{
return;
}
if (!this.hasFocus())
{
this.showOutOfDate();
}
else if (!this._treeIgnoreFiles)
{
// Refresh parent node
this._refreshNode(resourceTarget.getParameters().parentId);
}
else
{
// open corresponding folder in a dedicated tool
var parentNode = this._tree.getStore().getNodeById(resourceTarget.getParameters().parentId);
Ametys.tool.ToolsManager.openTool("uitool-explorer-resource-collection",
{
id: node.getId(),
applicationId: node.get('applicationId')
});
}
}
},
/**
* Listener on {@link Ametys.message.Message#DELETED} message.
* If the tool is concerned by the deleted object, the object's node will be deleted.
* @param {Ametys.message.Message} message The deleted message.
* @private
*/
_onResourceDeleted: function(message)
{
var targets = message.getTargets(function(target) {
return Ametys.message.MessageTarget.EXPLORER_COLLECTION == (target.id || target.getId()) || Ametys.message.MessageTarget.RESOURCE == (target.id || target.getId());
});
if (targets.length > 0)
{
// Ensure this explorer tool is concerned by the message.
if (!this._isOnTree (targets[0].getParameters().rootId))
{
return;
}
if (!this.hasFocus())
{
this.showOutOfDate(true);
}
else
{
for (var i=0; i < targets.length; i++)
{
this._deleteNode(targets[i].getParameters().id);
}
}
}
},
/**
* Listener on {@link Ametys.message.Message#MOVED} message.
* If the tool is concerned by the moved object, the new parent node will be refreshed.
* @param {Ametys.message.Message} message The moved message.
* @private
*/
_onResourceMoved: function(message)
{
var me = this;
var targets = message.getTargets(function(target) {
return Ametys.message.MessageTarget.EXPLORER_COLLECTION == (target.id || target.getId()) || Ametys.message.MessageTarget.RESOURCE == (target.id || target.getId());
});
if (targets.length > 0)
{
// Ensure this explorer tool is concerned by the message.
if (!this._isOnTree (targets[0].getParameters().rootId))
{
return;
}
if (!this.hasFocus())
{
this.showOutOfDate(true);
}
else
{
for (var i=0; i < targets.length; i++)
{
var movedNode = this._tree.getStore().getNodeById(targets[i].getParameters().id);
var targetNode = this._tree.getStore().getNodeById(targets[i].getParameters().parentId);
if (movedNode.parentNode && movedNode.parentNode.getId() == targetNode.getId())
{
// Do nothing, the node was already moved in the tree
}
else
{
var movedNodeParentNodeInPath = false;
var targetNodeNodeInPath = false;
if (movedNode.parentNode && targetNode)
{
var movedNodeParentNodePath = movedNode.parentNode.getPath('name');
var targetNodeNodePath = targetNode.getPath('name');
movedNodeParentNodeInPath = !targetNodeNodePath || movedNodeParentNodePath && Ext.String.startsWith(movedNodeParentNodePath, targetNodeNodePath + '/');
targetNodeNodeInPath = !movedNodeParentNodePath || targetNodeNodePath && Ext.String.startsWith(targetNodeNodePath, movedNodeParentNodePath + '/');
}
if (movedNodeParentNodeInPath == false && movedNode.parentNode)
{
this._refreshNode(movedNode.parentNode.getId());
}
if (targetNodeNodeInPath == false && targetNode)
{
this._refreshNode(targetNode.getId());
}
}
}
}
}
},
/**
* Listener on {@link Ametys.message.Message#MODIFIED} message.
* If the tool is concerned by the moved object, the node of modified object will be updated.
* @param {Ametys.message.Message} message The modified message.
* @private
*/
_onResourceModified: function(message)
{
var me = this;
var targets = message.getTargets(function(target) {
return Ametys.message.MessageTarget.EXPLORER_COLLECTION == (target.id || target.getId()) || Ametys.message.MessageTarget.RESOURCE == (target.id || target.getId());
});
if (targets.length > 0)
{
// Ensure this explorer tool is concerned by the message.
if (!this._isOnTree (targets[0].getParameters().rootId))
{
return;
}
if (!this.hasFocus())
{
this.showOutOfDate();
}
else
{
for (var i=0; i < targets.length; i++)
{
var id = targets[i].getParameters().id;
var type = targets[i].id;
if (message.getParameters().major && type != Ametys.message.MessageTarget.RESOURCE)
{
this._refreshNode(id);
}
this._updateNodeUI(id);
}
}
}
},
/**
* Listener on {@link Ametys.message.Message#SELECTION_CHANGED} message.
* If the tool is concerned by the targets of the message, the selection on the tree will be updated accordingly
* @param {Ametys.message.Message} message The selection changed message
* @private
*/
_onSelectionChanged: function(message)
{
var explorerNodeTarget = message.getTarget(this._tree.applicationsTargetFilter);
if (explorerNodeTarget)
{
// Ensure this explorer tool is concerned by the message.
if (!this._isOnTree(explorerNodeTarget.getParameters().rootId))
{
return;
}
else
{
var id = explorerNodeTarget.getParameters().id;
if (!this._currentNode || this._currentNode.getId() != id)
{
var node = this._tree.getStore().getNodeById(id);
if (node)
{
this._currentNode = node; // avoid sending a new selection message.
this.selectNode(node);
}
}
}
}
var resourceTarget = message.getTarget(Ametys.message.MessageTarget.RESOURCE);
if (resourceTarget)
{
// Ensure this explorer tool is concerned by the message.
if (!this._isOnTree(resourceTarget.getParameters().rootId))
{
return;
}
else
{
var id = this._treeIgnoreFiles ? resourceTarget.getParameters().parentId : resourceTarget.getParameters().id;
if (!this._currentNode || this._currentNode.getId() != id)
{
var node = this._tree.getStore().getNodeById(id);
if (node)
{
this._currentNode = node; // avoid sending a new selection message.
this.selectNode(node);
}
}
}
}
}
});