pkg-tree-debug.js
上传用户:shuoshiled
上传日期:2018-01-28
资源大小:10124k
文件大小:145k
源码类别:

中间件编程

开发平台:

JavaScript

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