source: trunk/web/addons/job_monarch/lib/extjs/source/data/Tree.js @ 619

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

lib/:

  • added new AJAX dependancies: ExtJS, pChart, Lightbox2
File size: 24.7 KB
Line 
1/*
2 * Ext JS Library 2.2.1
3 * Copyright(c) 2006-2009, Ext JS, LLC.
4 * licensing@extjs.com
5 *
6 * http://extjs.com/license
7 */
8
9/**
10 * @class Ext.data.Tree
11 * @extends Ext.util.Observable
12 * Represents a tree data structure and bubbles all the events for its nodes. The nodes
13 * in the tree have most standard DOM functionality.
14 * @constructor
15 * @param {Node} root (optional) The root node
16 */
17Ext.data.Tree = function(root){
18   this.nodeHash = {};
19   /**
20    * The root node for this tree
21    * @type Node
22    */
23   this.root = null;
24   if(root){
25       this.setRootNode(root);
26   }
27   this.addEvents(
28       /**
29        * @event append
30        * Fires when a new child node is appended to a node in this tree.
31        * @param {Tree} tree The owner tree
32        * @param {Node} parent The parent node
33        * @param {Node} node The newly appended node
34        * @param {Number} index The index of the newly appended node
35        */
36       "append",
37       /**
38        * @event remove
39        * Fires when a child node is removed from a node in this tree.
40        * @param {Tree} tree The owner tree
41        * @param {Node} parent The parent node
42        * @param {Node} node The child node removed
43        */
44       "remove",
45       /**
46        * @event move
47        * Fires when a node is moved to a new location in the tree
48        * @param {Tree} tree The owner tree
49        * @param {Node} node The node moved
50        * @param {Node} oldParent The old parent of this node
51        * @param {Node} newParent The new parent of this node
52        * @param {Number} index The index it was moved to
53        */
54       "move",
55       /**
56        * @event insert
57        * Fires when a new child node is inserted in a node in this tree.
58        * @param {Tree} tree The owner tree
59        * @param {Node} parent The parent node
60        * @param {Node} node The child node inserted
61        * @param {Node} refNode The child node the node was inserted before
62        */
63       "insert",
64       /**
65        * @event beforeappend
66        * Fires before a new child is appended to a node in this tree, return false to cancel the append.
67        * @param {Tree} tree The owner tree
68        * @param {Node} parent The parent node
69        * @param {Node} node The child node to be appended
70        */
71       "beforeappend",
72       /**
73        * @event beforeremove
74        * Fires before a child is removed from a node in this tree, return false to cancel the remove.
75        * @param {Tree} tree The owner tree
76        * @param {Node} parent The parent node
77        * @param {Node} node The child node to be removed
78        */
79       "beforeremove",
80       /**
81        * @event beforemove
82        * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
83        * @param {Tree} tree The owner tree
84        * @param {Node} node The node being moved
85        * @param {Node} oldParent The parent of the node
86        * @param {Node} newParent The new parent the node is moving to
87        * @param {Number} index The index it is being moved to
88        */
89       "beforemove",
90       /**
91        * @event beforeinsert
92        * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
93        * @param {Tree} tree The owner tree
94        * @param {Node} parent The parent node
95        * @param {Node} node The child node to be inserted
96        * @param {Node} refNode The child node the node is being inserted before
97        */
98       "beforeinsert"
99   );
100
101    Ext.data.Tree.superclass.constructor.call(this);
102};
103
104Ext.extend(Ext.data.Tree, Ext.util.Observable, {
105    /**
106     * @cfg {String} pathSeparator
107     * The token used to separate paths in node ids (defaults to '/').
108     */
109    pathSeparator: "/",
110
111    // private
112    proxyNodeEvent : function(){
113        return this.fireEvent.apply(this, arguments);
114    },
115
116    /**
117     * Returns the root node for this tree.
118     * @return {Node}
119     */
120    getRootNode : function(){
121        return this.root;
122    },
123
124    /**
125     * Sets the root node for this tree.
126     * @param {Node} node
127     * @return {Node}
128     */
129    setRootNode : function(node){
130        this.root = node;
131        node.ownerTree = this;
132        node.isRoot = true;
133        this.registerNode(node);
134        return node;
135    },
136
137    /**
138     * Gets a node in this tree by its id.
139     * @param {String} id
140     * @return {Node}
141     */
142    getNodeById : function(id){
143        return this.nodeHash[id];
144    },
145
146    // private
147    registerNode : function(node){
148        this.nodeHash[node.id] = node;
149    },
150
151    // private
152    unregisterNode : function(node){
153        delete this.nodeHash[node.id];
154    },
155
156    toString : function(){
157        return "[Tree"+(this.id?" "+this.id:"")+"]";
158    }
159});
160
161/**
162 * @class Ext.data.Node
163 * @extends Ext.util.Observable
164 * @cfg {Boolean} leaf true if this node is a leaf and does not have children
165 * @cfg {String} id The id for this node. If one is not specified, one is generated.
166 * @constructor
167 * @param {Object} attributes The attributes/config for the node
168 */
169Ext.data.Node = function(attributes){
170    /**
171     * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
172     * @type {Object}
173     */
174    this.attributes = attributes || {};
175    this.leaf = this.attributes.leaf;
176    /**
177     * The node id. @type String
178     */
179    this.id = this.attributes.id;
180    if(!this.id){
181        this.id = Ext.id(null, "ynode-");
182        this.attributes.id = this.id;
183    }
184    /**
185     * All child nodes of this node. @type Array
186     */
187    this.childNodes = [];
188    if(!this.childNodes.indexOf){ // indexOf is a must
189        this.childNodes.indexOf = function(o){
190            for(var i = 0, len = this.length; i < len; i++){
191                if(this[i] == o) return i;
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 it's 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's 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     * Returns the path for this node. The path can be used to expand or select this node programmatically.
608     * @param {String} attr (optional) The attr to use for the path (defaults to the node's id)
609     * @return {String} The path
610     */
611    getPath : function(attr){
612        attr = attr || "id";
613        var p = this.parentNode;
614        var b = [this.attributes[attr]];
615        while(p){
616            b.unshift(p.attributes[attr]);
617            p = p.parentNode;
618        }
619        var sep = this.getOwnerTree().pathSeparator;
620        return sep + b.join(sep);
621    },
622
623    /**
624     * Bubbles up the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of
625     * function call will be the scope provided or the current node. The arguments to the function
626     * will be the args provided or the current node. If the function returns false at any point,
627     * the bubble is stopped.
628     * @param {Function} fn The function to call
629     * @param {Object} scope (optional) The scope of the function (defaults to current node)
630     * @param {Array} args (optional) The args to call the function with (default to passing the current node)
631     */
632    bubble : function(fn, scope, args){
633        var p = this;
634        while(p){
635            if(fn.apply(scope || p, args || [p]) === false){
636                break;
637            }
638            p = p.parentNode;
639        }
640    },
641
642    /**
643     * Cascades down the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of
644     * function call will be the scope provided or the current node. The arguments to the function
645     * will be the args provided or the current node. If the function returns false at any point,
646     * the cascade is stopped on that branch.
647     * @param {Function} fn The function to call
648     * @param {Object} scope (optional) The scope of the function (defaults to current node)
649     * @param {Array} args (optional) The args to call the function with (default to passing the current node)
650     */
651    cascade : function(fn, scope, args){
652        if(fn.apply(scope || this, args || [this]) !== false){
653            var cs = this.childNodes;
654            for(var i = 0, len = cs.length; i < len; i++) {
655                cs[i].cascade(fn, scope, args);
656            }
657        }
658    },
659
660    /**
661     * Iterates the child nodes of this node, calling the specified function with each node. The scope (<i>this</i>) of
662     * function call will be the scope provided or the current node. The arguments to the function
663     * will be the args provided or the current node. If the function returns false at any point,
664     * the iteration stops.
665     * @param {Function} fn The function to call
666     * @param {Object} scope (optional) The scope of the function (defaults to current node)
667     * @param {Array} args (optional) The args to call the function with (default to passing the current node)
668     */
669    eachChild : function(fn, scope, args){
670        var cs = this.childNodes;
671        for(var i = 0, len = cs.length; i < len; i++) {
672                if(fn.apply(scope || this, args || [cs[i]]) === false){
673                    break;
674                }
675        }
676    },
677
678    /**
679     * Finds the first child that has the attribute with the specified value.
680     * @param {String} attribute The attribute name
681     * @param {Mixed} value The value to search for
682     * @return {Node} The found child or null if none was found
683     */
684    findChild : function(attribute, value){
685        var cs = this.childNodes;
686        for(var i = 0, len = cs.length; i < len; i++) {
687                if(cs[i].attributes[attribute] == value){
688                    return cs[i];
689                }
690        }
691        return null;
692    },
693
694    /**
695     * Finds the first child by a custom function. The child matches if the function passed
696     * returns true.
697     * @param {Function} fn
698     * @param {Object} scope (optional)
699     * @return {Node} The found child or null if none was found
700     */
701    findChildBy : function(fn, scope){
702        var cs = this.childNodes;
703        for(var i = 0, len = cs.length; i < len; i++) {
704                if(fn.call(scope||cs[i], cs[i]) === true){
705                    return cs[i];
706                }
707        }
708        return null;
709    },
710
711    /**
712     * Sorts this nodes children using the supplied sort function
713     * @param {Function} fn
714     * @param {Object} scope (optional)
715     */
716    sort : function(fn, scope){
717        var cs = this.childNodes;
718        var len = cs.length;
719        if(len > 0){
720            var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
721            cs.sort(sortFn);
722            for(var i = 0; i < len; i++){
723                var n = cs[i];
724                n.previousSibling = cs[i-1];
725                n.nextSibling = cs[i+1];
726                if(i == 0){
727                    this.setFirstChild(n);
728                }
729                if(i == len-1){
730                    this.setLastChild(n);
731                }
732            }
733        }
734    },
735
736    /**
737     * Returns true if this node is an ancestor (at any point) of the passed node.
738     * @param {Node} node
739     * @return {Boolean}
740     */
741    contains : function(node){
742        return node.isAncestor(this);
743    },
744
745    /**
746     * Returns true if the passed node is an ancestor (at any point) of this node.
747     * @param {Node} node
748     * @return {Boolean}
749     */
750    isAncestor : function(node){
751        var p = this.parentNode;
752        while(p){
753            if(p == node){
754                return true;
755            }
756            p = p.parentNode;
757        }
758        return false;
759    },
760
761    toString : function(){
762        return "[Node"+(this.id?" "+this.id:"")+"]";
763    }
764});
Note: See TracBrowser for help on using the repository browser.