X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/ee06f37b0f6f6d94cd05a6ffae556660f7c4a2bc..c930e9176a5a85509c5b0230e2bff5c22a591432:/pkgs/pkg-tree-debug.js?ds=inline diff --git a/pkgs/pkg-tree-debug.js b/pkgs/pkg-tree-debug.js new file mode 100644 index 00000000..848afea5 --- /dev/null +++ b/pkgs/pkg-tree-debug.js @@ -0,0 +1,4522 @@ +/*! + * 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();
+ }
+ }
+});
\ No newline at end of file