/* This file is part of Ext JS 3.4 Copyright (c) 2011-2013 Sencha Inc Contact: http://www.sencha.com/contact GNU General Public License Usage This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact. Build date: 2013-04-03 15:07:25 */ /** * @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 || cs[i], 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 = Ext.extend(Ext.data.Node, {
constructor : 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);
},
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){
var rendered = node.ui.rendered;
// if it's been rendered remove dom node
if(rendered){
node.ui.remove();
}
if(rendered && 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);
},
/**
* Sets the icon class for this node.
* @param {String} cls
*/
setIconCls : function(cls){
var old = this.attributes.iconCls;
this.attributes.iconCls = cls;
if(this.rendered){
this.ui.onIconClsChange(this, cls, old);
}
},
/**
* Sets the tooltip for this node.
* @param {String} tip The text for the tip
* @param {String} title (Optional) The title for the tip
*/
setTooltip : function(tip, title){
this.attributes.qtip = tip;
this.attributes.qtipTitle = title;
if(this.rendered){
this.ui.onTipChange(this, tip, title);
}
},
/**
* Sets the icon for this node.
* @param {String} icon
*/
setIcon : function(icon){
this.attributes.icon = icon;
if(this.rendered){
this.ui.onIconChange(this, icon);
}
},
/**
* Sets the href for the node.
* @param {String} href The href to set
* @param {String} (Optional) target The target of the href
*/
setHref : function(href, target){
this.attributes.href = href;
this.attributes.hrefTarget = target;
if(this.rendered){
this.ui.onHrefChange(this, href, target);
}
},
/**
* Sets the class on this node.
* @param {String} cls
*/
setCls : function(cls){
var old = this.attributes.cls;
this.attributes.cls = cls;
if(this.rendered){
this.ui.onClsChange(this, cls, old);
}
},
/**
* 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, 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, anim) {
var cs = this.childNodes,
i,
len = cs.length;
for (i = 0; i < len; i++) {
cs[i].expand(deep, anim);
}
},
/**
* 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 = Ext.extend(Object, {
constructor : function(node){
Ext.apply(this, {
node: node,
rendered: false,
animating: false,
wasLeaf: true,
ecc: 'x-tree-ec-icon x-tree-elbow',
emptyIcon: Ext.BLANK_IMAGE_URL
});
},
// 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
onIconClsChange : function(node, cls, oldCls){
if(this.rendered){
Ext.fly(this.iconNode).replaceClass(oldCls, cls);
}
},
// private
onIconChange : function(node, icon){
if(this.rendered){
//'',
var empty = Ext.isEmpty(icon);
this.iconNode.src = empty ? this.emptyIcon : icon;
Ext.fly(this.iconNode)[empty ? 'removeClass' : 'addClass']('x-tree-node-inline-icon');
}
},
// private
onTipChange : function(node, tip, title){
if(this.rendered){
var hasTitle = Ext.isDefined(title);
if(this.textNode.setAttributeNS){
this.textNode.setAttributeNS("ext", "qtip", tip);
if(hasTitle){
this.textNode.setAttributeNS("ext", "qtitle", title);
}
}else{
this.textNode.setAttribute("ext:qtip", tip);
if(hasTitle){
this.textNode.setAttribute("ext:qtitle", title);
}
}
}
},
// private
onHrefChange : function(node, href, target){
if(this.rendered){
this.anchor.href = this.getHref(href);
if(Ext.isDefined(target)){
this.anchor.target = target;
}
}
},
// private
onClsChange : function(node, cls, oldCls){
if(this.rendered){
Ext.fly(this.elNode).replaceClass(oldCls, cls);
}
},
// private
onDisableChange : function(node, state){
this.disabled = state;
if (this.checkbox) {
this.checkbox.disabled = state;
}
this[state ? 'addClass' : '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){
this.onTipChange(n, a.qtip, 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 = this.getHref(a.href),
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(value, node) {
// sort by a custom, typed attribute:
return parseInt(node.id, 10);
}
});
* @constructor
* @param {TreePanel} tree
* @param {Object} config
*/
Ext.tree.TreeSorter = Ext.extend(Object, {
constructor: 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 two parameters, the {@link #property} value for the node and the {@link Ext.tree.TreeNode} being evaluated.
* It 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.
*/
Ext.apply(this, config);
tree.on({
scope: this,
beforechildrenrendered: this.doSort,
append: this.updateSort,
insert: this.updateSort,
textchange: this.updateSortParent
});
var desc = this.dir && this.dir.toLowerCase() == 'desc',
prop = this.property || 'text',
sortType = this.sortType,
folderSort = this.folderSort,
caseSensitive = this.caseSensitive === true,
leafAttr = this.leafAttr || 'leaf';
if(Ext.isString(sortType)){
sortType = Ext.data.SortTypes[sortType];
}
this.sortFn = function(n1, n2){
var attr1 = n1.attributes,
attr2 = n2.attributes;
if(folderSort){
if(attr1[leafAttr] && !attr2[leafAttr]){
return 1;
}
if(!attr1[leafAttr] && attr2[leafAttr]){
return -1;
}
}
var prop1 = attr1[prop],
prop2 = attr2[prop],
v1 = sortType ? sortType(prop1, n1) : (caseSensitive ? prop1 : prop1.toUpperCase()),
v2 = sortType ? sortType(prop2, n2) : (caseSensitive ? prop2 : prop2.toUpperCase());
if(v1 < v2){
return desc ? 1 : -1;
}else if(v1 > v2){
return desc ? -1 : 1;
}
return 0;
};
},
doSort : function(node){
node.sort(this.sortFn);
},
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);
}
});