PDA

View Full Version : treePanel plugin: Ext.ux.tree.TreePanel.NodeManagement



willf1976
13 Aug 2009, 4:07 PM
Hi All

Here is a simple plug in to make the mundane tasks you usually have to code again and again into your tree panel toolbars a bit easier to deal with -- This plugin allows you to:

insert nodes into a tree
Copy nodes (in the same tree or to a different tree)
Move nodes around (in the same tree or to a different tree)
Shift nodes around in the tree
delete nodes

(all operations may work on a batch of nodes -- or multiple selected nodes)

Plugin may be used with a minimum of fuss just by calling the functions which it will attach to an object called: [treePanel].nodeManagement -- IE: myTreePanel.nodeManagement.copyNodes(); and letting the plug in do the rest, or you can play around with the configuration options/pass more specific arguments into the functions to tune the plugins operations to your specific needs.

Plug in is not extensively tested yet but I will be getting to that soon.

Please let me know any problems you encounter or suggestions you have.

Best regards

Will Ferrer

Update (09/03/09): This code now uses Ext.ux.tree.TreePanel.toObject to copy childNodes that have been dynamically added to branches. This code is available in thread: http://www.extjs.com/forum/showthread.php?t=20793.

Here is Ext.ux.tree.TreePanel.toObject:


/*
original author: dpasichnik
modified by: Will Ferrer
date: 09/03/09
history:
09/03/09 -- posted to ExtJS forums
*/
/**
This code is based on dpasichnik's treeSerializer found at:
http://www.extjs.com/forum/showthread.php?t=20793
*/
/**
* Returns a object that represents the tree
* @param {Function} nodeFilter (optional) A function, which when passed the node, returns true or false to include
* or exclude the node.
* @param {Function} attributeFilter (optional) A function, which when passed an attribute name, and an attribute value,
* returns true or false to include or exclude the attribute.
* @param {Object} attributeMapping (optional) An associative array which can you use to remap attribute names on the nodes as they are converted to an object. Keys in the array represent attributes to be remapped, and their associated values represent the new keys that those attributes will be remapped onto in the returned object.
* @param {Object} scope (optional) The scope to call the filter functions in. Defaults to this.
* @return {String}
*/
Ext.tree.TreePanel.prototype.toObject = function(nodeFilter, attributeFilter, attributeMapping, scope){
return this.getRootNode().toObject(nodeFilter, attributeFilter, attributeMapping, scope);
};

/**
* Returns an object that represents the node
* @param {Function} nodeFilter (optional) A function, which when passed the node, returns true or false to include
* or exclude the node.
* @param {Function} attributeFilter (optional) A function, which when passed an attribute name, and an attribute value,
* returns true or false to include or exclude the attribute.
* @param {Object} attributeMapping (optional) An associative array which can you use to remap attribute names on the nodes as they are converted to an object. Keys in the array represent attributes to be remapped, and their associated values represent the new keys that those attributes will be remapped onto in the returned object.
* @return {String}
*/

Ext.tree.TreeNode.prototype.toObject = function(nodeFilter, attributeFilter, attributeMapping, scope){
var scope = (!Ext.isEmpty(scope))?scope:this;

// Exclude nodes based on caller-supplied filtering function
if (nodeFilter && (nodeFilter.call(scope, this) == false)) {
return undefined;
}
var c = false, returnObj = {};

// Add the id attribute unless the attribute filter rejects it.
if (!attributeFilter || attributeFilter.call(scope, "id", this.id)) {
returnObj.id = this.id;
c = true;
}

// Add all user-added attributes unless rejected by the attributeFilter.
for(var key in this.attributes) {
if ((key != 'id') && (!attributeFilter || attributeFilter.call(scope, key, this.attributes[key]))) {
if (attributeMapping && attributeMapping[key]) {
thisKey = attributeMapping[key];
} else {
thisKey = key;
}
returnObj[thisKey] = this.attributes[key];
c = true;
}
}
// Add child nodes if any
var children = this.childNodes;
//console.trace();
//console.log(children);
var clen = children.length;
returnObj['children'] = [];
if(clen != 0){
for(var i = 0; i < clen; i++){
returnObj['children'][i] = children[i].toObject(nodeFilter, attributeFilter, attributeMapping, scope);
}
}
//console.log(returnObj['children']);
return returnObj;
}


/**
* preloads a tree so that all its child nodes are created
* @return {Boolean}
*/
Ext.tree.TreePanel.prototype.prepareNodes = function(){
return this.getRootNode().prepareNode();
};

/**
* Returns an object that represents the node
* @return {Boolean}
*/

Ext.tree.TreeNode.prototype.prepareNode = function(){
var loader = this.loader || this.attributes.loader || this.getOwnerTree().getLoader();
loader.load(this, /*this.loadComplete.createDelegate(this, [false, false, null, null])*/null, this);

var children = this.childNodes;
var clen = children.length;

if(clen != 0){
for(var i = 0; i < clen; i++){
children[i].prepareNode();
}
}
return true;
}
Ext.ux.tree.TreePanel.NodeManagement:

