Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('tree-node', function (Y, NAME) {
2
 
3
/*jshint expr:true, onevar:false */
4
 
5
/**
6
Provides the `Tree.Node` class, which represents a tree node contained in a
7
`Tree` data structure.
8
 
9
@module tree
10
@submodule tree-node
11
**/
12
 
13
/**
14
Represents a tree node in a `Tree` data structure.
15
 
16
@class Tree.Node
17
@param {Tree} tree `Tree` instance with which this node should be associated.
18
@param {Object} [config] Configuration hash for this node.
19
 
20
    @param {Boolean} [config.canHaveChildren=false] Whether or not this node can
21
        contain child nodes. Will be automatically set to `true` if not
22
        specified and `config.children` contains one or more children.
23
 
24
    @param {Tree.Node[]} [config.children] Array of `Tree.Node` instances
25
        for child nodes of this node.
26
 
27
    @param {Object} [config.data] Implementation-specific data related to this
28
        node. You may add arbitrary properties to this hash for your own use.
29
 
30
    @param {String} [config.id] Unique id for this node. This id must be unique
31
        among all tree nodes on the entire page, and will also be used as this
32
        node's DOM id when it's rendered by a TreeView. A unique id will be
33
        automatically generated unless you specify a custom value.
34
 
35
    @param {Object} [config.state] State hash for this node. You may add
36
        arbitrary state properties to this hash for your own use. See the
37
        docs for `Tree.Node`'s `state` property for details on state values used
38
        internally by `Tree.Node`.
39
 
40
@constructor
41
**/
42
 
43
function TreeNode(tree, config) {
44
    config || (config = {});
45
 
46
    this.id   = this._yuid = config.id || this.id || Y.guid('treeNode-');
47
    this.tree = tree;
48
 
49
    this.children = config.children || [];
50
    this.data     = config.data || {};
51
    this.state    = config.state || {};
52
 
53
    if (config.canHaveChildren) {
54
        this.canHaveChildren = config.canHaveChildren;
55
    } else if (this.children.length) {
56
        this.canHaveChildren = true;
57
    }
58
 
59
    // Mix in arbitrary properties on the config object, but don't overwrite any
60
    // existing properties of this node.
61
    Y.mix(this, config);
62
 
63
    // If this node has children, loop through them and ensure their parent
64
    // references are all set to this node.
65
    for (var i = 0, len = this.children.length; i < len; i++) {
66
        this.children[i].parent = this;
67
    }
68
}
69
 
70
TreeNode.prototype = {
71
    // -- Public Properties ----------------------------------------------------
72
 
73
    /**
74
    Whether or not this node can contain child nodes.
75
 
76
    This value is falsy by default unless child nodes are added at instantiation
77
    time, in which case it will be automatically set to `true`. You can also
78
    manually set it to `true` to indicate that a node can have children even
79
    though it might not currently have any children.
80
 
81
    Note that regardless of the value of this property, appending, prepending,
82
    or inserting a node into this node will cause `canHaveChildren` to be set to
83
    true automatically.
84
 
85
    @property {Boolean} canHaveChildren
86
    **/
87
 
88
    /**
89
    Child nodes contained within this node.
90
 
91
    @property {Tree.Node[]} children
92
    @default []
93
    @readOnly
94
    **/
95
 
96
    /**
97
    Arbitrary serializable data related to this node.
98
 
99
    Use this property to store any data that should accompany this node when it
100
    is serialized to JSON.
101
 
102
    @property {Object} data
103
    @default {}
104
    **/
105
 
106
    /**
107
    Unique id for this node.
108
 
109
    @property {String} id
110
    @readOnly
111
    **/
112
 
113
    /**
114
    Parent node of this node, or `undefined` if this is an unattached node or
115
    the root node.
116
 
117
    @property {Tree.Node} parent
118
    @readOnly
119
    **/
120
 
121
    /**
122
    Current state of this node.
123
 
124
    Use this property to store state-specific info -- such as whether this node
125
    is "open", "selected", or any other arbitrary state -- that should accompany
126
    this node when it is serialized to JSON.
127
 
128
    @property {Object} state
129
    **/
130
 
131
    /**
132
    The Tree instance with which this node is associated.
133
 
134
    @property {Tree} tree
135
    @readOnly
136
    **/
137
 
138
    // -- Protected Properties -------------------------------------------------
139
 
140
    /**
141
    Mapping of child node ids to indices.
142
 
143
    @property {Object} _indexMap
144
    @protected
145
    **/
146
 
147
    /**
148
    Flag indicating whether the `_indexMap` is stale and needs to be rebuilt.
149
 
150
    @property {Boolean} _isIndexStale
151
    @default true
152
    @protected
153
    **/
154
    _isIndexStale: true,
155
 
156
    /**
157
    Simple way to type-check that this is an instance of Tree.Node.
158
 
159
    @property {Boolean} _isYUITreeNode
160
    @default true
161
    @protected
162
    **/
163
    _isYUITreeNode: true,
164
 
165
    /**
166
    Array of property names on this node that should be serialized to JSON when
167
    `toJSON()` is called.
168
 
169
    Note that the `children` property is a special case that is managed
170
    separately.
171
 
172
    @property {String[]} _serializable
173
    @protected
174
    **/
175
    _serializable: ['canHaveChildren', 'data', 'id', 'state'],
176
 
177
    // -- Public Methods -------------------------------------------------------
178
 
179
    /**
180
    Appends the given tree node or array of nodes to the end of this node's
181
    children.
182
 
183
    @method append
184
    @param {Object|Object[]|Tree.Node|Tree.Node[]} node Child node, node config
185
        object, array of child nodes, or array of node config objects to append
186
        to the given parent. Node config objects will automatically be converted
187
        into node instances.
188
    @param {Object} [options] Options.
189
        @param {Boolean} [options.silent=false] If `true`, the `add` event will
190
            be suppressed.
191
    @return {Tree.Node|Tree.Node[]} Node or array of nodes that were appended.
192
    **/
193
    append: function (node, options) {
194
        return this.tree.appendNode(this, node, options);
195
    },
196
 
197
    /**
198
    Returns this node's depth.
199
 
200
    The root node of a tree always has a depth of 0. A child of the root has a
201
    depth of 1, a child of that child will have a depth of 2, and so on.
202
 
203
    @method depth
204
    @return {Number} This node's depth.
205
    @since 3.11.0
206
    **/
207
    depth: function () {
208
        if (this.isRoot()) {
209
            return 0;
210
        }
211
 
212
        var depth  = 0,
213
            parent = this.parent;
214
 
215
        while (parent) {
216
            depth += 1;
217
            parent = parent.parent;
218
        }
219
 
220
        return depth;
221
    },
222
 
223
    /**
224
    Removes all children from this node. The removed children will still be
225
    reusable unless the `destroy` option is truthy.
226
 
227
    @method empty
228
    @param {Object} [options] Options.
229
        @param {Boolean} [options.destroy=false] If `true`, the children will
230
            also be destroyed, which makes them available for garbage collection
231
            and means they can't be reused.
232
        @param {Boolean} [options.silent=false] If `true`, `remove` events will
233
            be suppressed.
234
        @param {String} [options.src] Source of the change, to be passed along
235
            to the event facade of the resulting event. This can be used to
236
            distinguish between changes triggered by a user and changes
237
            triggered programmatically, for example.
238
    @return {Tree.Node[]} Array of removed child nodes.
239
    **/
240
    empty: function (options) {
241
        return this.tree.emptyNode(this, options);
242
    },
243
 
244
    /**
245
    Performs a depth-first traversal of this node, passing it and each of its
246
    descendants to the specified _callback_, and returning the first node for
247
    which the callback returns a truthy value.
248
 
249
    Traversal will stop as soon as a truthy value is returned from the callback.
250
 
251
    See `Tree#traverseNode()` for more details on how depth-first traversal
252
    works.
253
 
254
    @method find
255
    @param {Object} [options] Options.
256
        @param {Number} [options.depth] Depth limit. If specified, descendants
257
            will only be traversed to this depth before backtracking and moving
258
            on.
259
    @param {Function} callback Callback function to call with the traversed
260
        node and each of its descendants. If this function returns a truthy
261
        value, traversal will be stopped and the current node will be returned.
262
 
263
        @param {Tree.Node} callback.node Node being traversed.
264
 
265
    @param {Object} [thisObj] `this` object to use when executing _callback_.
266
    @return {Tree.Node|null} Returns the first node for which the _callback_
267
        returns a truthy value, or `null` if the callback never returns a truthy
268
        value.
269
    **/
270
    find: function (options, callback, thisObj) {
271
        return this.tree.findNode(this, options, callback, thisObj);
272
    },
273
 
274
    /**
275
    Returns `true` if this node has one or more child nodes.
276
 
277
    @method hasChildren
278
    @return {Boolean} `true` if this node has one or more child nodes, `false`
279
        otherwise.
280
    **/
281
    hasChildren: function () {
282
        return !!this.children.length;
283
    },
284
 
285
    /**
286
    Returns the numerical index of this node within its parent node, or `-1` if
287
    this node doesn't have a parent node.
288
 
289
    @method index
290
    @return {Number} Index of this node within its parent node, or `-1` if this
291
        node doesn't have a parent node.
292
    **/
293
    index: function () {
294
        return this.parent ? this.parent.indexOf(this) : -1;
295
    },
296
 
297
    /**
298
    Returns the numerical index of the given child node, or `-1` if the node is
299
    not a child of this node.
300
 
301
    @method indexOf
302
    @param {Tree.Node} node Child node.
303
    @return {Number} Index of the child, or `-1` if the node is not a child of
304
        this node.
305
    **/
306
    indexOf: function (node) {
307
        var index;
308
 
309
        if (this._isIndexStale) {
310
            this._reindex();
311
        }
312
 
313
        index = this._indexMap[node.id];
314
 
315
        return typeof index === 'undefined' ? -1 : index;
316
    },
317
 
318
    /**
319
    Inserts a node or array of nodes at the specified index under this node, or
320
    appends them to this node if no index is specified.
321
 
322
    If a node being inserted is from another tree, it and all its children will
323
    be removed from that tree and moved to this one.
324
 
325
    @method insert
326
    @param {Object|Object[]|Tree.Node|Tree.Node[]} node Child node, node config
327
        object, array of child nodes, or array of node config objects to insert
328
        under the given parent. Node config objects will automatically be
329
        converted into node instances.
330
 
331
    @param {Object} [options] Options.
332
        @param {Number} [options.index] Index at which to insert the child node.
333
            If not specified, the node will be appended as the last child of the
334
            parent.
335
        @param {Boolean} [options.silent=false] If `true`, the `add` event will
336
            be suppressed.
337
        @param {String} [options.src='insert'] Source of the change, to be
338
            passed along to the event facade of the resulting event. This can be
339
            used to distinguish between changes triggered by a user and changes
340
            triggered programmatically, for example.
341
 
342
    @return {Tree.Node[]} Node or array of nodes that were inserted.
343
    **/
344
    insert: function (node, options) {
345
        return this.tree.insertNode(this, node, options);
346
    },
347
 
348
    /**
349
    Returns `true` if this node has been inserted into a tree, `false` if it is
350
    merely associated with a tree and has not yet been inserted.
351
 
352
    @method isInTree
353
    @return {Boolean} `true` if this node has been inserted into a tree, `false`
354
        otherwise.
355
    **/
356
    isInTree: function () {
357
        if (this.tree && this.tree.rootNode === this) {
358
            return true;
359
        }
360
 
361
        return !!(this.parent && this.parent.isInTree());
362
    },
363
 
364
    /**
365
    Returns `true` if this node is the root of the tree.
366
 
367
    @method isRoot
368
    @return {Boolean} `true` if this node is the root of the tree, `false`
369
        otherwise.
370
    **/
371
    isRoot: function () {
372
        return !!(this.tree && this.tree.rootNode === this);
373
    },
374
 
375
    /**
376
    Returns this node's next sibling, or `undefined` if this node is the last
377
    child.
378
 
379
    @method next
380
    @return {Tree.Node} This node's next sibling, or `undefined` if this node is
381
        the last child.
382
    **/
383
    next: function () {
384
        if (this.parent) {
385
            return this.parent.children[this.index() + 1];
386
        }
387
    },
388
 
389
    /**
390
    Prepends a node or array of nodes at the beginning of this node's children.
391
 
392
    If a node being prepended is from another tree, it and all its children will
393
    be removed from that tree and moved to this one.
394
 
395
    @method prepend
396
    @param {Object|Object[]|Tree.Node|Tree.Node[]} node Child node, node config
397
        object, array of child nodes, or array of node config objects to prepend
398
        to this node. Node config objects will automatically be converted into
399
        node instances.
400
    @param {Object} [options] Options.
401
        @param {Boolean} [options.silent=false] If `true`, the `add` event will
402
            be suppressed.
403
    @return {Tree.Node|Tree.Node[]} Node or array of nodes that were prepended.
404
    **/
405
    prepend: function (node, options) {
406
        return this.tree.prependNode(this, node, options);
407
    },
408
 
409
    /**
410
    Returns this node's previous sibling, or `undefined` if this node is the
411
    first child
412
 
413
    @method previous
414
    @return {Tree.Node} This node's previous sibling, or `undefined` if this
415
        node is the first child.
416
    **/
417
    previous: function () {
418
        if (this.parent) {
419
            return this.parent.children[this.index() - 1];
420
        }
421
    },
422
 
423
    /**
424
    Removes this node from its parent node.
425
 
426
    @method remove
427
    @param {Object} [options] Options.
428
        @param {Boolean} [options.destroy=false] If `true`, this node and all
429
            its children will also be destroyed, which makes them available for
430
            garbage collection and means they can't be reused.
431
        @param {Boolean} [options.silent=false] If `true`, the `remove` event
432
            will be suppressed.
433
        @param {String} [options.src] Source of the change, to be passed along
434
            to the event facade of the resulting event. This can be used to
435
            distinguish between changes triggered by a user and changes
436
            triggered programmatically, for example.
437
    @chainable
438
    **/
439
    remove: function (options) {
440
        return this.tree.removeNode(this, options);
441
    },
442
 
443
    /**
444
    Returns the total number of nodes contained within this node, including all
445
    descendants of this node's children.
446
 
447
    @method size
448
    @return {Number} Total number of nodes contained within this node, including
449
        all descendants.
450
    **/
451
    size: function () {
452
        var children = this.children,
453
            len      = children.length,
454
            total    = len;
455
 
456
        for (var i = 0; i < len; i++) {
457
            total += children[i].size();
458
        }
459
 
460
        return total;
461
    },
462
 
463
    /**
464
    Serializes this node to an object suitable for use in JSON.
465
 
466
    @method toJSON
467
    @return {Object} Serialized node object.
468
    **/
469
    toJSON: function () {
470
        var obj   = {},
471
            state = this.state,
472
            i, key, len;
473
 
474
        // Do nothing if this node is marked as destroyed.
475
        if (state.destroyed) {
476
            return null;
477
        }
478
 
479
        // Serialize properties explicitly marked as serializable.
480
        for (i = 0, len = this._serializable.length; i < len; i++) {
481
            key = this._serializable[i];
482
 
483
            if (key in this) {
484
                obj[key] = this[key];
485
            }
486
        }
487
 
488
        // Serialize child nodes.
489
        if (this.canHaveChildren) {
490
            obj.children = [];
491
 
492
            for (i = 0, len = this.children.length; i < len; i++) {
493
                obj.children.push(this.children[i].toJSON());
494
            }
495
        }
496
 
497
        return obj;
498
    },
499
 
500
    /**
501
    Performs a depth-first traversal of this node, passing it and each of its
502
    descendants to the specified _callback_.
503
 
504
    If the callback function returns `Tree.STOP_TRAVERSAL`, traversal will be
505
    stopped immediately. Otherwise, it will continue until the deepest
506
    descendant of _node_ has been traversed, or until each branch has been
507
    traversed to the optional maximum depth limit.
508
 
509
    Since traversal is depth-first, that means nodes are traversed like this:
510
 
511
                1
512
              / | \
513
             2  8  9
514
            / \     \
515
           3   7    10
516
         / | \      / \
517
        4  5  6    11 12
518
 
519
    @method traverse
520
    @param {Object} [options] Options.
521
        @param {Number} [options.depth] Depth limit. If specified, descendants
522
            will only be traversed to this depth before backtracking and moving
523
            on.
524
    @param {Function} callback Callback function to call with the traversed
525
        node and each of its descendants.
526
 
527
        @param {Tree.Node} callback.node Node being traversed.
528
 
529
    @param {Object} [thisObj] `this` object to use when executing _callback_.
530
    @return {Mixed} Returns `Tree.STOP_TRAVERSAL` if traversal was stopped;
531
        otherwise returns `undefined`.
532
    **/
533
    traverse: function (options, callback, thisObj) {
534
        return this.tree.traverseNode(this, options, callback, thisObj);
535
    },
536
 
537
    // -- Protected Methods ----------------------------------------------------
538
    _reindex: function () {
539
        var children = this.children,
540
            indexMap = {},
541
            i, len;
542
 
543
        for (i = 0, len = children.length; i < len; i++) {
544
            indexMap[children[i].id] = i;
545
        }
546
 
547
        this._indexMap     = indexMap;
548
        this._isIndexStale = false;
549
    }
550
};
551
 
552
Y.namespace('Tree').Node = TreeNode;
553
 
554
 
555
}, '3.18.1');