~cdparra/gelee/trunk

« back to all changes in this revision

Viewing changes to webui/ecosystem/extjs/source/data/Tree.js

  • Committer: parra
  • Date: 2010-03-15 02:39:02 UTC
  • Revision ID: svn-v4:ac5bba68-f036-4e09-846e-8f32731cc928:trunk/gelee:1433
merged gelee at svn

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Ext JS Library 3.0 RC2
 
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
 */
 
17
Ext.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
 
 
104
Ext.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
 */
 
169
Ext.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, "xnode-");
 
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
 
 
294
Ext.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
});
 
 
b'\\ No newline at end of file'