/*
author: Will Ferrer
date: 10/08/09
history:
08/13/09 -- posted to ExtJS forums
08/21/09 -- added superclass.constructor.call for posterities sake
08/24/09 -- bug fixes where getSelectedNodes was returning an empty array which my code was treating in the same way as if it were a legit array of nodes
09/03/09 -- added fullBranchCopy, nodeFilter, attributeFilter, attributeMapping
10/08/09 -- fixed a problem where trying to shift a node off the top or bottom of a tree caused an error
10/24/09 -- added function searchNodes
04/13/10 -- made the befoer copy nodes event receive the nodes that have already been coppied so as not to alter the origenal nodes. also added owner tree option to copyNodes. added loadCoppiedNodes config option
06/02/10 -- added clearIdsOnCopy config option (avoids child nodes getting entangled with other child nodes).
*/
/**
* @class Ext.ux.tree.TreePanel.NodeManagement
* @extends Ext.util.Observable
* A simple plug in to make the tasks you usually have to code again and again into tree panel toolbars abit more manageable. Class allows you to:
* insert nodes into a tree
* Copy nodes (in the same tree or to a different tree)
* Move nodes around (in the same tree or to a different tree)
* Shift nodes around in the tree
* delete nodes
* Functions will work automatically using the nodes that the user has selected in the tree, but this functionality may be overridden by passing in your own array of nodes.
* The class is highly configurable allowing you to specify what where new nodes should be added in the tree and all host of other configuration options. All configurations may be overriden by passing arguments to the classes functions.
* @constructor
* @param {Object} config The config object
* @ptype ux-tree-treepanel-nodemanagement
*
* Notes:
* Important: Once this plugin has been applied to a tree you may access the plugin functions via [treePanel].nodeManagement -- IE: myTreePanel.nodeManagement.copyNodes();
*
* Many of the public properties of the class may be set to a string value to dictate where new nodes will be inserted in the tree relative to the node/nodes the user currently has selected (or were passed into the function). These properties may be set to the following values:
* above: insert nodes above selected node
* below: insert nodes below selected node
* top: insert nodes at the top of the root node of the tree
* bottom: append nodes the bottom of the root node of the tree
* branch-top: relevant when a branch is directly selected -- insert nodes at the top of the child nodes in the branch
* branch-bottom: relevant when a branch is directly selected -- append nodes at the bottom of the child nodes in the branch
*
* The word 'Anchor' in my property declarations and else where generally refers to the node the user currently has selected but depending on the circumstance it may be a node that you pass into a function. The anchor node is the node where new nodes/nodes being moved will be placed relative to.
*
* A value of 'null' or no value may be passed to any of the optional arguments of the public functions in order to make the default value of the argument be used.
*
* Events are available for all functions, the listeners attached to these events may return false to stop the function from running or may substitute new arrays of nodes for the one currently by used by the function.
*/
Ext.ns('Ext.ux.tree.TreePanel');
Ext.ux.tree.TreePanel.NodeManagement = function(config){
Ext.apply(this, config);
Ext.ux.tree.TreePanel.NodeManagement.superclass.constructor.call(this);
};
Ext.extend(Ext.ux.tree.TreePanel.NodeManagement, Ext.util.Observable, {
//Public Properties:
/**
* @cfg {String} leafAnchorInsert
* Controls where new nodes are inserted when a leaf node is the anchor. May be set to: above, below, top, bottom, branch-top or branch-bottom. Defaults to 'below'.
*/
leafAnchorInsert : 'below',
/**
* @cfg {String} expandedBranchAnchorInsert
* Controls where new nodes are inserted when an expanded brand node is the anchor. May be set to: above, below, top, bottom, branch-top or branch-bottom. Defaults to 'branch-bottom'.
*/
expandedBranchAnchorInsert : 'branch-bottom',
/**
* @cfg {String} collapsedBranchAnchorInsert
* Controls where new nodes are inserted when an collapsed (not currently expanded) branch node is the anchor. May be set to: above, below, top, bottom, branch-top or branch-bottom. Defaults to 'branch-bottom'.
*/
collapsedBranchAnchorInsert : 'below',
/**
* @cfg {String} rootAnchorInsert
* Controls where new nodes are inserted when the root node or no node is the anchor. May be set to: above, below, top, bottom, branch-top or branch-bottom. Defaults to 'branch-bottom'.
*/
rootAnchorInsert : 'bottom',
/**
* @cfg {String} copyNodesSameTreeInsert
* Controls where new nodes are inserted when a copy operation takes place where the source and destination tree are the same. May be set to: above, below, top, bottom. Defaults to 'below'. If set to null then the standard criteria for where to insert the new nodes created by the copy will apply.
*/
copyNodesSameTreeInsert : 'below',
/**
* @cfg {String} copyNodesDifferentTreeInsert
* Controls where new nodes are inserted when a copy operation takes place between to different trees. May be set to: above, below, top, bottom. Defaults to 'bottom'.
*/
copyNodesDifferentTreeInsert : 'bottom',
/**
* @cfg {String} moveNodesDifferentTreeInsert
* Controls where new nodes are inserted when a move operation takes place between to different trees. May be set to: above, below, top, bottom. Defaults to 'bottom'. If set to null then the standard criteria for where to insert the new nodes created by the copy will apply.
*/
moveNodesDifferentTreeInsert : 'bottom',
/**
* @cfg {String} multiInsertNodesGo
* When multiple nodes are inserted at once this setting controls where the additional nodes are placed relative to the first node that is added. May be set to: above, below, top, bottom, branch-top or branch-bottom. Defaults to 'below'.
*/
multiInsertNodesGo : 'below',
/**
* @cfg {String} multiNodesSelectedAnchorIs
* When multiple nodes are selected we need to choose one of them to insert the new nodes next to (Which node will be used as the anchor) -- this may be set to: 'first' to treat the top most node as the primary node, or 'last' to treat the node lowest in the tree as the primary node. Defaults to 'last'
*/
multiNodesSelectedAnchorIs : 'last',
/**
* @cfg {Boolean} expandBranchOnInsertInside
* Sets the behaviour for when a new node is inserted in a branch -- true will cause the branch to expand, false will leave the branch collapsed. Defaults to true.
*/
expandBranchOnInsertInside : true,
/**
* @cfg {Boolean} copyInSameTreeChangeId
* When a copy takes place with in the same tree that the nodes originated from should we generated a new id for that node in order to prevent duplicate nodes ids. Defaults to true.
*/
copyInSameTreeChangeId : true,
/**
* @cfg {Boolean} copyToDifferentTreeChangeId
* When a copy takes place between 2 trees should we generated a new id for that node in order to prevent duplicate nodes ids. Defaults to false.
*/
copyToDifferentTreeChangeId : true,
/**
* @cfg {Boolean} selectNodesOnInsert
* When nodes are inserted/moved should we attempt to select them automatically for the user. Defaults to true.
*/
selectNodesOnInsert : true,
//!!NOTE: The following features requires Ext.ux.tree.TreePanel.toObject which can be found at: http://www.extjs.com/forum/showthread.php?t=20793.
/**
* @cfg {Boolean} fullBranchCopy
* If set to true child nodes that have been added to branches of the tree will be properly coppied when you copy a branch node -- other wise these child nodes may not be coppied properly. Defaults to false.
*/
fullBranchCopy : false,
/**
* @cfg {Function|null} nodeFilter
* A function that will be used when a fullBranchCopyOccures -- which when passed the node, returns true or false to include or exclude the node. Defaults to null;
*/
nodeFilter : null,
/**
* @cfg {Function|null} attributeFilter
* A function that will be used when a fullBranchCopyOccures -- which when passed an attribute name, and an attribute value, returns true or false to include or exclude the attribute.. Defaults to null;
*/
attributeFilter : null,
/**
* @cfg {Array|null} attributeMapping
* A associative array that will be used when a fullBranchCopyOccures -- which can you use to remap attribute names on the nodes as they are converted to an object. Keys in the array represent attributes to be remapped, and their associated values represent the new keys that those attributes will be remapped onto in the returned object. Defaults to null;
*/
attributeMapping : null,
/**
* @cfg {Boolean} loadCoppiedNodes
* Whether or not to load coppied nodes -- be aware that nodes that have not been loaded do not have there children attribute transfered into childNodes. Defaults to true;
*/
loadCoppiedNodes : true,
/**
* @cfg {Boolean} clearIdsOnCopy
* Gets rid of the ids on nodes that are coppied so that new ids will be generated and no engtanglemen will occure with existing treenodes. Defaults to true;
*/
clearIdsOnCopy : true,
//Private Properties:
/**
* @private internal config {String} currentOperation
* The name of the function that started the current chain of events -- reported to event listeners. This is tracked since conditional logic put on some listeners may be aided by knowing what function was called to start the current chain of events. Defaults to null.
*/
currentOperation : null,
//Public Functions:
/** Public Function: insertNodes
* Inserts nodes into the tree. Where the nodes are inserted can either be dictated by passing arguments to the function or the default values configured for the class will be used.
* @param {Mixed} nodes (Required) An array of nodes to insert. Just 1 node may be passed instead of an array.
* @param {String} position (Optional) Where the nodes should be inserted relative to the anchorNode. May be: above, below, top, bottom, branch-top, branch-bottom. Defaults to this.getDefaultInsertPosition(anchorNode) -- a function call to check what kind of node is currently the anchor and get the current configuration for that type of node.
* @param {Object} anchorNode (Optional) The node around which new nodes will be added -- combination of position and anchorNode dictate where in the tree the new nodes will be inserted. Defaults to this.getAnchorNode(selectedNodes) -- a function call to figure out if the first currently selected node or the last currently selected node should be used as the anchor depending on the configuration of the class.
* @param {String} multiInsertNodesGo (Optional) When you are inserting multiple nodes at onces this dictates where nodes are added after the first node. Generally the class will be configured to have this set to 'below', so that all additional nodes appear bellow the insertion point of the first node. Defaults to this.multiInsertNodesGo.
* @return {Array} insertedNodes The nodes that were inserted by the function
*/
insertNodes : function (nodes, position, anchorNode, multiInsertNodesGo) {
this.currentOperation = (this.currentOperation)?this.currentOperation:'insertNodes';

var nodes = (typeof(nodes)!='undefined')?nodes:null;
nodes = (Ext.isArray(nodes) || !nodes)?nodes:[nodes];

var selectedNodes = this.getSelectedNodes();
var anchorNode = (typeof(anchorNode)!='undefined' && anchorNode)?anchorNode:this.getAnchorNode(selectedNodes);

var position = (typeof(position)!='undefined' && position)?position:this.getDefaultInsertPosition(anchorNode);
var multiInsertNodesGo = (typeof(multiInsertNodesGo)!='undefined' && multiInsertNodesGo)?multiInsertNodesGo:this.multiInsertNodesGo;

var insertedNodes = null;
var eventResult = this.parent.fireEvent("beforeinsertnodes", this, this.currentOperation, position, nodes, anchorNode, multiInsertNodesGo);

if (eventResult) {
if (Ext.isObject(eventResult)) {
nodes = eventResult;
nodes = (Ext.isArray(nodes) || !nodes)?nodes:[nodes];
}
if (Ext.isArray(nodes)) {
insertedNodes = [];
var curNode = nodes[0];
var baseNode = this.insertNode(curNode, position, anchorNode);
insertedNodes.push(baseNode);
for (var n=1; n<nodes.length; n++){
curNode = nodes[n];
baseNode = this.insertNode(curNode, multiInsertNodesGo, baseNode);
insertedNodes.push(baseNode);
}
}
if (this.selectNodesOnInsert) {
for (var n=0; n<insertedNodes.length; n++) {
var curNode = insertedNodes[n];
if (!Ext.isEmpty(curNode) && Ext.isFunction(curNode.select)) {
curNode.select();
}
}
}
}
this.parent.fireEvent("afterinsertnodes", this, this.currentOperation, position, insertedNodes, anchorNode, multiInsertNodesGo);
this.currentOperation = (this.currentOperation=='insertNodes')?null:this.currentOperation;
return insertedNodes;
},
/** Public Function: copyNodes
* Uses the insertNodes function to insert copys of the nodes you pass to this function or simply works on the nodes the user currently has selected.
* @param {Mixed} toTree (Optional) The tree objet or the id of the tree where you want to copy the nodes to. If a value of null is passed default value will be used. Defaults to this.parent -- the tree object on which this function was called.
* @param {String} position (Optional) Where the nodes should be inserted realitive to the anchorNode. May be: above, below, top, bottom, branch-top, branch-bottom. Defaults to this.getDefaultInsertPosition(anchorNode) -- a function call to check what kind of node is currently the anchor and get the current configuration for that type of node.
* @param {Mixed} nodes (Optional) An array of nodes to copy. Just 1 node may be passed instead of an array. Defaults to this.getSelectedNodes(true) -- nodes that are currently selected by the user
* @param {Object} anchorNode (Optional) The node around which new nodes will be added -- combination of position and anchorNode dictate where in the tree the new nodes will be inserted. Defaults to toTree.getAnchorNode(selectedNodes) -- a function call to figure out if the first currently selected node or the last currently selected node should be used as the anchor depending on the configuration of the class.
* @return {Array} copiedNodes The nodes that were inserted by the function
*/
copyNodes : function (toTree, ownerTree, position, nodes, anchorNode) {
this.currentOperation = (this.currentOperation)?this.currentOperation:'copyNodes';

var toTree = (typeof(toTree)!='undefined' && toTree)?toTree:this.parent;
toTree = (typeof(toTree)!='string')?toTree:Ext.getCmp(toTree);
var anchorNode = (typeof(anchorNode)!='undefined' && anchorNode)?anchorNode:this.getAnchorNode(selectedNodes);
var fromSelf = (toTree.id == this.parent.id);
var attributes;
if (typeof(position)=='undefined' || !position) {
position = (fromSelf)?toTree.copyNodesSameTreeInsert:toTree.copyNodesDifferentTreeInsert;
}
var selectedNodes = this.getSelectedNodes(true);
var nodes = (typeof(nodes)!='undefined' && nodes)?nodes:selectedNodes;
nodes = (Ext.isArray(nodes) || !nodes)?nodes:[nodes];
var copyNodes = null;
var copiedNodes = null;

if (Ext.isArray(nodes)) {
copyNodes = [];
for (var n=0;n<nodes.length;n++) {
var curNode = nodes[n];
if (!Ext.isFunction(curNode)) {
if (this.fullBranchCopy) {
attributes = curNode.toObject(this.nodeFilter, this.attributeFilter, this.attributeMapping);
} else {
attributes = curNode.attributes;
}
if (this.clearIdsOnCopy) {
attributes = this.clearIds(attributes);
}
var copiedNode = new Ext.tree.AsyncTreeNode(attributes);
if ((fromSelf && this.copyInSameTreeChangeId) || (!fromSelf && this.copyToDifferentTreeChangeId)) {
var dummyNode = new Ext.tree.AsyncTreeNode({});
copiedNode.setId(dummyNode.id);
}
copiedNode.ownerTree = ownerTree;
copyNodes.push(copiedNode);
}
}
}

var eventResult = this.parent.fireEvent("beforecopynodes", this, this.currentOperation, toTree, position, copyNodes, anchorNode, fromSelf);
if (Ext.isObject(eventResult)) {
copyNodes = eventResult;
copyNodes = (Ext.isArray(copyNodes) || !copyNodes)?copyNodes:[copyNodes];
}

if (eventResult) {
copiedNodes = toTree.nodeManagement.insertNodes(copyNodes, position, anchorNode);
if (this.loadCoppiedNodes) {
for (var n=0;n<copiedNodes.length;n++) {
var curNode = copiedNodes[n];
var loader = curNode.loader || curNode.attributes.loader || curNode.getOwnerTree().getLoader();
loader.load(curNode, curNode.loadComplete.createDelegate(curNode, [false, false, null, null]), curNode);
}
}
this.parent.fireEvent("aftercopynodes", this, this.currentOperation, toTree, position, copiedNodes, anchorNode, fromSelf);
this.currentOperation = (this.currentOperation=='copyNodes')?null:this.currentOperation;
return copiedNodes;
} else {
return false;
}




},
/** Public Function: moveNodes
* Uses the insertNodes function to move nodes between trees or around with in the same tree
* @param {Mixed} toTree (Optional) The tree objet or the id of the tree where you want to move the nodes to. If a value of null is passed then the default value will be used. Defaults to this.parent -- the tree object on which this function was called.
* @param {String} position (Optional) Where the nodes should be inserted realitive to the anchorNode. May be: above, below, top, bottom, branch-top, branch-bottom. Defaults to this.getDefaultInsertPosition(anchorNode) -- a function call to check what kind of node is currently the anchor and get the current configuration for that type of node.
* @param {Mixed} nodes (Optional) An array of nodes to move. Just 1 node may be passed instead of an array. Defaults to this.getSelectedNodes(true) -- nodes that are currently selected by the user
* @param {Object} anchorNode (Optional) The node around which new nodes will be added -- combination of position and anchorNode dictate where in the tree the new nodes will be inserted. Defaults to toTree.getAnchorNode(selectedNodes) -- a function call to figure out if the first currently selected node or the last currently selected node should be used as the anchor depending on the configuration of the class.
* @return {Array} movedNodes The nodes that were moved by the function
*/
moveNodes : function (toTree, position, nodes, anchorNode) {
this.currentOperation = (this.currentOperation)?this.currentOperation:'moveNodes';
var toTree = (typeof(toTree)!='undefined' && toTree)?toTree:this.parent;
toTree = (typeof(toTree)!='string')?toTree:Ext.getCmp(toTree);
var anchorNode = (typeof(anchorNode)!='undefined' && anchorNode)?anchorNode:this.getAnchorNode(selectedNodes);
var fromSelf = (toTree.id == this.parent.id);
if (typeof(position)=='undefined' || !position) {
position = (!fromSelf)?toTree.moveNodesDifferentTreeInsert:null;
}

var selectedNodes = this.getSelectedNodes(true);
var nodes = (typeof(nodes)!='undefined' && nodes)?nodes:selectedNodes;
nodes = (Ext.isArray(nodes) || !nodes)?nodes:[nodes];
var movedNodes = null;

var eventResult = this.parent.fireEvent("beforemovenodes", this, this.currentOperation, toTree, position, nodes, anchorNode, fromSelf);

if (eventResult) {
if (Ext.isObject(eventResult)) {
nodes = eventResult;
nodes = (Ext.isArray(nodes) || !nodes)?nodes:[nodes];
}

movedNodes = toTree.nodeManagement.insertNodes(nodes, position, anchorNode);
}
this.parent.fireEvent("aftermovenodes", this, this.currentOperation, toTree, position, movedNodes, anchorNode, fromSelf);
this.currentOperation = (this.currentOperation=='moveNodes')?null:this.currentOperation;
return movedNodes;
},
/** Public Function: shiftNodes
* Uses the insertNodes function to shift nodes around in the same tree.
* @param {String} destination (Required) How you would like to shift the nodes around -- May be: up (to move the nodes up), down (to move the nodes down), top (to move the nodes to the top of the tree) or bottom (to move the nodes to the bottom of the tree)
* @param {Mixed} nodes (Optional) An array of nodes to move. Just 1 node may be passed instead of an array. Defaults to this.getSelectedNodes(true) -- nodes that are currently selected by the user
* @param {Object} anchorNode (Optional) The node around which new nodes will be added -- combination of position and anchorNode dictate where in the tree the new nodes will be inserted. Defaults to toTree.getAnchorNode(selectedNodes) -- a function call to figure out if the first currently selected node or the last currently selected node should be used as the anchor depending on the configuration of the class.
* @return {Array} shiftedNodes The nodes that were shifted by the function
*/
shiftNodes : function (destination, nodes, anchorNode) {
this.currentOperation = (this.currentOperation)?this.currentOperation:'shiftNodes';
var selectedNodes = this.getSelectedNodes(true);
var nodes = (typeof(nodes)!='undefined' && nodes)?nodes:selectedNodes;
nodes = (Ext.isArray(nodes) || !nodes)?nodes:[nodes];
var anchorNode = (typeof(anchorNode)!='undefined' && anchorNode)?anchorNode:this.getAnchorNode(selectedNodes);
var shiftedNodes = null;

if (anchorNode) {
switch (destination) {
case 'up' :
var position = 'above';
anchorNode = anchorNode.previousSibling;
break;
case 'down' :
var position = 'below';
anchorNode = anchorNode.nextSibling;
break;
case 'top' :
var position = 'top';
break;
case 'bottom' :
var position = 'bottom';
break;
default:
var position = null;
}
}

var eventResult = this.parent.fireEvent("beforeshiftnodes", this, this.currentOperation, destination, nodes, anchorNode);

if (eventResult) {
if (Ext.isObject(eventResult)) {
nodes = eventResult;
nodes = (Ext.isArray(nodes) || !nodes)?nodes:[nodes];
}
shiftedNodes = this.insertNodes(nodes, position, anchorNode);
}
this.parent.fireEvent("aftershiftnodes", this, this.currentOperation, destination, shiftedNodes, anchorNode);
this.currentOperation = (this.currentOperation=='shiftNodes')?null:this.currentOperation;

return shiftedNodes;
},
/** Public Function: deleteNodes
* Deletes nodes from the tree
* @param {Mixed} nodes (Optional) An array of nodes to delete. Just 1 node may be passed instead of an array. Defaults to this.getSelectedNodes(true) -- nodes that are currently selected by the user
* @return {Array} deletedNodes The nodes that were deleted by the function
*/
deleteNodes : function (nodes) {
this.currentOperation = (this.currentOperation)?this.currentOperation:'deleteNodes';
var selectedNodes = this.getSelectedNodes(true);
var nodes = (typeof(nodes)!='undefined' && nodes)?nodes:selectedNodes;
nodes = (Ext.isArray(nodes) || !nodes)?nodes:[nodes];
var deletedNodes = null;

var eventResult = this.parent.fireEvent("beforedeletenodes", this, this.currentOperation, nodes);

if (eventResult) {
if (Ext.isObject(eventResult)) {
nodes = eventResult;
nodes = (Ext.isArray(nodes) || !nodes)?nodes:[nodes];
}
deletedNodes = [];
for (var n=0;n<nodes.length; n++) {
var curNode = nodes[n];
var deletedNode = this.deleteNode(curNode);
deletedNodes.push(deletedNode);
}
}
this.parent.fireEvent("afterdeletenodes", this, this.currentOperation, deletedNodes);
this.currentOperation = (this.currentOperation=='deleteNodes')?null:this.currentOperation;
return deletedNodes;
},
/** Public Function: searchNodes
* Convenience search function -- Searchs recursively for a node based on the attribute and valule you pass to the function. May either be given a node to start its search at or it will default to the rootNode of the tree.
* @param {String} attribute (Required) The attribute name
* @param {String} value (Required) The value to search for
* @param {String} node (Optional) The node to start searching at. Defaults to this.parent.getRootNode();
*/
searchNodes : function(attribute, value, node) {
var node = (Ext.isEmpty(node))?this.parent.getRootNode():node,
childNodes=node.childNodes,
n,
curNode,
result;
for (n=0; n<childNodes.length; n++) {
curNode = childNodes[n];
if (curNode.attributes[attribute] == value) {
return curNode;
} else if (!curNode.isLeaf()) {
result = this.searchNodes(attribute, value, curNode);
if (result) {
return result;
}
}
}
return null;
},
//Private Functions:
// @private
init: function(parent){
this.parent = parent;
this.parent.nodeManagement = this;
this.parent.addEvents(
/**
* @event beforeinsertnodes
* Fires right before the insertNodes function runs. You may return false from a listener attached to this event to stop the insert from taking place. You may also return an array of new nodes from a listener attached to this event to have these nodes be inserted instead of the ones originally passed to the function.
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {String} position The position at which the new nodes were added (often realitive to anchorNode -- see notes at the top of the class for more details)
* @param {Object} nodes Nodes to be inserted
* @param {Object} anchorNode Node around which the new nodes will be added (usually the node selected by the user)
* @param {Object} multiInsertNodesGo Dictates where to put additional nodes when inserting more than 1 at a time (usually below the first node added by the function)
*/
'beforeinsertnodes',
/**
* @event afterinsertnode
* Fires right after the insertNodes function has run
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {String} position The position at which the new nodes were added (often realitive to anchorNode -- see notes at the top of the class for more details)
* @param {Object} insertedNodes The node that were inserted
* @param {Object} anchorNode Node around which the new nodes will be added (usually the node selected by the user)
* @param {Object} multiInsertNodesGo Dictates where to put additional nodes when inserting more than 1 at a time (usually below the first node added by the function)
*/
'afterinsertnodes',
/**
* @event beforeinsertnode
* Fires right before the insertNode function runs (Note: This function is the insertion of a single node is called multiple times by the insertNodes function). You may return false from a listener attached to this event to stop the insert from taking place. You may also return a new node from a listener attached to this event to substitute it for the one about to be inserted.
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {String} position The position at which the new node will be added (often realitive to anchorNode -- see notes at the top of the class for more details)
* @param {Object} node Node to be inserted
* @param {Object} anchorNode Node around which the new node will be added (usually the node selected by the user)
*/
'beforeinsertnode',
/**
* @event afterinsertnode
* Fires right after the insertNode function has run (Note: This function is the insertion of a single node is called multiple times by the insertNodes function)
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {String} position The position at which the new nodes were added (often realitive to anchorNode -- see notes at the top of the class for more details)
* @param {Object} insertedNode The node that was inserted
* @param {Object} anchorNode Node around which the new nodes will be added (usually the node selected by the user)
*/
'afterinsertnode',
/**
* @event beforecopynodes
* Fires right before the copyNodes function is run. You may return false from a listener attached to this event to stop the copy from taking place. You may also return an array of new nodes from a listener attached to this event to have these nodes be copied instead of the ones orgenaly passed to the function.
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {Object} toTree The tree the nodes are being copied to
* @param {String} position The position at which the new nodes will be added (often realitive to anchorNode -- see notes at the top of the class for more details)
* @param {Object} nodes Nodes to be copied
* @param {Object} anchorNode Node around which the new nodes will be added (usually the node selected by the user)
* @param {Boolean} fromSelf Whether or not the copy action originated from this tree or not -- false when nodes are being copied from another tree
*/
'beforecopynodes',
/**
* @event aftercopynodes
* Fires right after the copyNodes function has run
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {Object} toTree The tree the nodes are being copied to
* @param {String} position The position at which the new nodes were added (often realitive to anchorNode -- see notes at the top of the class for more details)
* @param {Object} copiedNodes Nodes that were created by the copy
* @param {Object} anchorNode Node around which the new nodes will be added (usually the node selected by the user)
* @param {Boolean} fromSelf Whether or not the copy action originated from this tree or not -- false when nodes are being copied from another tree
*/
'aftercopynodes',
/**
* @event beforemovenodes
* Fires right before the moveNodes function is run. You may return false from a listener attached to this event to stop the move from taking place. You may also return an array of new nodes from a listener attached to this event to have these nodes be moved instead of the ones orgenaly passed to the function.
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {Object} toTree The tree the nodes are being copied to
* @param {String} position The position at which the new nodes were added (often realitive to anchorNode -- see notes at the top of the class for more details)
* @param {Object} nodes Nodes to be moved
* @param {Object} anchorNode Node around which the new nodes will be added (usually the node selected by the user)
* @param {Boolean} fromSelf Whether or not the move action originated from this tree or not -- false when nodes are being moved from another tree
*/
'beforemovenodes',
/**
* @event aftermovenodes
* Fires right after the moveNodes function is run.
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {Object} toTree The tree the nodes are being copied to
* @param {String} position The position at which the new nodes were added (often realitive to anchorNode -- see notes at the top of the class for more details)
* @param {Object} movedNodes Nodes that were moved
* @param {Object} anchorNode Node around which the new nodes were added (usually the node selected by the user)
* @param {Boolean} fromSelf Whether or not the move action originated from this tree or not -- false when nodes are being moved from another tree
*/
'aftermovenodes',
/**
* @event beforeshiftnodes
* Fires right before the shiftNodes function is run. You may return false from a listener attached to this event to stop the move from taking place. You may also return an array of new nodes from a listener attached to this event to have these nodes be shifted instead of the ones orgenaly passed to the function.
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {String} destination The where the nodes will be shifted to: up -- up one space, down -- down space, top -- to the top of the tree, bottom -- to the bottom of the tree
* @param {Object} nodes Nodes to be shifted
* @param {Object} anchorNode Node around which the nodes will be shifted (usually the node selected by the user)
*/
'beforeshiftnodes',
/**
* @event aftershiftnodes
* Fires right after the shiftNodes function has run
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {String} destination The where the nodes will be shifted to: up -- up one space, down -- down space, top -- to the top of the tree, bottom -- to the bottom of the tree
* @param {Object} shiftedNodes Nodes that were shifted
* @param {Object} anchorNode Node around which the nodes were shifted (usually the node selected by the user)
*/
'aftershiftnodes',
/**
* @event beforedeletenodes
* Fires right before the deleteNodes function is run. You may return false from a listener attached to this event to stop the move from taking place. You may also return an array of new nodes from a listener attached to this event to have these nodes be deleted instead of the ones orgenaly passed to the function.
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {Object} nodes Nodes to be deleted
*/
'beforedeletenodes',
/**
* @event afterdeletenodes
* Fires right after the shiftNodes function has run
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {Object} deletedNode Nodes that were deleted
*/
'afterdeletenodes',
/**
* @event afterdeletenodes
* Fires right after the deleteNodes function has run
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {Object} deletedNode Nodes that were deleted
*/
'afterdeletenodes',
/**
* @event beforedeletenode
* Fires right before the deleteNode function runs. You may return false from a listener attached to this event to stop the insert from taking place. You may also return a new node from a listener attached to this event to substitute it for the one about to be deleted.
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {Object} node Node to be deleted
*/
'beforedeletenode',
/**
* @event afterdeletenode
* Fires right after the deleteNode function has run
* @param {Ext.ux.tree.TreePanel.nodeManagement} this
* @param {String} currentOperation This is the name of the function that triggered the current chain of events
* @param {Object} deletedNode Node that was deleted
*/
'afterdeletenode'
);
},
// @private
insertNode : function (node, position, anchorNode) {
var insertedNode = null;

var rootNode = this.parent.getRootNode();

var eventResult = this.parent.fireEvent("beforeinsertnode", this, this.currentOperation, position, node, anchorNode);

if (eventResult) {
if (Ext.isObject(eventResult)) {
var node = eventResult;
}
if (node) {
switch (position) {
case 'above' :
insertedNode = (!Ext.isEmpty(anchorNode.parentNode))?anchorNode.parentNode.insertBefore(node, anchorNode):null;
break;
case 'below' :
insertedNode = (!Ext.isEmpty(anchorNode.parentNode))?anchorNode.parentNode.insertBefore(node, anchorNode.nextSibling):null;
break;
case 'top' :
insertedNode = rootNode.insertBefore(node, rootNode.firstChild);
break;
case 'bottom' :
insertedNode = rootNode.appendChild(node);
break;
case 'branch-top' :
this.expandOnInsert(anchorNode);
insertedNode = anchorNode.insertBefore(node, anchorNode.firstChild);
break;
case 'branch-bottom' :
this.expandOnInsert(anchorNode);
insertedNode = anchorNode.appendChild(node);
break;
}
}
}
this.parent.fireEvent("afterinsertnode", this, this.currentOperation, position, insertedNode, anchorNode);
return insertedNode;
},
// @private
deleteNode : function (node) {
var eventResult = this.parent.fireEvent("beforedeletenode", this, this.currentOperation, node);

if (eventResult) {
if (Ext.isObject(eventResult)) {
var node = eventResult;
}
if (typeof(node)!='undefined'&&node) {
var deletedNode = node.remove();
}
}
this.parent.fireEvent("afterdeletenode", this, this.currentOperation, deletedNode);
return node;
},
// @private
getSelectedNodes : function(omitRoot) {
var selectedNodes;
var omitRoot = (typeof(omitRoot)!='undefined')?omitRoot:null;
var selectionModel = this.parent.getSelectionModel();
var rootNode = this.parent.getRootNode();

if (Ext.isFunction(selectionModel.getSelectedNodes)) {
selectedNodes = selectionModel.getSelectedNodes();
} else {
selectedNodes = selectionModel.getSelectedNode();
}

if (omitRoot) {
selectedNodes = (selectedNodes)?selectedNodes:null;
} else {
selectedNodes = (selectedNodes&&selectedNodes.length)?selectedNodes:rootNode;
}
selectedNodes = (Ext.isArray(selectedNodes))?selectedNodes:[selectedNodes];

return selectedNodes;
},
// @private
getAnchorNode : function(nodes) {
if (typeof(nodes)!='undefined'&&Ext.isArray(nodes)&&nodes.length) {
if (this.multiNodesSelectedAnchorIs == 'first') {
anchorNode = nodes[0];
} else if (this.multiNodesSelectedAnchorIs == 'last') {
anchorNode = nodes[nodes.length-1];
}
} else {
anchorNode = null;
}
return anchorNode;
},
// @private
getDefaultInsertPosition : function (node) {
var position;
if (node.getDepth()==0) {
position = this.rootAnchorInsert;
} else if (node.isLeaf()) {
position = this.leafAnchorInsert;
} else if (node.isExpanded()) {
position = this.expandedBranchAnchorInsert;
} else {
position = this.collapsedBranchAnchorInsert;
}
return position;
},
// @private
expandOnInsert : function (node) {
if (this.expandBranchOnInsertInside && node.isExpandable() &&!node.isExpanded()){
node.expand();
}
},
// @private
clearIds : function (attributes) {
var n;
delete attributes.id;
if (!Ext.isEmpty(attributes.children)) {
for(n=0;n<attributes.children.length;n++){
attributes.children[n] = this.clearIds(attributes.children[n]);
}
}
return attributes;
}
});
Ext.preg('ux-tree-treepanel-nodemanagement', Ext.ux.tree.TreePanel.NodeManagement);

/*
Some simple sample usages:
myTreePanel.nodeManagement.insertNodes([nodes_array]); // inserts nodes in array
myTreePanel.nodeManagement.copyNodes(); // copies currently selected nodes
myTreePanel.nodeManagement.moveNodes('myOtherTreePanel'); // moves currently selected nodes to a tree with an id of 'myOtherTreePanel'
myTreePanel.nodeManagement.shiftNodes('up'); // shifts selected nodes up one
myTreePanel.nodeManagement.deleteNodes(); // deletes selected nodes
myTreePanel.nodeManagement.copyNodes('myOtherTreePanel', 'top'); // copies selected nodes and puts them at the top of another tree
*/