/*! * Ext JS Library 3.0.0 * Copyright(c) 2006-2009 Ext JS, LLC * 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: "/",
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(typeof l == 'object' && !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 checkchange
* Fires when a node with a checkbox's checked property changes
* @param {Node} this This node
* @param {Boolean} checked
*/
"checkchange",
/**
* @event dblclick
* Fires when a node is double clicked
* @param {Node} node The node
* @param {Ext.EventObject} e The event object
*/
"dblclick",
/**
* @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 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:
* 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.addClass("x-tree-node-disabled"); if (this.checkbox) { this.checkbox.disabled = 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.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} (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; }, // private 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 = typeof a.checked == 'boolean'; var href = a.href ? a.href : Ext.isGecko ? "" : "#"; var 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(typeof this.paramOrder == 'string'){
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.
* A list of params to be executed
* server side. Specify the params in the order in which they must be executed on the server-side
* 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:
paramOrder: ['param1','param2','param3']
paramOrder: 'param1 param2 param3'
paramOrder: 'param1,param2,param3'
paramOrder: 'param1|param2|param'
*/
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 {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
* @param (Object) scope
*/
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, []);
}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 buf = [], bp = this.baseParams;
if(this.directFn){
buf.push(node.id);
if(bp){
if(this.paramOrder){
for(var i = 0, len = this.paramOrder.length; i < len; i++){
buf.push(bp[this.paramOrder[i]]);
}
}else if(this.paramsAsHash){
buf.push(bp);
}
}
return buf;
}else{
for(var key in bp){
if(!Ext.isFunction(bp[key])){
buf.push(encodeURIComponent(key), "=", encodeURIComponent(bp[key]), "&");
}
}
buf.push("node=", encodeURIComponent(node.id));
return buf.join("");
}
},
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({
...
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.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 = this;
}
if(typeof attr.uiProvider == 'string'){
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]);
}
});/**
* @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 of the function (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('beforeclick', this.beforeNodeClick, this);
tree.on('dblclick', this.onNodeDblClick, this);
this.on('complete', this.updateNode, this);
this.on('beforestartedit', this.fitToTree, this);
this.on('startedit', this.bindScroll, this, {delay:10});
this.on('specialkey', this.onSpecialKey, this);
},
// 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();
}
}
});