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

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.data.Tree
  3.  * @extends Ext.util.Observable
  4.  * Represents a tree data structure and bubbles all the events for its nodes. The nodes
  5.  * in the tree have most standard DOM functionality.
  6.  * @constructor
  7.  * @param {Node} root (optional) The root node
  8.  */
  9. Ext.data.Tree = function(root){
  10.    this.nodeHash = {};
  11.    /**
  12.     * The root node for this tree
  13.     * @type Node
  14.     */
  15.    this.root = null;
  16.    if(root){
  17.        this.setRootNode(root);
  18.    }
  19.    this.addEvents(
  20.        /**
  21.         * @event append
  22.         * Fires when a new child node is appended to a node in this tree.
  23.         * @param {Tree} tree The owner tree
  24.         * @param {Node} parent The parent node
  25.         * @param {Node} node The newly appended node
  26.         * @param {Number} index The index of the newly appended node
  27.         */
  28.        "append",
  29.        /**
  30.         * @event remove
  31.         * Fires when a child node is removed from a node in this tree.
  32.         * @param {Tree} tree The owner tree
  33.         * @param {Node} parent The parent node
  34.         * @param {Node} node The child node removed
  35.         */
  36.        "remove",
  37.        /**
  38.         * @event move
  39.         * Fires when a node is moved to a new location in the tree
  40.         * @param {Tree} tree The owner tree
  41.         * @param {Node} node The node moved
  42.         * @param {Node} oldParent The old parent of this node
  43.         * @param {Node} newParent The new parent of this node
  44.         * @param {Number} index The index it was moved to
  45.         */
  46.        "move",
  47.        /**
  48.         * @event insert
  49.         * Fires when a new child node is inserted in a node in this tree.
  50.         * @param {Tree} tree The owner tree
  51.         * @param {Node} parent The parent node
  52.         * @param {Node} node The child node inserted
  53.         * @param {Node} refNode The child node the node was inserted before
  54.         */
  55.        "insert",
  56.        /**
  57.         * @event beforeappend
  58.         * Fires before a new child is appended to a node in this tree, return false to cancel the append.
  59.         * @param {Tree} tree The owner tree
  60.         * @param {Node} parent The parent node
  61.         * @param {Node} node The child node to be appended
  62.         */
  63.        "beforeappend",
  64.        /**
  65.         * @event beforeremove
  66.         * Fires before a child is removed from a node in this tree, return false to cancel the remove.
  67.         * @param {Tree} tree The owner tree
  68.         * @param {Node} parent The parent node
  69.         * @param {Node} node The child node to be removed
  70.         */
  71.        "beforeremove",
  72.        /**
  73.         * @event beforemove
  74.         * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
  75.         * @param {Tree} tree The owner tree
  76.         * @param {Node} node The node being moved
  77.         * @param {Node} oldParent The parent of the node
  78.         * @param {Node} newParent The new parent the node is moving to
  79.         * @param {Number} index The index it is being moved to
  80.         */
  81.        "beforemove",
  82.        /**
  83.         * @event beforeinsert
  84.         * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
  85.         * @param {Tree} tree The owner tree
  86.         * @param {Node} parent The parent node
  87.         * @param {Node} node The child node to be inserted
  88.         * @param {Node} refNode The child node the node is being inserted before
  89.         */
  90.        "beforeinsert"
  91.    );
  92.     Ext.data.Tree.superclass.constructor.call(this);
  93. };
  94. Ext.extend(Ext.data.Tree, Ext.util.Observable, {
  95.     /**
  96.      * @cfg {String} pathSeparator
  97.      * The token used to separate paths in node ids (defaults to '/').
  98.      */
  99.     pathSeparator: "/",
  100.     // private
  101.     proxyNodeEvent : function(){
  102.         return this.fireEvent.apply(this, arguments);
  103.     },
  104.     /**
  105.      * Returns the root node for this tree.
  106.      * @return {Node}
  107.      */
  108.     getRootNode : function(){
  109.         return this.root;
  110.     },
  111.     /**
  112.      * Sets the root node for this tree.
  113.      * @param {Node} node
  114.      * @return {Node}
  115.      */
  116.     setRootNode : function(node){
  117.         this.root = node;
  118.         node.ownerTree = this;
  119.         node.isRoot = true;
  120.         this.registerNode(node);
  121.         return node;
  122.     },
  123.     /**
  124.      * Gets a node in this tree by its id.
  125.      * @param {String} id
  126.      * @return {Node}
  127.      */
  128.     getNodeById : function(id){
  129.         return this.nodeHash[id];
  130.     },
  131.     // private
  132.     registerNode : function(node){
  133.         this.nodeHash[node.id] = node;
  134.     },
  135.     // private
  136.     unregisterNode : function(node){
  137.         delete this.nodeHash[node.id];
  138.     },
  139.     toString : function(){
  140.         return "[Tree"+(this.id?" "+this.id:"")+"]";
  141.     }
  142. });
  143. /**
  144.  * @class Ext.data.Node
  145.  * @extends Ext.util.Observable
  146.  * @cfg {Boolean} leaf true if this node is a leaf and does not have children
  147.  * @cfg {String} id The id for this node. If one is not specified, one is generated.
  148.  * @constructor
  149.  * @param {Object} attributes The attributes/config for the node
  150.  */
  151. Ext.data.Node = function(attributes){
  152.     /**
  153.      * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
  154.      * @type {Object}
  155.      */
  156.     this.attributes = attributes || {};
  157.     this.leaf = this.attributes.leaf;
  158.     /**
  159.      * The node id. @type String
  160.      */
  161.     this.id = this.attributes.id;
  162.     if(!this.id){
  163.         this.id = Ext.id(null, "xnode-");
  164.         this.attributes.id = this.id;
  165.     }
  166.     /**
  167.      * All child nodes of this node. @type Array
  168.      */
  169.     this.childNodes = [];
  170.     if(!this.childNodes.indexOf){ // indexOf is a must
  171.         this.childNodes.indexOf = function(o){
  172.             for(var i = 0, len = this.length; i < len; i++){
  173.                 if(this[i] == o){
  174.                     return i;
  175.                 }
  176.             }
  177.             return -1;
  178.         };
  179.     }
  180.     /**
  181.      * The parent node for this node. @type Node
  182.      */
  183.     this.parentNode = null;
  184.     /**
  185.      * The first direct child node of this node, or null if this node has no child nodes. @type Node
  186.      */
  187.     this.firstChild = null;
  188.     /**
  189.      * The last direct child node of this node, or null if this node has no child nodes. @type Node
  190.      */
  191.     this.lastChild = null;
  192.     /**
  193.      * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
  194.      */
  195.     this.previousSibling = null;
  196.     /**
  197.      * The node immediately following this node in the tree, or null if there is no sibling node. @type Node
  198.      */
  199.     this.nextSibling = null;
  200.     this.addEvents({
  201.        /**
  202.         * @event append
  203.         * Fires when a new child node is appended
  204.         * @param {Tree} tree The owner tree
  205.         * @param {Node} this This node
  206.         * @param {Node} node The newly appended node
  207.         * @param {Number} index The index of the newly appended node
  208.         */
  209.        "append" : true,
  210.        /**
  211.         * @event remove
  212.         * Fires when a child node is removed
  213.         * @param {Tree} tree The owner tree
  214.         * @param {Node} this This node
  215.         * @param {Node} node The removed node
  216.         */
  217.        "remove" : true,
  218.        /**
  219.         * @event move
  220.         * Fires when this node is moved to a new location in the tree
  221.         * @param {Tree} tree The owner tree
  222.         * @param {Node} this This node
  223.         * @param {Node} oldParent The old parent of this node
  224.         * @param {Node} newParent The new parent of this node
  225.         * @param {Number} index The index it was moved to
  226.         */
  227.        "move" : true,
  228.        /**
  229.         * @event insert
  230.         * Fires when a new child node is inserted.
  231.         * @param {Tree} tree The owner tree
  232.         * @param {Node} this This node
  233.         * @param {Node} node The child node inserted
  234.         * @param {Node} refNode The child node the node was inserted before
  235.         */
  236.        "insert" : true,
  237.        /**
  238.         * @event beforeappend
  239.         * Fires before a new child is appended, return false to cancel the append.
  240.         * @param {Tree} tree The owner tree
  241.         * @param {Node} this This node
  242.         * @param {Node} node The child node to be appended
  243.         */
  244.        "beforeappend" : true,
  245.        /**
  246.         * @event beforeremove
  247.         * Fires before a child is removed, return false to cancel the remove.
  248.         * @param {Tree} tree The owner tree
  249.         * @param {Node} this This node
  250.         * @param {Node} node The child node to be removed
  251.         */
  252.        "beforeremove" : true,
  253.        /**
  254.         * @event beforemove
  255.         * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
  256.         * @param {Tree} tree The owner tree
  257.         * @param {Node} this This node
  258.         * @param {Node} oldParent The parent of this node
  259.         * @param {Node} newParent The new parent this node is moving to
  260.         * @param {Number} index The index it is being moved to
  261.         */
  262.        "beforemove" : true,
  263.        /**
  264.         * @event beforeinsert
  265.         * Fires before a new child is inserted, return false to cancel the insert.
  266.         * @param {Tree} tree The owner tree
  267.         * @param {Node} this This node
  268.         * @param {Node} node The child node to be inserted
  269.         * @param {Node} refNode The child node the node is being inserted before
  270.         */
  271.        "beforeinsert" : true
  272.    });
  273.     this.listeners = this.attributes.listeners;
  274.     Ext.data.Node.superclass.constructor.call(this);
  275. };
  276. Ext.extend(Ext.data.Node, Ext.util.Observable, {
  277.     // private
  278.     fireEvent : function(evtName){
  279.         // first do standard event for this node
  280.         if(Ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
  281.             return false;
  282.         }
  283.         // then bubble it up to the tree if the event wasn't cancelled
  284.         var ot = this.getOwnerTree();
  285.         if(ot){
  286.             if(ot.proxyNodeEvent.apply(ot, arguments) === false){
  287.                 return false;
  288.             }
  289.         }
  290.         return true;
  291.     },
  292.     /**
  293.      * Returns true if this node is a leaf
  294.      * @return {Boolean}
  295.      */
  296.     isLeaf : function(){
  297.         return this.leaf === true;
  298.     },
  299.     // private
  300.     setFirstChild : function(node){
  301.         this.firstChild = node;
  302.     },
  303.     //private
  304.     setLastChild : function(node){
  305.         this.lastChild = node;
  306.     },
  307.     /**
  308.      * Returns true if this node is the last child of its parent
  309.      * @return {Boolean}
  310.      */
  311.     isLast : function(){
  312.        return (!this.parentNode ? true : this.parentNode.lastChild == this);
  313.     },
  314.     /**
  315.      * Returns true if this node is the first child of its parent
  316.      * @return {Boolean}
  317.      */
  318.     isFirst : function(){
  319.        return (!this.parentNode ? true : this.parentNode.firstChild == this);
  320.     },
  321.     /**
  322.      * Returns true if this node has one or more child nodes, else false.
  323.      * @return {Boolean}
  324.      */
  325.     hasChildNodes : function(){
  326.         return !this.isLeaf() && this.childNodes.length > 0;
  327.     },
  328.     
  329.     /**
  330.      * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
  331.      * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
  332.      * @return {Boolean}
  333.      */
  334.     isExpandable : function(){
  335.         return this.attributes.expandable || this.hasChildNodes();
  336.     },
  337.     /**
  338.      * Insert node(s) as the last child node of this node.
  339.      * @param {Node/Array} node The node or Array of nodes to append
  340.      * @return {Node} The appended node if single append, or null if an array was passed
  341.      */
  342.     appendChild : function(node){
  343.         var multi = false;
  344.         if(Ext.isArray(node)){
  345.             multi = node;
  346.         }else if(arguments.length > 1){
  347.             multi = arguments;
  348.         }
  349.         // if passed an array or multiple args do them one by one
  350.         if(multi){
  351.             for(var i = 0, len = multi.length; i < len; i++) {
  352.              this.appendChild(multi[i]);
  353.             }
  354.         }else{
  355.             if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){
  356.                 return false;
  357.             }
  358.             var index = this.childNodes.length;
  359.             var oldParent = node.parentNode;
  360.             // it's a move, make sure we move it cleanly
  361.             if(oldParent){
  362.                 if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){
  363.                     return false;
  364.                 }
  365.                 oldParent.removeChild(node);
  366.             }
  367.             index = this.childNodes.length;
  368.             if(index === 0){
  369.                 this.setFirstChild(node);
  370.             }
  371.             this.childNodes.push(node);
  372.             node.parentNode = this;
  373.             var ps = this.childNodes[index-1];
  374.             if(ps){
  375.                 node.previousSibling = ps;
  376.                 ps.nextSibling = node;
  377.             }else{
  378.                 node.previousSibling = null;
  379.             }
  380.             node.nextSibling = null;
  381.             this.setLastChild(node);
  382.             node.setOwnerTree(this.getOwnerTree());
  383.             this.fireEvent("append", this.ownerTree, this, node, index);
  384.             if(oldParent){
  385.                 node.fireEvent("move", this.ownerTree, node, oldParent, this, index);
  386.             }
  387.             return node;
  388.         }
  389.     },
  390.     /**
  391.      * Removes a child node from this node.
  392.      * @param {Node} node The node to remove
  393.      * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
  394.      * @return {Node} The removed node
  395.      */
  396.     removeChild : function(node, destroy){
  397.         var index = this.childNodes.indexOf(node);
  398.         if(index == -1){
  399.             return false;
  400.         }
  401.         if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){
  402.             return false;
  403.         }
  404.         // remove it from childNodes collection
  405.         this.childNodes.splice(index, 1);
  406.         // update siblings
  407.         if(node.previousSibling){
  408.             node.previousSibling.nextSibling = node.nextSibling;
  409.         }
  410.         if(node.nextSibling){
  411.             node.nextSibling.previousSibling = node.previousSibling;
  412.         }
  413.         // update child refs
  414.         if(this.firstChild == node){
  415.             this.setFirstChild(node.nextSibling);
  416.         }
  417.         if(this.lastChild == node){
  418.             this.setLastChild(node.previousSibling);
  419.         }
  420.         node.clear();
  421.         this.fireEvent("remove", this.ownerTree, this, node);
  422.         if(destroy){
  423.             node.destroy();
  424.         }
  425.         return node;
  426.     },
  427.     
  428.     // private
  429.     clear : function(destroy){
  430.         // clear any references from the node
  431.         this.setOwnerTree(null, destroy);
  432.         this.parentNode = this.previousSibling = this.nextSibling = null
  433.         if(destroy){
  434.             this.firstChild = this.lastChild = null; 
  435.         }
  436.     },
  437.     
  438.     /**
  439.      * Destroys the node.
  440.      */
  441.     destroy : function(){
  442.         this.purgeListeners();
  443.         this.clear(true);  
  444.         Ext.each(this.childNodes, function(n){
  445.             n.destroy();
  446.         });
  447.         this.childNodes = null;
  448.     },
  449.     /**
  450.      * Inserts the first node before the second node in this nodes childNodes collection.
  451.      * @param {Node} node The node to insert
  452.      * @param {Node} refNode The node to insert before (if null the node is appended)
  453.      * @return {Node} The inserted node
  454.      */
  455.     insertBefore : function(node, refNode){
  456.         if(!refNode){ // like standard Dom, refNode can be null for append
  457.             return this.appendChild(node);
  458.         }
  459.         // nothing to do
  460.         if(node == refNode){
  461.             return false;
  462.         }
  463.         if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){
  464.             return false;
  465.         }
  466.         var index = this.childNodes.indexOf(refNode);
  467.         var oldParent = node.parentNode;
  468.         var refIndex = index;
  469.         // when moving internally, indexes will change after remove
  470.         if(oldParent == this && this.childNodes.indexOf(node) < index){
  471.             refIndex--;
  472.         }
  473.         // it's a move, make sure we move it cleanly
  474.         if(oldParent){
  475.             if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
  476.                 return false;
  477.             }
  478.             oldParent.removeChild(node);
  479.         }
  480.         if(refIndex === 0){
  481.             this.setFirstChild(node);
  482.         }
  483.         this.childNodes.splice(refIndex, 0, node);
  484.         node.parentNode = this;
  485.         var ps = this.childNodes[refIndex-1];
  486.         if(ps){
  487.             node.previousSibling = ps;
  488.             ps.nextSibling = node;
  489.         }else{
  490.             node.previousSibling = null;
  491.         }
  492.         node.nextSibling = refNode;
  493.         refNode.previousSibling = node;
  494.         node.setOwnerTree(this.getOwnerTree());
  495.         this.fireEvent("insert", this.ownerTree, this, node, refNode);
  496.         if(oldParent){
  497.             node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);
  498.         }
  499.         return node;
  500.     },
  501.     /**
  502.      * Removes this node from its parent
  503.      * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
  504.      * @return {Node} this
  505.      */
  506.     remove : function(destroy){
  507.         this.parentNode.removeChild(this, destroy);
  508.         return this;
  509.     },
  510.     /**
  511.      * Returns the child node at the specified index.
  512.      * @param {Number} index
  513.      * @return {Node}
  514.      */
  515.     item : function(index){
  516.         return this.childNodes[index];
  517.     },
  518.     /**
  519.      * Replaces one child node in this node with another.
  520.      * @param {Node} newChild The replacement node
  521.      * @param {Node} oldChild The node to replace
  522.      * @return {Node} The replaced node
  523.      */
  524.     replaceChild : function(newChild, oldChild){
  525.         var s = oldChild ? oldChild.nextSibling : null;
  526.         this.removeChild(oldChild);
  527.         this.insertBefore(newChild, s);
  528.         return oldChild;
  529.     },
  530.     /**
  531.      * Returns the index of a child node
  532.      * @param {Node} node
  533.      * @return {Number} The index of the node or -1 if it was not found
  534.      */
  535.     indexOf : function(child){
  536.         return this.childNodes.indexOf(child);
  537.     },
  538.     /**
  539.      * Returns the tree this node is in.
  540.      * @return {Tree}
  541.      */
  542.     getOwnerTree : function(){
  543.         // if it doesn't have one, look for one
  544.         if(!this.ownerTree){
  545.             var p = this;
  546.             while(p){
  547.                 if(p.ownerTree){
  548.                     this.ownerTree = p.ownerTree;
  549.                     break;
  550.                 }
  551.                 p = p.parentNode;
  552.             }
  553.         }
  554.         return this.ownerTree;
  555.     },
  556.     /**
  557.      * Returns depth of this node (the root node has a depth of 0)
  558.      * @return {Number}
  559.      */
  560.     getDepth : function(){
  561.         var depth = 0;
  562.         var p = this;
  563.         while(p.parentNode){
  564.             ++depth;
  565.             p = p.parentNode;
  566.         }
  567.         return depth;
  568.     },
  569.     // private
  570.     setOwnerTree : function(tree, destroy){
  571.         // if it is a move, we need to update everyone
  572.         if(tree != this.ownerTree){
  573.             if(this.ownerTree){
  574.                 this.ownerTree.unregisterNode(this);
  575.             }
  576.             this.ownerTree = tree;
  577.             // If we're destroying, we don't need to recurse since it will be called on each child node
  578.             if(destroy !== true){
  579.                 Ext.each(this.childNodes, function(n){
  580.                     n.setOwnerTree(tree);
  581.                 });
  582.             }
  583.             if(tree){
  584.                 tree.registerNode(this);
  585.             }
  586.         }
  587.     },
  588.     
  589.     /**
  590.      * Changes the id of this node.
  591.      * @param {String} id The new id for the node.
  592.      */
  593.     setId: function(id){
  594.         if(id !== this.id){
  595.             var t = this.ownerTree;
  596.             if(t){
  597.                 t.unregisterNode(this);
  598.             }
  599.             this.id = this.attributes.id = id;
  600.             if(t){
  601.                 t.registerNode(this);
  602.             }
  603.             this.onIdChange(id);
  604.         }
  605.     },
  606.     
  607.     // private
  608.     onIdChange: Ext.emptyFn,
  609.     /**
  610.      * Returns the path for this node. The path can be used to expand or select this node programmatically.
  611.      * @param {String} attr (optional) The attr to use for the path (defaults to the node's id)
  612.      * @return {String} The path
  613.      */
  614.     getPath : function(attr){
  615.         attr = attr || "id";
  616.         var p = this.parentNode;
  617.         var b = [this.attributes[attr]];
  618.         while(p){
  619.             b.unshift(p.attributes[attr]);
  620.             p = p.parentNode;
  621.         }
  622.         var sep = this.getOwnerTree().pathSeparator;
  623.         return sep + b.join(sep);
  624.     },
  625.     /**
  626.      * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
  627.      * will be the args provided or the current node. If the function returns false at any point,
  628.      * the bubble is stopped.
  629.      * @param {Function} fn The function to call
  630.      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
  631.      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
  632.      */
  633.     bubble : function(fn, scope, args){
  634.         var p = this;
  635.         while(p){
  636.             if(fn.apply(scope || p, args || [p]) === false){
  637.                 break;
  638.             }
  639.             p = p.parentNode;
  640.         }
  641.     },
  642.     /**
  643.      * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
  644.      * will be the args provided or the current node. If the function returns false at any point,
  645.      * the cascade is stopped on that branch.
  646.      * @param {Function} fn The function to call
  647.      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
  648.      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
  649.      */
  650.     cascade : function(fn, scope, args){
  651.         if(fn.apply(scope || this, args || [this]) !== false){
  652.             var cs = this.childNodes;
  653.             for(var i = 0, len = cs.length; i < len; i++) {
  654.              cs[i].cascade(fn, scope, args);
  655.             }
  656.         }
  657.     },
  658.     /**
  659.      * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
  660.      * will be the args provided or the current node. If the function returns false at any point,
  661.      * the iteration stops.
  662.      * @param {Function} fn The function to call
  663.      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node in the iteration.
  664.      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
  665.      */
  666.     eachChild : function(fn, scope, args){
  667.         var cs = this.childNodes;
  668.         for(var i = 0, len = cs.length; i < len; i++) {
  669.          if(fn.apply(scope || this, args || [cs[i]]) === false){
  670.              break;
  671.          }
  672.         }
  673.     },
  674.     /**
  675.      * Finds the first child that has the attribute with the specified value.
  676.      * @param {String} attribute The attribute name
  677.      * @param {Mixed} value The value to search for
  678.      * @return {Node} The found child or null if none was found
  679.      */
  680.     findChild : function(attribute, value){
  681.         var cs = this.childNodes;
  682.         for(var i = 0, len = cs.length; i < len; i++) {
  683.          if(cs[i].attributes[attribute] == value){
  684.              return cs[i];
  685.          }
  686.         }
  687.         return null;
  688.     },
  689.     /**
  690.      * Finds the first child by a custom function. The child matches if the function passed returns <code>true</code>.
  691.      * @param {Function} fn A function which must return <code>true</code> if the passed Node is the required Node.
  692.      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Node being tested.
  693.      * @return {Node} The found child or null if none was found
  694.      */
  695.     findChildBy : function(fn, scope){
  696.         var cs = this.childNodes;
  697.         for(var i = 0, len = cs.length; i < len; i++) {
  698.          if(fn.call(scope||cs[i], cs[i]) === true){
  699.              return cs[i];
  700.          }
  701.         }
  702.         return null;
  703.     },
  704.     /**
  705.      * Sorts this nodes children using the supplied sort function.
  706.      * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
  707.      * @param {Object} scope (optional)The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
  708.      */
  709.     sort : function(fn, scope){
  710.         var cs = this.childNodes;
  711.         var len = cs.length;
  712.         if(len > 0){
  713.             var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
  714.             cs.sort(sortFn);
  715.             for(var i = 0; i < len; i++){
  716.                 var n = cs[i];
  717.                 n.previousSibling = cs[i-1];
  718.                 n.nextSibling = cs[i+1];
  719.                 if(i === 0){
  720.                     this.setFirstChild(n);
  721.                 }
  722.                 if(i == len-1){
  723.                     this.setLastChild(n);
  724.                 }
  725.             }
  726.         }
  727.     },
  728.     /**
  729.      * Returns true if this node is an ancestor (at any point) of the passed node.
  730.      * @param {Node} node
  731.      * @return {Boolean}
  732.      */
  733.     contains : function(node){
  734.         return node.isAncestor(this);
  735.     },
  736.     /**
  737.      * Returns true if the passed node is an ancestor (at any point) of this node.
  738.      * @param {Node} node
  739.      * @return {Boolean}
  740.      */
  741.     isAncestor : function(node){
  742.         var p = this.parentNode;
  743.         while(p){
  744.             if(p == node){
  745.                 return true;
  746.             }
  747.             p = p.parentNode;
  748.         }
  749.         return false;
  750.     },
  751.     toString : function(){
  752.         return "[Node"+(this.id?" "+this.id:"")+"]";
  753.     }
  754. });