/*! * Ext JS Library 3.2.0 * Copyright(c) 2006-2010 Ext JS, Inc. * licensing@extjs.com * http://www.extjs.com/license */ /** * @class Ext.tree.TreePanel * @extends Ext.Panel *
The TreePanel provides tree-structured UI representation of tree-structured data.
*{@link Ext.tree.TreeNode TreeNode}s added to the TreePanel may each contain metadata * used by your application in their {@link Ext.tree.TreeNode#attributes attributes} property.
*A TreePanel must have a {@link #root} node before it is rendered. This may either be * specified using the {@link #root} config option, or using the {@link #setRootNode} method. *
An example of tree rendered to an existing div:
var tree = new Ext.tree.TreePanel({
renderTo: 'tree-div',
useArrows: true,
autoScroll: true,
animate: true,
enableDD: true,
containerScroll: true,
border: false,
// auto create TreeLoader
dataUrl: 'get-nodes.php',
root: {
nodeType: 'async',
text: 'Ext JS',
draggable: false,
id: 'source'
}
});
tree.getRootNode().expand();
*
* The example above would work with a data packet similar to this:
[{
"text": "adapter",
"id": "source\/adapter",
"cls": "folder"
}, {
"text": "dd",
"id": "source\/dd",
"cls": "folder"
}, {
"text": "debug.js",
"id": "source\/debug.js",
"leaf": true,
"cls": "file"
}]
*
* An example of tree within a Viewport:
new Ext.Viewport({
layout: 'border',
items: [{
region: 'west',
collapsible: true,
title: 'Navigation',
xtype: 'treepanel',
width: 200,
autoScroll: true,
split: true,
loader: new Ext.tree.TreeLoader(),
root: new Ext.tree.AsyncTreeNode({
expanded: true,
children: [{
text: 'Menu Option 1',
leaf: true
}, {
text: 'Menu Option 2',
leaf: true
}, {
text: 'Menu Option 3',
leaf: true
}]
}),
rootVisible: false,
listeners: {
click: function(n) {
Ext.Msg.alert('Navigation Tree Click', 'You clicked: "' + n.attributes.text + '"');
}
}
}, {
region: 'center',
xtype: 'tabpanel',
// remaining code not shown ...
}]
});
*
* @cfg {Ext.tree.TreeNode} root The root node for the tree.
* @cfg {Boolean} rootVisible false to hide the root node (defaults to true)
* @cfg {Boolean} lines false to disable tree lines (defaults to true)
* @cfg {Boolean} enableDD true to enable drag and drop
* @cfg {Boolean} enableDrag true to enable just drag
* @cfg {Boolean} enableDrop true to enable just drop
* @cfg {Object} dragConfig Custom config to pass to the {@link Ext.tree.TreeDragZone} instance
* @cfg {Object} dropConfig Custom config to pass to the {@link Ext.tree.TreeDropZone} instance
* @cfg {String} ddGroup The DD group this TreePanel belongs to
* @cfg {Boolean} ddAppendOnly true if the tree should only allow append drops (use for trees which are sorted)
* @cfg {Boolean} ddScroll true to enable body scrolling
* @cfg {Boolean} containerScroll true to register this container with ScrollManager
* @cfg {Boolean} hlDrop false to disable node highlight on drop (defaults to the value of {@link Ext#enableFx})
* @cfg {String} hlColor The color of the node highlight (defaults to 'C3DAF9')
* @cfg {Boolean} animate true to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx})
* @cfg {Boolean} singleExpand true if only 1 node per branch may be expanded
* @cfg {Object} selModel A tree selection model to use with this TreePanel (defaults to an {@link Ext.tree.DefaultSelectionModel})
* @cfg {Boolean} trackMouseOver false to disable mouse over highlighting
* @cfg {Ext.tree.TreeLoader} loader A {@link Ext.tree.TreeLoader} for use with this TreePanel
* @cfg {String} pathSeparator The token used to separate sub-paths in path strings (defaults to '/')
* @cfg {Boolean} useArrows true to use Vista-style arrows in the tree (defaults to false)
* @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).
*
* @constructor
* @param {Object} config
* @xtype treepanel
*/
Ext.tree.TreePanel = Ext.extend(Ext.Panel, {
rootVisible : true,
animate : Ext.enableFx,
lines : true,
enableDD : false,
hlDrop : Ext.enableFx,
pathSeparator : '/',
/**
* @cfg {Array} bubbleEvents
* An array of events that, when fired, should be bubbled to any parent container. * See {@link Ext.util.Observable#enableBubble}. * Defaults to []. */ bubbleEvents : [], initComponent : function(){ Ext.tree.TreePanel.superclass.initComponent.call(this); if(!this.eventModel){ this.eventModel = new Ext.tree.TreeEventModel(this); } // initialize the loader var l = this.loader; if(!l){ l = new Ext.tree.TreeLoader({ dataUrl: this.dataUrl, requestMethod: this.requestMethod }); }else if(Ext.isObject(l) && !l.load){ l = new Ext.tree.TreeLoader(l); } this.loader = l; this.nodeHash = {}; /** * The root node of this tree. * @type Ext.tree.TreeNode * @property root */ if(this.root){ var r = this.root; delete this.root; this.setRootNode(r); } this.addEvents( /** * @event append * Fires when a new child node is appended to a node in this tree. * @param {Tree} tree The owner tree * @param {Node} parent The parent node * @param {Node} node The newly appended node * @param {Number} index The index of the newly appended node */ 'append', /** * @event remove * Fires when a child node is removed from a node in this tree. * @param {Tree} tree The owner tree * @param {Node} parent The parent node * @param {Node} node The child node removed */ 'remove', /** * @event movenode * Fires when a node is moved to a new location in the tree * @param {Tree} tree The owner tree * @param {Node} node The node moved * @param {Node} oldParent The old parent of this node * @param {Node} newParent The new parent of this node * @param {Number} index The index it was moved to */ 'movenode', /** * @event insert * Fires when a new child node is inserted in a node in this tree. * @param {Tree} tree The owner tree * @param {Node} parent The parent node * @param {Node} node The child node inserted * @param {Node} refNode The child node the node was inserted before */ 'insert', /** * @event beforeappend * Fires before a new child is appended to a node in this tree, return false to cancel the append. * @param {Tree} tree The owner tree * @param {Node} parent The parent node * @param {Node} node The child node to be appended */ 'beforeappend', /** * @event beforeremove * Fires before a child is removed from a node in this tree, return false to cancel the remove. * @param {Tree} tree The owner tree * @param {Node} parent The parent node * @param {Node} node The child node to be removed */ 'beforeremove', /** * @event beforemovenode * Fires before a node is moved to a new location in the tree. Return false to cancel the move. * @param {Tree} tree The owner tree * @param {Node} node The node being moved * @param {Node} oldParent The parent of the node * @param {Node} newParent The new parent the node is moving to * @param {Number} index The index it is being moved to */ 'beforemovenode', /** * @event beforeinsert * Fires before a new child is inserted in a node in this tree, return false to cancel the insert. * @param {Tree} tree The owner tree * @param {Node} parent The parent node * @param {Node} node The child node to be inserted * @param {Node} refNode The child node the node is being inserted before */ 'beforeinsert', /** * @event beforeload * Fires before a node is loaded, return false to cancel * @param {Node} node The node being loaded */ 'beforeload', /** * @event load * Fires when a node is loaded * @param {Node} node The node that was loaded */ 'load', /** * @event textchange * Fires when the text for a node is changed * @param {Node} node The node * @param {String} text The new text * @param {String} oldText The old text */ 'textchange', /** * @event beforeexpandnode * Fires before a node is expanded, return false to cancel. * @param {Node} node The node * @param {Boolean} deep * @param {Boolean} anim */ 'beforeexpandnode', /** * @event beforecollapsenode * Fires before a node is collapsed, return false to cancel. * @param {Node} node The node * @param {Boolean} deep * @param {Boolean} anim */ 'beforecollapsenode', /** * @event expandnode * Fires when a node is expanded * @param {Node} node The node */ 'expandnode', /** * @event disabledchange * Fires when the disabled status of a node changes * @param {Node} node The node * @param {Boolean} disabled */ 'disabledchange', /** * @event collapsenode * Fires when a node is collapsed * @param {Node} node The node */ 'collapsenode', /** * @event beforeclick * Fires before click processing on a node. Return false to cancel the default action. * @param {Node} node The node * @param {Ext.EventObject} e The event object */ 'beforeclick', /** * @event click * Fires when a node is clicked * @param {Node} node The node * @param {Ext.EventObject} e The event object */ 'click', /** * @event containerclick * Fires when the tree container is clicked * @param {Tree} this * @param {Ext.EventObject} e The event object */ 'containerclick', /** * @event checkchange * Fires when a node with a checkbox's checked property changes * @param {Node} this This node * @param {Boolean} checked */ 'checkchange', /** * @event beforedblclick * Fires before double click processing on a node. Return false to cancel the default action. * @param {Node} node The node * @param {Ext.EventObject} e The event object */ 'beforedblclick', /** * @event dblclick * Fires when a node is double clicked * @param {Node} node The node * @param {Ext.EventObject} e The event object */ 'dblclick', /** * @event containerdblclick * Fires when the tree container is double clicked * @param {Tree} this * @param {Ext.EventObject} e The event object */ 'containerdblclick', /** * @event contextmenu * Fires when a node is right clicked. To display a context menu in response to this * event, first create a Menu object (see {@link Ext.menu.Menu} for details), then add * a handler for this event:
new Ext.tree.TreePanel({
title: 'My TreePanel',
root: new Ext.tree.AsyncTreeNode({
text: 'The Root',
children: [
{ text: 'Child node 1', leaf: true },
{ text: 'Child node 2', leaf: true }
]
}),
contextMenu: new Ext.menu.Menu({
items: [{
id: 'delete-node',
text: 'Delete Node'
}],
listeners: {
itemclick: function(item) {
switch (item.id) {
case 'delete-node':
var n = item.parentMenu.contextNode;
if (n.parentNode) {
n.remove();
}
break;
}
}
}
}),
listeners: {
contextmenu: function(node, e) {
// Register the context node with the menu so that a Menu Item's handler function can access
// it via its {@link Ext.menu.BaseItem#parentMenu parentMenu} property.
node.select();
var c = node.getOwnerTree().contextMenu;
c.contextNode = node;
c.showAt(e.getXY());
}
}
});
* @param {Node} node The node
* @param {Ext.EventObject} e The event object
*/
'contextmenu',
/**
* @event containercontextmenu
* Fires when the tree container is right clicked
* @param {Tree} this
* @param {Ext.EventObject} e The event object
*/
'containercontextmenu',
/**
* @event beforechildrenrendered
* Fires right before the child nodes for a node are rendered
* @param {Node} node The node
*/
'beforechildrenrendered',
/**
* @event startdrag
* Fires when a node starts being dragged
* @param {Ext.tree.TreePanel} this
* @param {Ext.tree.TreeNode} node
* @param {event} e The raw browser event
*/
'startdrag',
/**
* @event enddrag
* Fires when a drag operation is complete
* @param {Ext.tree.TreePanel} this
* @param {Ext.tree.TreeNode} node
* @param {event} e The raw browser event
*/
'enddrag',
/**
* @event dragdrop
* Fires when a dragged node is dropped on a valid DD target
* @param {Ext.tree.TreePanel} this
* @param {Ext.tree.TreeNode} node
* @param {DD} dd The dd it was dropped on
* @param {event} e The raw browser event
*/
'dragdrop',
/**
* @event beforenodedrop
* Fires when a DD object is dropped on a node in this tree for preprocessing. Return false to cancel the drop. The dropEvent
* passed to handlers has the following properties:this
reference) in which the function is executed. Defaults to the current Node.
* @param {Array} args (optional) The args to call the function with (default to passing the current Node)
*/
bubble : function(fn, scope, args){
var p = this;
while(p){
if(fn.apply(scope || p, args || [p]) === false){
break;
}
p = p.parentNode;
}
},
/**
* Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
* will be the args provided or the current node. If the function returns false at any point,
* the cascade is stopped on that branch.
* @param {Function} fn The function to call
* @param {Object} scope (optional) The scope (this
reference) in which the function is executed. Defaults to the current Node.
* @param {Array} args (optional) The args to call the function with (default to passing the current Node)
*/
cascade : function(fn, scope, args){
if(fn.apply(scope || this, args || [this]) !== false){
var cs = this.childNodes;
for(var i = 0, len = cs.length; i < len; i++) {
cs[i].cascade(fn, scope, args);
}
}
},
/**
* Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
* will be the args provided or the current node. If the function returns false at any point,
* the iteration stops.
* @param {Function} fn The function to call
* @param {Object} scope (optional) The scope (this
reference) in which the function is executed. Defaults to the current Node in the iteration.
* @param {Array} args (optional) The args to call the function with (default to passing the current Node)
*/
eachChild : function(fn, scope, args){
var cs = this.childNodes;
for(var i = 0, len = cs.length; i < len; i++) {
if(fn.apply(scope || this, args || [cs[i]]) === false){
break;
}
}
},
/**
* Finds the first child that has the attribute with the specified value.
* @param {String} attribute The attribute name
* @param {Mixed} value The value to search for
* @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
* @return {Node} The found child or null if none was found
*/
findChild : function(attribute, value, deep){
return this.findChildBy(function(){
return this.attributes[attribute] == value;
}, null, deep);
},
/**
* Finds the first child by a custom function. The child matches if the function passed returns true
.
* @param {Function} fn A function which must return true
if the passed Node is the required Node.
* @param {Object} scope (optional) The scope (this
reference) in which the function is executed. Defaults to the Node being tested.
* @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
* @return {Node} The found child or null if none was found
*/
findChildBy : function(fn, scope, deep){
var cs = this.childNodes,
len = cs.length,
i = 0,
n,
res;
for(; i < len; i++){
n = cs[i];
if(fn.call(scope || n, n) === true){
return n;
}else if (deep){
res = n.findChildBy(fn, scope, deep);
if(res != null){
return res;
}
}
}
return null;
},
/**
* Sorts this nodes children using the supplied sort function.
* @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
* @param {Object} scope (optional)The scope (this
reference) in which the function is executed. Defaults to the browser window.
*/
sort : function(fn, scope){
var cs = this.childNodes;
var len = cs.length;
if(len > 0){
var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
cs.sort(sortFn);
for(var i = 0; i < len; i++){
var n = cs[i];
n.previousSibling = cs[i-1];
n.nextSibling = cs[i+1];
if(i === 0){
this.setFirstChild(n);
}
if(i == len-1){
this.setLastChild(n);
}
}
}
},
/**
* Returns true if this node is an ancestor (at any point) of the passed node.
* @param {Node} node
* @return {Boolean}
*/
contains : function(node){
return node.isAncestor(this);
},
/**
* Returns true if the passed node is an ancestor (at any point) of this node.
* @param {Node} node
* @return {Boolean}
*/
isAncestor : function(node){
var p = this.parentNode;
while(p){
if(p == node){
return true;
}
p = p.parentNode;
}
return false;
},
toString : function(){
return "[Node"+(this.id?" "+this.id:"")+"]";
}
});/**
* @class Ext.tree.TreeNode
* @extends Ext.data.Node
* @cfg {String} text The text for this node
* @cfg {Boolean} expanded true to start the node expanded
* @cfg {Boolean} allowDrag False to make this node undraggable if {@link #draggable} = true (defaults to true)
* @cfg {Boolean} allowDrop False if this node cannot have child nodes dropped on it (defaults to true)
* @cfg {Boolean} disabled true to start the node disabled
* @cfg {String} icon The path to an icon for the node. The preferred way to do this
* is to use the cls or iconCls attributes and add the icon via a CSS background image.
* @cfg {String} cls A css class to be added to the node
* @cfg {String} iconCls A css class to be added to the nodes icon element for applying css background images
* @cfg {String} href URL of the link used for the node (defaults to #)
* @cfg {String} hrefTarget target frame for the link
* @cfg {Boolean} hidden True to render hidden. (Defaults to false).
* @cfg {String} qtip An Ext QuickTip for the node
* @cfg {Boolean} expandable If set to true, the node will always show a plus/minus icon, even when empty
* @cfg {String} qtipCfg An Ext QuickTip config for the node (used instead of qtip)
* @cfg {Boolean} singleClickExpand True for single click expand on this node
* @cfg {Function} uiProvider A UI class to use for this node (defaults to Ext.tree.TreeNodeUI)
* @cfg {Boolean} checked True to render a checked checkbox for this node, false to render an unchecked checkbox
* (defaults to undefined with no checkbox rendered)
* @cfg {Boolean} draggable True to make this node draggable (defaults to false)
* @cfg {Boolean} isTarget False to not allow this node to act as a drop target (defaults to true)
* @cfg {Boolean} allowChildren False to not allow this node to have child nodes (defaults to true)
* @cfg {Boolean} editable False to not allow this node to be edited by an {@link Ext.tree.TreeEditor} (defaults to true)
* @constructor
* @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node
*/
Ext.tree.TreeNode = function(attributes){
attributes = attributes || {};
if(Ext.isString(attributes)){
attributes = {text: attributes};
}
this.childrenRendered = false;
this.rendered = false;
Ext.tree.TreeNode.superclass.constructor.call(this, attributes);
this.expanded = attributes.expanded === true;
this.isTarget = attributes.isTarget !== false;
this.draggable = attributes.draggable !== false && attributes.allowDrag !== false;
this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false;
/**
* Read-only. The text for this node. To change it use {@link #setText}
.
* @type String
*/
this.text = attributes.text;
/**
* True if this node is disabled.
* @type Boolean
*/
this.disabled = attributes.disabled === true;
/**
* True if this node is hidden.
* @type Boolean
*/
this.hidden = attributes.hidden === true;
this.addEvents(
/**
* @event textchange
* Fires when the text for this node is changed
* @param {Node} this This node
* @param {String} text The new text
* @param {String} oldText The old text
*/
'textchange',
/**
* @event beforeexpand
* Fires before this node is expanded, return false to cancel.
* @param {Node} this This node
* @param {Boolean} deep
* @param {Boolean} anim
*/
'beforeexpand',
/**
* @event beforecollapse
* Fires before this node is collapsed, return false to cancel.
* @param {Node} this This node
* @param {Boolean} deep
* @param {Boolean} anim
*/
'beforecollapse',
/**
* @event expand
* Fires when this node is expanded
* @param {Node} this This node
*/
'expand',
/**
* @event disabledchange
* Fires when the disabled status of this node changes
* @param {Node} this This node
* @param {Boolean} disabled
*/
'disabledchange',
/**
* @event collapse
* Fires when this node is collapsed
* @param {Node} this This node
*/
'collapse',
/**
* @event beforeclick
* Fires before click processing. Return false to cancel the default action.
* @param {Node} this This node
* @param {Ext.EventObject} e The event object
*/
'beforeclick',
/**
* @event click
* Fires when this node is clicked
* @param {Node} this This node
* @param {Ext.EventObject} e The event object
*/
'click',
/**
* @event checkchange
* Fires when a node with a checkbox's checked property changes
* @param {Node} this This node
* @param {Boolean} checked
*/
'checkchange',
/**
* @event beforedblclick
* Fires before double click processing. Return false to cancel the default action.
* @param {Node} this This node
* @param {Ext.EventObject} e The event object
*/
'beforedblclick',
/**
* @event dblclick
* Fires when this node is double clicked
* @param {Node} this This node
* @param {Ext.EventObject} e The event object
*/
'dblclick',
/**
* @event contextmenu
* Fires when this node is right clicked
* @param {Node} this This node
* @param {Ext.EventObject} e The event object
*/
'contextmenu',
/**
* @event beforechildrenrendered
* Fires right before the child nodes for this node are rendered
* @param {Node} this This node
*/
'beforechildrenrendered'
);
var uiClass = this.attributes.uiProvider || this.defaultUI || Ext.tree.TreeNodeUI;
/**
* Read-only. The UI for this node
* @type TreeNodeUI
*/
this.ui = new uiClass(this);
};
Ext.extend(Ext.tree.TreeNode, Ext.data.Node, {
preventHScroll : true,
/**
* Returns true if this node is expanded
* @return {Boolean}
*/
isExpanded : function(){
return this.expanded;
},
/**
* Returns the UI object for this node.
* @return {TreeNodeUI} The object which is providing the user interface for this tree
* node. Unless otherwise specified in the {@link #uiProvider}, this will be an instance
* of {@link Ext.tree.TreeNodeUI}
*/
getUI : function(){
return this.ui;
},
getLoader : function(){
var owner;
return this.loader || ((owner = this.getOwnerTree()) && owner.loader ? owner.loader : (this.loader = new Ext.tree.TreeLoader()));
},
// private override
setFirstChild : function(node){
var of = this.firstChild;
Ext.tree.TreeNode.superclass.setFirstChild.call(this, node);
if(this.childrenRendered && of && node != of){
of.renderIndent(true, true);
}
if(this.rendered){
this.renderIndent(true, true);
}
},
// private override
setLastChild : function(node){
var ol = this.lastChild;
Ext.tree.TreeNode.superclass.setLastChild.call(this, node);
if(this.childrenRendered && ol && node != ol){
ol.renderIndent(true, true);
}
if(this.rendered){
this.renderIndent(true, true);
}
},
// these methods are overridden to provide lazy rendering support
// private override
appendChild : function(n){
if(!n.render && !Ext.isArray(n)){
n = this.getLoader().createNode(n);
}
var node = Ext.tree.TreeNode.superclass.appendChild.call(this, n);
if(node && this.childrenRendered){
node.render();
}
this.ui.updateExpandIcon();
return node;
},
// private override
removeChild : function(node, destroy){
this.ownerTree.getSelectionModel().unselect(node);
Ext.tree.TreeNode.superclass.removeChild.apply(this, arguments);
// only update the ui if we're not destroying
if(!destroy){
// if it's been rendered remove dom node
if(node.ui.rendered){
node.ui.remove();
}
if(this.childNodes.length < 1){
this.collapse(false, false);
}else{
this.ui.updateExpandIcon();
}
if(!this.firstChild && !this.isHiddenRoot()){
this.childrenRendered = false;
}
}
return node;
},
// private override
insertBefore : function(node, refNode){
if(!node.render){
node = this.getLoader().createNode(node);
}
var newNode = Ext.tree.TreeNode.superclass.insertBefore.call(this, node, refNode);
if(newNode && refNode && this.childrenRendered){
node.render();
}
this.ui.updateExpandIcon();
return newNode;
},
/**
* Sets the text for this node
* @param {String} text
*/
setText : function(text){
var oldText = this.text;
this.text = this.attributes.text = text;
if(this.rendered){ // event without subscribing
this.ui.onTextChange(this, text, oldText);
}
this.fireEvent('textchange', this, text, oldText);
},
/**
* Triggers selection of this node
*/
select : function(){
var t = this.getOwnerTree();
if(t){
t.getSelectionModel().select(this);
}
},
/**
* Triggers deselection of this node
* @param {Boolean} silent (optional) True to stop selection change events from firing.
*/
unselect : function(silent){
var t = this.getOwnerTree();
if(t){
t.getSelectionModel().unselect(this, silent);
}
},
/**
* Returns true if this node is selected
* @return {Boolean}
*/
isSelected : function(){
var t = this.getOwnerTree();
return t ? t.getSelectionModel().isSelected(this) : false;
},
/**
* Expand this node.
* @param {Boolean} deep (optional) True to expand all children as well
* @param {Boolean} anim (optional) false to cancel the default animation
* @param {Function} callback (optional) A callback to be called when
* expanding this node completes (does not wait for deep expand to complete).
* Called with 1 parameter, this node.
* @param {Object} scope (optional) The scope (this
reference) in which the callback is executed. Defaults to this TreeNode.
*/
expand : function(deep, anim, callback, scope){
if(!this.expanded){
if(this.fireEvent('beforeexpand', this, deep, anim) === false){
return;
}
if(!this.childrenRendered){
this.renderChildren();
}
this.expanded = true;
if(!this.isHiddenRoot() && (this.getOwnerTree().animate && anim !== false) || anim){
this.ui.animExpand(function(){
this.fireEvent('expand', this);
this.runCallback(callback, scope || this, [this]);
if(deep === true){
this.expandChildNodes(true);
}
}.createDelegate(this));
return;
}else{
this.ui.expand();
this.fireEvent('expand', this);
this.runCallback(callback, scope || this, [this]);
}
}else{
this.runCallback(callback, scope || this, [this]);
}
if(deep === true){
this.expandChildNodes(true);
}
},
runCallback : function(cb, scope, args){
if(Ext.isFunction(cb)){
cb.apply(scope, args);
}
},
isHiddenRoot : function(){
return this.isRoot && !this.getOwnerTree().rootVisible;
},
/**
* Collapse this node.
* @param {Boolean} deep (optional) True to collapse all children as well
* @param {Boolean} anim (optional) false to cancel the default animation
* @param {Function} callback (optional) A callback to be called when
* expanding this node completes (does not wait for deep expand to complete).
* Called with 1 parameter, this node.
* @param {Object} scope (optional) The scope (this
reference) in which the callback is executed. Defaults to this TreeNode.
*/
collapse : function(deep, anim, callback, scope){
if(this.expanded && !this.isHiddenRoot()){
if(this.fireEvent('beforecollapse', this, deep, anim) === false){
return;
}
this.expanded = false;
if((this.getOwnerTree().animate && anim !== false) || anim){
this.ui.animCollapse(function(){
this.fireEvent('collapse', this);
this.runCallback(callback, scope || this, [this]);
if(deep === true){
this.collapseChildNodes(true);
}
}.createDelegate(this));
return;
}else{
this.ui.collapse();
this.fireEvent('collapse', this);
this.runCallback(callback, scope || this, [this]);
}
}else if(!this.expanded){
this.runCallback(callback, scope || this, [this]);
}
if(deep === true){
var cs = this.childNodes;
for(var i = 0, len = cs.length; i < len; i++) {
cs[i].collapse(true, false);
}
}
},
// private
delayedExpand : function(delay){
if(!this.expandProcId){
this.expandProcId = this.expand.defer(delay, this);
}
},
// private
cancelExpand : function(){
if(this.expandProcId){
clearTimeout(this.expandProcId);
}
this.expandProcId = false;
},
/**
* Toggles expanded/collapsed state of the node
*/
toggle : function(){
if(this.expanded){
this.collapse();
}else{
this.expand();
}
},
/**
* Ensures all parent nodes are expanded, and if necessary, scrolls
* the node into view.
* @param {Function} callback (optional) A function to call when the node has been made visible.
* @param {Object} scope (optional) The scope (this
reference) in which the callback is executed. Defaults to this TreeNode.
*/
ensureVisible : function(callback, scope){
var tree = this.getOwnerTree();
tree.expandPath(this.parentNode ? this.parentNode.getPath() : this.getPath(), false, function(){
var node = tree.getNodeById(this.id); // Somehow if we don't do this, we lose changes that happened to node in the meantime
tree.getTreeEl().scrollChildIntoView(node.ui.anchor);
this.runCallback(callback, scope || this, [this]);
}.createDelegate(this));
},
/**
* Expand all child nodes
* @param {Boolean} deep (optional) true if the child nodes should also expand their child nodes
*/
expandChildNodes : function(deep){
var cs = this.childNodes;
for(var i = 0, len = cs.length; i < len; i++) {
cs[i].expand(deep);
}
},
/**
* Collapse all child nodes
* @param {Boolean} deep (optional) true if the child nodes should also collapse their child nodes
*/
collapseChildNodes : function(deep){
var cs = this.childNodes;
for(var i = 0, len = cs.length; i < len; i++) {
cs[i].collapse(deep);
}
},
/**
* Disables this node
*/
disable : function(){
this.disabled = true;
this.unselect();
if(this.rendered && this.ui.onDisableChange){ // event without subscribing
this.ui.onDisableChange(this, true);
}
this.fireEvent('disabledchange', this, true);
},
/**
* Enables this node
*/
enable : function(){
this.disabled = false;
if(this.rendered && this.ui.onDisableChange){ // event without subscribing
this.ui.onDisableChange(this, false);
}
this.fireEvent('disabledchange', this, false);
},
// private
renderChildren : function(suppressEvent){
if(suppressEvent !== false){
this.fireEvent('beforechildrenrendered', this);
}
var cs = this.childNodes;
for(var i = 0, len = cs.length; i < len; i++){
cs[i].render(true);
}
this.childrenRendered = true;
},
// private
sort : function(fn, scope){
Ext.tree.TreeNode.superclass.sort.apply(this, arguments);
if(this.childrenRendered){
var cs = this.childNodes;
for(var i = 0, len = cs.length; i < len; i++){
cs[i].render(true);
}
}
},
// private
render : function(bulkRender){
this.ui.render(bulkRender);
if(!this.rendered){
// make sure it is registered
this.getOwnerTree().registerNode(this);
this.rendered = true;
if(this.expanded){
this.expanded = false;
this.expand(false, false);
}
}
},
// private
renderIndent : function(deep, refresh){
if(refresh){
this.ui.childIndent = null;
}
this.ui.renderIndent();
if(deep === true && this.childrenRendered){
var cs = this.childNodes;
for(var i = 0, len = cs.length; i < len; i++){
cs[i].renderIndent(true, refresh);
}
}
},
beginUpdate : function(){
this.childrenRendered = false;
},
endUpdate : function(){
if(this.expanded && this.rendered){
this.renderChildren();
}
},
//inherit docs
destroy : function(silent){
if(silent === true){
this.unselect(true);
}
Ext.tree.TreeNode.superclass.destroy.call(this, silent);
Ext.destroy(this.ui, this.loader);
this.ui = this.loader = null;
},
// private
onIdChange : function(id){
this.ui.onIdChange(id);
}
});
Ext.tree.TreePanel.nodeTypes.node = Ext.tree.TreeNode;/**
* @class Ext.tree.AsyncTreeNode
* @extends Ext.tree.TreeNode
* @cfg {TreeLoader} loader A TreeLoader to be used by this node (defaults to the loader defined on the tree)
* @constructor
* @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node
*/
Ext.tree.AsyncTreeNode = function(config){
this.loaded = config && config.loaded === true;
this.loading = false;
Ext.tree.AsyncTreeNode.superclass.constructor.apply(this, arguments);
/**
* @event beforeload
* Fires before this node is loaded, return false to cancel
* @param {Node} this This node
*/
this.addEvents('beforeload', 'load');
/**
* @event load
* Fires when this node is loaded
* @param {Node} this This node
*/
/**
* The loader used by this node (defaults to using the tree's defined loader)
* @type TreeLoader
* @property loader
*/
};
Ext.extend(Ext.tree.AsyncTreeNode, Ext.tree.TreeNode, {
expand : function(deep, anim, callback, scope){
if(this.loading){ // if an async load is already running, waiting til it's done
var timer;
var f = function(){
if(!this.loading){ // done loading
clearInterval(timer);
this.expand(deep, anim, callback, scope);
}
}.createDelegate(this);
timer = setInterval(f, 200);
return;
}
if(!this.loaded){
if(this.fireEvent("beforeload", this) === false){
return;
}
this.loading = true;
this.ui.beforeLoad(this);
var loader = this.loader || this.attributes.loader || this.getOwnerTree().getLoader();
if(loader){
loader.load(this, this.loadComplete.createDelegate(this, [deep, anim, callback, scope]), this);
return;
}
}
Ext.tree.AsyncTreeNode.superclass.expand.call(this, deep, anim, callback, scope);
},
/**
* Returns true if this node is currently loading
* @return {Boolean}
*/
isLoading : function(){
return this.loading;
},
loadComplete : function(deep, anim, callback, scope){
this.loading = false;
this.loaded = true;
this.ui.afterLoad(this);
this.fireEvent("load", this);
this.expand(deep, anim, callback, scope);
},
/**
* Returns true if this node has been loaded
* @return {Boolean}
*/
isLoaded : function(){
return this.loaded;
},
hasChildNodes : function(){
if(!this.isLeaf() && !this.loaded){
return true;
}else{
return Ext.tree.AsyncTreeNode.superclass.hasChildNodes.call(this);
}
},
/**
* Trigger a reload for this node
* @param {Function} callback
* @param {Object} scope (optional) The scope (this
reference) in which the callback is executed. Defaults to this Node.
*/
reload : function(callback, scope){
this.collapse(false, false);
while(this.firstChild){
this.removeChild(this.firstChild).destroy();
}
this.childrenRendered = false;
this.loaded = false;
if(this.isHiddenRoot()){
this.expanded = false;
}
this.expand(false, false, callback, scope);
}
});
Ext.tree.TreePanel.nodeTypes.async = Ext.tree.AsyncTreeNode;/**
* @class Ext.tree.TreeNodeUI
* This class provides the default UI implementation for Ext TreeNodes.
* The TreeNode UI implementation is separate from the
* tree implementation, and allows customizing of the appearance of
* tree nodes.
* If you are customizing the Tree's user interface, you
* may need to extend this class, but you should never need to instantiate this class.
*
* This class provides access to the user interface components of an Ext TreeNode, through
* {@link Ext.tree.TreeNode#getUI}
*/
Ext.tree.TreeNodeUI = function(node){
this.node = node;
this.rendered = false;
this.animating = false;
this.wasLeaf = true;
this.ecc = 'x-tree-ec-icon x-tree-elbow';
this.emptyIcon = Ext.BLANK_IMAGE_URL;
};
Ext.tree.TreeNodeUI.prototype = {
// private
removeChild : function(node){
if(this.rendered){
this.ctNode.removeChild(node.ui.getEl());
}
},
// private
beforeLoad : function(){
this.addClass("x-tree-node-loading");
},
// private
afterLoad : function(){
this.removeClass("x-tree-node-loading");
},
// private
onTextChange : function(node, text, oldText){
if(this.rendered){
this.textNode.innerHTML = text;
}
},
// private
onDisableChange : function(node, state){
this.disabled = state;
if (this.checkbox) {
this.checkbox.disabled = state;
}
if(state){
this.addClass("x-tree-node-disabled");
}else{
this.removeClass("x-tree-node-disabled");
}
},
// private
onSelectedChange : function(state){
if(state){
this.focus();
this.addClass("x-tree-selected");
}else{
//this.blur();
this.removeClass("x-tree-selected");
}
},
// private
onMove : function(tree, node, oldParent, newParent, index, refNode){
this.childIndent = null;
if(this.rendered){
var targetNode = newParent.ui.getContainer();
if(!targetNode){//target not rendered
this.holder = document.createElement("div");
this.holder.appendChild(this.wrap);
return;
}
var insertBefore = refNode ? refNode.ui.getEl() : null;
if(insertBefore){
targetNode.insertBefore(this.wrap, insertBefore);
}else{
targetNode.appendChild(this.wrap);
}
this.node.renderIndent(true, oldParent != newParent);
}
},
/**
* Adds one or more CSS classes to the node's UI element.
* Duplicate classes are automatically filtered out.
* @param {String/Array} className The CSS class to add, or an array of classes
*/
addClass : function(cls){
if(this.elNode){
Ext.fly(this.elNode).addClass(cls);
}
},
/**
* Removes one or more CSS classes from the node's UI element.
* @param {String/Array} className The CSS class to remove, or an array of classes
*/
removeClass : function(cls){
if(this.elNode){
Ext.fly(this.elNode).removeClass(cls);
}
},
// private
remove : function(){
if(this.rendered){
this.holder = document.createElement("div");
this.holder.appendChild(this.wrap);
}
},
// private
fireEvent : function(){
return this.node.fireEvent.apply(this.node, arguments);
},
// private
initEvents : function(){
this.node.on("move", this.onMove, this);
if(this.node.disabled){
this.onDisableChange(this.node, true);
}
if(this.node.hidden){
this.hide();
}
var ot = this.node.getOwnerTree();
var dd = ot.enableDD || ot.enableDrag || ot.enableDrop;
if(dd && (!this.node.isRoot || ot.rootVisible)){
Ext.dd.Registry.register(this.elNode, {
node: this.node,
handles: this.getDDHandles(),
isHandle: false
});
}
},
// private
getDDHandles : function(){
return [this.iconNode, this.textNode, this.elNode];
},
/**
* Hides this node.
*/
hide : function(){
this.node.hidden = true;
if(this.wrap){
this.wrap.style.display = "none";
}
},
/**
* Shows this node.
*/
show : function(){
this.node.hidden = false;
if(this.wrap){
this.wrap.style.display = "";
}
},
// private
onContextMenu : function(e){
if (this.node.hasListener("contextmenu") || this.node.getOwnerTree().hasListener("contextmenu")) {
e.preventDefault();
this.focus();
this.fireEvent("contextmenu", this.node, e);
}
},
// private
onClick : function(e){
if(this.dropping){
e.stopEvent();
return;
}
if(this.fireEvent("beforeclick", this.node, e) !== false){
var a = e.getTarget('a');
if(!this.disabled && this.node.attributes.href && a){
this.fireEvent("click", this.node, e);
return;
}else if(a && e.ctrlKey){
e.stopEvent();
}
e.preventDefault();
if(this.disabled){
return;
}
if(this.node.attributes.singleClickExpand && !this.animating && this.node.isExpandable()){
this.node.toggle();
}
this.fireEvent("click", this.node, e);
}else{
e.stopEvent();
}
},
// private
onDblClick : function(e){
e.preventDefault();
if(this.disabled){
return;
}
if(this.fireEvent("beforedblclick", this.node, e) !== false){
if(this.checkbox){
this.toggleCheck();
}
if(!this.animating && this.node.isExpandable()){
this.node.toggle();
}
this.fireEvent("dblclick", this.node, e);
}
},
onOver : function(e){
this.addClass('x-tree-node-over');
},
onOut : function(e){
this.removeClass('x-tree-node-over');
},
// private
onCheckChange : function(){
var checked = this.checkbox.checked;
// fix for IE6
this.checkbox.defaultChecked = checked;
this.node.attributes.checked = checked;
this.fireEvent('checkchange', this.node, checked);
},
// private
ecClick : function(e){
if(!this.animating && this.node.isExpandable()){
this.node.toggle();
}
},
// private
startDrop : function(){
this.dropping = true;
},
// delayed drop so the click event doesn't get fired on a drop
endDrop : function(){
setTimeout(function(){
this.dropping = false;
}.createDelegate(this), 50);
},
// private
expand : function(){
this.updateExpandIcon();
this.ctNode.style.display = "";
},
// private
focus : function(){
if(!this.node.preventHScroll){
try{this.anchor.focus();
}catch(e){}
}else{
try{
var noscroll = this.node.getOwnerTree().getTreeEl().dom;
var l = noscroll.scrollLeft;
this.anchor.focus();
noscroll.scrollLeft = l;
}catch(e){}
}
},
/**
* Sets the checked status of the tree node to the passed value, or, if no value was passed,
* toggles the checked status. If the node was rendered with no checkbox, this has no effect.
* @param {Boolean} value (optional) The new checked status.
*/
toggleCheck : function(value){
var cb = this.checkbox;
if(cb){
cb.checked = (value === undefined ? !cb.checked : value);
this.onCheckChange();
}
},
// private
blur : function(){
try{
this.anchor.blur();
}catch(e){}
},
// private
animExpand : function(callback){
var ct = Ext.get(this.ctNode);
ct.stopFx();
if(!this.node.isExpandable()){
this.updateExpandIcon();
this.ctNode.style.display = "";
Ext.callback(callback);
return;
}
this.animating = true;
this.updateExpandIcon();
ct.slideIn('t', {
callback : function(){
this.animating = false;
Ext.callback(callback);
},
scope: this,
duration: this.node.ownerTree.duration || .25
});
},
// private
highlight : function(){
var tree = this.node.getOwnerTree();
Ext.fly(this.wrap).highlight(
tree.hlColor || "C3DAF9",
{endColor: tree.hlBaseColor}
);
},
// private
collapse : function(){
this.updateExpandIcon();
this.ctNode.style.display = "none";
},
// private
animCollapse : function(callback){
var ct = Ext.get(this.ctNode);
ct.enableDisplayMode('block');
ct.stopFx();
this.animating = true;
this.updateExpandIcon();
ct.slideOut('t', {
callback : function(){
this.animating = false;
Ext.callback(callback);
},
scope: this,
duration: this.node.ownerTree.duration || .25
});
},
// private
getContainer : function(){
return this.ctNode;
},
/**
* Returns the element which encapsulates this node.
* @return {HtmlElement} The DOM element. The default implementation uses a <li>
.
*/
getEl : function(){
return this.wrap;
},
// private
appendDDGhost : function(ghostNode){
ghostNode.appendChild(this.elNode.cloneNode(true));
},
// private
getDDRepairXY : function(){
return Ext.lib.Dom.getXY(this.iconNode);
},
// private
onRender : function(){
this.render();
},
// private
render : function(bulkRender){
var n = this.node, a = n.attributes;
var targetNode = n.parentNode ?
n.parentNode.ui.getContainer() : n.ownerTree.innerCt.dom;
if(!this.rendered){
this.rendered = true;
this.renderElements(n, a, targetNode, bulkRender);
if(a.qtip){
if(this.textNode.setAttributeNS){
this.textNode.setAttributeNS("ext", "qtip", a.qtip);
if(a.qtipTitle){
this.textNode.setAttributeNS("ext", "qtitle", a.qtipTitle);
}
}else{
this.textNode.setAttribute("ext:qtip", a.qtip);
if(a.qtipTitle){
this.textNode.setAttribute("ext:qtitle", a.qtipTitle);
}
}
}else if(a.qtipCfg){
a.qtipCfg.target = Ext.id(this.textNode);
Ext.QuickTips.register(a.qtipCfg);
}
this.initEvents();
if(!this.node.expanded){
this.updateExpandIcon(true);
}
}else{
if(bulkRender === true) {
targetNode.appendChild(this.wrap);
}
}
},
// private
renderElements : function(n, a, targetNode, bulkRender){
// add some indent caching, this helps performance when rendering a large tree
this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
var cb = Ext.isBoolean(a.checked),
nel,
href = a.href ? a.href : Ext.isGecko ? "" : "#",
buf = ['
* If you are customizing the Tree's user interface, you
* may need to extend this class, but you should never need to instantiate this class.
*/
Ext.tree.RootTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
// private
render : function(){
if(!this.rendered){
var targetNode = this.node.ownerTree.innerCt.dom;
this.node.expanded = true;
targetNode.innerHTML = '
[{
id: 1,
text: 'A leaf Node',
leaf: true
},{
id: 2,
text: 'A folder Node',
children: [{
id: 3,
text: 'A child Node',
leaf: true
}]
}]
*
myTreeLoader.on("beforeload", function(treeLoader, node) {
this.baseParams.category = node.attributes.category;
}, this);
* This would pass an HTTP parameter called "category" to the server containing
* the value of the Node's "category" attribute.
* @constructor
* Creates a new Treeloader.
* @param {Object} config A config object containing config properties.
*/
Ext.tree.TreeLoader = function(config){
this.baseParams = {};
Ext.apply(this, config);
this.addEvents(
/**
* @event beforeload
* Fires before a network request is made to retrieve the Json text which specifies a node's children.
* @param {Object} This TreeLoader object.
* @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
* @param {Object} callback The callback function specified in the {@link #load} call.
*/
"beforeload",
/**
* @event load
* Fires when the node has been successfuly loaded.
* @param {Object} This TreeLoader object.
* @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
* @param {Object} response The response object containing the data from the server.
*/
"load",
/**
* @event loadexception
* Fires if the network request failed.
* @param {Object} This TreeLoader object.
* @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
* @param {Object} response The response object containing the data from the server.
*/
"loadexception"
);
Ext.tree.TreeLoader.superclass.constructor.call(this);
if(Ext.isString(this.paramOrder)){
this.paramOrder = this.paramOrder.split(/[\s,|]/);
}
};
Ext.extend(Ext.tree.TreeLoader, Ext.util.Observable, {
/**
* @cfg {String} dataUrl The URL from which to request a Json string which
* specifies an array of node definition objects representing the child nodes
* to be loaded.
*/
/**
* @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).
*/
/**
* @cfg {String} url Equivalent to {@link #dataUrl}.
*/
/**
* @cfg {Boolean} preloadChildren If set to true, the loader recursively loads "children" attributes when doing the first load on nodes.
*/
/**
* @cfg {Object} baseParams (optional) An object containing properties which
* specify HTTP parameters to be passed to each request for child nodes.
*/
/**
* @cfg {Object} baseAttrs (optional) An object containing attributes to be added to all nodes
* created by this loader. If the attributes sent by the server have an attribute in this object,
* they take priority.
*/
/**
* @cfg {Object} uiProviders (optional) An object containing properties which
* specify custom {@link Ext.tree.TreeNodeUI} implementations. If the optional
* uiProvider attribute of a returned child node is a string rather
* than a reference to a TreeNodeUI implementation, then that string value
* is used as a property name in the uiProviders object.
*/
uiProviders : {},
/**
* @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing
* child nodes before loading.
*/
clearOnLoad : true,
/**
* @cfg {Array/String} paramOrder Defaults to undefined. Only used when using directFn.
* Specifies the params in the order in which they must be passed to the server-side Direct method
* as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,
* comma, or pipe. For example,
* any of the following would be acceptable:
nodeParameter: 'node',
paramOrder: ['param1','param2','param3']
paramOrder: 'node param1 param2 param3'
paramOrder: 'param1,node,param2,param3'
paramOrder: 'param1|param2|param|node'
*/
paramOrder: undefined,
/**
* @cfg {Boolean} paramsAsHash Only used when using directFn.
* Send parameters as a collection of named arguments (defaults to false). Providing a
* {@link #paramOrder} nullifies this configuration.
*/
paramsAsHash: false,
/**
* @cfg {String} nodeParameter The name of the parameter sent to the server which contains
* the identifier of the node. Defaults to 'node'.
*/
nodeParameter: 'node',
/**
* @cfg {Function} directFn
* Function to call when executing a request.
*/
directFn : undefined,
/**
* Load an {@link Ext.tree.TreeNode} from the URL specified in the constructor.
* This is called automatically when a node is expanded, but may be used to reload
* a node (or append new children if the {@link #clearOnLoad} option is false.)
* @param {Ext.tree.TreeNode} node
* @param {Function} callback Function to call after the node has been loaded. The
* function is passed the TreeNode which was requested to be loaded.
* @param {Object} scope The scope (this
reference) in which the callback is executed.
* defaults to the loaded TreeNode.
*/
load : function(node, callback, scope){
if(this.clearOnLoad){
while(node.firstChild){
node.removeChild(node.firstChild);
}
}
if(this.doPreload(node)){ // preloaded json children
this.runCallback(callback, scope || node, [node]);
}else if(this.directFn || this.dataUrl || this.url){
this.requestData(node, callback, scope || node);
}
},
doPreload : function(node){
if(node.attributes.children){
if(node.childNodes.length < 1){ // preloaded?
var cs = node.attributes.children;
node.beginUpdate();
for(var i = 0, len = cs.length; i < len; i++){
var cn = node.appendChild(this.createNode(cs[i]));
if(this.preloadChildren){
this.doPreload(cn);
}
}
node.endUpdate();
}
return true;
}
return false;
},
getParams: function(node){
var bp = Ext.apply({}, this.baseParams),
np = this.nodeParameter,
po = this.paramOrder;
np && (bp[ np ] = node.id);
if(this.directFn){
var buf = [node.id];
if(po){
// reset 'buf' if the nodeParameter was included in paramOrder
if(np && po.indexOf(np) > -1){
buf = [];
}
for(var i = 0, len = po.length; i < len; i++){
buf.push(bp[ po[i] ]);
}
}else if(this.paramsAsHash){
buf = [bp];
}
return buf;
}else{
return bp;
}
},
requestData : function(node, callback, scope){
if(this.fireEvent("beforeload", this, node, callback) !== false){
if(this.directFn){
var args = this.getParams(node);
args.push(this.processDirectResponse.createDelegate(this, [{callback: callback, node: node, scope: scope}], true));
this.directFn.apply(window, args);
}else{
this.transId = Ext.Ajax.request({
method:this.requestMethod,
url: this.dataUrl||this.url,
success: this.handleResponse,
failure: this.handleFailure,
scope: this,
argument: {callback: callback, node: node, scope: scope},
params: this.getParams(node)
});
}
}else{
// if the load is cancelled, make sure we notify
// the node that we are done
this.runCallback(callback, scope || node, []);
}
},
processDirectResponse: function(result, response, args){
if(response.status){
this.handleResponse({
responseData: Ext.isArray(result) ? result : null,
responseText: result,
argument: args
});
}else{
this.handleFailure({
argument: args
});
}
},
// private
runCallback: function(cb, scope, args){
if(Ext.isFunction(cb)){
cb.apply(scope, args);
}
},
isLoading : function(){
return !!this.transId;
},
abort : function(){
if(this.isLoading()){
Ext.Ajax.abort(this.transId);
}
},
/**
* Override this function for custom TreeNode node implementation, or to * modify the attributes at creation time.
* Example:
new Ext.tree.TreePanel({
...
loader: new Ext.tree.TreeLoader({
url: 'dataUrl',
createNode: function(attr) {
// Allow consolidation consignments to have
// consignments dropped into them.
if (attr.isConsolidation) {
attr.iconCls = 'x-consol',
attr.allowDrop = true;
}
return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
}
}),
...
});
* @param attr {Object} The attributes from which to create the new node.
*/
createNode : function(attr){
// apply baseAttrs, nice idea Corey!
if(this.baseAttrs){
Ext.applyIf(attr, this.baseAttrs);
}
if(this.applyLoader !== false && !attr.loader){
attr.loader = this;
}
if(Ext.isString(attr.uiProvider)){
attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
}
if(attr.nodeType){
return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);
}else{
return attr.leaf ?
new Ext.tree.TreeNode(attr) :
new Ext.tree.AsyncTreeNode(attr);
}
},
processResponse : function(response, node, callback, scope){
var json = response.responseText;
try {
var o = response.responseData || Ext.decode(json);
node.beginUpdate();
for(var i = 0, len = o.length; i < len; i++){
var n = this.createNode(o[i]);
if(n){
node.appendChild(n);
}
}
node.endUpdate();
this.runCallback(callback, scope || node, [node]);
}catch(e){
this.handleFailure(response);
}
},
handleResponse : function(response){
this.transId = false;
var a = response.argument;
this.processResponse(response, a.node, a.callback, a.scope);
this.fireEvent("load", this, a.node, response);
},
handleFailure : function(response){
this.transId = false;
var a = response.argument;
this.fireEvent("loadexception", this, a.node, response);
this.runCallback(a.callback, a.scope || a.node, [a.node]);
},
destroy : function(){
this.abort();
this.purgeListeners();
}
});/**
* @class Ext.tree.TreeFilter
* Note this class is experimental and doesn't update the indent (lines) or expand collapse icons of the nodes
* @param {TreePanel} tree
* @param {Object} config (optional)
*/
Ext.tree.TreeFilter = function(tree, config){
this.tree = tree;
this.filtered = {};
Ext.apply(this, config);
};
Ext.tree.TreeFilter.prototype = {
clearBlank:false,
reverse:false,
autoClear:false,
remove:false,
/**
* Filter the data by a specific attribute.
* @param {String/RegExp} value Either string that the attribute value
* should start with or a RegExp to test against the attribute
* @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
* @param {TreeNode} startNode (optional) The node to start the filter at.
*/
filter : function(value, attr, startNode){
attr = attr || "text";
var f;
if(typeof value == "string"){
var vlen = value.length;
// auto clear empty filter
if(vlen == 0 && this.clearBlank){
this.clear();
return;
}
value = value.toLowerCase();
f = function(n){
return n.attributes[attr].substr(0, vlen).toLowerCase() == value;
};
}else if(value.exec){ // regex?
f = function(n){
return value.test(n.attributes[attr]);
};
}else{
throw 'Illegal filter type, must be string or regex';
}
this.filterBy(f, null, startNode);
},
/**
* Filter by a function. The passed function will be called with each
* node in the tree (or from the startNode). If the function returns true, the node is kept
* otherwise it is filtered. If a node is filtered, its children are also filtered.
* @param {Function} fn The filter function
* @param {Object} scope (optional) The scope (this
reference) in which the function is executed. Defaults to the current Node.
*/
filterBy : function(fn, scope, startNode){
startNode = startNode || this.tree.root;
if(this.autoClear){
this.clear();
}
var af = this.filtered, rv = this.reverse;
var f = function(n){
if(n == startNode){
return true;
}
if(af[n.id]){
return false;
}
var m = fn.call(scope || n, n);
if(!m || rv){
af[n.id] = n;
n.ui.hide();
return false;
}
return true;
};
startNode.cascade(f);
if(this.remove){
for(var id in af){
if(typeof id != "function"){
var n = af[id];
if(n && n.parentNode){
n.parentNode.removeChild(n);
}
}
}
}
},
/**
* Clears the current filter. Note: with the "remove" option
* set a filter cannot be cleared.
*/
clear : function(){
var t = this.tree;
var af = this.filtered;
for(var id in af){
if(typeof id != "function"){
var n = af[id];
if(n){
n.ui.show();
}
}
}
this.filtered = {};
}
};
/**
* @class Ext.tree.TreeSorter
* Provides sorting of nodes in a {@link Ext.tree.TreePanel}. The TreeSorter automatically monitors events on the
* associated TreePanel that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange).
* Example usage:
new Ext.tree.TreeSorter(myTree, {
folderSort: true,
dir: "desc",
sortType: function(node) {
// sort by a custom, typed attribute:
return parseInt(node.id, 10);
}
});
* @constructor
* @param {TreePanel} tree
* @param {Object} config
*/
Ext.tree.TreeSorter = function(tree, config){
/**
* @cfg {Boolean} folderSort True to sort leaf nodes under non-leaf nodes (defaults to false)
*/
/**
* @cfg {String} property The named attribute on the node to sort by (defaults to "text"). Note that this
* property is only used if no {@link #sortType} function is specified, otherwise it is ignored.
*/
/**
* @cfg {String} dir The direction to sort ("asc" or "desc," case-insensitive, defaults to "asc")
*/
/**
* @cfg {String} leafAttr The attribute used to determine leaf nodes when {@link #folderSort} = true (defaults to "leaf")
*/
/**
* @cfg {Boolean} caseSensitive true for case-sensitive sort (defaults to false)
*/
/**
* @cfg {Function} sortType A custom "casting" function used to convert node values before sorting. The function
* will be called with a single parameter (the {@link Ext.tree.TreeNode} being evaluated) and is expected to return
* the node's sort value cast to the specific data type required for sorting. This could be used, for example, when
* a node's text (or other attribute) should be sorted as a date or numeric value. See the class description for
* example usage. Note that if a sortType is specified, any {@link #property} config will be ignored.
*/
Ext.apply(this, config);
tree.on("beforechildrenrendered", this.doSort, this);
tree.on("append", this.updateSort, this);
tree.on("insert", this.updateSort, this);
tree.on("textchange", this.updateSortParent, this);
var dsc = this.dir && this.dir.toLowerCase() == "desc";
var p = this.property || "text";
var sortType = this.sortType;
var fs = this.folderSort;
var cs = this.caseSensitive === true;
var leafAttr = this.leafAttr || 'leaf';
this.sortFn = function(n1, n2){
if(fs){
if(n1.attributes[leafAttr] && !n2.attributes[leafAttr]){
return 1;
}
if(!n1.attributes[leafAttr] && n2.attributes[leafAttr]){
return -1;
}
}
var v1 = sortType ? sortType(n1) : (cs ? n1.attributes[p] : n1.attributes[p].toUpperCase());
var v2 = sortType ? sortType(n2) : (cs ? n2.attributes[p] : n2.attributes[p].toUpperCase());
if(v1 < v2){
return dsc ? +1 : -1;
}else if(v1 > v2){
return dsc ? -1 : +1;
}else{
return 0;
}
};
};
Ext.tree.TreeSorter.prototype = {
doSort : function(node){
node.sort(this.sortFn);
},
compareNodes : function(n1, n2){
return (n1.text.toUpperCase() > n2.text.toUpperCase() ? 1 : -1);
},
updateSort : function(tree, node){
if(node.childrenRendered){
this.doSort.defer(1, this, [node]);
}
},
updateSortParent : function(node){
var p = node.parentNode;
if(p && p.childrenRendered){
this.doSort.defer(1, this, [p]);
}
}
};/**
* @class Ext.tree.TreeDropZone
* @extends Ext.dd.DropZone
* @constructor
* @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dropping
* @param {Object} config
*/
if(Ext.dd.DropZone){
Ext.tree.TreeDropZone = function(tree, config){
/**
* @cfg {Boolean} allowParentInsert
* Allow inserting a dragged node between an expanded parent node and its first child that will become a
* sibling of the parent when dropped (defaults to false)
*/
this.allowParentInsert = config.allowParentInsert || false;
/**
* @cfg {String} allowContainerDrop
* True if drops on the tree container (outside of a specific tree node) are allowed (defaults to false)
*/
this.allowContainerDrop = config.allowContainerDrop || false;
/**
* @cfg {String} appendOnly
* True if the tree should only allow append drops (use for trees which are sorted, defaults to false)
*/
this.appendOnly = config.appendOnly || false;
Ext.tree.TreeDropZone.superclass.constructor.call(this, tree.getTreeEl(), config);
/**
* The TreePanel for this drop zone
* @type Ext.tree.TreePanel
* @property
*/
this.tree = tree;
/**
* Arbitrary data that can be associated with this tree and will be included in the event object that gets
* passed to any nodedragover event handler (defaults to {})
* @type Ext.tree.TreePanel
* @property
*/
this.dragOverData = {};
// private
this.lastInsertClass = "x-tree-no-status";
};
Ext.extend(Ext.tree.TreeDropZone, Ext.dd.DropZone, {
/**
* @cfg {String} ddGroup
* A named drag drop group to which this object belongs. If a group is specified, then this object will only
* interact with other drag drop objects in the same group (defaults to 'TreeDD').
*/
ddGroup : "TreeDD",
/**
* @cfg {String} expandDelay
* The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
* over the target (defaults to 1000)
*/
expandDelay : 1000,
// private
expandNode : function(node){
if(node.hasChildNodes() && !node.isExpanded()){
node.expand(false, null, this.triggerCacheRefresh.createDelegate(this));
}
},
// private
queueExpand : function(node){
this.expandProcId = this.expandNode.defer(this.expandDelay, this, [node]);
},
// private
cancelExpand : function(){
if(this.expandProcId){
clearTimeout(this.expandProcId);
this.expandProcId = false;
}
},
// private
isValidDropPoint : function(n, pt, dd, e, data){
if(!n || !data){ return false; }
var targetNode = n.node;
var dropNode = data.node;
// default drop rules
if(!(targetNode && targetNode.isTarget && pt)){
return false;
}
if(pt == "append" && targetNode.allowChildren === false){
return false;
}
if((pt == "above" || pt == "below") && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){
return false;
}
if(dropNode && (targetNode == dropNode || dropNode.contains(targetNode))){
return false;
}
// reuse the object
var overEvent = this.dragOverData;
overEvent.tree = this.tree;
overEvent.target = targetNode;
overEvent.data = data;
overEvent.point = pt;
overEvent.source = dd;
overEvent.rawEvent = e;
overEvent.dropNode = dropNode;
overEvent.cancel = false;
var result = this.tree.fireEvent("nodedragover", overEvent);
return overEvent.cancel === false && result !== false;
},
// private
getDropPoint : function(e, n, dd){
var tn = n.node;
if(tn.isRoot){
return tn.allowChildren !== false ? "append" : false; // always append for root
}
var dragEl = n.ddel;
var t = Ext.lib.Dom.getY(dragEl), b = t + dragEl.offsetHeight;
var y = Ext.lib.Event.getPageY(e);
var noAppend = tn.allowChildren === false || tn.isLeaf();
if(this.appendOnly || tn.parentNode.allowChildren === false){
return noAppend ? false : "append";
}
var noBelow = false;
if(!this.allowParentInsert){
noBelow = tn.hasChildNodes() && tn.isExpanded();
}
var q = (b - t) / (noAppend ? 2 : 3);
if(y >= t && y < (t + q)){
return "above";
}else if(!noBelow && (noAppend || y >= b-q && y <= b)){
return "below";
}else{
return "append";
}
},
// private
onNodeEnter : function(n, dd, e, data){
this.cancelExpand();
},
onContainerOver : function(dd, e, data) {
if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
return this.dropAllowed;
}
return this.dropNotAllowed;
},
// private
onNodeOver : function(n, dd, e, data){
var pt = this.getDropPoint(e, n, dd);
var node = n.node;
// auto node expand check
if(!this.expandProcId && pt == "append" && node.hasChildNodes() && !n.node.isExpanded()){
this.queueExpand(node);
}else if(pt != "append"){
this.cancelExpand();
}
// set the insert point style on the target node
var returnCls = this.dropNotAllowed;
if(this.isValidDropPoint(n, pt, dd, e, data)){
if(pt){
var el = n.ddel;
var cls;
if(pt == "above"){
returnCls = n.node.isFirst() ? "x-tree-drop-ok-above" : "x-tree-drop-ok-between";
cls = "x-tree-drag-insert-above";
}else if(pt == "below"){
returnCls = n.node.isLast() ? "x-tree-drop-ok-below" : "x-tree-drop-ok-between";
cls = "x-tree-drag-insert-below";
}else{
returnCls = "x-tree-drop-ok-append";
cls = "x-tree-drag-append";
}
if(this.lastInsertClass != cls){
Ext.fly(el).replaceClass(this.lastInsertClass, cls);
this.lastInsertClass = cls;
}
}
}
return returnCls;
},
// private
onNodeOut : function(n, dd, e, data){
this.cancelExpand();
this.removeDropIndicators(n);
},
// private
onNodeDrop : function(n, dd, e, data){
var point = this.getDropPoint(e, n, dd);
var targetNode = n.node;
targetNode.ui.startDrop();
if(!this.isValidDropPoint(n, point, dd, e, data)){
targetNode.ui.endDrop();
return false;
}
// first try to find the drop node
var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, point, e) : null);
return this.processDrop(targetNode, data, point, dd, e, dropNode);
},
onContainerDrop : function(dd, e, data){
if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
var targetNode = this.tree.getRootNode();
targetNode.ui.startDrop();
var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, 'append', e) : null);
return this.processDrop(targetNode, data, 'append', dd, e, dropNode);
}
return false;
},
// private
processDrop: function(target, data, point, dd, e, dropNode){
var dropEvent = {
tree : this.tree,
target: target,
data: data,
point: point,
source: dd,
rawEvent: e,
dropNode: dropNode,
cancel: !dropNode,
dropStatus: false
};
var retval = this.tree.fireEvent("beforenodedrop", dropEvent);
if(retval === false || dropEvent.cancel === true || !dropEvent.dropNode){
target.ui.endDrop();
return dropEvent.dropStatus;
}
target = dropEvent.target;
if(point == 'append' && !target.isExpanded()){
target.expand(false, null, function(){
this.completeDrop(dropEvent);
}.createDelegate(this));
}else{
this.completeDrop(dropEvent);
}
return true;
},
// private
completeDrop : function(de){
var ns = de.dropNode, p = de.point, t = de.target;
if(!Ext.isArray(ns)){
ns = [ns];
}
var n;
for(var i = 0, len = ns.length; i < len; i++){
n = ns[i];
if(p == "above"){
t.parentNode.insertBefore(n, t);
}else if(p == "below"){
t.parentNode.insertBefore(n, t.nextSibling);
}else{
t.appendChild(n);
}
}
n.ui.focus();
if(Ext.enableFx && this.tree.hlDrop){
n.ui.highlight();
}
t.ui.endDrop();
this.tree.fireEvent("nodedrop", de);
},
// private
afterNodeMoved : function(dd, data, e, targetNode, dropNode){
if(Ext.enableFx && this.tree.hlDrop){
dropNode.ui.focus();
dropNode.ui.highlight();
}
this.tree.fireEvent("nodedrop", this.tree, targetNode, data, dd, e);
},
// private
getTree : function(){
return this.tree;
},
// private
removeDropIndicators : function(n){
if(n && n.ddel){
var el = n.ddel;
Ext.fly(el).removeClass([
"x-tree-drag-insert-above",
"x-tree-drag-insert-below",
"x-tree-drag-append"]);
this.lastInsertClass = "_noclass";
}
},
// private
beforeDragDrop : function(target, e, id){
this.cancelExpand();
return true;
},
// private
afterRepair : function(data){
if(data && Ext.enableFx){
data.node.ui.highlight();
}
this.hideProxy();
}
});
}/**
* @class Ext.tree.TreeDragZone
* @extends Ext.dd.DragZone
* @constructor
* @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dragging
* @param {Object} config
*/
if(Ext.dd.DragZone){
Ext.tree.TreeDragZone = function(tree, config){
Ext.tree.TreeDragZone.superclass.constructor.call(this, tree.innerCt, config);
/**
* The TreePanel for this drag zone
* @type Ext.tree.TreePanel
* @property
*/
this.tree = tree;
};
Ext.extend(Ext.tree.TreeDragZone, Ext.dd.DragZone, {
/**
* @cfg {String} ddGroup
* A named drag drop group to which this object belongs. If a group is specified, then this object will only
* interact with other drag drop objects in the same group (defaults to 'TreeDD').
*/
ddGroup : "TreeDD",
// private
onBeforeDrag : function(data, e){
var n = data.node;
return n && n.draggable && !n.disabled;
},
// private
onInitDrag : function(e){
var data = this.dragData;
this.tree.getSelectionModel().select(data.node);
this.tree.eventModel.disable();
this.proxy.update("");
data.node.ui.appendDDGhost(this.proxy.ghost.dom);
this.tree.fireEvent("startdrag", this.tree, data.node, e);
},
// private
getRepairXY : function(e, data){
return data.node.ui.getDDRepairXY();
},
// private
onEndDrag : function(data, e){
this.tree.eventModel.enable.defer(100, this.tree.eventModel);
this.tree.fireEvent("enddrag", this.tree, data.node, e);
},
// private
onValidDrop : function(dd, e, id){
this.tree.fireEvent("dragdrop", this.tree, this.dragData.node, dd, e);
this.hideProxy();
},
// private
beforeInvalidDrop : function(e, id){
// this scrolls the original position back into view
var sm = this.tree.getSelectionModel();
sm.clearSelections();
sm.select(this.dragData.node);
},
// private
afterRepair : function(){
if (Ext.enableFx && this.tree.hlDrop) {
Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || "c3daf9");
}
this.dragging = false;
}
});
}/**
* @class Ext.tree.TreeEditor
* @extends Ext.Editor
* Provides editor functionality for inline tree node editing. Any valid {@link Ext.form.Field} subclass can be used
* as the editor field.
* @constructor
* @param {TreePanel} tree
* @param {Object} fieldConfig (optional) Either a prebuilt {@link Ext.form.Field} instance or a Field config object
* that will be applied to the default field instance (defaults to a {@link Ext.form.TextField}).
* @param {Object} config (optional) A TreeEditor config object
*/
Ext.tree.TreeEditor = function(tree, fc, config){
fc = fc || {};
var field = fc.events ? fc : new Ext.form.TextField(fc);
Ext.tree.TreeEditor.superclass.constructor.call(this, field, config);
this.tree = tree;
if(!tree.rendered){
tree.on('render', this.initEditor, this);
}else{
this.initEditor(tree);
}
};
Ext.extend(Ext.tree.TreeEditor, Ext.Editor, {
/**
* @cfg {String} alignment
* The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "l-l").
*/
alignment: "l-l",
// inherit
autoSize: false,
/**
* @cfg {Boolean} hideEl
* True to hide the bound element while the editor is displayed (defaults to false)
*/
hideEl : false,
/**
* @cfg {String} cls
* CSS class to apply to the editor (defaults to "x-small-editor x-tree-editor")
*/
cls: "x-small-editor x-tree-editor",
/**
* @cfg {Boolean} shim
* True to shim the editor if selects/iframes could be displayed beneath it (defaults to false)
*/
shim:false,
// inherit
shadow:"frame",
/**
* @cfg {Number} maxWidth
* The maximum width in pixels of the editor field (defaults to 250). Note that if the maxWidth would exceed
* the containing tree element's size, it will be automatically limited for you to the container width, taking
* scroll and client offsets into account prior to each edit.
*/
maxWidth: 250,
/**
* @cfg {Number} editDelay The number of milliseconds between clicks to register a double-click that will trigger
* editing on the current node (defaults to 350). If two clicks occur on the same node within this time span,
* the editor for the node will display, otherwise it will be processed as a regular click.
*/
editDelay : 350,
initEditor : function(tree){
tree.on({
scope : this,
beforeclick: this.beforeNodeClick,
dblclick : this.onNodeDblClick
});
this.on({
scope : this,
complete : this.updateNode,
beforestartedit: this.fitToTree,
specialkey : this.onSpecialKey
});
this.on('startedit', this.bindScroll, this, {delay:10});
},
// private
fitToTree : function(ed, el){
var td = this.tree.getTreeEl().dom, nd = el.dom;
if(td.scrollLeft > nd.offsetLeft){ // ensure the node left point is visible
td.scrollLeft = nd.offsetLeft;
}
var w = Math.min(
this.maxWidth,
(td.clientWidth > 20 ? td.clientWidth : td.offsetWidth) - Math.max(0, nd.offsetLeft-td.scrollLeft) - /*cushion*/5);
this.setSize(w, '');
},
/**
* Edit the text of the passed {@link Ext.tree.TreeNode TreeNode}.
* @param node {Ext.tree.TreeNode} The TreeNode to edit. The TreeNode must be {@link Ext.tree.TreeNode#editable editable}.
*/
triggerEdit : function(node, defer){
this.completeEdit();
if(node.attributes.editable !== false){
/**
* The {@link Ext.tree.TreeNode TreeNode} this editor is bound to. Read-only.
* @type Ext.tree.TreeNode
* @property editNode
*/
this.editNode = node;
if(this.tree.autoScroll){
Ext.fly(node.ui.getEl()).scrollIntoView(this.tree.body);
}
var value = node.text || '';
if (!Ext.isGecko && Ext.isEmpty(node.text)){
node.setText(' ');
}
this.autoEditTimer = this.startEdit.defer(this.editDelay, this, [node.ui.textNode, value]);
return false;
}
},
// private
bindScroll : function(){
this.tree.getTreeEl().on('scroll', this.cancelEdit, this);
},
// private
beforeNodeClick : function(node, e){
clearTimeout(this.autoEditTimer);
if(this.tree.getSelectionModel().isSelected(node)){
e.stopEvent();
return this.triggerEdit(node);
}
},
onNodeDblClick : function(node, e){
clearTimeout(this.autoEditTimer);
},
// private
updateNode : function(ed, value){
this.tree.getTreeEl().un('scroll', this.cancelEdit, this);
this.editNode.setText(value);
},
// private
onHide : function(){
Ext.tree.TreeEditor.superclass.onHide.call(this);
if(this.editNode){
this.editNode.ui.focus.defer(50, this.editNode.ui);
}
},
// private
onSpecialKey : function(field, e){
var k = e.getKey();
if(k == e.ESC){
e.stopEvent();
this.cancelEdit();
}else if(k == e.ENTER && !e.hasModifier()){
e.stopEvent();
this.completeEdit();
}
},
onDestroy : function(){
clearTimeout(this.autoEditTimer);
Ext.tree.TreeEditor.superclass.onDestroy.call(this);
var tree = this.tree;
tree.un('beforeclick', this.beforeNodeClick, this);
tree.un('dblclick', this.onNodeDblClick, this);
}
});