~ubuntu-branches/ubuntu/utopic/moodle/utopic

« back to all changes in this revision

Viewing changes to lib/yuilib/3.13.0/tree-node/tree-node.js

  • Committer: Package Import Robot
  • Author(s): Thijs Kinkhorst
  • Date: 2014-05-12 16:10:38 UTC
  • mfrom: (36.1.3 sid)
  • Revision ID: package-import@ubuntu.com-20140512161038-puyqf65k4e0s8ytz
Tags: 2.6.3-1
New upstream release.

Show diffs side-by-side

added added

removed removed

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