/*! * Ext JS Library 3.2.0 * Copyright(c) 2006-2010 Ext JS, Inc. * licensing@extjs.com * http://www.extjs.com/license */ /** * @class Ext.tree.TreePanel * @extends Ext.Panel *

The TreePanel provides tree-structured UI representation of tree-structured data.

*

{@link Ext.tree.TreeNode TreeNode}s added to the TreePanel may each contain metadata * used by your application in their {@link Ext.tree.TreeNode#attributes attributes} property.

*

A TreePanel must have a {@link #root} node before it is rendered. This may either be * specified using the {@link #root} config option, or using the {@link #setRootNode} method. *

An example of tree rendered to an existing div:


var tree = new Ext.tree.TreePanel({
    renderTo: 'tree-div',
    useArrows: true,
    autoScroll: true,
    animate: true,
    enableDD: true,
    containerScroll: true,
    border: false,
    // auto create TreeLoader
    dataUrl: 'get-nodes.php',

    root: {
        nodeType: 'async',
        text: 'Ext JS',
        draggable: false,
        id: 'source'
    }
});

tree.getRootNode().expand();
 * 
*

The example above would work with a data packet similar to this:


[{
    "text": "adapter",
    "id": "source\/adapter",
    "cls": "folder"
}, {
    "text": "dd",
    "id": "source\/dd",
    "cls": "folder"
}, {
    "text": "debug.js",
    "id": "source\/debug.js",
    "leaf": true,
    "cls": "file"
}]
 * 
*

An example of tree within a Viewport:


new Ext.Viewport({
    layout: 'border',
    items: [{
        region: 'west',
        collapsible: true,
        title: 'Navigation',
        xtype: 'treepanel',
        width: 200,
        autoScroll: true,
        split: true,
        loader: new Ext.tree.TreeLoader(),
        root: new Ext.tree.AsyncTreeNode({
            expanded: true,
            children: [{
                text: 'Menu Option 1',
                leaf: true
            }, {
                text: 'Menu Option 2',
                leaf: true
            }, {
                text: 'Menu Option 3',
                leaf: true
            }]
        }),
        rootVisible: false,
        listeners: {
            click: function(n) {
                Ext.Msg.alert('Navigation Tree Click', 'You clicked: "' + n.attributes.text + '"');
            }
        }
    }, {
        region: 'center',
        xtype: 'tabpanel',
        // remaining code not shown ...
    }]
});
* * @cfg {Ext.tree.TreeNode} root The root node for the tree. * @cfg {Boolean} rootVisible false to hide the root node (defaults to true) * @cfg {Boolean} lines false to disable tree lines (defaults to true) * @cfg {Boolean} enableDD true to enable drag and drop * @cfg {Boolean} enableDrag true to enable just drag * @cfg {Boolean} enableDrop true to enable just drop * @cfg {Object} dragConfig Custom config to pass to the {@link Ext.tree.TreeDragZone} instance * @cfg {Object} dropConfig Custom config to pass to the {@link Ext.tree.TreeDropZone} instance * @cfg {String} ddGroup The DD group this TreePanel belongs to * @cfg {Boolean} ddAppendOnly true if the tree should only allow append drops (use for trees which are sorted) * @cfg {Boolean} ddScroll true to enable body scrolling * @cfg {Boolean} containerScroll true to register this container with ScrollManager * @cfg {Boolean} hlDrop false to disable node highlight on drop (defaults to the value of {@link Ext#enableFx}) * @cfg {String} hlColor The color of the node highlight (defaults to 'C3DAF9') * @cfg {Boolean} animate true to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx}) * @cfg {Boolean} singleExpand true if only 1 node per branch may be expanded * @cfg {Object} selModel A tree selection model to use with this TreePanel (defaults to an {@link Ext.tree.DefaultSelectionModel}) * @cfg {Boolean} trackMouseOver false to disable mouse over highlighting * @cfg {Ext.tree.TreeLoader} loader A {@link Ext.tree.TreeLoader} for use with this TreePanel * @cfg {String} pathSeparator The token used to separate sub-paths in path strings (defaults to '/') * @cfg {Boolean} useArrows true to use Vista-style arrows in the tree (defaults to false) * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}). * * @constructor * @param {Object} config * @xtype treepanel */ Ext.tree.TreePanel = Ext.extend(Ext.Panel, { rootVisible : true, animate : Ext.enableFx, lines : true, enableDD : false, hlDrop : Ext.enableFx, pathSeparator : '/', /** * @cfg {Array} bubbleEvents *

An array of events that, when fired, should be bubbled to any parent container. * See {@link Ext.util.Observable#enableBubble}. * Defaults to []. */ bubbleEvents : [], initComponent : function(){ Ext.tree.TreePanel.superclass.initComponent.call(this); if(!this.eventModel){ this.eventModel = new Ext.tree.TreeEventModel(this); } // initialize the loader var l = this.loader; if(!l){ l = new Ext.tree.TreeLoader({ dataUrl: this.dataUrl, requestMethod: this.requestMethod }); }else if(Ext.isObject(l) && !l.load){ l = new Ext.tree.TreeLoader(l); } this.loader = l; this.nodeHash = {}; /** * The root node of this tree. * @type Ext.tree.TreeNode * @property root */ if(this.root){ var r = this.root; delete this.root; this.setRootNode(r); } this.addEvents( /** * @event append * Fires when a new child node is appended to a node in this tree. * @param {Tree} tree The owner tree * @param {Node} parent The parent node * @param {Node} node The newly appended node * @param {Number} index The index of the newly appended node */ 'append', /** * @event remove * Fires when a child node is removed from a node in this tree. * @param {Tree} tree The owner tree * @param {Node} parent The parent node * @param {Node} node The child node removed */ 'remove', /** * @event movenode * Fires when a node is moved to a new location in the tree * @param {Tree} tree The owner tree * @param {Node} node The node moved * @param {Node} oldParent The old parent of this node * @param {Node} newParent The new parent of this node * @param {Number} index The index it was moved to */ 'movenode', /** * @event insert * Fires when a new child node is inserted in a node in this tree. * @param {Tree} tree The owner tree * @param {Node} parent The parent node * @param {Node} node The child node inserted * @param {Node} refNode The child node the node was inserted before */ 'insert', /** * @event beforeappend * Fires before a new child is appended to a node in this tree, return false to cancel the append. * @param {Tree} tree The owner tree * @param {Node} parent The parent node * @param {Node} node The child node to be appended */ 'beforeappend', /** * @event beforeremove * Fires before a child is removed from a node in this tree, return false to cancel the remove. * @param {Tree} tree The owner tree * @param {Node} parent The parent node * @param {Node} node The child node to be removed */ 'beforeremove', /** * @event beforemovenode * Fires before a node is moved to a new location in the tree. Return false to cancel the move. * @param {Tree} tree The owner tree * @param {Node} node The node being moved * @param {Node} oldParent The parent of the node * @param {Node} newParent The new parent the node is moving to * @param {Number} index The index it is being moved to */ 'beforemovenode', /** * @event beforeinsert * Fires before a new child is inserted in a node in this tree, return false to cancel the insert. * @param {Tree} tree The owner tree * @param {Node} parent The parent node * @param {Node} node The child node to be inserted * @param {Node} refNode The child node the node is being inserted before */ 'beforeinsert', /** * @event beforeload * Fires before a node is loaded, return false to cancel * @param {Node} node The node being loaded */ 'beforeload', /** * @event load * Fires when a node is loaded * @param {Node} node The node that was loaded */ 'load', /** * @event textchange * Fires when the text for a node is changed * @param {Node} node The node * @param {String} text The new text * @param {String} oldText The old text */ 'textchange', /** * @event beforeexpandnode * Fires before a node is expanded, return false to cancel. * @param {Node} node The node * @param {Boolean} deep * @param {Boolean} anim */ 'beforeexpandnode', /** * @event beforecollapsenode * Fires before a node is collapsed, return false to cancel. * @param {Node} node The node * @param {Boolean} deep * @param {Boolean} anim */ 'beforecollapsenode', /** * @event expandnode * Fires when a node is expanded * @param {Node} node The node */ 'expandnode', /** * @event disabledchange * Fires when the disabled status of a node changes * @param {Node} node The node * @param {Boolean} disabled */ 'disabledchange', /** * @event collapsenode * Fires when a node is collapsed * @param {Node} node The node */ 'collapsenode', /** * @event beforeclick * Fires before click processing on a node. Return false to cancel the default action. * @param {Node} node The node * @param {Ext.EventObject} e The event object */ 'beforeclick', /** * @event click * Fires when a node is clicked * @param {Node} node The node * @param {Ext.EventObject} e The event object */ 'click', /** * @event containerclick * Fires when the tree container is clicked * @param {Tree} this * @param {Ext.EventObject} e The event object */ 'containerclick', /** * @event checkchange * Fires when a node with a checkbox's checked property changes * @param {Node} this This node * @param {Boolean} checked */ 'checkchange', /** * @event beforedblclick * Fires before double click processing on a node. Return false to cancel the default action. * @param {Node} node The node * @param {Ext.EventObject} e The event object */ 'beforedblclick', /** * @event dblclick * Fires when a node is double clicked * @param {Node} node The node * @param {Ext.EventObject} e The event object */ 'dblclick', /** * @event containerdblclick * Fires when the tree container is double clicked * @param {Tree} this * @param {Ext.EventObject} e The event object */ 'containerdblclick', /** * @event contextmenu * Fires when a node is right clicked. To display a context menu in response to this * event, first create a Menu object (see {@link Ext.menu.Menu} for details), then add * a handler for this event:


new Ext.tree.TreePanel({
    title: 'My TreePanel',
    root: new Ext.tree.AsyncTreeNode({
        text: 'The Root',
        children: [
            { text: 'Child node 1', leaf: true },
            { text: 'Child node 2', leaf: true }
        ]
    }),
    contextMenu: new Ext.menu.Menu({
        items: [{
            id: 'delete-node',
            text: 'Delete Node'
        }],
        listeners: {
            itemclick: function(item) {
                switch (item.id) {
                    case 'delete-node':
                        var n = item.parentMenu.contextNode;
                        if (n.parentNode) {
                            n.remove();
                        }
                        break;
                }
            }
        }
    }),
    listeners: {
        contextmenu: function(node, e) {
//          Register the context node with the menu so that a Menu Item's handler function can access
//          it via its {@link Ext.menu.BaseItem#parentMenu parentMenu} property.
            node.select();
            var c = node.getOwnerTree().contextMenu;
            c.contextNode = node;
            c.showAt(e.getXY());
        }
    }
});
* @param {Node} node The node * @param {Ext.EventObject} e The event object */ 'contextmenu', /** * @event containercontextmenu * Fires when the tree container is right clicked * @param {Tree} this * @param {Ext.EventObject} e The event object */ 'containercontextmenu', /** * @event beforechildrenrendered * Fires right before the child nodes for a node are rendered * @param {Node} node The node */ 'beforechildrenrendered', /** * @event startdrag * Fires when a node starts being dragged * @param {Ext.tree.TreePanel} this * @param {Ext.tree.TreeNode} node * @param {event} e The raw browser event */ 'startdrag', /** * @event enddrag * Fires when a drag operation is complete * @param {Ext.tree.TreePanel} this * @param {Ext.tree.TreeNode} node * @param {event} e The raw browser event */ 'enddrag', /** * @event dragdrop * Fires when a dragged node is dropped on a valid DD target * @param {Ext.tree.TreePanel} this * @param {Ext.tree.TreeNode} node * @param {DD} dd The dd it was dropped on * @param {event} e The raw browser event */ 'dragdrop', /** * @event beforenodedrop * Fires when a DD object is dropped on a node in this tree for preprocessing. Return false to cancel the drop. The dropEvent * passed to handlers has the following properties:
* * @param {Object} dropEvent */ 'beforenodedrop', /** * @event nodedrop * Fires after a DD object is dropped on a node in this tree. The dropEvent * passed to handlers has the following properties:
* * @param {Object} dropEvent */ 'nodedrop', /** * @event nodedragover * Fires when a tree node is being targeted for a drag drop, return false to signal drop not allowed. The dragOverEvent * passed to handlers has the following properties:
* * @param {Object} dragOverEvent */ 'nodedragover' ); if(this.singleExpand){ this.on('beforeexpandnode', this.restrictExpand, this); } }, // private proxyNodeEvent : function(ename, a1, a2, a3, a4, a5, a6){ if(ename == 'collapse' || ename == 'expand' || ename == 'beforecollapse' || ename == 'beforeexpand' || ename == 'move' || ename == 'beforemove'){ ename = ename+'node'; } // args inline for performance while bubbling events return this.fireEvent(ename, a1, a2, a3, a4, a5, a6); }, /** * Returns this root node for this tree * @return {Node} */ getRootNode : function(){ return this.root; }, /** * Sets the root node for this tree. If the TreePanel has already rendered a root node, the * previous root node (and all of its descendants) are destroyed before the new root node is rendered. * @param {Node} node * @return {Node} */ setRootNode : function(node){ this.destroyRoot(); if(!node.render){ // attributes passed node = this.loader.createNode(node); } this.root = node; node.ownerTree = this; node.isRoot = true; this.registerNode(node); if(!this.rootVisible){ var uiP = node.attributes.uiProvider; node.ui = uiP ? new uiP(node) : new Ext.tree.RootTreeNodeUI(node); } if(this.innerCt){ this.clearInnerCt(); this.renderRoot(); } return node; }, clearInnerCt : function(){ this.innerCt.update(''); }, // private renderRoot : function(){ this.root.render(); if(!this.rootVisible){ this.root.renderChildren(); } }, /** * Gets a node in this tree by its id * @param {String} id * @return {Node} */ getNodeById : function(id){ return this.nodeHash[id]; }, // private registerNode : function(node){ this.nodeHash[node.id] = node; }, // private unregisterNode : function(node){ delete this.nodeHash[node.id]; }, // private toString : function(){ return '[Tree'+(this.id?' '+this.id:'')+']'; }, // private restrictExpand : function(node){ var p = node.parentNode; if(p){ if(p.expandedChild && p.expandedChild.parentNode == p){ p.expandedChild.collapse(); } p.expandedChild = node; } }, /** * Retrieve an array of checked nodes, or an array of a specific attribute of checked nodes (e.g. 'id') * @param {String} attribute (optional) Defaults to null (return the actual nodes) * @param {TreeNode} startNode (optional) The node to start from, defaults to the root * @return {Array} */ getChecked : function(a, startNode){ startNode = startNode || this.root; var r = []; var f = function(){ if(this.attributes.checked){ r.push(!a ? this : (a == 'id' ? this.id : this.attributes[a])); } }; startNode.cascade(f); return r; }, /** * Returns the default {@link Ext.tree.TreeLoader} for this TreePanel. * @return {Ext.tree.TreeLoader} The TreeLoader for this TreePanel. */ getLoader : function(){ return this.loader; }, /** * Expand all nodes */ expandAll : function(){ this.root.expand(true); }, /** * Collapse all nodes */ collapseAll : function(){ this.root.collapse(true); }, /** * Returns the selection model used by this TreePanel. * @return {TreeSelectionModel} The selection model used by this TreePanel */ getSelectionModel : function(){ if(!this.selModel){ this.selModel = new Ext.tree.DefaultSelectionModel(); } return this.selModel; }, /** * Expands a specified path in this TreePanel. A path can be retrieved from a node with {@link Ext.data.Node#getPath} * @param {String} path * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info) * @param {Function} callback (optional) The callback to call when the expand is complete. The callback will be called with * (bSuccess, oLastNode) where bSuccess is if the expand was successful and oLastNode is the last node that was expanded. */ expandPath : function(path, attr, callback){ attr = attr || 'id'; var keys = path.split(this.pathSeparator); var curNode = this.root; if(curNode.attributes[attr] != keys[1]){ // invalid root if(callback){ callback(false, null); } return; } var index = 1; var f = function(){ if(++index == keys.length){ if(callback){ callback(true, curNode); } return; } var c = curNode.findChild(attr, keys[index]); if(!c){ if(callback){ callback(false, curNode); } return; } curNode = c; c.expand(false, false, f); }; curNode.expand(false, false, f); }, /** * Selects the node in this tree at the specified path. A path can be retrieved from a node with {@link Ext.data.Node#getPath} * @param {String} path * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info) * @param {Function} callback (optional) The callback to call when the selection is complete. The callback will be called with * (bSuccess, oSelNode) where bSuccess is if the selection was successful and oSelNode is the selected node. */ selectPath : function(path, attr, callback){ attr = attr || 'id'; var keys = path.split(this.pathSeparator), v = keys.pop(); if(keys.length > 1){ var f = function(success, node){ if(success && node){ var n = node.findChild(attr, v); if(n){ n.select(); if(callback){ callback(true, n); } }else if(callback){ callback(false, n); } }else{ if(callback){ callback(false, n); } } }; this.expandPath(keys.join(this.pathSeparator), attr, f); }else{ this.root.select(); if(callback){ callback(true, this.root); } } }, /** * Returns the underlying Element for this tree * @return {Ext.Element} The Element */ getTreeEl : function(){ return this.body; }, // private onRender : function(ct, position){ Ext.tree.TreePanel.superclass.onRender.call(this, ct, position); this.el.addClass('x-tree'); this.innerCt = this.body.createChild({tag:'ul', cls:'x-tree-root-ct ' + (this.useArrows ? 'x-tree-arrows' : this.lines ? 'x-tree-lines' : 'x-tree-no-lines')}); }, // private initEvents : function(){ Ext.tree.TreePanel.superclass.initEvents.call(this); if(this.containerScroll){ Ext.dd.ScrollManager.register(this.body); } if((this.enableDD || this.enableDrop) && !this.dropZone){ /** * The dropZone used by this tree if drop is enabled (see {@link #enableDD} or {@link #enableDrop}) * @property dropZone * @type Ext.tree.TreeDropZone */ this.dropZone = new Ext.tree.TreeDropZone(this, this.dropConfig || { ddGroup: this.ddGroup || 'TreeDD', appendOnly: this.ddAppendOnly === true }); } if((this.enableDD || this.enableDrag) && !this.dragZone){ /** * The dragZone used by this tree if drag is enabled (see {@link #enableDD} or {@link #enableDrag}) * @property dragZone * @type Ext.tree.TreeDragZone */ this.dragZone = new Ext.tree.TreeDragZone(this, this.dragConfig || { ddGroup: this.ddGroup || 'TreeDD', scroll: this.ddScroll }); } this.getSelectionModel().init(this); }, // private afterRender : function(){ Ext.tree.TreePanel.superclass.afterRender.call(this); this.renderRoot(); }, beforeDestroy : function(){ if(this.rendered){ Ext.dd.ScrollManager.unregister(this.body); Ext.destroy(this.dropZone, this.dragZone); } this.destroyRoot(); Ext.destroy(this.loader); this.nodeHash = this.root = this.loader = null; Ext.tree.TreePanel.superclass.beforeDestroy.call(this); }, /** * Destroy the root node. Not included by itself because we need to pass the silent parameter. * @private */ destroyRoot : function(){ if(this.root && this.root.destroy){ this.root.destroy(true); } } /** * @cfg {String/Number} activeItem * @hide */ /** * @cfg {Boolean} autoDestroy * @hide */ /** * @cfg {Object/String/Function} autoLoad * @hide */ /** * @cfg {Boolean} autoWidth * @hide */ /** * @cfg {Boolean/Number} bufferResize * @hide */ /** * @cfg {String} defaultType * @hide */ /** * @cfg {Object} defaults * @hide */ /** * @cfg {Boolean} hideBorders * @hide */ /** * @cfg {Mixed} items * @hide */ /** * @cfg {String} layout * @hide */ /** * @cfg {Object} layoutConfig * @hide */ /** * @cfg {Boolean} monitorResize * @hide */ /** * @property items * @hide */ /** * @method cascade * @hide */ /** * @method doLayout * @hide */ /** * @method find * @hide */ /** * @method findBy * @hide */ /** * @method findById * @hide */ /** * @method findByType * @hide */ /** * @method getComponent * @hide */ /** * @method getLayout * @hide */ /** * @method getUpdater * @hide */ /** * @method insert * @hide */ /** * @method load * @hide */ /** * @method remove * @hide */ /** * @event add * @hide */ /** * @method removeAll * @hide */ /** * @event afterLayout * @hide */ /** * @event beforeadd * @hide */ /** * @event beforeremove * @hide */ /** * @event remove * @hide */ /** * @cfg {String} allowDomMove @hide */ /** * @cfg {String} autoEl @hide */ /** * @cfg {String} applyTo @hide */ /** * @cfg {String} contentEl @hide */ /** * @cfg {Mixed} data @hide */ /** * @cfg {Mixed} tpl @hide */ /** * @cfg {String} tplWriteMode @hide */ /** * @cfg {String} disabledClass @hide */ /** * @cfg {String} elements @hide */ /** * @cfg {String} html @hide */ /** * @cfg {Boolean} preventBodyReset * @hide */ /** * @property disabled * @hide */ /** * @method applyToMarkup * @hide */ /** * @method enable * @hide */ /** * @method disable * @hide */ /** * @method setDisabled * @hide */ }); Ext.tree.TreePanel.nodeTypes = {}; Ext.reg('treepanel', Ext.tree.TreePanel);Ext.tree.TreeEventModel = function(tree){ this.tree = tree; this.tree.on('render', this.initEvents, this); }; Ext.tree.TreeEventModel.prototype = { initEvents : function(){ var t = this.tree; if(t.trackMouseOver !== false){ t.mon(t.innerCt, { scope: this, mouseover: this.delegateOver, mouseout: this.delegateOut }); } t.mon(t.getTreeEl(), { scope: this, click: this.delegateClick, dblclick: this.delegateDblClick, contextmenu: this.delegateContextMenu }); }, getNode : function(e){ var t; if(t = e.getTarget('.x-tree-node-el', 10)){ var id = Ext.fly(t, '_treeEvents').getAttribute('tree-node-id', 'ext'); if(id){ return this.tree.getNodeById(id); } } return null; }, getNodeTarget : function(e){ var t = e.getTarget('.x-tree-node-icon', 1); if(!t){ t = e.getTarget('.x-tree-node-el', 6); } return t; }, delegateOut : function(e, t){ if(!this.beforeEvent(e)){ return; } if(e.getTarget('.x-tree-ec-icon', 1)){ var n = this.getNode(e); this.onIconOut(e, n); if(n == this.lastEcOver){ delete this.lastEcOver; } } if((t = this.getNodeTarget(e)) && !e.within(t, true)){ this.onNodeOut(e, this.getNode(e)); } }, delegateOver : function(e, t){ if(!this.beforeEvent(e)){ return; } if(Ext.isGecko && !this.trackingDoc){ // prevent hanging in FF Ext.getBody().on('mouseover', this.trackExit, this); this.trackingDoc = true; } if(this.lastEcOver){ // prevent hung highlight this.onIconOut(e, this.lastEcOver); delete this.lastEcOver; } if(e.getTarget('.x-tree-ec-icon', 1)){ this.lastEcOver = this.getNode(e); this.onIconOver(e, this.lastEcOver); } if(t = this.getNodeTarget(e)){ this.onNodeOver(e, this.getNode(e)); } }, trackExit : function(e){ if(this.lastOverNode){ if(this.lastOverNode.ui && !e.within(this.lastOverNode.ui.getEl())){ this.onNodeOut(e, this.lastOverNode); } delete this.lastOverNode; Ext.getBody().un('mouseover', this.trackExit, this); this.trackingDoc = false; } }, delegateClick : function(e, t){ if(this.beforeEvent(e)){ if(e.getTarget('input[type=checkbox]', 1)){ this.onCheckboxClick(e, this.getNode(e)); }else if(e.getTarget('.x-tree-ec-icon', 1)){ this.onIconClick(e, this.getNode(e)); }else if(this.getNodeTarget(e)){ this.onNodeClick(e, this.getNode(e)); } }else{ this.checkContainerEvent(e, 'click'); } }, delegateDblClick : function(e, t){ if(this.beforeEvent(e)){ if(this.getNodeTarget(e)){ this.onNodeDblClick(e, this.getNode(e)); } }else{ this.checkContainerEvent(e, 'dblclick'); } }, delegateContextMenu : function(e, t){ if(this.beforeEvent(e)){ if(this.getNodeTarget(e)){ this.onNodeContextMenu(e, this.getNode(e)); } }else{ this.checkContainerEvent(e, 'contextmenu'); } }, checkContainerEvent: function(e, type){ if(this.disabled){ e.stopEvent(); return false; } this.onContainerEvent(e, type); }, onContainerEvent: function(e, type){ this.tree.fireEvent('container' + type, this.tree, e); }, onNodeClick : function(e, node){ node.ui.onClick(e); }, onNodeOver : function(e, node){ this.lastOverNode = node; node.ui.onOver(e); }, onNodeOut : function(e, node){ node.ui.onOut(e); }, onIconOver : function(e, node){ node.ui.addClass('x-tree-ec-over'); }, onIconOut : function(e, node){ node.ui.removeClass('x-tree-ec-over'); }, onIconClick : function(e, node){ node.ui.ecClick(e); }, onCheckboxClick : function(e, node){ node.ui.onCheckChange(e); }, onNodeDblClick : function(e, node){ node.ui.onDblClick(e); }, onNodeContextMenu : function(e, node){ node.ui.onContextMenu(e); }, beforeEvent : function(e){ var node = this.getNode(e); if(this.disabled || !node || !node.ui){ e.stopEvent(); return false; } return true; }, disable: function(){ this.disabled = true; }, enable: function(){ this.disabled = false; } };/** * @class Ext.tree.DefaultSelectionModel * @extends Ext.util.Observable * The default single selection for a TreePanel. */ Ext.tree.DefaultSelectionModel = function(config){ this.selNode = null; this.addEvents( /** * @event selectionchange * Fires when the selected node changes * @param {DefaultSelectionModel} this * @param {TreeNode} node the new selection */ 'selectionchange', /** * @event beforeselect * Fires before the selected node changes, return false to cancel the change * @param {DefaultSelectionModel} this * @param {TreeNode} node the new selection * @param {TreeNode} node the old selection */ 'beforeselect' ); Ext.apply(this, config); Ext.tree.DefaultSelectionModel.superclass.constructor.call(this); }; Ext.extend(Ext.tree.DefaultSelectionModel, Ext.util.Observable, { init : function(tree){ this.tree = tree; tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this); tree.on('click', this.onNodeClick, this); }, onNodeClick : function(node, e){ this.select(node); }, /** * Select a node. * @param {TreeNode} node The node to select * @return {TreeNode} The selected node */ select : function(node, /* private*/ selectNextNode){ // If node is hidden, select the next node in whatever direction was being moved in. if (!Ext.fly(node.ui.wrap).isVisible() && selectNextNode) { return selectNextNode.call(this, node); } var last = this.selNode; if(node == last){ node.ui.onSelectedChange(true); }else if(this.fireEvent('beforeselect', this, node, last) !== false){ if(last && last.ui){ last.ui.onSelectedChange(false); } this.selNode = node; node.ui.onSelectedChange(true); this.fireEvent('selectionchange', this, node, last); } return node; }, /** * Deselect a node. * @param {TreeNode} node The node to unselect * @param {Boolean} silent True to stop the selectionchange event from firing. */ unselect : function(node, silent){ if(this.selNode == node){ this.clearSelections(silent); } }, /** * Clear all selections * @param {Boolean} silent True to stop the selectionchange event from firing. */ clearSelections : function(silent){ var n = this.selNode; if(n){ n.ui.onSelectedChange(false); this.selNode = null; if(silent !== true){ this.fireEvent('selectionchange', this, null); } } return n; }, /** * Get the selected node * @return {TreeNode} The selected node */ getSelectedNode : function(){ return this.selNode; }, /** * Returns true if the node is selected * @param {TreeNode} node The node to check * @return {Boolean} */ isSelected : function(node){ return this.selNode == node; }, /** * Selects the node above the selected node in the tree, intelligently walking the nodes * @return TreeNode The new selection */ selectPrevious : function(/* private */ s){ if(!(s = s || this.selNode || this.lastSelNode)){ return null; } // Here we pass in the current function to select to indicate the direction we're moving var ps = s.previousSibling; if(ps){ if(!ps.isExpanded() || ps.childNodes.length < 1){ return this.select(ps, this.selectPrevious); } else{ var lc = ps.lastChild; while(lc && lc.isExpanded() && Ext.fly(lc.ui.wrap).isVisible() && lc.childNodes.length > 0){ lc = lc.lastChild; } return this.select(lc, this.selectPrevious); } } else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){ return this.select(s.parentNode, this.selectPrevious); } return null; }, /** * Selects the node above the selected node in the tree, intelligently walking the nodes * @return TreeNode The new selection */ selectNext : function(/* private */ s){ if(!(s = s || this.selNode || this.lastSelNode)){ return null; } // Here we pass in the current function to select to indicate the direction we're moving if(s.firstChild && s.isExpanded() && Ext.fly(s.ui.wrap).isVisible()){ return this.select(s.firstChild, this.selectNext); }else if(s.nextSibling){ return this.select(s.nextSibling, this.selectNext); }else if(s.parentNode){ var newS = null; s.parentNode.bubble(function(){ if(this.nextSibling){ newS = this.getOwnerTree().selModel.select(this.nextSibling, this.selectNext); return false; } }); return newS; } return null; }, onKeyDown : function(e){ var s = this.selNode || this.lastSelNode; // undesirable, but required var sm = this; if(!s){ return; } var k = e.getKey(); switch(k){ case e.DOWN: e.stopEvent(); this.selectNext(); break; case e.UP: e.stopEvent(); this.selectPrevious(); break; case e.RIGHT: e.preventDefault(); if(s.hasChildNodes()){ if(!s.isExpanded()){ s.expand(); }else if(s.firstChild){ this.select(s.firstChild, e); } } break; case e.LEFT: e.preventDefault(); if(s.hasChildNodes() && s.isExpanded()){ s.collapse(); }else if(s.parentNode && (this.tree.rootVisible || s.parentNode != this.tree.getRootNode())){ this.select(s.parentNode, e); } break; }; } }); /** * @class Ext.tree.MultiSelectionModel * @extends Ext.util.Observable * Multi selection for a TreePanel. */ Ext.tree.MultiSelectionModel = function(config){ this.selNodes = []; this.selMap = {}; this.addEvents( /** * @event selectionchange * Fires when the selected nodes change * @param {MultiSelectionModel} this * @param {Array} nodes Array of the selected nodes */ 'selectionchange' ); Ext.apply(this, config); Ext.tree.MultiSelectionModel.superclass.constructor.call(this); }; Ext.extend(Ext.tree.MultiSelectionModel, Ext.util.Observable, { init : function(tree){ this.tree = tree; tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this); tree.on('click', this.onNodeClick, this); }, onNodeClick : function(node, e){ if(e.ctrlKey && this.isSelected(node)){ this.unselect(node); }else{ this.select(node, e, e.ctrlKey); } }, /** * Select a node. * @param {TreeNode} node The node to select * @param {EventObject} e (optional) An event associated with the selection * @param {Boolean} keepExisting True to retain existing selections * @return {TreeNode} The selected node */ select : function(node, e, keepExisting){ if(keepExisting !== true){ this.clearSelections(true); } if(this.isSelected(node)){ this.lastSelNode = node; return node; } this.selNodes.push(node); this.selMap[node.id] = node; this.lastSelNode = node; node.ui.onSelectedChange(true); this.fireEvent('selectionchange', this, this.selNodes); return node; }, /** * Deselect a node. * @param {TreeNode} node The node to unselect */ unselect : function(node){ if(this.selMap[node.id]){ node.ui.onSelectedChange(false); var sn = this.selNodes; var index = sn.indexOf(node); if(index != -1){ this.selNodes.splice(index, 1); } delete this.selMap[node.id]; this.fireEvent('selectionchange', this, this.selNodes); } }, /** * Clear all selections */ clearSelections : function(suppressEvent){ var sn = this.selNodes; if(sn.length > 0){ for(var i = 0, len = sn.length; i < len; i++){ sn[i].ui.onSelectedChange(false); } this.selNodes = []; this.selMap = {}; if(suppressEvent !== true){ this.fireEvent('selectionchange', this, this.selNodes); } } }, /** * Returns true if the node is selected * @param {TreeNode} node The node to check * @return {Boolean} */ isSelected : function(node){ return this.selMap[node.id] ? true : false; }, /** * Returns an array of the selected nodes * @return {Array} */ getSelectedNodes : function(){ return this.selNodes; }, onKeyDown : Ext.tree.DefaultSelectionModel.prototype.onKeyDown, selectNext : Ext.tree.DefaultSelectionModel.prototype.selectNext, selectPrevious : Ext.tree.DefaultSelectionModel.prototype.selectPrevious });/** * @class Ext.data.Tree * @extends Ext.util.Observable * Represents a tree data structure and bubbles all the events for its nodes. The nodes * in the tree have most standard DOM functionality. * @constructor * @param {Node} root (optional) The root node */ Ext.data.Tree = function(root){ this.nodeHash = {}; /** * The root node for this tree * @type Node */ this.root = null; if(root){ this.setRootNode(root); } 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 move * 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 */ "move", /** * @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 beforemove * 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 */ "beforemove", /** * @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" ); Ext.data.Tree.superclass.constructor.call(this); }; Ext.extend(Ext.data.Tree, Ext.util.Observable, { /** * @cfg {String} pathSeparator * The token used to separate paths in node ids (defaults to '/'). */ pathSeparator: "/", // private proxyNodeEvent : function(){ return this.fireEvent.apply(this, arguments); }, /** * Returns the root node for this tree. * @return {Node} */ getRootNode : function(){ return this.root; }, /** * Sets the root node for this tree. * @param {Node} node * @return {Node} */ setRootNode : function(node){ this.root = node; node.ownerTree = this; node.isRoot = true; this.registerNode(node); return node; }, /** * Gets a node in this tree by its id. * @param {String} id * @return {Node} */ getNodeById : function(id){ return this.nodeHash[id]; }, // private registerNode : function(node){ this.nodeHash[node.id] = node; }, // private unregisterNode : function(node){ delete this.nodeHash[node.id]; }, toString : function(){ return "[Tree"+(this.id?" "+this.id:"")+"]"; } }); /** * @class Ext.data.Node * @extends Ext.util.Observable * @cfg {Boolean} leaf true if this node is a leaf and does not have children * @cfg {String} id The id for this node. If one is not specified, one is generated. * @constructor * @param {Object} attributes The attributes/config for the node */ Ext.data.Node = function(attributes){ /** * The attributes supplied for the node. You can use this property to access any custom attributes you supplied. * @type {Object} */ this.attributes = attributes || {}; this.leaf = this.attributes.leaf; /** * The node id. @type String */ this.id = this.attributes.id; if(!this.id){ this.id = Ext.id(null, "xnode-"); this.attributes.id = this.id; } /** * All child nodes of this node. @type Array */ this.childNodes = []; if(!this.childNodes.indexOf){ // indexOf is a must this.childNodes.indexOf = function(o){ for(var i = 0, len = this.length; i < len; i++){ if(this[i] == o){ return i; } } return -1; }; } /** * The parent node for this node. @type Node */ this.parentNode = null; /** * The first direct child node of this node, or null if this node has no child nodes. @type Node */ this.firstChild = null; /** * The last direct child node of this node, or null if this node has no child nodes. @type Node */ this.lastChild = null; /** * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node */ this.previousSibling = null; /** * The node immediately following this node in the tree, or null if there is no sibling node. @type Node */ this.nextSibling = null; this.addEvents({ /** * @event append * Fires when a new child node is appended * @param {Tree} tree The owner tree * @param {Node} this This node * @param {Node} node The newly appended node * @param {Number} index The index of the newly appended node */ "append" : true, /** * @event remove * Fires when a child node is removed * @param {Tree} tree The owner tree * @param {Node} this This node * @param {Node} node The removed node */ "remove" : true, /** * @event move * Fires when this node is moved to a new location in the tree * @param {Tree} tree The owner tree * @param {Node} this This node * @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 */ "move" : true, /** * @event insert * Fires when a new child node is inserted. * @param {Tree} tree The owner tree * @param {Node} this This node * @param {Node} node The child node inserted * @param {Node} refNode The child node the node was inserted before */ "insert" : true, /** * @event beforeappend * Fires before a new child is appended, return false to cancel the append. * @param {Tree} tree The owner tree * @param {Node} this This node * @param {Node} node The child node to be appended */ "beforeappend" : true, /** * @event beforeremove * Fires before a child is removed, return false to cancel the remove. * @param {Tree} tree The owner tree * @param {Node} this This node * @param {Node} node The child node to be removed */ "beforeremove" : true, /** * @event beforemove * Fires before this node is moved to a new location in the tree. Return false to cancel the move. * @param {Tree} tree The owner tree * @param {Node} this This node * @param {Node} oldParent The parent of this node * @param {Node} newParent The new parent this node is moving to * @param {Number} index The index it is being moved to */ "beforemove" : true, /** * @event beforeinsert * Fires before a new child is inserted, return false to cancel the insert. * @param {Tree} tree The owner tree * @param {Node} this This node * @param {Node} node The child node to be inserted * @param {Node} refNode The child node the node is being inserted before */ "beforeinsert" : true }); this.listeners = this.attributes.listeners; Ext.data.Node.superclass.constructor.call(this); }; Ext.extend(Ext.data.Node, Ext.util.Observable, { // private fireEvent : function(evtName){ // first do standard event for this node if(Ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){ return false; } // then bubble it up to the tree if the event wasn't cancelled var ot = this.getOwnerTree(); if(ot){ if(ot.proxyNodeEvent.apply(ot, arguments) === false){ return false; } } return true; }, /** * Returns true if this node is a leaf * @return {Boolean} */ isLeaf : function(){ return this.leaf === true; }, // private setFirstChild : function(node){ this.firstChild = node; }, //private setLastChild : function(node){ this.lastChild = node; }, /** * Returns true if this node is the last child of its parent * @return {Boolean} */ isLast : function(){ return (!this.parentNode ? true : this.parentNode.lastChild == this); }, /** * Returns true if this node is the first child of its parent * @return {Boolean} */ isFirst : function(){ return (!this.parentNode ? true : this.parentNode.firstChild == this); }, /** * Returns true if this node has one or more child nodes, else false. * @return {Boolean} */ hasChildNodes : function(){ return !this.isLeaf() && this.childNodes.length > 0; }, /** * Returns true if this node has one or more child nodes, or if the expandable * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false. * @return {Boolean} */ isExpandable : function(){ return this.attributes.expandable || this.hasChildNodes(); }, /** * Insert node(s) as the last child node of this node. * @param {Node/Array} node The node or Array of nodes to append * @return {Node} The appended node if single append, or null if an array was passed */ appendChild : function(node){ var multi = false; if(Ext.isArray(node)){ multi = node; }else if(arguments.length > 1){ multi = arguments; } // if passed an array or multiple args do them one by one if(multi){ for(var i = 0, len = multi.length; i < len; i++) { this.appendChild(multi[i]); } }else{ if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){ return false; } var index = this.childNodes.length; var oldParent = node.parentNode; // it's a move, make sure we move it cleanly if(oldParent){ if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){ return false; } oldParent.removeChild(node); } index = this.childNodes.length; if(index === 0){ this.setFirstChild(node); } this.childNodes.push(node); node.parentNode = this; var ps = this.childNodes[index-1]; if(ps){ node.previousSibling = ps; ps.nextSibling = node; }else{ node.previousSibling = null; } node.nextSibling = null; this.setLastChild(node); node.setOwnerTree(this.getOwnerTree()); this.fireEvent("append", this.ownerTree, this, node, index); if(oldParent){ node.fireEvent("move", this.ownerTree, node, oldParent, this, index); } return node; } }, /** * Removes a child node from this node. * @param {Node} node The node to remove * @param {Boolean} destroy true to destroy the node upon removal. Defaults to false. * @return {Node} The removed node */ removeChild : function(node, destroy){ var index = this.childNodes.indexOf(node); if(index == -1){ return false; } if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){ return false; } // remove it from childNodes collection this.childNodes.splice(index, 1); // update siblings if(node.previousSibling){ node.previousSibling.nextSibling = node.nextSibling; } if(node.nextSibling){ node.nextSibling.previousSibling = node.previousSibling; } // update child refs if(this.firstChild == node){ this.setFirstChild(node.nextSibling); } if(this.lastChild == node){ this.setLastChild(node.previousSibling); } this.fireEvent("remove", this.ownerTree, this, node); if(destroy){ node.destroy(true); }else{ node.clear(); } return node; }, // private clear : function(destroy){ // clear any references from the node this.setOwnerTree(null, destroy); this.parentNode = this.previousSibling = this.nextSibling = null; if(destroy){ this.firstChild = this.lastChild = null; } }, /** * Destroys the node. */ destroy : function(/* private */ silent){ /* * Silent is to be used in a number of cases * 1) When setRootNode is called. * 2) When destroy on the tree is called * 3) For destroying child nodes on a node */ if(silent === true){ this.purgeListeners(); this.clear(true); Ext.each(this.childNodes, function(n){ n.destroy(true); }); this.childNodes = null; }else{ this.remove(true); } }, /** * Inserts the first node before the second node in this nodes childNodes collection. * @param {Node} node The node to insert * @param {Node} refNode The node to insert before (if null the node is appended) * @return {Node} The inserted node */ insertBefore : function(node, refNode){ if(!refNode){ // like standard Dom, refNode can be null for append return this.appendChild(node); } // nothing to do if(node == refNode){ return false; } if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){ return false; } var index = this.childNodes.indexOf(refNode); var oldParent = node.parentNode; var refIndex = index; // when moving internally, indexes will change after remove if(oldParent == this && this.childNodes.indexOf(node) < index){ refIndex--; } // it's a move, make sure we move it cleanly if(oldParent){ if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){ return false; } oldParent.removeChild(node); } if(refIndex === 0){ this.setFirstChild(node); } this.childNodes.splice(refIndex, 0, node); node.parentNode = this; var ps = this.childNodes[refIndex-1]; if(ps){ node.previousSibling = ps; ps.nextSibling = node; }else{ node.previousSibling = null; } node.nextSibling = refNode; refNode.previousSibling = node; node.setOwnerTree(this.getOwnerTree()); this.fireEvent("insert", this.ownerTree, this, node, refNode); if(oldParent){ node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode); } return node; }, /** * Removes this node from its parent * @param {Boolean} destroy true to destroy the node upon removal. Defaults to false. * @return {Node} this */ remove : function(destroy){ if (this.parentNode) { this.parentNode.removeChild(this, destroy); } return this; }, /** * Removes all child nodes from this node. * @param {Boolean} destroy true to destroy the node upon removal. Defaults to false. * @return {Node} this */ removeAll : function(destroy){ var cn = this.childNodes, n; while((n = cn[0])){ this.removeChild(n, destroy); } return this; }, /** * Returns the child node at the specified index. * @param {Number} index * @return {Node} */ item : function(index){ return this.childNodes[index]; }, /** * Replaces one child node in this node with another. * @param {Node} newChild The replacement node * @param {Node} oldChild The node to replace * @return {Node} The replaced node */ replaceChild : function(newChild, oldChild){ var s = oldChild ? oldChild.nextSibling : null; this.removeChild(oldChild); this.insertBefore(newChild, s); return oldChild; }, /** * Returns the index of a child node * @param {Node} node * @return {Number} The index of the node or -1 if it was not found */ indexOf : function(child){ return this.childNodes.indexOf(child); }, /** * Returns the tree this node is in. * @return {Tree} */ getOwnerTree : function(){ // if it doesn't have one, look for one if(!this.ownerTree){ var p = this; while(p){ if(p.ownerTree){ this.ownerTree = p.ownerTree; break; } p = p.parentNode; } } return this.ownerTree; }, /** * Returns depth of this node (the root node has a depth of 0) * @return {Number} */ getDepth : function(){ var depth = 0; var p = this; while(p.parentNode){ ++depth; p = p.parentNode; } return depth; }, // private setOwnerTree : function(tree, destroy){ // if it is a move, we need to update everyone if(tree != this.ownerTree){ if(this.ownerTree){ this.ownerTree.unregisterNode(this); } this.ownerTree = tree; // If we're destroying, we don't need to recurse since it will be called on each child node if(destroy !== true){ Ext.each(this.childNodes, function(n){ n.setOwnerTree(tree); }); } if(tree){ tree.registerNode(this); } } }, /** * Changes the id of this node. * @param {String} id The new id for the node. */ setId: function(id){ if(id !== this.id){ var t = this.ownerTree; if(t){ t.unregisterNode(this); } this.id = this.attributes.id = id; if(t){ t.registerNode(this); } this.onIdChange(id); } }, // private onIdChange: Ext.emptyFn, /** * Returns the path for this node. The path can be used to expand or select this node programmatically. * @param {String} attr (optional) The attr to use for the path (defaults to the node's id) * @return {String} The path */ getPath : function(attr){ attr = attr || "id"; var p = this.parentNode; var b = [this.attributes[attr]]; while(p){ b.unshift(p.attributes[attr]); p = p.parentNode; } var sep = this.getOwnerTree().pathSeparator; return sep + b.join(sep); }, /** * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function * will be the args provided or the current node. If the function returns false at any point, * the bubble is stopped. * @param {Function} fn The function to call * @param {Object} scope (optional) The scope (this reference) in which the function is executed. Defaults to the current Node. * @param {Array} args (optional) The args to call the function with (default to passing the current Node) */ bubble : function(fn, scope, args){ var p = this; while(p){ if(fn.apply(scope || p, args || [p]) === false){ break; } p = p.parentNode; } }, /** * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function * will be the args provided or the current node. If the function returns false at any point, * the cascade is stopped on that branch. * @param {Function} fn The function to call * @param {Object} scope (optional) The scope (this reference) in which the function is executed. Defaults to the current Node. * @param {Array} args (optional) The args to call the function with (default to passing the current Node) */ cascade : function(fn, scope, args){ if(fn.apply(scope || this, args || [this]) !== false){ var cs = this.childNodes; for(var i = 0, len = cs.length; i < len; i++) { cs[i].cascade(fn, scope, args); } } }, /** * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function * will be the args provided or the current node. If the function returns false at any point, * the iteration stops. * @param {Function} fn The function to call * @param {Object} scope (optional) The scope (this reference) in which the function is executed. Defaults to the current Node in the iteration. * @param {Array} args (optional) The args to call the function with (default to passing the current Node) */ eachChild : function(fn, scope, args){ var cs = this.childNodes; for(var i = 0, len = cs.length; i < len; i++) { if(fn.apply(scope || this, args || [cs[i]]) === false){ break; } } }, /** * Finds the first child that has the attribute with the specified value. * @param {String} attribute The attribute name * @param {Mixed} value The value to search for * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children * @return {Node} The found child or null if none was found */ findChild : function(attribute, value, deep){ return this.findChildBy(function(){ return this.attributes[attribute] == value; }, null, deep); }, /** * Finds the first child by a custom function. The child matches if the function passed returns true. * @param {Function} fn A function which must return true if the passed Node is the required Node. * @param {Object} scope (optional) The scope (this reference) in which the function is executed. Defaults to the Node being tested. * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children * @return {Node} The found child or null if none was found */ findChildBy : function(fn, scope, deep){ var cs = this.childNodes, len = cs.length, i = 0, n, res; for(; i < len; i++){ n = cs[i]; if(fn.call(scope || n, n) === true){ return n; }else if (deep){ res = n.findChildBy(fn, scope, deep); if(res != null){ return res; } } } return null; }, /** * Sorts this nodes children using the supplied sort function. * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order. * @param {Object} scope (optional)The scope (this reference) in which the function is executed. Defaults to the browser window. */ sort : function(fn, scope){ var cs = this.childNodes; var len = cs.length; if(len > 0){ var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn; cs.sort(sortFn); for(var i = 0; i < len; i++){ var n = cs[i]; n.previousSibling = cs[i-1]; n.nextSibling = cs[i+1]; if(i === 0){ this.setFirstChild(n); } if(i == len-1){ this.setLastChild(n); } } } }, /** * Returns true if this node is an ancestor (at any point) of the passed node. * @param {Node} node * @return {Boolean} */ contains : function(node){ return node.isAncestor(this); }, /** * Returns true if the passed node is an ancestor (at any point) of this node. * @param {Node} node * @return {Boolean} */ isAncestor : function(node){ var p = this.parentNode; while(p){ if(p == node){ return true; } p = p.parentNode; } return false; }, toString : function(){ return "[Node"+(this.id?" "+this.id:"")+"]"; } });/** * @class Ext.tree.TreeNode * @extends Ext.data.Node * @cfg {String} text The text for this node * @cfg {Boolean} expanded true to start the node expanded * @cfg {Boolean} allowDrag False to make this node undraggable if {@link #draggable} = true (defaults to true) * @cfg {Boolean} allowDrop False if this node cannot have child nodes dropped on it (defaults to true) * @cfg {Boolean} disabled true to start the node disabled * @cfg {String} icon The path to an icon for the node. The preferred way to do this * is to use the cls or iconCls attributes and add the icon via a CSS background image. * @cfg {String} cls A css class to be added to the node * @cfg {String} iconCls A css class to be added to the nodes icon element for applying css background images * @cfg {String} href URL of the link used for the node (defaults to #) * @cfg {String} hrefTarget target frame for the link * @cfg {Boolean} hidden True to render hidden. (Defaults to false). * @cfg {String} qtip An Ext QuickTip for the node * @cfg {Boolean} expandable If set to true, the node will always show a plus/minus icon, even when empty * @cfg {String} qtipCfg An Ext QuickTip config for the node (used instead of qtip) * @cfg {Boolean} singleClickExpand True for single click expand on this node * @cfg {Function} uiProvider A UI class to use for this node (defaults to Ext.tree.TreeNodeUI) * @cfg {Boolean} checked True to render a checked checkbox for this node, false to render an unchecked checkbox * (defaults to undefined with no checkbox rendered) * @cfg {Boolean} draggable True to make this node draggable (defaults to false) * @cfg {Boolean} isTarget False to not allow this node to act as a drop target (defaults to true) * @cfg {Boolean} allowChildren False to not allow this node to have child nodes (defaults to true) * @cfg {Boolean} editable False to not allow this node to be edited by an {@link Ext.tree.TreeEditor} (defaults to true) * @constructor * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node */ Ext.tree.TreeNode = function(attributes){ attributes = attributes || {}; if(Ext.isString(attributes)){ attributes = {text: attributes}; } this.childrenRendered = false; this.rendered = false; Ext.tree.TreeNode.superclass.constructor.call(this, attributes); this.expanded = attributes.expanded === true; this.isTarget = attributes.isTarget !== false; this.draggable = attributes.draggable !== false && attributes.allowDrag !== false; this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false; /** * Read-only. The text for this node. To change it use {@link #setText}. * @type String */ this.text = attributes.text; /** * True if this node is disabled. * @type Boolean */ this.disabled = attributes.disabled === true; /** * True if this node is hidden. * @type Boolean */ this.hidden = attributes.hidden === true; this.addEvents( /** * @event textchange * Fires when the text for this node is changed * @param {Node} this This node * @param {String} text The new text * @param {String} oldText The old text */ 'textchange', /** * @event beforeexpand * Fires before this node is expanded, return false to cancel. * @param {Node} this This node * @param {Boolean} deep * @param {Boolean} anim */ 'beforeexpand', /** * @event beforecollapse * Fires before this node is collapsed, return false to cancel. * @param {Node} this This node * @param {Boolean} deep * @param {Boolean} anim */ 'beforecollapse', /** * @event expand * Fires when this node is expanded * @param {Node} this This node */ 'expand', /** * @event disabledchange * Fires when the disabled status of this node changes * @param {Node} this This node * @param {Boolean} disabled */ 'disabledchange', /** * @event collapse * Fires when this node is collapsed * @param {Node} this This node */ 'collapse', /** * @event beforeclick * Fires before click processing. Return false to cancel the default action. * @param {Node} this This node * @param {Ext.EventObject} e The event object */ 'beforeclick', /** * @event click * Fires when this node is clicked * @param {Node} this This node * @param {Ext.EventObject} e The event object */ 'click', /** * @event checkchange * Fires when a node with a checkbox's checked property changes * @param {Node} this This node * @param {Boolean} checked */ 'checkchange', /** * @event beforedblclick * Fires before double click processing. Return false to cancel the default action. * @param {Node} this This node * @param {Ext.EventObject} e The event object */ 'beforedblclick', /** * @event dblclick * Fires when this node is double clicked * @param {Node} this This node * @param {Ext.EventObject} e The event object */ 'dblclick', /** * @event contextmenu * Fires when this node is right clicked * @param {Node} this This node * @param {Ext.EventObject} e The event object */ 'contextmenu', /** * @event beforechildrenrendered * Fires right before the child nodes for this node are rendered * @param {Node} this This node */ 'beforechildrenrendered' ); var uiClass = this.attributes.uiProvider || this.defaultUI || Ext.tree.TreeNodeUI; /** * Read-only. The UI for this node * @type TreeNodeUI */ this.ui = new uiClass(this); }; Ext.extend(Ext.tree.TreeNode, Ext.data.Node, { preventHScroll : true, /** * Returns true if this node is expanded * @return {Boolean} */ isExpanded : function(){ return this.expanded; }, /** * Returns the UI object for this node. * @return {TreeNodeUI} The object which is providing the user interface for this tree * node. Unless otherwise specified in the {@link #uiProvider}, this will be an instance * of {@link Ext.tree.TreeNodeUI} */ getUI : function(){ return this.ui; }, getLoader : function(){ var owner; return this.loader || ((owner = this.getOwnerTree()) && owner.loader ? owner.loader : (this.loader = new Ext.tree.TreeLoader())); }, // private override setFirstChild : function(node){ var of = this.firstChild; Ext.tree.TreeNode.superclass.setFirstChild.call(this, node); if(this.childrenRendered && of && node != of){ of.renderIndent(true, true); } if(this.rendered){ this.renderIndent(true, true); } }, // private override setLastChild : function(node){ var ol = this.lastChild; Ext.tree.TreeNode.superclass.setLastChild.call(this, node); if(this.childrenRendered && ol && node != ol){ ol.renderIndent(true, true); } if(this.rendered){ this.renderIndent(true, true); } }, // these methods are overridden to provide lazy rendering support // private override appendChild : function(n){ if(!n.render && !Ext.isArray(n)){ n = this.getLoader().createNode(n); } var node = Ext.tree.TreeNode.superclass.appendChild.call(this, n); if(node && this.childrenRendered){ node.render(); } this.ui.updateExpandIcon(); return node; }, // private override removeChild : function(node, destroy){ this.ownerTree.getSelectionModel().unselect(node); Ext.tree.TreeNode.superclass.removeChild.apply(this, arguments); // only update the ui if we're not destroying if(!destroy){ // if it's been rendered remove dom node if(node.ui.rendered){ node.ui.remove(); } if(this.childNodes.length < 1){ this.collapse(false, false); }else{ this.ui.updateExpandIcon(); } if(!this.firstChild && !this.isHiddenRoot()){ this.childrenRendered = false; } } return node; }, // private override insertBefore : function(node, refNode){ if(!node.render){ node = this.getLoader().createNode(node); } var newNode = Ext.tree.TreeNode.superclass.insertBefore.call(this, node, refNode); if(newNode && refNode && this.childrenRendered){ node.render(); } this.ui.updateExpandIcon(); return newNode; }, /** * Sets the text for this node * @param {String} text */ setText : function(text){ var oldText = this.text; this.text = this.attributes.text = text; if(this.rendered){ // event without subscribing this.ui.onTextChange(this, text, oldText); } this.fireEvent('textchange', this, text, oldText); }, /** * Triggers selection of this node */ select : function(){ var t = this.getOwnerTree(); if(t){ t.getSelectionModel().select(this); } }, /** * Triggers deselection of this node * @param {Boolean} silent (optional) True to stop selection change events from firing. */ unselect : function(silent){ var t = this.getOwnerTree(); if(t){ t.getSelectionModel().unselect(this, silent); } }, /** * Returns true if this node is selected * @return {Boolean} */ isSelected : function(){ var t = this.getOwnerTree(); return t ? t.getSelectionModel().isSelected(this) : false; }, /** * Expand this node. * @param {Boolean} deep (optional) True to expand all children as well * @param {Boolean} anim (optional) false to cancel the default animation * @param {Function} callback (optional) A callback to be called when * expanding this node completes (does not wait for deep expand to complete). * Called with 1 parameter, this node. * @param {Object} scope (optional) The scope (this reference) in which the callback is executed. Defaults to this TreeNode. */ expand : function(deep, anim, callback, scope){ if(!this.expanded){ if(this.fireEvent('beforeexpand', this, deep, anim) === false){ return; } if(!this.childrenRendered){ this.renderChildren(); } this.expanded = true; if(!this.isHiddenRoot() && (this.getOwnerTree().animate && anim !== false) || anim){ this.ui.animExpand(function(){ this.fireEvent('expand', this); this.runCallback(callback, scope || this, [this]); if(deep === true){ this.expandChildNodes(true); } }.createDelegate(this)); return; }else{ this.ui.expand(); this.fireEvent('expand', this); this.runCallback(callback, scope || this, [this]); } }else{ this.runCallback(callback, scope || this, [this]); } if(deep === true){ this.expandChildNodes(true); } }, runCallback : function(cb, scope, args){ if(Ext.isFunction(cb)){ cb.apply(scope, args); } }, isHiddenRoot : function(){ return this.isRoot && !this.getOwnerTree().rootVisible; }, /** * Collapse this node. * @param {Boolean} deep (optional) True to collapse all children as well * @param {Boolean} anim (optional) false to cancel the default animation * @param {Function} callback (optional) A callback to be called when * expanding this node completes (does not wait for deep expand to complete). * Called with 1 parameter, this node. * @param {Object} scope (optional) The scope (this reference) in which the callback is executed. Defaults to this TreeNode. */ collapse : function(deep, anim, callback, scope){ if(this.expanded && !this.isHiddenRoot()){ if(this.fireEvent('beforecollapse', this, deep, anim) === false){ return; } this.expanded = false; if((this.getOwnerTree().animate && anim !== false) || anim){ this.ui.animCollapse(function(){ this.fireEvent('collapse', this); this.runCallback(callback, scope || this, [this]); if(deep === true){ this.collapseChildNodes(true); } }.createDelegate(this)); return; }else{ this.ui.collapse(); this.fireEvent('collapse', this); this.runCallback(callback, scope || this, [this]); } }else if(!this.expanded){ this.runCallback(callback, scope || this, [this]); } if(deep === true){ var cs = this.childNodes; for(var i = 0, len = cs.length; i < len; i++) { cs[i].collapse(true, false); } } }, // private delayedExpand : function(delay){ if(!this.expandProcId){ this.expandProcId = this.expand.defer(delay, this); } }, // private cancelExpand : function(){ if(this.expandProcId){ clearTimeout(this.expandProcId); } this.expandProcId = false; }, /** * Toggles expanded/collapsed state of the node */ toggle : function(){ if(this.expanded){ this.collapse(); }else{ this.expand(); } }, /** * Ensures all parent nodes are expanded, and if necessary, scrolls * the node into view. * @param {Function} callback (optional) A function to call when the node has been made visible. * @param {Object} scope (optional) The scope (this reference) in which the callback is executed. Defaults to this TreeNode. */ ensureVisible : function(callback, scope){ var tree = this.getOwnerTree(); tree.expandPath(this.parentNode ? this.parentNode.getPath() : this.getPath(), false, function(){ var node = tree.getNodeById(this.id); // Somehow if we don't do this, we lose changes that happened to node in the meantime tree.getTreeEl().scrollChildIntoView(node.ui.anchor); this.runCallback(callback, scope || this, [this]); }.createDelegate(this)); }, /** * Expand all child nodes * @param {Boolean} deep (optional) true if the child nodes should also expand their child nodes */ expandChildNodes : function(deep){ var cs = this.childNodes; for(var i = 0, len = cs.length; i < len; i++) { cs[i].expand(deep); } }, /** * Collapse all child nodes * @param {Boolean} deep (optional) true if the child nodes should also collapse their child nodes */ collapseChildNodes : function(deep){ var cs = this.childNodes; for(var i = 0, len = cs.length; i < len; i++) { cs[i].collapse(deep); } }, /** * Disables this node */ disable : function(){ this.disabled = true; this.unselect(); if(this.rendered && this.ui.onDisableChange){ // event without subscribing this.ui.onDisableChange(this, true); } this.fireEvent('disabledchange', this, true); }, /** * Enables this node */ enable : function(){ this.disabled = false; if(this.rendered && this.ui.onDisableChange){ // event without subscribing this.ui.onDisableChange(this, false); } this.fireEvent('disabledchange', this, false); }, // private renderChildren : function(suppressEvent){ if(suppressEvent !== false){ this.fireEvent('beforechildrenrendered', this); } var cs = this.childNodes; for(var i = 0, len = cs.length; i < len; i++){ cs[i].render(true); } this.childrenRendered = true; }, // private sort : function(fn, scope){ Ext.tree.TreeNode.superclass.sort.apply(this, arguments); if(this.childrenRendered){ var cs = this.childNodes; for(var i = 0, len = cs.length; i < len; i++){ cs[i].render(true); } } }, // private render : function(bulkRender){ this.ui.render(bulkRender); if(!this.rendered){ // make sure it is registered this.getOwnerTree().registerNode(this); this.rendered = true; if(this.expanded){ this.expanded = false; this.expand(false, false); } } }, // private renderIndent : function(deep, refresh){ if(refresh){ this.ui.childIndent = null; } this.ui.renderIndent(); if(deep === true && this.childrenRendered){ var cs = this.childNodes; for(var i = 0, len = cs.length; i < len; i++){ cs[i].renderIndent(true, refresh); } } }, beginUpdate : function(){ this.childrenRendered = false; }, endUpdate : function(){ if(this.expanded && this.rendered){ this.renderChildren(); } }, //inherit docs destroy : function(silent){ if(silent === true){ this.unselect(true); } Ext.tree.TreeNode.superclass.destroy.call(this, silent); Ext.destroy(this.ui, this.loader); this.ui = this.loader = null; }, // private onIdChange : function(id){ this.ui.onIdChange(id); } }); Ext.tree.TreePanel.nodeTypes.node = Ext.tree.TreeNode;/** * @class Ext.tree.AsyncTreeNode * @extends Ext.tree.TreeNode * @cfg {TreeLoader} loader A TreeLoader to be used by this node (defaults to the loader defined on the tree) * @constructor * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node */ Ext.tree.AsyncTreeNode = function(config){ this.loaded = config && config.loaded === true; this.loading = false; Ext.tree.AsyncTreeNode.superclass.constructor.apply(this, arguments); /** * @event beforeload * Fires before this node is loaded, return false to cancel * @param {Node} this This node */ this.addEvents('beforeload', 'load'); /** * @event load * Fires when this node is loaded * @param {Node} this This node */ /** * The loader used by this node (defaults to using the tree's defined loader) * @type TreeLoader * @property loader */ }; Ext.extend(Ext.tree.AsyncTreeNode, Ext.tree.TreeNode, { expand : function(deep, anim, callback, scope){ if(this.loading){ // if an async load is already running, waiting til it's done var timer; var f = function(){ if(!this.loading){ // done loading clearInterval(timer); this.expand(deep, anim, callback, scope); } }.createDelegate(this); timer = setInterval(f, 200); return; } if(!this.loaded){ if(this.fireEvent("beforeload", this) === false){ return; } this.loading = true; this.ui.beforeLoad(this); var loader = this.loader || this.attributes.loader || this.getOwnerTree().getLoader(); if(loader){ loader.load(this, this.loadComplete.createDelegate(this, [deep, anim, callback, scope]), this); return; } } Ext.tree.AsyncTreeNode.superclass.expand.call(this, deep, anim, callback, scope); }, /** * Returns true if this node is currently loading * @return {Boolean} */ isLoading : function(){ return this.loading; }, loadComplete : function(deep, anim, callback, scope){ this.loading = false; this.loaded = true; this.ui.afterLoad(this); this.fireEvent("load", this); this.expand(deep, anim, callback, scope); }, /** * Returns true if this node has been loaded * @return {Boolean} */ isLoaded : function(){ return this.loaded; }, hasChildNodes : function(){ if(!this.isLeaf() && !this.loaded){ return true; }else{ return Ext.tree.AsyncTreeNode.superclass.hasChildNodes.call(this); } }, /** * Trigger a reload for this node * @param {Function} callback * @param {Object} scope (optional) The scope (this reference) in which the callback is executed. Defaults to this Node. */ reload : function(callback, scope){ this.collapse(false, false); while(this.firstChild){ this.removeChild(this.firstChild).destroy(); } this.childrenRendered = false; this.loaded = false; if(this.isHiddenRoot()){ this.expanded = false; } this.expand(false, false, callback, scope); } }); Ext.tree.TreePanel.nodeTypes.async = Ext.tree.AsyncTreeNode;/** * @class Ext.tree.TreeNodeUI * This class provides the default UI implementation for Ext TreeNodes. * The TreeNode UI implementation is separate from the * tree implementation, and allows customizing of the appearance of * tree nodes.
*

* If you are customizing the Tree's user interface, you * may need to extend this class, but you should never need to instantiate this class.
*

* This class provides access to the user interface components of an Ext TreeNode, through * {@link Ext.tree.TreeNode#getUI} */ Ext.tree.TreeNodeUI = function(node){ this.node = node; this.rendered = false; this.animating = false; this.wasLeaf = true; this.ecc = 'x-tree-ec-icon x-tree-elbow'; this.emptyIcon = Ext.BLANK_IMAGE_URL; }; Ext.tree.TreeNodeUI.prototype = { // private removeChild : function(node){ if(this.rendered){ this.ctNode.removeChild(node.ui.getEl()); } }, // private beforeLoad : function(){ this.addClass("x-tree-node-loading"); }, // private afterLoad : function(){ this.removeClass("x-tree-node-loading"); }, // private onTextChange : function(node, text, oldText){ if(this.rendered){ this.textNode.innerHTML = text; } }, // private onDisableChange : function(node, state){ this.disabled = state; if (this.checkbox) { this.checkbox.disabled = state; } if(state){ this.addClass("x-tree-node-disabled"); }else{ this.removeClass("x-tree-node-disabled"); } }, // private onSelectedChange : function(state){ if(state){ this.focus(); this.addClass("x-tree-selected"); }else{ //this.blur(); this.removeClass("x-tree-selected"); } }, // private onMove : function(tree, node, oldParent, newParent, index, refNode){ this.childIndent = null; if(this.rendered){ var targetNode = newParent.ui.getContainer(); if(!targetNode){//target not rendered this.holder = document.createElement("div"); this.holder.appendChild(this.wrap); return; } var insertBefore = refNode ? refNode.ui.getEl() : null; if(insertBefore){ targetNode.insertBefore(this.wrap, insertBefore); }else{ targetNode.appendChild(this.wrap); } this.node.renderIndent(true, oldParent != newParent); } }, /** * Adds one or more CSS classes to the node's UI element. * Duplicate classes are automatically filtered out. * @param {String/Array} className The CSS class to add, or an array of classes */ addClass : function(cls){ if(this.elNode){ Ext.fly(this.elNode).addClass(cls); } }, /** * Removes one or more CSS classes from the node's UI element. * @param {String/Array} className The CSS class to remove, or an array of classes */ removeClass : function(cls){ if(this.elNode){ Ext.fly(this.elNode).removeClass(cls); } }, // private remove : function(){ if(this.rendered){ this.holder = document.createElement("div"); this.holder.appendChild(this.wrap); } }, // private fireEvent : function(){ return this.node.fireEvent.apply(this.node, arguments); }, // private initEvents : function(){ this.node.on("move", this.onMove, this); if(this.node.disabled){ this.onDisableChange(this.node, true); } if(this.node.hidden){ this.hide(); } var ot = this.node.getOwnerTree(); var dd = ot.enableDD || ot.enableDrag || ot.enableDrop; if(dd && (!this.node.isRoot || ot.rootVisible)){ Ext.dd.Registry.register(this.elNode, { node: this.node, handles: this.getDDHandles(), isHandle: false }); } }, // private getDDHandles : function(){ return [this.iconNode, this.textNode, this.elNode]; }, /** * Hides this node. */ hide : function(){ this.node.hidden = true; if(this.wrap){ this.wrap.style.display = "none"; } }, /** * Shows this node. */ show : function(){ this.node.hidden = false; if(this.wrap){ this.wrap.style.display = ""; } }, // private onContextMenu : function(e){ if (this.node.hasListener("contextmenu") || this.node.getOwnerTree().hasListener("contextmenu")) { e.preventDefault(); this.focus(); this.fireEvent("contextmenu", this.node, e); } }, // private onClick : function(e){ if(this.dropping){ e.stopEvent(); return; } if(this.fireEvent("beforeclick", this.node, e) !== false){ var a = e.getTarget('a'); if(!this.disabled && this.node.attributes.href && a){ this.fireEvent("click", this.node, e); return; }else if(a && e.ctrlKey){ e.stopEvent(); } e.preventDefault(); if(this.disabled){ return; } if(this.node.attributes.singleClickExpand && !this.animating && this.node.isExpandable()){ this.node.toggle(); } this.fireEvent("click", this.node, e); }else{ e.stopEvent(); } }, // private onDblClick : function(e){ e.preventDefault(); if(this.disabled){ return; } if(this.fireEvent("beforedblclick", this.node, e) !== false){ if(this.checkbox){ this.toggleCheck(); } if(!this.animating && this.node.isExpandable()){ this.node.toggle(); } this.fireEvent("dblclick", this.node, e); } }, onOver : function(e){ this.addClass('x-tree-node-over'); }, onOut : function(e){ this.removeClass('x-tree-node-over'); }, // private onCheckChange : function(){ var checked = this.checkbox.checked; // fix for IE6 this.checkbox.defaultChecked = checked; this.node.attributes.checked = checked; this.fireEvent('checkchange', this.node, checked); }, // private ecClick : function(e){ if(!this.animating && this.node.isExpandable()){ this.node.toggle(); } }, // private startDrop : function(){ this.dropping = true; }, // delayed drop so the click event doesn't get fired on a drop endDrop : function(){ setTimeout(function(){ this.dropping = false; }.createDelegate(this), 50); }, // private expand : function(){ this.updateExpandIcon(); this.ctNode.style.display = ""; }, // private focus : function(){ if(!this.node.preventHScroll){ try{this.anchor.focus(); }catch(e){} }else{ try{ var noscroll = this.node.getOwnerTree().getTreeEl().dom; var l = noscroll.scrollLeft; this.anchor.focus(); noscroll.scrollLeft = l; }catch(e){} } }, /** * Sets the checked status of the tree node to the passed value, or, if no value was passed, * toggles the checked status. If the node was rendered with no checkbox, this has no effect. * @param {Boolean} value (optional) The new checked status. */ toggleCheck : function(value){ var cb = this.checkbox; if(cb){ cb.checked = (value === undefined ? !cb.checked : value); this.onCheckChange(); } }, // private blur : function(){ try{ this.anchor.blur(); }catch(e){} }, // private animExpand : function(callback){ var ct = Ext.get(this.ctNode); ct.stopFx(); if(!this.node.isExpandable()){ this.updateExpandIcon(); this.ctNode.style.display = ""; Ext.callback(callback); return; } this.animating = true; this.updateExpandIcon(); ct.slideIn('t', { callback : function(){ this.animating = false; Ext.callback(callback); }, scope: this, duration: this.node.ownerTree.duration || .25 }); }, // private highlight : function(){ var tree = this.node.getOwnerTree(); Ext.fly(this.wrap).highlight( tree.hlColor || "C3DAF9", {endColor: tree.hlBaseColor} ); }, // private collapse : function(){ this.updateExpandIcon(); this.ctNode.style.display = "none"; }, // private animCollapse : function(callback){ var ct = Ext.get(this.ctNode); ct.enableDisplayMode('block'); ct.stopFx(); this.animating = true; this.updateExpandIcon(); ct.slideOut('t', { callback : function(){ this.animating = false; Ext.callback(callback); }, scope: this, duration: this.node.ownerTree.duration || .25 }); }, // private getContainer : function(){ return this.ctNode; }, /** * Returns the element which encapsulates this node. * @return {HtmlElement} The DOM element. The default implementation uses a <li>. */ getEl : function(){ return this.wrap; }, // private appendDDGhost : function(ghostNode){ ghostNode.appendChild(this.elNode.cloneNode(true)); }, // private getDDRepairXY : function(){ return Ext.lib.Dom.getXY(this.iconNode); }, // private onRender : function(){ this.render(); }, // private render : function(bulkRender){ var n = this.node, a = n.attributes; var targetNode = n.parentNode ? n.parentNode.ui.getContainer() : n.ownerTree.innerCt.dom; if(!this.rendered){ this.rendered = true; this.renderElements(n, a, targetNode, bulkRender); if(a.qtip){ if(this.textNode.setAttributeNS){ this.textNode.setAttributeNS("ext", "qtip", a.qtip); if(a.qtipTitle){ this.textNode.setAttributeNS("ext", "qtitle", a.qtipTitle); } }else{ this.textNode.setAttribute("ext:qtip", a.qtip); if(a.qtipTitle){ this.textNode.setAttribute("ext:qtitle", a.qtipTitle); } } }else if(a.qtipCfg){ a.qtipCfg.target = Ext.id(this.textNode); Ext.QuickTips.register(a.qtipCfg); } this.initEvents(); if(!this.node.expanded){ this.updateExpandIcon(true); } }else{ if(bulkRender === true) { targetNode.appendChild(this.wrap); } } }, // private renderElements : function(n, a, targetNode, bulkRender){ // add some indent caching, this helps performance when rendering a large tree this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : ''; var cb = Ext.isBoolean(a.checked), nel, href = a.href ? a.href : Ext.isGecko ? "" : "#", buf = ['

  • ', '',this.indentMarkup,"", '', '', cb ? ('' : '/>')) : '', '',n.text,"
    ", '', "
  • "].join(''); if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){ this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf); }else{ this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf); } this.elNode = this.wrap.childNodes[0]; this.ctNode = this.wrap.childNodes[1]; var cs = this.elNode.childNodes; this.indentNode = cs[0]; this.ecNode = cs[1]; this.iconNode = cs[2]; var index = 3; if(cb){ this.checkbox = cs[3]; // fix for IE6 this.checkbox.defaultChecked = this.checkbox.checked; index++; } this.anchor = cs[index]; this.textNode = cs[index].firstChild; }, /** * Returns the <a> element that provides focus for the node's UI. * @return {HtmlElement} The DOM anchor element. */ getAnchor : function(){ return this.anchor; }, /** * Returns the text node. * @return {HtmlNode} The DOM text node. */ getTextEl : function(){ return this.textNode; }, /** * Returns the icon <img> element. * @return {HtmlElement} The DOM image element. */ getIconEl : function(){ return this.iconNode; }, /** * Returns the checked status of the node. If the node was rendered with no * checkbox, it returns false. * @return {Boolean} The checked flag. */ isChecked : function(){ return this.checkbox ? this.checkbox.checked : false; }, // private updateExpandIcon : function(){ if(this.rendered){ var n = this.node, c1, c2, cls = n.isLast() ? "x-tree-elbow-end" : "x-tree-elbow", hasChild = n.hasChildNodes(); if(hasChild || n.attributes.expandable){ if(n.expanded){ cls += "-minus"; c1 = "x-tree-node-collapsed"; c2 = "x-tree-node-expanded"; }else{ cls += "-plus"; c1 = "x-tree-node-expanded"; c2 = "x-tree-node-collapsed"; } if(this.wasLeaf){ this.removeClass("x-tree-node-leaf"); this.wasLeaf = false; } if(this.c1 != c1 || this.c2 != c2){ Ext.fly(this.elNode).replaceClass(c1, c2); this.c1 = c1; this.c2 = c2; } }else{ if(!this.wasLeaf){ Ext.fly(this.elNode).replaceClass("x-tree-node-expanded", "x-tree-node-collapsed"); delete this.c1; delete this.c2; this.wasLeaf = true; } } var ecc = "x-tree-ec-icon "+cls; if(this.ecc != ecc){ this.ecNode.className = ecc; this.ecc = ecc; } } }, // private onIdChange: function(id){ if(this.rendered){ this.elNode.setAttribute('ext:tree-node-id', id); } }, // private getChildIndent : function(){ if(!this.childIndent){ var buf = [], p = this.node; while(p){ if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){ if(!p.isLast()) { buf.unshift(''); } else { buf.unshift(''); } } p = p.parentNode; } this.childIndent = buf.join(""); } return this.childIndent; }, // private renderIndent : function(){ if(this.rendered){ var indent = "", p = this.node.parentNode; if(p){ indent = p.ui.getChildIndent(); } if(this.indentMarkup != indent){ // don't rerender if not required this.indentNode.innerHTML = indent; this.indentMarkup = indent; } this.updateExpandIcon(); } }, destroy : function(){ if(this.elNode){ Ext.dd.Registry.unregister(this.elNode.id); } Ext.each(['textnode', 'anchor', 'checkbox', 'indentNode', 'ecNode', 'iconNode', 'elNode', 'ctNode', 'wrap', 'holder'], function(el){ if(this[el]){ Ext.fly(this[el]).remove(); delete this[el]; } }, this); delete this.node; } }; /** * @class Ext.tree.RootTreeNodeUI * This class provides the default UI implementation for root Ext TreeNodes. * The RootTreeNode UI implementation allows customizing the appearance of the root tree node.
    *

    * 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 = '

    '; this.wrap = this.ctNode = targetNode.firstChild; } }, collapse : Ext.emptyFn, expand : Ext.emptyFn });/** * @class Ext.tree.TreeLoader * @extends Ext.util.Observable * A TreeLoader provides for lazy loading of an {@link Ext.tree.TreeNode}'s child * nodes from a specified URL. The response must be a JavaScript Array definition * whose elements are node definition objects. e.g.: *
    
        [{
            id: 1,
            text: 'A leaf Node',
            leaf: true
        },{
            id: 2,
            text: 'A folder Node',
            children: [{
                id: 3,
                text: 'A child Node',
                leaf: true
            }]
       }]
    
    *

    * A server request is sent, and child nodes are loaded only when a node is expanded. * The loading node's id is passed to the server under the parameter name "node" to * enable the server to produce the correct child nodes. *

    * To pass extra parameters, an event handler may be attached to the "beforeload" * event, and the parameters specified in the TreeLoader's baseParams property: *
    
        myTreeLoader.on("beforeload", function(treeLoader, node) {
            this.baseParams.category = node.attributes.category;
        }, this);
    
    * This would pass an HTTP parameter called "category" to the server containing * the value of the Node's "category" attribute. * @constructor * Creates a new Treeloader. * @param {Object} config A config object containing config properties. */ Ext.tree.TreeLoader = function(config){ this.baseParams = {}; Ext.apply(this, config); this.addEvents( /** * @event beforeload * Fires before a network request is made to retrieve the Json text which specifies a node's children. * @param {Object} This TreeLoader object. * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded. * @param {Object} callback The callback function specified in the {@link #load} call. */ "beforeload", /** * @event load * Fires when the node has been successfuly loaded. * @param {Object} This TreeLoader object. * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded. * @param {Object} response The response object containing the data from the server. */ "load", /** * @event loadexception * Fires if the network request failed. * @param {Object} This TreeLoader object. * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded. * @param {Object} response The response object containing the data from the server. */ "loadexception" ); Ext.tree.TreeLoader.superclass.constructor.call(this); if(Ext.isString(this.paramOrder)){ this.paramOrder = this.paramOrder.split(/[\s,|]/); } }; Ext.extend(Ext.tree.TreeLoader, Ext.util.Observable, { /** * @cfg {String} dataUrl The URL from which to request a Json string which * specifies an array of node definition objects representing the child nodes * to be loaded. */ /** * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}). */ /** * @cfg {String} url Equivalent to {@link #dataUrl}. */ /** * @cfg {Boolean} preloadChildren If set to true, the loader recursively loads "children" attributes when doing the first load on nodes. */ /** * @cfg {Object} baseParams (optional) An object containing properties which * specify HTTP parameters to be passed to each request for child nodes. */ /** * @cfg {Object} baseAttrs (optional) An object containing attributes to be added to all nodes * created by this loader. If the attributes sent by the server have an attribute in this object, * they take priority. */ /** * @cfg {Object} uiProviders (optional) An object containing properties which * specify custom {@link Ext.tree.TreeNodeUI} implementations. If the optional * uiProvider attribute of a returned child node is a string rather * than a reference to a TreeNodeUI implementation, then that string value * is used as a property name in the uiProviders object. */ uiProviders : {}, /** * @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing * child nodes before loading. */ clearOnLoad : true, /** * @cfg {Array/String} paramOrder Defaults to undefined. Only used when using directFn. * Specifies the params in the order in which they must be passed to the server-side Direct method * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace, * comma, or pipe. For example, * any of the following would be acceptable:
    
    nodeParameter: 'node',
    paramOrder: ['param1','param2','param3']
    paramOrder: 'node param1 param2 param3'
    paramOrder: 'param1,node,param2,param3'
    paramOrder: 'param1|param2|param|node'
         
    */ paramOrder: undefined, /** * @cfg {Boolean} paramsAsHash Only used when using directFn. * Send parameters as a collection of named arguments (defaults to false). Providing a * {@link #paramOrder} nullifies this configuration. */ paramsAsHash: false, /** * @cfg {String} nodeParameter The name of the parameter sent to the server which contains * the identifier of the node. Defaults to 'node'. */ nodeParameter: 'node', /** * @cfg {Function} directFn * Function to call when executing a request. */ directFn : undefined, /** * Load an {@link Ext.tree.TreeNode} from the URL specified in the constructor. * This is called automatically when a node is expanded, but may be used to reload * a node (or append new children if the {@link #clearOnLoad} option is false.) * @param {Ext.tree.TreeNode} node * @param {Function} callback Function to call after the node has been loaded. The * function is passed the TreeNode which was requested to be loaded. * @param {Object} scope The scope (this reference) in which the callback is executed. * defaults to the loaded TreeNode. */ load : function(node, callback, scope){ if(this.clearOnLoad){ while(node.firstChild){ node.removeChild(node.firstChild); } } if(this.doPreload(node)){ // preloaded json children this.runCallback(callback, scope || node, [node]); }else if(this.directFn || this.dataUrl || this.url){ this.requestData(node, callback, scope || node); } }, doPreload : function(node){ if(node.attributes.children){ if(node.childNodes.length < 1){ // preloaded? var cs = node.attributes.children; node.beginUpdate(); for(var i = 0, len = cs.length; i < len; i++){ var cn = node.appendChild(this.createNode(cs[i])); if(this.preloadChildren){ this.doPreload(cn); } } node.endUpdate(); } return true; } return false; }, getParams: function(node){ var bp = Ext.apply({}, this.baseParams), np = this.nodeParameter, po = this.paramOrder; np && (bp[ np ] = node.id); if(this.directFn){ var buf = [node.id]; if(po){ // reset 'buf' if the nodeParameter was included in paramOrder if(np && po.indexOf(np) > -1){ buf = []; } for(var i = 0, len = po.length; i < len; i++){ buf.push(bp[ po[i] ]); } }else if(this.paramsAsHash){ buf = [bp]; } return buf; }else{ return bp; } }, requestData : function(node, callback, scope){ if(this.fireEvent("beforeload", this, node, callback) !== false){ if(this.directFn){ var args = this.getParams(node); args.push(this.processDirectResponse.createDelegate(this, [{callback: callback, node: node, scope: scope}], true)); this.directFn.apply(window, args); }else{ this.transId = Ext.Ajax.request({ method:this.requestMethod, url: this.dataUrl||this.url, success: this.handleResponse, failure: this.handleFailure, scope: this, argument: {callback: callback, node: node, scope: scope}, params: this.getParams(node) }); } }else{ // if the load is cancelled, make sure we notify // the node that we are done this.runCallback(callback, scope || node, []); } }, processDirectResponse: function(result, response, args){ if(response.status){ this.handleResponse({ responseData: Ext.isArray(result) ? result : null, responseText: result, argument: args }); }else{ this.handleFailure({ argument: args }); } }, // private runCallback: function(cb, scope, args){ if(Ext.isFunction(cb)){ cb.apply(scope, args); } }, isLoading : function(){ return !!this.transId; }, abort : function(){ if(this.isLoading()){ Ext.Ajax.abort(this.transId); } }, /** *

    Override this function for custom TreeNode node implementation, or to * modify the attributes at creation time.

    * Example:
    
    new Ext.tree.TreePanel({
        ...
        loader: new Ext.tree.TreeLoader({
            url: 'dataUrl',
            createNode: function(attr) {
    //          Allow consolidation consignments to have
    //          consignments dropped into them.
                if (attr.isConsolidation) {
                    attr.iconCls = 'x-consol',
                    attr.allowDrop = true;
                }
                return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
            }
        }),
        ...
    });
    
    * @param attr {Object} The attributes from which to create the new node. */ createNode : function(attr){ // apply baseAttrs, nice idea Corey! if(this.baseAttrs){ Ext.applyIf(attr, this.baseAttrs); } if(this.applyLoader !== false && !attr.loader){ attr.loader = this; } if(Ext.isString(attr.uiProvider)){ attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider); } if(attr.nodeType){ return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr); }else{ return attr.leaf ? new Ext.tree.TreeNode(attr) : new Ext.tree.AsyncTreeNode(attr); } }, processResponse : function(response, node, callback, scope){ var json = response.responseText; try { var o = response.responseData || Ext.decode(json); node.beginUpdate(); for(var i = 0, len = o.length; i < len; i++){ var n = this.createNode(o[i]); if(n){ node.appendChild(n); } } node.endUpdate(); this.runCallback(callback, scope || node, [node]); }catch(e){ this.handleFailure(response); } }, handleResponse : function(response){ this.transId = false; var a = response.argument; this.processResponse(response, a.node, a.callback, a.scope); this.fireEvent("load", this, a.node, response); }, handleFailure : function(response){ this.transId = false; var a = response.argument; this.fireEvent("loadexception", this, a.node, response); this.runCallback(a.callback, a.scope || a.node, [a.node]); }, destroy : function(){ this.abort(); this.purgeListeners(); } });/** * @class Ext.tree.TreeFilter * Note this class is experimental and doesn't update the indent (lines) or expand collapse icons of the nodes * @param {TreePanel} tree * @param {Object} config (optional) */ Ext.tree.TreeFilter = function(tree, config){ this.tree = tree; this.filtered = {}; Ext.apply(this, config); }; Ext.tree.TreeFilter.prototype = { clearBlank:false, reverse:false, autoClear:false, remove:false, /** * Filter the data by a specific attribute. * @param {String/RegExp} value Either string that the attribute value * should start with or a RegExp to test against the attribute * @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text". * @param {TreeNode} startNode (optional) The node to start the filter at. */ filter : function(value, attr, startNode){ attr = attr || "text"; var f; if(typeof value == "string"){ var vlen = value.length; // auto clear empty filter if(vlen == 0 && this.clearBlank){ this.clear(); return; } value = value.toLowerCase(); f = function(n){ return n.attributes[attr].substr(0, vlen).toLowerCase() == value; }; }else if(value.exec){ // regex? f = function(n){ return value.test(n.attributes[attr]); }; }else{ throw 'Illegal filter type, must be string or regex'; } this.filterBy(f, null, startNode); }, /** * Filter by a function. The passed function will be called with each * node in the tree (or from the startNode). If the function returns true, the node is kept * otherwise it is filtered. If a node is filtered, its children are also filtered. * @param {Function} fn The filter function * @param {Object} scope (optional) The scope (this reference) in which the function is executed. Defaults to the current Node. */ filterBy : function(fn, scope, startNode){ startNode = startNode || this.tree.root; if(this.autoClear){ this.clear(); } var af = this.filtered, rv = this.reverse; var f = function(n){ if(n == startNode){ return true; } if(af[n.id]){ return false; } var m = fn.call(scope || n, n); if(!m || rv){ af[n.id] = n; n.ui.hide(); return false; } return true; }; startNode.cascade(f); if(this.remove){ for(var id in af){ if(typeof id != "function"){ var n = af[id]; if(n && n.parentNode){ n.parentNode.removeChild(n); } } } } }, /** * Clears the current filter. Note: with the "remove" option * set a filter cannot be cleared. */ clear : function(){ var t = this.tree; var af = this.filtered; for(var id in af){ if(typeof id != "function"){ var n = af[id]; if(n){ n.ui.show(); } } } this.filtered = {}; } }; /** * @class Ext.tree.TreeSorter * Provides sorting of nodes in a {@link Ext.tree.TreePanel}. The TreeSorter automatically monitors events on the * associated TreePanel that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange). * Example usage:
    *
    
    new Ext.tree.TreeSorter(myTree, {
        folderSort: true,
        dir: "desc",
        sortType: function(node) {
            // sort by a custom, typed attribute:
            return parseInt(node.id, 10);
        }
    });
    
    * @constructor * @param {TreePanel} tree * @param {Object} config */ Ext.tree.TreeSorter = function(tree, config){ /** * @cfg {Boolean} folderSort True to sort leaf nodes under non-leaf nodes (defaults to false) */ /** * @cfg {String} property The named attribute on the node to sort by (defaults to "text"). Note that this * property is only used if no {@link #sortType} function is specified, otherwise it is ignored. */ /** * @cfg {String} dir The direction to sort ("asc" or "desc," case-insensitive, defaults to "asc") */ /** * @cfg {String} leafAttr The attribute used to determine leaf nodes when {@link #folderSort} = true (defaults to "leaf") */ /** * @cfg {Boolean} caseSensitive true for case-sensitive sort (defaults to false) */ /** * @cfg {Function} sortType A custom "casting" function used to convert node values before sorting. The function * will be called with a single parameter (the {@link Ext.tree.TreeNode} being evaluated) and is expected to return * the node's sort value cast to the specific data type required for sorting. This could be used, for example, when * a node's text (or other attribute) should be sorted as a date or numeric value. See the class description for * example usage. Note that if a sortType is specified, any {@link #property} config will be ignored. */ Ext.apply(this, config); tree.on("beforechildrenrendered", this.doSort, this); tree.on("append", this.updateSort, this); tree.on("insert", this.updateSort, this); tree.on("textchange", this.updateSortParent, this); var dsc = this.dir && this.dir.toLowerCase() == "desc"; var p = this.property || "text"; var sortType = this.sortType; var fs = this.folderSort; var cs = this.caseSensitive === true; var leafAttr = this.leafAttr || 'leaf'; this.sortFn = function(n1, n2){ if(fs){ if(n1.attributes[leafAttr] && !n2.attributes[leafAttr]){ return 1; } if(!n1.attributes[leafAttr] && n2.attributes[leafAttr]){ return -1; } } var v1 = sortType ? sortType(n1) : (cs ? n1.attributes[p] : n1.attributes[p].toUpperCase()); var v2 = sortType ? sortType(n2) : (cs ? n2.attributes[p] : n2.attributes[p].toUpperCase()); if(v1 < v2){ return dsc ? +1 : -1; }else if(v1 > v2){ return dsc ? -1 : +1; }else{ return 0; } }; }; Ext.tree.TreeSorter.prototype = { doSort : function(node){ node.sort(this.sortFn); }, compareNodes : function(n1, n2){ return (n1.text.toUpperCase() > n2.text.toUpperCase() ? 1 : -1); }, updateSort : function(tree, node){ if(node.childrenRendered){ this.doSort.defer(1, this, [node]); } }, updateSortParent : function(node){ var p = node.parentNode; if(p && p.childrenRendered){ this.doSort.defer(1, this, [p]); } } };/** * @class Ext.tree.TreeDropZone * @extends Ext.dd.DropZone * @constructor * @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dropping * @param {Object} config */ if(Ext.dd.DropZone){ Ext.tree.TreeDropZone = function(tree, config){ /** * @cfg {Boolean} allowParentInsert * Allow inserting a dragged node between an expanded parent node and its first child that will become a * sibling of the parent when dropped (defaults to false) */ this.allowParentInsert = config.allowParentInsert || false; /** * @cfg {String} allowContainerDrop * True if drops on the tree container (outside of a specific tree node) are allowed (defaults to false) */ this.allowContainerDrop = config.allowContainerDrop || false; /** * @cfg {String} appendOnly * True if the tree should only allow append drops (use for trees which are sorted, defaults to false) */ this.appendOnly = config.appendOnly || false; Ext.tree.TreeDropZone.superclass.constructor.call(this, tree.getTreeEl(), config); /** * The TreePanel for this drop zone * @type Ext.tree.TreePanel * @property */ this.tree = tree; /** * Arbitrary data that can be associated with this tree and will be included in the event object that gets * passed to any nodedragover event handler (defaults to {}) * @type Ext.tree.TreePanel * @property */ this.dragOverData = {}; // private this.lastInsertClass = "x-tree-no-status"; }; Ext.extend(Ext.tree.TreeDropZone, Ext.dd.DropZone, { /** * @cfg {String} ddGroup * A named drag drop group to which this object belongs. If a group is specified, then this object will only * interact with other drag drop objects in the same group (defaults to 'TreeDD'). */ ddGroup : "TreeDD", /** * @cfg {String} expandDelay * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node * over the target (defaults to 1000) */ expandDelay : 1000, // private expandNode : function(node){ if(node.hasChildNodes() && !node.isExpanded()){ node.expand(false, null, this.triggerCacheRefresh.createDelegate(this)); } }, // private queueExpand : function(node){ this.expandProcId = this.expandNode.defer(this.expandDelay, this, [node]); }, // private cancelExpand : function(){ if(this.expandProcId){ clearTimeout(this.expandProcId); this.expandProcId = false; } }, // private isValidDropPoint : function(n, pt, dd, e, data){ if(!n || !data){ return false; } var targetNode = n.node; var dropNode = data.node; // default drop rules if(!(targetNode && targetNode.isTarget && pt)){ return false; } if(pt == "append" && targetNode.allowChildren === false){ return false; } if((pt == "above" || pt == "below") && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){ return false; } if(dropNode && (targetNode == dropNode || dropNode.contains(targetNode))){ return false; } // reuse the object var overEvent = this.dragOverData; overEvent.tree = this.tree; overEvent.target = targetNode; overEvent.data = data; overEvent.point = pt; overEvent.source = dd; overEvent.rawEvent = e; overEvent.dropNode = dropNode; overEvent.cancel = false; var result = this.tree.fireEvent("nodedragover", overEvent); return overEvent.cancel === false && result !== false; }, // private getDropPoint : function(e, n, dd){ var tn = n.node; if(tn.isRoot){ return tn.allowChildren !== false ? "append" : false; // always append for root } var dragEl = n.ddel; var t = Ext.lib.Dom.getY(dragEl), b = t + dragEl.offsetHeight; var y = Ext.lib.Event.getPageY(e); var noAppend = tn.allowChildren === false || tn.isLeaf(); if(this.appendOnly || tn.parentNode.allowChildren === false){ return noAppend ? false : "append"; } var noBelow = false; if(!this.allowParentInsert){ noBelow = tn.hasChildNodes() && tn.isExpanded(); } var q = (b - t) / (noAppend ? 2 : 3); if(y >= t && y < (t + q)){ return "above"; }else if(!noBelow && (noAppend || y >= b-q && y <= b)){ return "below"; }else{ return "append"; } }, // private onNodeEnter : function(n, dd, e, data){ this.cancelExpand(); }, onContainerOver : function(dd, e, data) { if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) { return this.dropAllowed; } return this.dropNotAllowed; }, // private onNodeOver : function(n, dd, e, data){ var pt = this.getDropPoint(e, n, dd); var node = n.node; // auto node expand check if(!this.expandProcId && pt == "append" && node.hasChildNodes() && !n.node.isExpanded()){ this.queueExpand(node); }else if(pt != "append"){ this.cancelExpand(); } // set the insert point style on the target node var returnCls = this.dropNotAllowed; if(this.isValidDropPoint(n, pt, dd, e, data)){ if(pt){ var el = n.ddel; var cls; if(pt == "above"){ returnCls = n.node.isFirst() ? "x-tree-drop-ok-above" : "x-tree-drop-ok-between"; cls = "x-tree-drag-insert-above"; }else if(pt == "below"){ returnCls = n.node.isLast() ? "x-tree-drop-ok-below" : "x-tree-drop-ok-between"; cls = "x-tree-drag-insert-below"; }else{ returnCls = "x-tree-drop-ok-append"; cls = "x-tree-drag-append"; } if(this.lastInsertClass != cls){ Ext.fly(el).replaceClass(this.lastInsertClass, cls); this.lastInsertClass = cls; } } } return returnCls; }, // private onNodeOut : function(n, dd, e, data){ this.cancelExpand(); this.removeDropIndicators(n); }, // private onNodeDrop : function(n, dd, e, data){ var point = this.getDropPoint(e, n, dd); var targetNode = n.node; targetNode.ui.startDrop(); if(!this.isValidDropPoint(n, point, dd, e, data)){ targetNode.ui.endDrop(); return false; } // first try to find the drop node var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, point, e) : null); return this.processDrop(targetNode, data, point, dd, e, dropNode); }, onContainerDrop : function(dd, e, data){ if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) { var targetNode = this.tree.getRootNode(); targetNode.ui.startDrop(); var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, 'append', e) : null); return this.processDrop(targetNode, data, 'append', dd, e, dropNode); } return false; }, // private processDrop: function(target, data, point, dd, e, dropNode){ var dropEvent = { tree : this.tree, target: target, data: data, point: point, source: dd, rawEvent: e, dropNode: dropNode, cancel: !dropNode, dropStatus: false }; var retval = this.tree.fireEvent("beforenodedrop", dropEvent); if(retval === false || dropEvent.cancel === true || !dropEvent.dropNode){ target.ui.endDrop(); return dropEvent.dropStatus; } target = dropEvent.target; if(point == 'append' && !target.isExpanded()){ target.expand(false, null, function(){ this.completeDrop(dropEvent); }.createDelegate(this)); }else{ this.completeDrop(dropEvent); } return true; }, // private completeDrop : function(de){ var ns = de.dropNode, p = de.point, t = de.target; if(!Ext.isArray(ns)){ ns = [ns]; } var n; for(var i = 0, len = ns.length; i < len; i++){ n = ns[i]; if(p == "above"){ t.parentNode.insertBefore(n, t); }else if(p == "below"){ t.parentNode.insertBefore(n, t.nextSibling); }else{ t.appendChild(n); } } n.ui.focus(); if(Ext.enableFx && this.tree.hlDrop){ n.ui.highlight(); } t.ui.endDrop(); this.tree.fireEvent("nodedrop", de); }, // private afterNodeMoved : function(dd, data, e, targetNode, dropNode){ if(Ext.enableFx && this.tree.hlDrop){ dropNode.ui.focus(); dropNode.ui.highlight(); } this.tree.fireEvent("nodedrop", this.tree, targetNode, data, dd, e); }, // private getTree : function(){ return this.tree; }, // private removeDropIndicators : function(n){ if(n && n.ddel){ var el = n.ddel; Ext.fly(el).removeClass([ "x-tree-drag-insert-above", "x-tree-drag-insert-below", "x-tree-drag-append"]); this.lastInsertClass = "_noclass"; } }, // private beforeDragDrop : function(target, e, id){ this.cancelExpand(); return true; }, // private afterRepair : function(data){ if(data && Ext.enableFx){ data.node.ui.highlight(); } this.hideProxy(); } }); }/** * @class Ext.tree.TreeDragZone * @extends Ext.dd.DragZone * @constructor * @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dragging * @param {Object} config */ if(Ext.dd.DragZone){ Ext.tree.TreeDragZone = function(tree, config){ Ext.tree.TreeDragZone.superclass.constructor.call(this, tree.innerCt, config); /** * The TreePanel for this drag zone * @type Ext.tree.TreePanel * @property */ this.tree = tree; }; Ext.extend(Ext.tree.TreeDragZone, Ext.dd.DragZone, { /** * @cfg {String} ddGroup * A named drag drop group to which this object belongs. If a group is specified, then this object will only * interact with other drag drop objects in the same group (defaults to 'TreeDD'). */ ddGroup : "TreeDD", // private onBeforeDrag : function(data, e){ var n = data.node; return n && n.draggable && !n.disabled; }, // private onInitDrag : function(e){ var data = this.dragData; this.tree.getSelectionModel().select(data.node); this.tree.eventModel.disable(); this.proxy.update(""); data.node.ui.appendDDGhost(this.proxy.ghost.dom); this.tree.fireEvent("startdrag", this.tree, data.node, e); }, // private getRepairXY : function(e, data){ return data.node.ui.getDDRepairXY(); }, // private onEndDrag : function(data, e){ this.tree.eventModel.enable.defer(100, this.tree.eventModel); this.tree.fireEvent("enddrag", this.tree, data.node, e); }, // private onValidDrop : function(dd, e, id){ this.tree.fireEvent("dragdrop", this.tree, this.dragData.node, dd, e); this.hideProxy(); }, // private beforeInvalidDrop : function(e, id){ // this scrolls the original position back into view var sm = this.tree.getSelectionModel(); sm.clearSelections(); sm.select(this.dragData.node); }, // private afterRepair : function(){ if (Ext.enableFx && this.tree.hlDrop) { Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || "c3daf9"); } this.dragging = false; } }); }/** * @class Ext.tree.TreeEditor * @extends Ext.Editor * Provides editor functionality for inline tree node editing. Any valid {@link Ext.form.Field} subclass can be used * as the editor field. * @constructor * @param {TreePanel} tree * @param {Object} fieldConfig (optional) Either a prebuilt {@link Ext.form.Field} instance or a Field config object * that will be applied to the default field instance (defaults to a {@link Ext.form.TextField}). * @param {Object} config (optional) A TreeEditor config object */ Ext.tree.TreeEditor = function(tree, fc, config){ fc = fc || {}; var field = fc.events ? fc : new Ext.form.TextField(fc); Ext.tree.TreeEditor.superclass.constructor.call(this, field, config); this.tree = tree; if(!tree.rendered){ tree.on('render', this.initEditor, this); }else{ this.initEditor(tree); } }; Ext.extend(Ext.tree.TreeEditor, Ext.Editor, { /** * @cfg {String} alignment * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "l-l"). */ alignment: "l-l", // inherit autoSize: false, /** * @cfg {Boolean} hideEl * True to hide the bound element while the editor is displayed (defaults to false) */ hideEl : false, /** * @cfg {String} cls * CSS class to apply to the editor (defaults to "x-small-editor x-tree-editor") */ cls: "x-small-editor x-tree-editor", /** * @cfg {Boolean} shim * True to shim the editor if selects/iframes could be displayed beneath it (defaults to false) */ shim:false, // inherit shadow:"frame", /** * @cfg {Number} maxWidth * The maximum width in pixels of the editor field (defaults to 250). Note that if the maxWidth would exceed * the containing tree element's size, it will be automatically limited for you to the container width, taking * scroll and client offsets into account prior to each edit. */ maxWidth: 250, /** * @cfg {Number} editDelay The number of milliseconds between clicks to register a double-click that will trigger * editing on the current node (defaults to 350). If two clicks occur on the same node within this time span, * the editor for the node will display, otherwise it will be processed as a regular click. */ editDelay : 350, initEditor : function(tree){ tree.on({ scope : this, beforeclick: this.beforeNodeClick, dblclick : this.onNodeDblClick }); this.on({ scope : this, complete : this.updateNode, beforestartedit: this.fitToTree, specialkey : this.onSpecialKey }); this.on('startedit', this.bindScroll, this, {delay:10}); }, // private fitToTree : function(ed, el){ var td = this.tree.getTreeEl().dom, nd = el.dom; if(td.scrollLeft > nd.offsetLeft){ // ensure the node left point is visible td.scrollLeft = nd.offsetLeft; } var w = Math.min( this.maxWidth, (td.clientWidth > 20 ? td.clientWidth : td.offsetWidth) - Math.max(0, nd.offsetLeft-td.scrollLeft) - /*cushion*/5); this.setSize(w, ''); }, /** * Edit the text of the passed {@link Ext.tree.TreeNode TreeNode}. * @param node {Ext.tree.TreeNode} The TreeNode to edit. The TreeNode must be {@link Ext.tree.TreeNode#editable editable}. */ triggerEdit : function(node, defer){ this.completeEdit(); if(node.attributes.editable !== false){ /** * The {@link Ext.tree.TreeNode TreeNode} this editor is bound to. Read-only. * @type Ext.tree.TreeNode * @property editNode */ this.editNode = node; if(this.tree.autoScroll){ Ext.fly(node.ui.getEl()).scrollIntoView(this.tree.body); } var value = node.text || ''; if (!Ext.isGecko && Ext.isEmpty(node.text)){ node.setText(' '); } this.autoEditTimer = this.startEdit.defer(this.editDelay, this, [node.ui.textNode, value]); return false; } }, // private bindScroll : function(){ this.tree.getTreeEl().on('scroll', this.cancelEdit, this); }, // private beforeNodeClick : function(node, e){ clearTimeout(this.autoEditTimer); if(this.tree.getSelectionModel().isSelected(node)){ e.stopEvent(); return this.triggerEdit(node); } }, onNodeDblClick : function(node, e){ clearTimeout(this.autoEditTimer); }, // private updateNode : function(ed, value){ this.tree.getTreeEl().un('scroll', this.cancelEdit, this); this.editNode.setText(value); }, // private onHide : function(){ Ext.tree.TreeEditor.superclass.onHide.call(this); if(this.editNode){ this.editNode.ui.focus.defer(50, this.editNode.ui); } }, // private onSpecialKey : function(field, e){ var k = e.getKey(); if(k == e.ESC){ e.stopEvent(); this.cancelEdit(); }else if(k == e.ENTER && !e.hasModifier()){ e.stopEvent(); this.completeEdit(); } }, onDestroy : function(){ clearTimeout(this.autoEditTimer); Ext.tree.TreeEditor.superclass.onDestroy.call(this); var tree = this.tree; tree.un('beforeclick', this.beforeNodeClick, this); tree.un('dblclick', this.onNodeDblClick, this); } });