pkg-tree-debug.js
上传用户:dawnssy
上传日期:2022-08-06
资源大小:9345k
文件大小:150k
源码类别:

JavaScript

开发平台:

JavaScript

  1. /*!  * Ext JS Library 3.1.0  * Copyright(c) 2006-2009 Ext JS, LLC  * licensing@extjs.com  * http://www.extjs.com/license  */ /**
  2.  * @class Ext.tree.TreePanel
  3.  * @extends Ext.Panel
  4.  * <p>The TreePanel provides tree-structured UI representation of tree-structured data.</p>
  5.  * <p>{@link Ext.tree.TreeNode TreeNode}s added to the TreePanel may each contain metadata
  6.  * used by your application in their {@link Ext.tree.TreeNode#attributes attributes} property.</p>
  7.  * <p><b>A TreePanel must have a {@link #root} node before it is rendered.</b> This may either be
  8.  * specified using the {@link #root} config option, or using the {@link #setRootNode} method.
  9.  * <p>An example of tree rendered to an existing div:</p><pre><code>
  10. var tree = new Ext.tree.TreePanel({
  11.     renderTo: 'tree-div',
  12.     useArrows: true,
  13.     autoScroll: true,
  14.     animate: true,
  15.     enableDD: true,
  16.     containerScroll: true,
  17.     border: false,
  18.     // auto create TreeLoader
  19.     dataUrl: 'get-nodes.php',
  20.     root: {
  21.         nodeType: 'async',
  22.         text: 'Ext JS',
  23.         draggable: false,
  24.         id: 'source'
  25.     }
  26. });
  27. tree.getRootNode().expand();
  28.  * </code></pre>
  29.  * <p>The example above would work with a data packet similar to this:</p><pre><code>
  30. [{
  31.     "text": "adapter",
  32.     "id": "source/adapter",
  33.     "cls": "folder"
  34. }, {
  35.     "text": "dd",
  36.     "id": "source/dd",
  37.     "cls": "folder"
  38. }, {
  39.     "text": "debug.js",
  40.     "id": "source/debug.js",
  41.     "leaf": true,
  42.     "cls": "file"
  43. }]
  44.  * </code></pre>
  45.  * <p>An example of tree within a Viewport:</p><pre><code>
  46. new Ext.Viewport({
  47.     layout: 'border',
  48.     items: [{
  49.         region: 'west',
  50.         collapsible: true,
  51.         title: 'Navigation',
  52.         xtype: 'treepanel',
  53.         width: 200,
  54.         autoScroll: true,
  55.         split: true,
  56.         loader: new Ext.tree.TreeLoader(),
  57.         root: new Ext.tree.AsyncTreeNode({
  58.             expanded: true,
  59.             children: [{
  60.                 text: 'Menu Option 1',
  61.                 leaf: true
  62.             }, {
  63.                 text: 'Menu Option 2',
  64.                 leaf: true
  65.             }, {
  66.                 text: 'Menu Option 3',
  67.                 leaf: true
  68.             }]
  69.         }),
  70.         rootVisible: false,
  71.         listeners: {
  72.             click: function(n) {
  73.                 Ext.Msg.alert('Navigation Tree Click', 'You clicked: "' + n.attributes.text + '"');
  74.             }
  75.         }
  76.     }, {
  77.         region: 'center',
  78.         xtype: 'tabpanel',
  79.         // remaining code not shown ...
  80.     }]
  81. });
  82. </code></pre>
  83.  *
  84.  * @cfg {Ext.tree.TreeNode} root The root node for the tree.
  85.  * @cfg {Boolean} rootVisible <tt>false</tt> to hide the root node (defaults to <tt>true</tt>)
  86.  * @cfg {Boolean} lines <tt>false</tt> to disable tree lines (defaults to <tt>true</tt>)
  87.  * @cfg {Boolean} enableDD <tt>true</tt> to enable drag and drop
  88.  * @cfg {Boolean} enableDrag <tt>true</tt> to enable just drag
  89.  * @cfg {Boolean} enableDrop <tt>true</tt> to enable just drop
  90.  * @cfg {Object} dragConfig Custom config to pass to the {@link Ext.tree.TreeDragZone} instance
  91.  * @cfg {Object} dropConfig Custom config to pass to the {@link Ext.tree.TreeDropZone} instance
  92.  * @cfg {String} ddGroup The DD group this TreePanel belongs to
  93.  * @cfg {Boolean} ddAppendOnly <tt>true</tt> if the tree should only allow append drops (use for trees which are sorted)
  94.  * @cfg {Boolean} ddScroll <tt>true</tt> to enable body scrolling
  95.  * @cfg {Boolean} containerScroll <tt>true</tt> to register this container with ScrollManager
  96.  * @cfg {Boolean} hlDrop <tt>false</tt> to disable node highlight on drop (defaults to the value of {@link Ext#enableFx})
  97.  * @cfg {String} hlColor The color of the node highlight (defaults to <tt>'C3DAF9'</tt>)
  98.  * @cfg {Boolean} animate <tt>true</tt> to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx})
  99.  * @cfg {Boolean} singleExpand <tt>true</tt> if only 1 node per branch may be expanded
  100.  * @cfg {Object} selModel A tree selection model to use with this TreePanel (defaults to an {@link Ext.tree.DefaultSelectionModel})
  101.  * @cfg {Boolean} trackMouseOver <tt>false</tt> to disable mouse over highlighting
  102.  * @cfg {Ext.tree.TreeLoader} loader A {@link Ext.tree.TreeLoader} for use with this TreePanel
  103.  * @cfg {String} pathSeparator The token used to separate sub-paths in path strings (defaults to <tt>'/'</tt>)
  104.  * @cfg {Boolean} useArrows <tt>true</tt> to use Vista-style arrows in the tree (defaults to <tt>false</tt>)
  105.  * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).
  106.  *
  107.  * @constructor
  108.  * @param {Object} config
  109.  * @xtype treepanel
  110.  */
  111. Ext.tree.TreePanel = Ext.extend(Ext.Panel, {
  112.     rootVisible : true,
  113.     animate : Ext.enableFx,
  114.     lines : true,
  115.     enableDD : false,
  116.     hlDrop : Ext.enableFx,
  117.     pathSeparator : '/',
  118.     /**
  119.      * @cfg {Array} bubbleEvents
  120.      * <p>An array of events that, when fired, should be bubbled to any parent container.
  121.      * See {@link Ext.util.Observable#enableBubble}.
  122.      * Defaults to <tt>[]</tt>.
  123.      */
  124.     bubbleEvents : [],
  125.     initComponent : function(){
  126.         Ext.tree.TreePanel.superclass.initComponent.call(this);
  127.         if(!this.eventModel){
  128.             this.eventModel = new Ext.tree.TreeEventModel(this);
  129.         }
  130.         // initialize the loader
  131.         var l = this.loader;
  132.         if(!l){
  133.             l = new Ext.tree.TreeLoader({
  134.                 dataUrl: this.dataUrl,
  135.                 requestMethod: this.requestMethod
  136.             });
  137.         }else if(Ext.isObject(l) && !l.load){
  138.             l = new Ext.tree.TreeLoader(l);
  139.         }
  140.         this.loader = l;
  141.         this.nodeHash = {};
  142.         /**
  143.         * The root node of this tree.
  144.         * @type Ext.tree.TreeNode
  145.         * @property root
  146.         */
  147.         if(this.root){
  148.             var r = this.root;
  149.             delete this.root;
  150.             this.setRootNode(r);
  151.         }
  152.         this.addEvents(
  153.             /**
  154.             * @event append
  155.             * Fires when a new child node is appended to a node in this tree.
  156.             * @param {Tree} tree The owner tree
  157.             * @param {Node} parent The parent node
  158.             * @param {Node} node The newly appended node
  159.             * @param {Number} index The index of the newly appended node
  160.             */
  161.            'append',
  162.            /**
  163.             * @event remove
  164.             * Fires when a child node is removed from a node in this tree.
  165.             * @param {Tree} tree The owner tree
  166.             * @param {Node} parent The parent node
  167.             * @param {Node} node The child node removed
  168.             */
  169.            'remove',
  170.            /**
  171.             * @event movenode
  172.             * Fires when a node is moved to a new location in the tree
  173.             * @param {Tree} tree The owner tree
  174.             * @param {Node} node The node moved
  175.             * @param {Node} oldParent The old parent of this node
  176.             * @param {Node} newParent The new parent of this node
  177.             * @param {Number} index The index it was moved to
  178.             */
  179.            'movenode',
  180.            /**
  181.             * @event insert
  182.             * Fires when a new child node is inserted in a node in this tree.
  183.             * @param {Tree} tree The owner tree
  184.             * @param {Node} parent The parent node
  185.             * @param {Node} node The child node inserted
  186.             * @param {Node} refNode The child node the node was inserted before
  187.             */
  188.            'insert',
  189.            /**
  190.             * @event beforeappend
  191.             * Fires before a new child is appended to a node in this tree, return false to cancel the append.
  192.             * @param {Tree} tree The owner tree
  193.             * @param {Node} parent The parent node
  194.             * @param {Node} node The child node to be appended
  195.             */
  196.            'beforeappend',
  197.            /**
  198.             * @event beforeremove
  199.             * Fires before a child is removed from a node in this tree, return false to cancel the remove.
  200.             * @param {Tree} tree The owner tree
  201.             * @param {Node} parent The parent node
  202.             * @param {Node} node The child node to be removed
  203.             */
  204.            'beforeremove',
  205.            /**
  206.             * @event beforemovenode
  207.             * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
  208.             * @param {Tree} tree The owner tree
  209.             * @param {Node} node The node being moved
  210.             * @param {Node} oldParent The parent of the node
  211.             * @param {Node} newParent The new parent the node is moving to
  212.             * @param {Number} index The index it is being moved to
  213.             */
  214.            'beforemovenode',
  215.            /**
  216.             * @event beforeinsert
  217.             * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
  218.             * @param {Tree} tree The owner tree
  219.             * @param {Node} parent The parent node
  220.             * @param {Node} node The child node to be inserted
  221.             * @param {Node} refNode The child node the node is being inserted before
  222.             */
  223.             'beforeinsert',
  224.             /**
  225.             * @event beforeload
  226.             * Fires before a node is loaded, return false to cancel
  227.             * @param {Node} node The node being loaded
  228.             */
  229.             'beforeload',
  230.             /**
  231.             * @event load
  232.             * Fires when a node is loaded
  233.             * @param {Node} node The node that was loaded
  234.             */
  235.             'load',
  236.             /**
  237.             * @event textchange
  238.             * Fires when the text for a node is changed
  239.             * @param {Node} node The node
  240.             * @param {String} text The new text
  241.             * @param {String} oldText The old text
  242.             */
  243.             'textchange',
  244.             /**
  245.             * @event beforeexpandnode
  246.             * Fires before a node is expanded, return false to cancel.
  247.             * @param {Node} node The node
  248.             * @param {Boolean} deep
  249.             * @param {Boolean} anim
  250.             */
  251.             'beforeexpandnode',
  252.             /**
  253.             * @event beforecollapsenode
  254.             * Fires before a node is collapsed, return false to cancel.
  255.             * @param {Node} node The node
  256.             * @param {Boolean} deep
  257.             * @param {Boolean} anim
  258.             */
  259.             'beforecollapsenode',
  260.             /**
  261.             * @event expandnode
  262.             * Fires when a node is expanded
  263.             * @param {Node} node The node
  264.             */
  265.             'expandnode',
  266.             /**
  267.             * @event disabledchange
  268.             * Fires when the disabled status of a node changes
  269.             * @param {Node} node The node
  270.             * @param {Boolean} disabled
  271.             */
  272.             'disabledchange',
  273.             /**
  274.             * @event collapsenode
  275.             * Fires when a node is collapsed
  276.             * @param {Node} node The node
  277.             */
  278.             'collapsenode',
  279.             /**
  280.             * @event beforeclick
  281.             * Fires before click processing on a node. Return false to cancel the default action.
  282.             * @param {Node} node The node
  283.             * @param {Ext.EventObject} e The event object
  284.             */
  285.             'beforeclick',
  286.             /**
  287.             * @event click
  288.             * Fires when a node is clicked
  289.             * @param {Node} node The node
  290.             * @param {Ext.EventObject} e The event object
  291.             */
  292.             'click',
  293.             /**
  294.             * @event containerclick
  295.             * Fires when the tree container is clicked
  296.             * @param {Tree} this
  297.             * @param {Ext.EventObject} e The event object
  298.             */
  299.             'containerclick',
  300.             /**
  301.             * @event checkchange
  302.             * Fires when a node with a checkbox's checked property changes
  303.             * @param {Node} this This node
  304.             * @param {Boolean} checked
  305.             */
  306.             'checkchange',
  307.             /**
  308.             * @event beforedblclick
  309.             * Fires before double click processing on a node. Return false to cancel the default action.
  310.             * @param {Node} node The node
  311.             * @param {Ext.EventObject} e The event object
  312.             */
  313.             'beforedblclick',
  314.             /**
  315.             * @event dblclick
  316.             * Fires when a node is double clicked
  317.             * @param {Node} node The node
  318.             * @param {Ext.EventObject} e The event object
  319.             */
  320.             'dblclick',
  321.             /**
  322.             * @event containerdblclick
  323.             * Fires when the tree container is double clicked
  324.             * @param {Tree} this
  325.             * @param {Ext.EventObject} e The event object
  326.             */
  327.             'containerdblclick',
  328.             /**
  329.             * @event contextmenu
  330.             * Fires when a node is right clicked. To display a context menu in response to this
  331.             * event, first create a Menu object (see {@link Ext.menu.Menu} for details), then add
  332.             * a handler for this event:<pre><code>
  333. new Ext.tree.TreePanel({
  334.     title: 'My TreePanel',
  335.     root: new Ext.tree.AsyncTreeNode({
  336.         text: 'The Root',
  337.         children: [
  338.             { text: 'Child node 1', leaf: true },
  339.             { text: 'Child node 2', leaf: true }
  340.         ]
  341.     }),
  342.     contextMenu: new Ext.menu.Menu({
  343.         items: [{
  344.             id: 'delete-node',
  345.             text: 'Delete Node'
  346.         }],
  347.         listeners: {
  348.             itemclick: function(item) {
  349.                 switch (item.id) {
  350.                     case 'delete-node':
  351.                         var n = item.parentMenu.contextNode;
  352.                         if (n.parentNode) {
  353.                             n.remove();
  354.                         }
  355.                         break;
  356.                 }
  357.             }
  358.         }
  359.     }),
  360.     listeners: {
  361.         contextmenu: function(node, e) {
  362. //          Register the context node with the menu so that a Menu Item's handler function can access
  363. //          it via its {@link Ext.menu.BaseItem#parentMenu parentMenu} property.
  364.             node.select();
  365.             var c = node.getOwnerTree().contextMenu;
  366.             c.contextNode = node;
  367.             c.showAt(e.getXY());
  368.         }
  369.     }
  370. });
  371. </code></pre>
  372.             * @param {Node} node The node
  373.             * @param {Ext.EventObject} e The event object
  374.             */
  375.             'contextmenu',
  376.             /**
  377.             * @event containercontextmenu
  378.             * Fires when the tree container is right clicked
  379.             * @param {Tree} this
  380.             * @param {Ext.EventObject} e The event object
  381.             */
  382.             'containercontextmenu',
  383.             /**
  384.             * @event beforechildrenrendered
  385.             * Fires right before the child nodes for a node are rendered
  386.             * @param {Node} node The node
  387.             */
  388.             'beforechildrenrendered',
  389.            /**
  390.              * @event startdrag
  391.              * Fires when a node starts being dragged
  392.              * @param {Ext.tree.TreePanel} this
  393.              * @param {Ext.tree.TreeNode} node
  394.              * @param {event} e The raw browser event
  395.              */
  396.             'startdrag',
  397.             /**
  398.              * @event enddrag
  399.              * Fires when a drag operation is complete
  400.              * @param {Ext.tree.TreePanel} this
  401.              * @param {Ext.tree.TreeNode} node
  402.              * @param {event} e The raw browser event
  403.              */
  404.             'enddrag',
  405.             /**
  406.              * @event dragdrop
  407.              * Fires when a dragged node is dropped on a valid DD target
  408.              * @param {Ext.tree.TreePanel} this
  409.              * @param {Ext.tree.TreeNode} node
  410.              * @param {DD} dd The dd it was dropped on
  411.              * @param {event} e The raw browser event
  412.              */
  413.             'dragdrop',
  414.             /**
  415.              * @event beforenodedrop
  416.              * Fires when a DD object is dropped on a node in this tree for preprocessing. Return false to cancel the drop. The dropEvent
  417.              * passed to handlers has the following properties:<br />
  418.              * <ul style="padding:5px;padding-left:16px;">
  419.              * <li>tree - The TreePanel</li>
  420.              * <li>target - The node being targeted for the drop</li>
  421.              * <li>data - The drag data from the drag source</li>
  422.              * <li>point - The point of the drop - append, above or below</li>
  423.              * <li>source - The drag source</li>
  424.              * <li>rawEvent - Raw mouse event</li>
  425.              * <li>dropNode - Drop node(s) provided by the source <b>OR</b> you can supply node(s)
  426.              * to be inserted by setting them on this object.</li>
  427.              * <li>cancel - Set this to true to cancel the drop.</li>
  428.              * <li>dropStatus - If the default drop action is cancelled but the drop is valid, setting this to true
  429.              * will prevent the animated 'repair' from appearing.</li>
  430.              * </ul>
  431.              * @param {Object} dropEvent
  432.              */
  433.             'beforenodedrop',
  434.             /**
  435.              * @event nodedrop
  436.              * Fires after a DD object is dropped on a node in this tree. The dropEvent
  437.              * passed to handlers has the following properties:<br />
  438.              * <ul style="padding:5px;padding-left:16px;">
  439.              * <li>tree - The TreePanel</li>
  440.              * <li>target - The node being targeted for the drop</li>
  441.              * <li>data - The drag data from the drag source</li>
  442.              * <li>point - The point of the drop - append, above or below</li>
  443.              * <li>source - The drag source</li>
  444.              * <li>rawEvent - Raw mouse event</li>
  445.              * <li>dropNode - Dropped node(s).</li>
  446.              * </ul>
  447.              * @param {Object} dropEvent
  448.              */
  449.             'nodedrop',
  450.              /**
  451.              * @event nodedragover
  452.              * Fires when a tree node is being targeted for a drag drop, return false to signal drop not allowed. The dragOverEvent
  453.              * passed to handlers has the following properties:<br />
  454.              * <ul style="padding:5px;padding-left:16px;">
  455.              * <li>tree - The TreePanel</li>
  456.              * <li>target - The node being targeted for the drop</li>
  457.              * <li>data - The drag data from the drag source</li>
  458.              * <li>point - The point of the drop - append, above or below</li>
  459.              * <li>source - The drag source</li>
  460.              * <li>rawEvent - Raw mouse event</li>
  461.              * <li>dropNode - Drop node(s) provided by the source.</li>
  462.              * <li>cancel - Set this to true to signal drop not allowed.</li>
  463.              * </ul>
  464.              * @param {Object} dragOverEvent
  465.              */
  466.             'nodedragover'
  467.         );
  468.         if(this.singleExpand){
  469.             this.on('beforeexpandnode', this.restrictExpand, this);
  470.         }
  471.     },
  472.     // private
  473.     proxyNodeEvent : function(ename, a1, a2, a3, a4, a5, a6){
  474.         if(ename == 'collapse' || ename == 'expand' || ename == 'beforecollapse' || ename == 'beforeexpand' || ename == 'move' || ename == 'beforemove'){
  475.             ename = ename+'node';
  476.         }
  477.         // args inline for performance while bubbling events
  478.         return this.fireEvent(ename, a1, a2, a3, a4, a5, a6);
  479.     },
  480.     /**
  481.      * Returns this root node for this tree
  482.      * @return {Node}
  483.      */
  484.     getRootNode : function(){
  485.         return this.root;
  486.     },
  487.     /**
  488.      * Sets the root node for this tree. If the TreePanel has already rendered a root node, the
  489.      * previous root node (and all of its descendants) are destroyed before the new root node is rendered.
  490.      * @param {Node} node
  491.      * @return {Node}
  492.      */
  493.     setRootNode : function(node){
  494.         Ext.destroy(this.root);
  495.         if(!node.render){ // attributes passed
  496.             node = this.loader.createNode(node);
  497.         }
  498.         this.root = node;
  499.         node.ownerTree = this;
  500.         node.isRoot = true;
  501.         this.registerNode(node);
  502.         if(!this.rootVisible){
  503.             var uiP = node.attributes.uiProvider;
  504.             node.ui = uiP ? new uiP(node) : new Ext.tree.RootTreeNodeUI(node);
  505.         }
  506.         if (this.innerCt) {
  507.             this.innerCt.update('');
  508.             this.afterRender();
  509.         }
  510.         return node;
  511.     },
  512.     /**
  513.      * Gets a node in this tree by its id
  514.      * @param {String} id
  515.      * @return {Node}
  516.      */
  517.     getNodeById : function(id){
  518.         return this.nodeHash[id];
  519.     },
  520.     // private
  521.     registerNode : function(node){
  522.         this.nodeHash[node.id] = node;
  523.     },
  524.     // private
  525.     unregisterNode : function(node){
  526.         delete this.nodeHash[node.id];
  527.     },
  528.     // private
  529.     toString : function(){
  530.         return '[Tree'+(this.id?' '+this.id:'')+']';
  531.     },
  532.     // private
  533.     restrictExpand : function(node){
  534.         var p = node.parentNode;
  535.         if(p){
  536.             if(p.expandedChild && p.expandedChild.parentNode == p){
  537.                 p.expandedChild.collapse();
  538.             }
  539.             p.expandedChild = node;
  540.         }
  541.     },
  542.     /**
  543.      * Retrieve an array of checked nodes, or an array of a specific attribute of checked nodes (e.g. 'id')
  544.      * @param {String} attribute (optional) Defaults to null (return the actual nodes)
  545.      * @param {TreeNode} startNode (optional) The node to start from, defaults to the root
  546.      * @return {Array}
  547.      */
  548.     getChecked : function(a, startNode){
  549.         startNode = startNode || this.root;
  550.         var r = [];
  551.         var f = function(){
  552.             if(this.attributes.checked){
  553.                 r.push(!a ? this : (a == 'id' ? this.id : this.attributes[a]));
  554.             }
  555.         };
  556.         startNode.cascade(f);
  557.         return r;
  558.     },
  559.     /**
  560.      * Returns the default {@link Ext.tree.TreeLoader} for this TreePanel.
  561.      * @return {Ext.tree.TreeLoader} The TreeLoader for this TreePanel.
  562.      */
  563.     getLoader : function(){
  564.         return this.loader;
  565.     },
  566.     /**
  567.      * Expand all nodes
  568.      */
  569.     expandAll : function(){
  570.         this.root.expand(true);
  571.     },
  572.     /**
  573.      * Collapse all nodes
  574.      */
  575.     collapseAll : function(){
  576.         this.root.collapse(true);
  577.     },
  578.     /**
  579.      * Returns the selection model used by this TreePanel.
  580.      * @return {TreeSelectionModel} The selection model used by this TreePanel
  581.      */
  582.     getSelectionModel : function(){
  583.         if(!this.selModel){
  584.             this.selModel = new Ext.tree.DefaultSelectionModel();
  585.         }
  586.         return this.selModel;
  587.     },
  588.     /**
  589.      * Expands a specified path in this TreePanel. A path can be retrieved from a node with {@link Ext.data.Node#getPath}
  590.      * @param {String} path
  591.      * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)
  592.      * @param {Function} callback (optional) The callback to call when the expand is complete. The callback will be called with
  593.      * (bSuccess, oLastNode) where bSuccess is if the expand was successful and oLastNode is the last node that was expanded.
  594.      */
  595.     expandPath : function(path, attr, callback){
  596.         attr = attr || 'id';
  597.         var keys = path.split(this.pathSeparator);
  598.         var curNode = this.root;
  599.         if(curNode.attributes[attr] != keys[1]){ // invalid root
  600.             if(callback){
  601.                 callback(false, null);
  602.             }
  603.             return;
  604.         }
  605.         var index = 1;
  606.         var f = function(){
  607.             if(++index == keys.length){
  608.                 if(callback){
  609.                     callback(true, curNode);
  610.                 }
  611.                 return;
  612.             }
  613.             var c = curNode.findChild(attr, keys[index]);
  614.             if(!c){
  615.                 if(callback){
  616.                     callback(false, curNode);
  617.                 }
  618.                 return;
  619.             }
  620.             curNode = c;
  621.             c.expand(false, false, f);
  622.         };
  623.         curNode.expand(false, false, f);
  624.     },
  625.     /**
  626.      * Selects the node in this tree at the specified path. A path can be retrieved from a node with {@link Ext.data.Node#getPath}
  627.      * @param {String} path
  628.      * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)
  629.      * @param {Function} callback (optional) The callback to call when the selection is complete. The callback will be called with
  630.      * (bSuccess, oSelNode) where bSuccess is if the selection was successful and oSelNode is the selected node.
  631.      */
  632.     selectPath : function(path, attr, callback){
  633.         attr = attr || 'id';
  634.         var keys = path.split(this.pathSeparator),
  635.             v = keys.pop();
  636.         if(keys.length > 1){
  637.             var f = function(success, node){
  638.                 if(success && node){
  639.                     var n = node.findChild(attr, v);
  640.                     if(n){
  641.                         n.select();
  642.                         if(callback){
  643.                             callback(true, n);
  644.                         }
  645.                     }else if(callback){
  646.                         callback(false, n);
  647.                     }
  648.                 }else{
  649.                     if(callback){
  650.                         callback(false, n);
  651.                     }
  652.                 }
  653.             };
  654.             this.expandPath(keys.join(this.pathSeparator), attr, f);
  655.         }else{
  656.             this.root.select();
  657.             if(callback){
  658.                 callback(true, this.root);
  659.             }
  660.         }
  661.     },
  662.     /**
  663.      * Returns the underlying Element for this tree
  664.      * @return {Ext.Element} The Element
  665.      */
  666.     getTreeEl : function(){
  667.         return this.body;
  668.     },
  669.     // private
  670.     onRender : function(ct, position){
  671.         Ext.tree.TreePanel.superclass.onRender.call(this, ct, position);
  672.         this.el.addClass('x-tree');
  673.         this.innerCt = this.body.createChild({tag:'ul',
  674.                cls:'x-tree-root-ct ' +
  675.                (this.useArrows ? 'x-tree-arrows' : this.lines ? 'x-tree-lines' : 'x-tree-no-lines')});
  676.     },
  677.     // private
  678.     initEvents : function(){
  679.         Ext.tree.TreePanel.superclass.initEvents.call(this);
  680.         if(this.containerScroll){
  681.             Ext.dd.ScrollManager.register(this.body);
  682.         }
  683.         if((this.enableDD || this.enableDrop) && !this.dropZone){
  684.            /**
  685.             * The dropZone used by this tree if drop is enabled (see {@link #enableDD} or {@link #enableDrop})
  686.             * @property dropZone
  687.             * @type Ext.tree.TreeDropZone
  688.             */
  689.              this.dropZone = new Ext.tree.TreeDropZone(this, this.dropConfig || {
  690.                ddGroup: this.ddGroup || 'TreeDD', appendOnly: this.ddAppendOnly === true
  691.            });
  692.         }
  693.         if((this.enableDD || this.enableDrag) && !this.dragZone){
  694.            /**
  695.             * The dragZone used by this tree if drag is enabled (see {@link #enableDD} or {@link #enableDrag})
  696.             * @property dragZone
  697.             * @type Ext.tree.TreeDragZone
  698.             */
  699.             this.dragZone = new Ext.tree.TreeDragZone(this, this.dragConfig || {
  700.                ddGroup: this.ddGroup || 'TreeDD',
  701.                scroll: this.ddScroll
  702.            });
  703.         }
  704.         this.getSelectionModel().init(this);
  705.     },
  706.     // private
  707.     afterRender : function(){
  708.         Ext.tree.TreePanel.superclass.afterRender.call(this);
  709.         this.root.render();
  710.         if(!this.rootVisible){
  711.             this.root.renderChildren();
  712.         }
  713.     },
  714.     beforeDestroy : function(){
  715.         if(this.rendered){
  716.             Ext.dd.ScrollManager.unregister(this.body);
  717.             Ext.destroy(this.dropZone, this.dragZone);
  718.         }
  719.         Ext.destroy(this.root, this.loader);
  720.         this.nodeHash = this.root = this.loader = null;
  721.         Ext.tree.TreePanel.superclass.beforeDestroy.call(this);
  722.     }
  723.     /**
  724.      * @cfg {String/Number} activeItem
  725.      * @hide
  726.      */
  727.     /**
  728.      * @cfg {Boolean} autoDestroy
  729.      * @hide
  730.      */
  731.     /**
  732.      * @cfg {Object/String/Function} autoLoad
  733.      * @hide
  734.      */
  735.     /**
  736.      * @cfg {Boolean} autoWidth
  737.      * @hide
  738.      */
  739.     /**
  740.      * @cfg {Boolean/Number} bufferResize
  741.      * @hide
  742.      */
  743.     /**
  744.      * @cfg {String} defaultType
  745.      * @hide
  746.      */
  747.     /**
  748.      * @cfg {Object} defaults
  749.      * @hide
  750.      */
  751.     /**
  752.      * @cfg {Boolean} hideBorders
  753.      * @hide
  754.      */
  755.     /**
  756.      * @cfg {Mixed} items
  757.      * @hide
  758.      */
  759.     /**
  760.      * @cfg {String} layout
  761.      * @hide
  762.      */
  763.     /**
  764.      * @cfg {Object} layoutConfig
  765.      * @hide
  766.      */
  767.     /**
  768.      * @cfg {Boolean} monitorResize
  769.      * @hide
  770.      */
  771.     /**
  772.      * @property items
  773.      * @hide
  774.      */
  775.     /**
  776.      * @method cascade
  777.      * @hide
  778.      */
  779.     /**
  780.      * @method doLayout
  781.      * @hide
  782.      */
  783.     /**
  784.      * @method find
  785.      * @hide
  786.      */
  787.     /**
  788.      * @method findBy
  789.      * @hide
  790.      */
  791.     /**
  792.      * @method findById
  793.      * @hide
  794.      */
  795.     /**
  796.      * @method findByType
  797.      * @hide
  798.      */
  799.     /**
  800.      * @method getComponent
  801.      * @hide
  802.      */
  803.     /**
  804.      * @method getLayout
  805.      * @hide
  806.      */
  807.     /**
  808.      * @method getUpdater
  809.      * @hide
  810.      */
  811.     /**
  812.      * @method insert
  813.      * @hide
  814.      */
  815.     /**
  816.      * @method load
  817.      * @hide
  818.      */
  819.     /**
  820.      * @method remove
  821.      * @hide
  822.      */
  823.     /**
  824.      * @event add
  825.      * @hide
  826.      */
  827.     /**
  828.      * @method removeAll
  829.      * @hide
  830.      */
  831.     /**
  832.      * @event afterLayout
  833.      * @hide
  834.      */
  835.     /**
  836.      * @event beforeadd
  837.      * @hide
  838.      */
  839.     /**
  840.      * @event beforeremove
  841.      * @hide
  842.      */
  843.     /**
  844.      * @event remove
  845.      * @hide
  846.      */
  847.     /**
  848.      * @cfg {String} allowDomMove  @hide
  849.      */
  850.     /**
  851.      * @cfg {String} autoEl @hide
  852.      */
  853.     /**
  854.      * @cfg {String} applyTo  @hide
  855.      */
  856.     /**
  857.      * @cfg {String} contentEl  @hide
  858.      */
  859.     /**
  860.      * @cfg {String} disabledClass  @hide
  861.      */
  862.     /**
  863.      * @cfg {String} elements  @hide
  864.      */
  865.     /**
  866.      * @cfg {String} html  @hide
  867.      */
  868.     /**
  869.      * @cfg {Boolean} preventBodyReset
  870.      * @hide
  871.      */
  872.     /**
  873.      * @property disabled
  874.      * @hide
  875.      */
  876.     /**
  877.      * @method applyToMarkup
  878.      * @hide
  879.      */
  880.     /**
  881.      * @method enable
  882.      * @hide
  883.      */
  884.     /**
  885.      * @method disable
  886.      * @hide
  887.      */
  888.     /**
  889.      * @method setDisabled
  890.      * @hide
  891.      */
  892. });
  893. Ext.tree.TreePanel.nodeTypes = {};
  894. Ext.reg('treepanel', Ext.tree.TreePanel);Ext.tree.TreeEventModel = function(tree){
  895.     this.tree = tree;
  896.     this.tree.on('render', this.initEvents, this);
  897. }
  898. Ext.tree.TreeEventModel.prototype = {
  899.     initEvents : function(){
  900.         var t = this.tree;
  901.             
  902.         if(t.trackMouseOver !== false){
  903.             t.mon(t.innerCt, {
  904.                 scope: this,
  905.                 mouseover: this.delegateOver,
  906.                 mouseout: this.delegateOut
  907.             });
  908.         }
  909.         t.mon(t.getTreeEl(), {
  910.             scope: this,
  911.             click: this.delegateClick,
  912.             dblclick: this.delegateDblClick,
  913.             contextmenu: this.delegateContextMenu
  914.         });
  915.     },
  916.     getNode : function(e){
  917.         var t;
  918.         if(t = e.getTarget('.x-tree-node-el', 10)){
  919.             var id = Ext.fly(t, '_treeEvents').getAttribute('tree-node-id', 'ext');
  920.             if(id){
  921.                 return this.tree.getNodeById(id);
  922.             }
  923.         }
  924.         return null;
  925.     },
  926.     getNodeTarget : function(e){
  927.         var t = e.getTarget('.x-tree-node-icon', 1);
  928.         if(!t){
  929.             t = e.getTarget('.x-tree-node-el', 6);
  930.         }
  931.         return t;
  932.     },
  933.     delegateOut : function(e, t){
  934.         if(!this.beforeEvent(e)){
  935.             return;
  936.         }
  937.         if(e.getTarget('.x-tree-ec-icon', 1)){
  938.             var n = this.getNode(e);
  939.             this.onIconOut(e, n);
  940.             if(n == this.lastEcOver){
  941.                 delete this.lastEcOver;
  942.             }
  943.         }
  944.         if((t = this.getNodeTarget(e)) && !e.within(t, true)){
  945.             this.onNodeOut(e, this.getNode(e));
  946.         }
  947.     },
  948.     delegateOver : function(e, t){
  949.         if(!this.beforeEvent(e)){
  950.             return;
  951.         }
  952.         if(Ext.isGecko && !this.trackingDoc){ // prevent hanging in FF
  953.             Ext.getBody().on('mouseover', this.trackExit, this);
  954.             this.trackingDoc = true;
  955.         }
  956.         if(this.lastEcOver){ // prevent hung highlight
  957.             this.onIconOut(e, this.lastEcOver);
  958.             delete this.lastEcOver;
  959.         }
  960.         if(e.getTarget('.x-tree-ec-icon', 1)){
  961.             this.lastEcOver = this.getNode(e);
  962.             this.onIconOver(e, this.lastEcOver);
  963.         }
  964.         if(t = this.getNodeTarget(e)){
  965.             this.onNodeOver(e, this.getNode(e));
  966.         }
  967.     },
  968.     trackExit : function(e){
  969.         if(this.lastOverNode && !e.within(this.lastOverNode.ui.getEl())){
  970.             this.onNodeOut(e, this.lastOverNode);
  971.             delete this.lastOverNode;
  972.             Ext.getBody().un('mouseover', this.trackExit, this);
  973.             this.trackingDoc = false;
  974.         }
  975.     },
  976.     delegateClick : function(e, t){
  977.         if(this.beforeEvent(e)){
  978.             if(e.getTarget('input[type=checkbox]', 1)){
  979.                 this.onCheckboxClick(e, this.getNode(e));
  980.             }else if(e.getTarget('.x-tree-ec-icon', 1)){
  981.                 this.onIconClick(e, this.getNode(e));
  982.             }else if(this.getNodeTarget(e)){
  983.                 this.onNodeClick(e, this.getNode(e));
  984.             }else{
  985.                 this.onContainerEvent(e, 'click');
  986.             }
  987.         }
  988.     },
  989.     delegateDblClick : function(e, t){
  990.         if(this.beforeEvent(e)){
  991.             if(this.getNodeTarget(e)){
  992.                 this.onNodeDblClick(e, this.getNode(e));
  993.             }else{
  994.                 this.onContainerEvent(e, 'dblclick');    
  995.             }
  996.         }
  997.     },
  998.     delegateContextMenu : function(e, t){
  999.         if(this.beforeEvent(e)){
  1000.             if(this.getNodeTarget(e)){
  1001.                 this.onNodeContextMenu(e, this.getNode(e));
  1002.             }else{
  1003.                 this.onContainerEvent(e, 'contextmenu');    
  1004.             }
  1005.         }
  1006.     },
  1007.     
  1008.     onContainerEvent: function(e, type){
  1009.         this.tree.fireEvent('container' + type, this.tree, e); 
  1010.     },
  1011.     onNodeClick : function(e, node){
  1012.         node.ui.onClick(e);
  1013.     },
  1014.     onNodeOver : function(e, node){
  1015.         this.lastOverNode = node;
  1016.         node.ui.onOver(e);
  1017.     },
  1018.     onNodeOut : function(e, node){
  1019.         node.ui.onOut(e);
  1020.     },
  1021.     onIconOver : function(e, node){
  1022.         node.ui.addClass('x-tree-ec-over');
  1023.     },
  1024.     onIconOut : function(e, node){
  1025.         node.ui.removeClass('x-tree-ec-over');
  1026.     },
  1027.     onIconClick : function(e, node){
  1028.         node.ui.ecClick(e);
  1029.     },
  1030.     onCheckboxClick : function(e, node){
  1031.         node.ui.onCheckChange(e);
  1032.     },
  1033.     onNodeDblClick : function(e, node){
  1034.         node.ui.onDblClick(e);
  1035.     },
  1036.     onNodeContextMenu : function(e, node){
  1037.         node.ui.onContextMenu(e);
  1038.     },
  1039.     beforeEvent : function(e){
  1040.         if(this.disabled){
  1041.             e.stopEvent();
  1042.             return false;
  1043.         }
  1044.         return true;
  1045.     },
  1046.     disable: function(){
  1047.         this.disabled = true;
  1048.     },
  1049.     enable: function(){
  1050.         this.disabled = false;
  1051.     }
  1052. };/**
  1053.  * @class Ext.tree.DefaultSelectionModel
  1054.  * @extends Ext.util.Observable
  1055.  * The default single selection for a TreePanel.
  1056.  */
  1057. Ext.tree.DefaultSelectionModel = function(config){
  1058.    this.selNode = null;
  1059.    
  1060.    this.addEvents(
  1061.        /**
  1062.         * @event selectionchange
  1063.         * Fires when the selected node changes
  1064.         * @param {DefaultSelectionModel} this
  1065.         * @param {TreeNode} node the new selection
  1066.         */
  1067.        'selectionchange',
  1068.        /**
  1069.         * @event beforeselect
  1070.         * Fires before the selected node changes, return false to cancel the change
  1071.         * @param {DefaultSelectionModel} this
  1072.         * @param {TreeNode} node the new selection
  1073.         * @param {TreeNode} node the old selection
  1074.         */
  1075.        'beforeselect'
  1076.    );
  1077.     Ext.apply(this, config);
  1078.     Ext.tree.DefaultSelectionModel.superclass.constructor.call(this);
  1079. };
  1080. Ext.extend(Ext.tree.DefaultSelectionModel, Ext.util.Observable, {
  1081.     init : function(tree){
  1082.         this.tree = tree;
  1083.         tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);
  1084.         tree.on('click', this.onNodeClick, this);
  1085.     },
  1086.     
  1087.     onNodeClick : function(node, e){
  1088.         this.select(node);
  1089.     },
  1090.     
  1091.     /**
  1092.      * Select a node.
  1093.      * @param {TreeNode} node The node to select
  1094.      * @return {TreeNode} The selected node
  1095.      */
  1096.     select : function(node, /* private*/ selectNextNode){
  1097.         // If node is hidden, select the next node in whatever direction was being moved in.
  1098.         if (!Ext.fly(node.ui.wrap).isVisible() && selectNextNode) {
  1099.             return selectNextNode.call(this, node);
  1100.         }
  1101.         var last = this.selNode;
  1102.         if(node == last){
  1103.             node.ui.onSelectedChange(true);
  1104.         }else if(this.fireEvent('beforeselect', this, node, last) !== false){
  1105.             if(last && last.ui){
  1106.                 last.ui.onSelectedChange(false);
  1107.             }
  1108.             this.selNode = node;
  1109.             node.ui.onSelectedChange(true);
  1110.             this.fireEvent('selectionchange', this, node, last);
  1111.         }
  1112.         return node;
  1113.     },
  1114.     
  1115.     /**
  1116.      * Deselect a node.
  1117.      * @param {TreeNode} node The node to unselect
  1118.      * @param {Boolean} silent True to stop the selectionchange event from firing.
  1119.      */
  1120.     unselect : function(node, silent){
  1121.         if(this.selNode == node){
  1122.             this.clearSelections(silent);
  1123.         }    
  1124.     },
  1125.     
  1126.     /**
  1127.      * Clear all selections
  1128.      * @param {Boolean} silent True to stop the selectionchange event from firing.
  1129.      */
  1130.     clearSelections : function(silent){
  1131.         var n = this.selNode;
  1132.         if(n){
  1133.             n.ui.onSelectedChange(false);
  1134.             this.selNode = null;
  1135.             if(silent !== true){
  1136.                 this.fireEvent('selectionchange', this, null);
  1137.             }
  1138.         }
  1139.         return n;
  1140.     },
  1141.     
  1142.     /**
  1143.      * Get the selected node
  1144.      * @return {TreeNode} The selected node
  1145.      */
  1146.     getSelectedNode : function(){
  1147.         return this.selNode;    
  1148.     },
  1149.     
  1150.     /**
  1151.      * Returns true if the node is selected
  1152.      * @param {TreeNode} node The node to check
  1153.      * @return {Boolean}
  1154.      */
  1155.     isSelected : function(node){
  1156.         return this.selNode == node;  
  1157.     },
  1158.     /**
  1159.      * Selects the node above the selected node in the tree, intelligently walking the nodes
  1160.      * @return TreeNode The new selection
  1161.      */
  1162.     selectPrevious : function(/* private */ s){
  1163.         if(!(s = s || this.selNode || this.lastSelNode)){
  1164.             return null;
  1165.         }
  1166.         // Here we pass in the current function to select to indicate the direction we're moving
  1167.         var ps = s.previousSibling;
  1168.         if(ps){
  1169.             if(!ps.isExpanded() || ps.childNodes.length < 1){
  1170.                 return this.select(ps, this.selectPrevious);
  1171.             } else{
  1172.                 var lc = ps.lastChild;
  1173.                 while(lc && lc.isExpanded() && Ext.fly(lc.ui.wrap).isVisible() && lc.childNodes.length > 0){
  1174.                     lc = lc.lastChild;
  1175.                 }
  1176.                 return this.select(lc, this.selectPrevious);
  1177.             }
  1178.         } else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){
  1179.             return this.select(s.parentNode, this.selectPrevious);
  1180.         }
  1181.         return null;
  1182.     },
  1183.     /**
  1184.      * Selects the node above the selected node in the tree, intelligently walking the nodes
  1185.      * @return TreeNode The new selection
  1186.      */
  1187.     selectNext : function(/* private */ s){
  1188.         if(!(s = s || this.selNode || this.lastSelNode)){
  1189.             return null;
  1190.         }
  1191.         // Here we pass in the current function to select to indicate the direction we're moving
  1192.         if(s.firstChild && s.isExpanded() && Ext.fly(s.ui.wrap).isVisible()){
  1193.              return this.select(s.firstChild, this.selectNext);
  1194.          }else if(s.nextSibling){
  1195.              return this.select(s.nextSibling, this.selectNext);
  1196.          }else if(s.parentNode){
  1197.             var newS = null;
  1198.             s.parentNode.bubble(function(){
  1199.                 if(this.nextSibling){
  1200.                     newS = this.getOwnerTree().selModel.select(this.nextSibling, this.selectNext);
  1201.                     return false;
  1202.                 }
  1203.             });
  1204.             return newS;
  1205.          }
  1206.         return null;
  1207.     },
  1208.     onKeyDown : function(e){
  1209.         var s = this.selNode || this.lastSelNode;
  1210.         // undesirable, but required
  1211.         var sm = this;
  1212.         if(!s){
  1213.             return;
  1214.         }
  1215.         var k = e.getKey();
  1216.         switch(k){
  1217.              case e.DOWN:
  1218.                  e.stopEvent();
  1219.                  this.selectNext();
  1220.              break;
  1221.              case e.UP:
  1222.                  e.stopEvent();
  1223.                  this.selectPrevious();
  1224.              break;
  1225.              case e.RIGHT:
  1226.                  e.preventDefault();
  1227.                  if(s.hasChildNodes()){
  1228.                      if(!s.isExpanded()){
  1229.                          s.expand();
  1230.                      }else if(s.firstChild){
  1231.                          this.select(s.firstChild, e);
  1232.                      }
  1233.                  }
  1234.              break;
  1235.              case e.LEFT:
  1236.                  e.preventDefault();
  1237.                  if(s.hasChildNodes() && s.isExpanded()){
  1238.                      s.collapse();
  1239.                  }else if(s.parentNode && (this.tree.rootVisible || s.parentNode != this.tree.getRootNode())){
  1240.                      this.select(s.parentNode, e);
  1241.                  }
  1242.              break;
  1243.         };
  1244.     }
  1245. });
  1246. /**
  1247.  * @class Ext.tree.MultiSelectionModel
  1248.  * @extends Ext.util.Observable
  1249.  * Multi selection for a TreePanel.
  1250.  */
  1251. Ext.tree.MultiSelectionModel = function(config){
  1252.    this.selNodes = [];
  1253.    this.selMap = {};
  1254.    this.addEvents(
  1255.        /**
  1256.         * @event selectionchange
  1257.         * Fires when the selected nodes change
  1258.         * @param {MultiSelectionModel} this
  1259.         * @param {Array} nodes Array of the selected nodes
  1260.         */
  1261.        'selectionchange'
  1262.    );
  1263.     Ext.apply(this, config);
  1264.     Ext.tree.MultiSelectionModel.superclass.constructor.call(this);
  1265. };
  1266. Ext.extend(Ext.tree.MultiSelectionModel, Ext.util.Observable, {
  1267.     init : function(tree){
  1268.         this.tree = tree;
  1269.         tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);
  1270.         tree.on('click', this.onNodeClick, this);
  1271.     },
  1272.     
  1273.     onNodeClick : function(node, e){
  1274.         if(e.ctrlKey && this.isSelected(node)){
  1275.             this.unselect(node);
  1276.         }else{
  1277.             this.select(node, e, e.ctrlKey);
  1278.         }
  1279.     },
  1280.     
  1281.     /**
  1282.      * Select a node.
  1283.      * @param {TreeNode} node The node to select
  1284.      * @param {EventObject} e (optional) An event associated with the selection
  1285.      * @param {Boolean} keepExisting True to retain existing selections
  1286.      * @return {TreeNode} The selected node
  1287.      */
  1288.     select : function(node, e, keepExisting){
  1289.         if(keepExisting !== true){
  1290.             this.clearSelections(true);
  1291.         }
  1292.         if(this.isSelected(node)){
  1293.             this.lastSelNode = node;
  1294.             return node;
  1295.         }
  1296.         this.selNodes.push(node);
  1297.         this.selMap[node.id] = node;
  1298.         this.lastSelNode = node;
  1299.         node.ui.onSelectedChange(true);
  1300.         this.fireEvent('selectionchange', this, this.selNodes);
  1301.         return node;
  1302.     },
  1303.     
  1304.     /**
  1305.      * Deselect a node.
  1306.      * @param {TreeNode} node The node to unselect
  1307.      */
  1308.     unselect : function(node){
  1309.         if(this.selMap[node.id]){
  1310.             node.ui.onSelectedChange(false);
  1311.             var sn = this.selNodes;
  1312.             var index = sn.indexOf(node);
  1313.             if(index != -1){
  1314.                 this.selNodes.splice(index, 1);
  1315.             }
  1316.             delete this.selMap[node.id];
  1317.             this.fireEvent('selectionchange', this, this.selNodes);
  1318.         }
  1319.     },
  1320.     
  1321.     /**
  1322.      * Clear all selections
  1323.      */
  1324.     clearSelections : function(suppressEvent){
  1325.         var sn = this.selNodes;
  1326.         if(sn.length > 0){
  1327.             for(var i = 0, len = sn.length; i < len; i++){
  1328.                 sn[i].ui.onSelectedChange(false);
  1329.             }
  1330.             this.selNodes = [];
  1331.             this.selMap = {};
  1332.             if(suppressEvent !== true){
  1333.                 this.fireEvent('selectionchange', this, this.selNodes);
  1334.             }
  1335.         }
  1336.     },
  1337.     
  1338.     /**
  1339.      * Returns true if the node is selected
  1340.      * @param {TreeNode} node The node to check
  1341.      * @return {Boolean}
  1342.      */
  1343.     isSelected : function(node){
  1344.         return this.selMap[node.id] ? true : false;  
  1345.     },
  1346.     
  1347.     /**
  1348.      * Returns an array of the selected nodes
  1349.      * @return {Array}
  1350.      */
  1351.     getSelectedNodes : function(){
  1352.         return this.selNodes;    
  1353.     },
  1354.     onKeyDown : Ext.tree.DefaultSelectionModel.prototype.onKeyDown,
  1355.     selectNext : Ext.tree.DefaultSelectionModel.prototype.selectNext,
  1356.     selectPrevious : Ext.tree.DefaultSelectionModel.prototype.selectPrevious
  1357. });/**
  1358.  * @class Ext.data.Tree
  1359.  * @extends Ext.util.Observable
  1360.  * Represents a tree data structure and bubbles all the events for its nodes. The nodes
  1361.  * in the tree have most standard DOM functionality.
  1362.  * @constructor
  1363.  * @param {Node} root (optional) The root node
  1364.  */
  1365. Ext.data.Tree = function(root){
  1366.    this.nodeHash = {};
  1367.    /**
  1368.     * The root node for this tree
  1369.     * @type Node
  1370.     */
  1371.    this.root = null;
  1372.    if(root){
  1373.        this.setRootNode(root);
  1374.    }
  1375.    this.addEvents(
  1376.        /**
  1377.         * @event append
  1378.         * Fires when a new child node is appended to a node in this tree.
  1379.         * @param {Tree} tree The owner tree
  1380.         * @param {Node} parent The parent node
  1381.         * @param {Node} node The newly appended node
  1382.         * @param {Number} index The index of the newly appended node
  1383.         */
  1384.        "append",
  1385.        /**
  1386.         * @event remove
  1387.         * Fires when a child node is removed from a node in this tree.
  1388.         * @param {Tree} tree The owner tree
  1389.         * @param {Node} parent The parent node
  1390.         * @param {Node} node The child node removed
  1391.         */
  1392.        "remove",
  1393.        /**
  1394.         * @event move
  1395.         * Fires when a node is moved to a new location in the tree
  1396.         * @param {Tree} tree The owner tree
  1397.         * @param {Node} node The node moved
  1398.         * @param {Node} oldParent The old parent of this node
  1399.         * @param {Node} newParent The new parent of this node
  1400.         * @param {Number} index The index it was moved to
  1401.         */
  1402.        "move",
  1403.        /**
  1404.         * @event insert
  1405.         * Fires when a new child node is inserted in a node in this tree.
  1406.         * @param {Tree} tree The owner tree
  1407.         * @param {Node} parent The parent node
  1408.         * @param {Node} node The child node inserted
  1409.         * @param {Node} refNode The child node the node was inserted before
  1410.         */
  1411.        "insert",
  1412.        /**
  1413.         * @event beforeappend
  1414.         * Fires before a new child is appended to a node in this tree, return false to cancel the append.
  1415.         * @param {Tree} tree The owner tree
  1416.         * @param {Node} parent The parent node
  1417.         * @param {Node} node The child node to be appended
  1418.         */
  1419.        "beforeappend",
  1420.        /**
  1421.         * @event beforeremove
  1422.         * Fires before a child is removed from a node in this tree, return false to cancel the remove.
  1423.         * @param {Tree} tree The owner tree
  1424.         * @param {Node} parent The parent node
  1425.         * @param {Node} node The child node to be removed
  1426.         */
  1427.        "beforeremove",
  1428.        /**
  1429.         * @event beforemove
  1430.         * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
  1431.         * @param {Tree} tree The owner tree
  1432.         * @param {Node} node The node being moved
  1433.         * @param {Node} oldParent The parent of the node
  1434.         * @param {Node} newParent The new parent the node is moving to
  1435.         * @param {Number} index The index it is being moved to
  1436.         */
  1437.        "beforemove",
  1438.        /**
  1439.         * @event beforeinsert
  1440.         * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
  1441.         * @param {Tree} tree The owner tree
  1442.         * @param {Node} parent The parent node
  1443.         * @param {Node} node The child node to be inserted
  1444.         * @param {Node} refNode The child node the node is being inserted before
  1445.         */
  1446.        "beforeinsert"
  1447.    );
  1448.     Ext.data.Tree.superclass.constructor.call(this);
  1449. };
  1450. Ext.extend(Ext.data.Tree, Ext.util.Observable, {
  1451.     /**
  1452.      * @cfg {String} pathSeparator
  1453.      * The token used to separate paths in node ids (defaults to '/').
  1454.      */
  1455.     pathSeparator: "/",
  1456.     // private
  1457.     proxyNodeEvent : function(){
  1458.         return this.fireEvent.apply(this, arguments);
  1459.     },
  1460.     /**
  1461.      * Returns the root node for this tree.
  1462.      * @return {Node}
  1463.      */
  1464.     getRootNode : function(){
  1465.         return this.root;
  1466.     },
  1467.     /**
  1468.      * Sets the root node for this tree.
  1469.      * @param {Node} node
  1470.      * @return {Node}
  1471.      */
  1472.     setRootNode : function(node){
  1473.         this.root = node;
  1474.         node.ownerTree = this;
  1475.         node.isRoot = true;
  1476.         this.registerNode(node);
  1477.         return node;
  1478.     },
  1479.     /**
  1480.      * Gets a node in this tree by its id.
  1481.      * @param {String} id
  1482.      * @return {Node}
  1483.      */
  1484.     getNodeById : function(id){
  1485.         return this.nodeHash[id];
  1486.     },
  1487.     // private
  1488.     registerNode : function(node){
  1489.         this.nodeHash[node.id] = node;
  1490.     },
  1491.     // private
  1492.     unregisterNode : function(node){
  1493.         delete this.nodeHash[node.id];
  1494.     },
  1495.     toString : function(){
  1496.         return "[Tree"+(this.id?" "+this.id:"")+"]";
  1497.     }
  1498. });
  1499. /**
  1500.  * @class Ext.data.Node
  1501.  * @extends Ext.util.Observable
  1502.  * @cfg {Boolean} leaf true if this node is a leaf and does not have children
  1503.  * @cfg {String} id The id for this node. If one is not specified, one is generated.
  1504.  * @constructor
  1505.  * @param {Object} attributes The attributes/config for the node
  1506.  */
  1507. Ext.data.Node = function(attributes){
  1508.     /**
  1509.      * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
  1510.      * @type {Object}
  1511.      */
  1512.     this.attributes = attributes || {};
  1513.     this.leaf = this.attributes.leaf;
  1514.     /**
  1515.      * The node id. @type String
  1516.      */
  1517.     this.id = this.attributes.id;
  1518.     if(!this.id){
  1519.         this.id = Ext.id(null, "xnode-");
  1520.         this.attributes.id = this.id;
  1521.     }
  1522.     /**
  1523.      * All child nodes of this node. @type Array
  1524.      */
  1525.     this.childNodes = [];
  1526.     if(!this.childNodes.indexOf){ // indexOf is a must
  1527.         this.childNodes.indexOf = function(o){
  1528.             for(var i = 0, len = this.length; i < len; i++){
  1529.                 if(this[i] == o){
  1530.                     return i;
  1531.                 }
  1532.             }
  1533.             return -1;
  1534.         };
  1535.     }
  1536.     /**
  1537.      * The parent node for this node. @type Node
  1538.      */
  1539.     this.parentNode = null;
  1540.     /**
  1541.      * The first direct child node of this node, or null if this node has no child nodes. @type Node
  1542.      */
  1543.     this.firstChild = null;
  1544.     /**
  1545.      * The last direct child node of this node, or null if this node has no child nodes. @type Node
  1546.      */
  1547.     this.lastChild = null;
  1548.     /**
  1549.      * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
  1550.      */
  1551.     this.previousSibling = null;
  1552.     /**
  1553.      * The node immediately following this node in the tree, or null if there is no sibling node. @type Node
  1554.      */
  1555.     this.nextSibling = null;
  1556.     this.addEvents({
  1557.        /**
  1558.         * @event append
  1559.         * Fires when a new child node is appended
  1560.         * @param {Tree} tree The owner tree
  1561.         * @param {Node} this This node
  1562.         * @param {Node} node The newly appended node
  1563.         * @param {Number} index The index of the newly appended node
  1564.         */
  1565.        "append" : true,
  1566.        /**
  1567.         * @event remove
  1568.         * Fires when a child node is removed
  1569.         * @param {Tree} tree The owner tree
  1570.         * @param {Node} this This node
  1571.         * @param {Node} node The removed node
  1572.         */
  1573.        "remove" : true,
  1574.        /**
  1575.         * @event move
  1576.         * Fires when this node is moved to a new location in the tree
  1577.         * @param {Tree} tree The owner tree
  1578.         * @param {Node} this This node
  1579.         * @param {Node} oldParent The old parent of this node
  1580.         * @param {Node} newParent The new parent of this node
  1581.         * @param {Number} index The index it was moved to
  1582.         */
  1583.        "move" : true,
  1584.        /**
  1585.         * @event insert
  1586.         * Fires when a new child node is inserted.
  1587.         * @param {Tree} tree The owner tree
  1588.         * @param {Node} this This node
  1589.         * @param {Node} node The child node inserted
  1590.         * @param {Node} refNode The child node the node was inserted before
  1591.         */
  1592.        "insert" : true,
  1593.        /**
  1594.         * @event beforeappend
  1595.         * Fires before a new child is appended, return false to cancel the append.
  1596.         * @param {Tree} tree The owner tree
  1597.         * @param {Node} this This node
  1598.         * @param {Node} node The child node to be appended
  1599.         */
  1600.        "beforeappend" : true,
  1601.        /**
  1602.         * @event beforeremove
  1603.         * Fires before a child is removed, return false to cancel the remove.
  1604.         * @param {Tree} tree The owner tree
  1605.         * @param {Node} this This node
  1606.         * @param {Node} node The child node to be removed
  1607.         */
  1608.        "beforeremove" : true,
  1609.        /**
  1610.         * @event beforemove
  1611.         * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
  1612.         * @param {Tree} tree The owner tree
  1613.         * @param {Node} this This node
  1614.         * @param {Node} oldParent The parent of this node
  1615.         * @param {Node} newParent The new parent this node is moving to
  1616.         * @param {Number} index The index it is being moved to
  1617.         */
  1618.        "beforemove" : true,
  1619.        /**
  1620.         * @event beforeinsert
  1621.         * Fires before a new child is inserted, return false to cancel the insert.
  1622.         * @param {Tree} tree The owner tree
  1623.         * @param {Node} this This node
  1624.         * @param {Node} node The child node to be inserted
  1625.         * @param {Node} refNode The child node the node is being inserted before
  1626.         */
  1627.        "beforeinsert" : true
  1628.    });
  1629.     this.listeners = this.attributes.listeners;
  1630.     Ext.data.Node.superclass.constructor.call(this);
  1631. };
  1632. Ext.extend(Ext.data.Node, Ext.util.Observable, {
  1633.     // private
  1634.     fireEvent : function(evtName){
  1635.         // first do standard event for this node
  1636.         if(Ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
  1637.             return false;
  1638.         }
  1639.         // then bubble it up to the tree if the event wasn't cancelled
  1640.         var ot = this.getOwnerTree();
  1641.         if(ot){
  1642.             if(ot.proxyNodeEvent.apply(ot, arguments) === false){
  1643.                 return false;
  1644.             }
  1645.         }
  1646.         return true;
  1647.     },
  1648.     /**
  1649.      * Returns true if this node is a leaf
  1650.      * @return {Boolean}
  1651.      */
  1652.     isLeaf : function(){
  1653.         return this.leaf === true;
  1654.     },
  1655.     // private
  1656.     setFirstChild : function(node){
  1657.         this.firstChild = node;
  1658.     },
  1659.     //private
  1660.     setLastChild : function(node){
  1661.         this.lastChild = node;
  1662.     },
  1663.     /**
  1664.      * Returns true if this node is the last child of its parent
  1665.      * @return {Boolean}
  1666.      */
  1667.     isLast : function(){
  1668.        return (!this.parentNode ? true : this.parentNode.lastChild == this);
  1669.     },
  1670.     /**
  1671.      * Returns true if this node is the first child of its parent
  1672.      * @return {Boolean}
  1673.      */
  1674.     isFirst : function(){
  1675.        return (!this.parentNode ? true : this.parentNode.firstChild == this);
  1676.     },
  1677.     /**
  1678.      * Returns true if this node has one or more child nodes, else false.
  1679.      * @return {Boolean}
  1680.      */
  1681.     hasChildNodes : function(){
  1682.         return !this.isLeaf() && this.childNodes.length > 0;
  1683.     },
  1684.     
  1685.     /**
  1686.      * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
  1687.      * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
  1688.      * @return {Boolean}
  1689.      */
  1690.     isExpandable : function(){
  1691.         return this.attributes.expandable || this.hasChildNodes();
  1692.     },
  1693.     /**
  1694.      * Insert node(s) as the last child node of this node.
  1695.      * @param {Node/Array} node The node or Array of nodes to append
  1696.      * @return {Node} The appended node if single append, or null if an array was passed
  1697.      */
  1698.     appendChild : function(node){
  1699.         var multi = false;
  1700.         if(Ext.isArray(node)){
  1701.             multi = node;
  1702.         }else if(arguments.length > 1){
  1703.             multi = arguments;
  1704.         }
  1705.         // if passed an array or multiple args do them one by one
  1706.         if(multi){
  1707.             for(var i = 0, len = multi.length; i < len; i++) {
  1708.              this.appendChild(multi[i]);
  1709.             }
  1710.         }else{
  1711.             if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){
  1712.                 return false;
  1713.             }
  1714.             var index = this.childNodes.length;
  1715.             var oldParent = node.parentNode;
  1716.             // it's a move, make sure we move it cleanly
  1717.             if(oldParent){
  1718.                 if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){
  1719.                     return false;
  1720.                 }
  1721.                 oldParent.removeChild(node);
  1722.             }
  1723.             index = this.childNodes.length;
  1724.             if(index === 0){
  1725.                 this.setFirstChild(node);
  1726.             }
  1727.             this.childNodes.push(node);
  1728.             node.parentNode = this;
  1729.             var ps = this.childNodes[index-1];
  1730.             if(ps){
  1731.                 node.previousSibling = ps;
  1732.                 ps.nextSibling = node;
  1733.             }else{
  1734.                 node.previousSibling = null;
  1735.             }
  1736.             node.nextSibling = null;
  1737.             this.setLastChild(node);
  1738.             node.setOwnerTree(this.getOwnerTree());
  1739.             this.fireEvent("append", this.ownerTree, this, node, index);
  1740.             if(oldParent){
  1741.                 node.fireEvent("move", this.ownerTree, node, oldParent, this, index);
  1742.             }
  1743.             return node;
  1744.         }
  1745.     },
  1746.     /**
  1747.      * Removes a child node from this node.
  1748.      * @param {Node} node The node to remove
  1749.      * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
  1750.      * @return {Node} The removed node
  1751.      */
  1752.     removeChild : function(node, destroy){
  1753.         var index = this.childNodes.indexOf(node);
  1754.         if(index == -1){
  1755.             return false;
  1756.         }
  1757.         if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){
  1758.             return false;
  1759.         }
  1760.         // remove it from childNodes collection
  1761.         this.childNodes.splice(index, 1);
  1762.         // update siblings
  1763.         if(node.previousSibling){
  1764.             node.previousSibling.nextSibling = node.nextSibling;
  1765.         }
  1766.         if(node.nextSibling){
  1767.             node.nextSibling.previousSibling = node.previousSibling;
  1768.         }
  1769.         // update child refs
  1770.         if(this.firstChild == node){
  1771.             this.setFirstChild(node.nextSibling);
  1772.         }
  1773.         if(this.lastChild == node){
  1774.             this.setLastChild(node.previousSibling);
  1775.         }
  1776.         node.clear();
  1777.         this.fireEvent("remove", this.ownerTree, this, node);
  1778.         if(destroy){
  1779.             node.destroy();
  1780.         }
  1781.         return node;
  1782.     },
  1783.     
  1784.     // private
  1785.     clear : function(destroy){
  1786.         // clear any references from the node
  1787.         this.setOwnerTree(null, destroy);
  1788.         this.parentNode = this.previousSibling = this.nextSibling = null
  1789.         if(destroy){
  1790.             this.firstChild = this.lastChild = null; 
  1791.         }
  1792.     },
  1793.     
  1794.     /**
  1795.      * Destroys the node.
  1796.      */
  1797.     destroy : function(){
  1798.         this.purgeListeners();
  1799.         this.clear(true);  
  1800.         Ext.each(this.childNodes, function(n){
  1801.             n.destroy();
  1802.         });
  1803.         this.childNodes = null;
  1804.     },
  1805.     /**
  1806.      * Inserts the first node before the second node in this nodes childNodes collection.
  1807.      * @param {Node} node The node to insert
  1808.      * @param {Node} refNode The node to insert before (if null the node is appended)
  1809.      * @return {Node} The inserted node
  1810.      */
  1811.     insertBefore : function(node, refNode){
  1812.         if(!refNode){ // like standard Dom, refNode can be null for append
  1813.             return this.appendChild(node);
  1814.         }
  1815.         // nothing to do
  1816.         if(node == refNode){
  1817.             return false;
  1818.         }
  1819.         if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){
  1820.             return false;
  1821.         }
  1822.         var index = this.childNodes.indexOf(refNode);
  1823.         var oldParent = node.parentNode;
  1824.         var refIndex = index;
  1825.         // when moving internally, indexes will change after remove
  1826.         if(oldParent == this && this.childNodes.indexOf(node) < index){
  1827.             refIndex--;
  1828.         }
  1829.         // it's a move, make sure we move it cleanly
  1830.         if(oldParent){
  1831.             if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
  1832.                 return false;
  1833.             }
  1834.             oldParent.removeChild(node);
  1835.         }
  1836.         if(refIndex === 0){
  1837.             this.setFirstChild(node);
  1838.         }
  1839.         this.childNodes.splice(refIndex, 0, node);
  1840.         node.parentNode = this;
  1841.         var ps = this.childNodes[refIndex-1];
  1842.         if(ps){
  1843.             node.previousSibling = ps;
  1844.             ps.nextSibling = node;
  1845.         }else{
  1846.             node.previousSibling = null;
  1847.         }
  1848.         node.nextSibling = refNode;
  1849.         refNode.previousSibling = node;
  1850.         node.setOwnerTree(this.getOwnerTree());
  1851.         this.fireEvent("insert", this.ownerTree, this, node, refNode);
  1852.         if(oldParent){
  1853.             node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);
  1854.         }
  1855.         return node;
  1856.     },
  1857.     /**
  1858.      * Removes this node from its parent
  1859.      * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
  1860.      * @return {Node} this
  1861.      */
  1862.     remove : function(destroy){
  1863.         this.parentNode.removeChild(this, destroy);
  1864.         return this;
  1865.     },
  1866.     /**
  1867.      * Returns the child node at the specified index.
  1868.      * @param {Number} index
  1869.      * @return {Node}
  1870.      */
  1871.     item : function(index){
  1872.         return this.childNodes[index];
  1873.     },
  1874.     /**
  1875.      * Replaces one child node in this node with another.
  1876.      * @param {Node} newChild The replacement node
  1877.      * @param {Node} oldChild The node to replace
  1878.      * @return {Node} The replaced node
  1879.      */
  1880.     replaceChild : function(newChild, oldChild){
  1881.         var s = oldChild ? oldChild.nextSibling : null;
  1882.         this.removeChild(oldChild);
  1883.         this.insertBefore(newChild, s);
  1884.         return oldChild;
  1885.     },
  1886.     /**
  1887.      * Returns the index of a child node
  1888.      * @param {Node} node
  1889.      * @return {Number} The index of the node or -1 if it was not found
  1890.      */
  1891.     indexOf : function(child){
  1892.         return this.childNodes.indexOf(child);
  1893.     },
  1894.     /**
  1895.      * Returns the tree this node is in.
  1896.      * @return {Tree}
  1897.      */
  1898.     getOwnerTree : function(){
  1899.         // if it doesn't have one, look for one
  1900.         if(!this.ownerTree){
  1901.             var p = this;
  1902.             while(p){
  1903.                 if(p.ownerTree){
  1904.                     this.ownerTree = p.ownerTree;
  1905.                     break;
  1906.                 }
  1907.                 p = p.parentNode;
  1908.             }
  1909.         }
  1910.         return this.ownerTree;
  1911.     },
  1912.     /**
  1913.      * Returns depth of this node (the root node has a depth of 0)
  1914.      * @return {Number}
  1915.      */
  1916.     getDepth : function(){
  1917.         var depth = 0;
  1918.         var p = this;
  1919.         while(p.parentNode){
  1920.             ++depth;
  1921.             p = p.parentNode;
  1922.         }
  1923.         return depth;
  1924.     },
  1925.     // private
  1926.     setOwnerTree : function(tree, destroy){
  1927.         // if it is a move, we need to update everyone
  1928.         if(tree != this.ownerTree){
  1929.             if(this.ownerTree){
  1930.                 this.ownerTree.unregisterNode(this);
  1931.             }
  1932.             this.ownerTree = tree;
  1933.             // If we're destroying, we don't need to recurse since it will be called on each child node
  1934.             if(destroy !== true){
  1935.                 Ext.each(this.childNodes, function(n){
  1936.                     n.setOwnerTree(tree);
  1937.                 });
  1938.             }
  1939.             if(tree){
  1940.                 tree.registerNode(this);
  1941.             }
  1942.         }
  1943.     },
  1944.     
  1945.     /**
  1946.      * Changes the id of this node.
  1947.      * @param {String} id The new id for the node.
  1948.      */
  1949.     setId: function(id){
  1950.         if(id !== this.id){
  1951.             var t = this.ownerTree;
  1952.             if(t){
  1953.                 t.unregisterNode(this);
  1954.             }
  1955.             this.id = this.attributes.id = id;
  1956.             if(t){
  1957.                 t.registerNode(this);
  1958.             }
  1959.             this.onIdChange(id);
  1960.         }
  1961.     },
  1962.     
  1963.     // private
  1964.     onIdChange: Ext.emptyFn,
  1965.     /**
  1966.      * Returns the path for this node. The path can be used to expand or select this node programmatically.
  1967.      * @param {String} attr (optional) The attr to use for the path (defaults to the node's id)
  1968.      * @return {String} The path
  1969.      */
  1970.     getPath : function(attr){
  1971.         attr = attr || "id";
  1972.         var p = this.parentNode;
  1973.         var b = [this.attributes[attr]];
  1974.         while(p){
  1975.             b.unshift(p.attributes[attr]);
  1976.             p = p.parentNode;
  1977.         }
  1978.         var sep = this.getOwnerTree().pathSeparator;
  1979.         return sep + b.join(sep);
  1980.     },
  1981.     /**
  1982.      * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
  1983.      * will be the args provided or the current node. If the function returns false at any point,
  1984.      * the bubble is stopped.
  1985.      * @param {Function} fn The function to call
  1986.      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
  1987.      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
  1988.      */
  1989.     bubble : function(fn, scope, args){
  1990.         var p = this;
  1991.         while(p){
  1992.             if(fn.apply(scope || p, args || [p]) === false){
  1993.                 break;
  1994.             }
  1995.             p = p.parentNode;
  1996.         }
  1997.     },
  1998.     /**
  1999.      * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
  2000.      * will be the args provided or the current node. If the function returns false at any point,
  2001.      * the cascade is stopped on that branch.
  2002.      * @param {Function} fn The function to call
  2003.      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
  2004.      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
  2005.      */
  2006.     cascade : function(fn, scope, args){
  2007.         if(fn.apply(scope || this, args || [this]) !== false){
  2008.             var cs = this.childNodes;
  2009.             for(var i = 0, len = cs.length; i < len; i++) {
  2010.              cs[i].cascade(fn, scope, args);
  2011.             }
  2012.         }
  2013.     },
  2014.     /**
  2015.      * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
  2016.      * will be the args provided or the current node. If the function returns false at any point,
  2017.      * the iteration stops.
  2018.      * @param {Function} fn The function to call
  2019.      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node in the iteration.
  2020.      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
  2021.      */
  2022.     eachChild : function(fn, scope, args){
  2023.         var cs = this.childNodes;
  2024.         for(var i = 0, len = cs.length; i < len; i++) {
  2025.          if(fn.apply(scope || this, args || [cs[i]]) === false){
  2026.              break;
  2027.          }
  2028.         }
  2029.     },
  2030.     /**
  2031.      * Finds the first child that has the attribute with the specified value.
  2032.      * @param {String} attribute The attribute name
  2033.      * @param {Mixed} value The value to search for
  2034.      * @return {Node} The found child or null if none was found
  2035.      */
  2036.     findChild : function(attribute, value){
  2037.         var cs = this.childNodes;
  2038.         for(var i = 0, len = cs.length; i < len; i++) {
  2039.          if(cs[i].attributes[attribute] == value){
  2040.              return cs[i];
  2041.          }
  2042.         }
  2043.         return null;
  2044.     },
  2045.     /**
  2046.      * Finds the first child by a custom function. The child matches if the function passed returns <code>true</code>.
  2047.      * @param {Function} fn A function which must return <code>true</code> if the passed Node is the required Node.
  2048.      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Node being tested.
  2049.      * @return {Node} The found child or null if none was found
  2050.      */
  2051.     findChildBy : function(fn, scope){
  2052.         var cs = this.childNodes;
  2053.         for(var i = 0, len = cs.length; i < len; i++) {