source: trunk/web/addons/job_monarch/lib/extjs-30/src/data/Tree.js @ 625

Last change on this file since 625 was 625, checked in by ramonb, 15 years ago

lib/extjs-30:

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