NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / tree-node / tree-node.js
blobc405b6504e01f5b275c3f4fbd09abbeaf3676500
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 */
8 YUI.add('tree-node', function (Y, NAME) {
10 /*jshint expr:true, onevar:false */
12 /**
13 Provides the `Tree.Node` class, which represents a tree node contained in a
14 `Tree` data structure.
16 @module tree
17 @submodule tree-node
18 **/
20 /**
21 Represents a tree node in a `Tree` data structure.
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.
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.
31     @param {Tree.Node[]} [config.children] Array of `Tree.Node` instances
32         for child nodes of this node.
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.
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.
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`.
47 @constructor
48 **/
50 function TreeNode(tree, config) {
51     config || (config = {});
53     this.id   = this._yuid = config.id || this.id || Y.guid('treeNode-');
54     this.tree = tree;
56     this.children = config.children || [];
57     this.data     = config.data || {};
58     this.state    = config.state || {};
60     if (config.canHaveChildren) {
61         this.canHaveChildren = config.canHaveChildren;
62     } else if (this.children.length) {
63         this.canHaveChildren = true;
64     }
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);
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     }
77 TreeNode.prototype = {
78     // -- Public Properties ----------------------------------------------------
80     /**
81     Whether or not this node can contain child nodes.
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.
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.
92     @property {Boolean} canHaveChildren
93     **/
95     /**
96     Child nodes contained within this node.
98     @property {Tree.Node[]} children
99     @default []
100     @readOnly
101     **/
103     /**
104     Arbitrary serializable data related to this node.
106     Use this property to store any data that should accompany this node when it
107     is serialized to JSON.
109     @property {Object} data
110     @default {}
111     **/
113     /**
114     Unique id for this node.
116     @property {String} id
117     @readOnly
118     **/
120     /**
121     Parent node of this node, or `undefined` if this is an unattached node or
122     the root node.
124     @property {Tree.Node} parent
125     @readOnly
126     **/
128     /**
129     Current state of this node.
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.
135     @property {Object} state
136     **/
138     /**
139     The Tree instance with which this node is associated.
141     @property {Tree} tree
142     @readOnly
143     **/
145     // -- Protected Properties -------------------------------------------------
147     /**
148     Mapping of child node ids to indices.
150     @property {Object} _indexMap
151     @protected
152     **/
154     /**
155     Flag indicating whether the `_indexMap` is stale and needs to be rebuilt.
157     @property {Boolean} _isIndexStale
158     @default true
159     @protected
160     **/
161     _isIndexStale: true,
163     /**
164     Simple way to type-check that this is an instance of Tree.Node.
166     @property {Boolean} _isYUITreeNode
167     @default true
168     @protected
169     **/
170     _isYUITreeNode: true,
172     /**
173     Array of property names on this node that should be serialized to JSON when
174     `toJSON()` is called.
176     Note that the `children` property is a special case that is managed
177     separately.
179     @property {String[]} _serializable
180     @protected
181     **/
182     _serializable: ['canHaveChildren', 'data', 'id', 'state'],
184     // -- Public Methods -------------------------------------------------------
186     /**
187     Appends the given tree node or array of nodes to the end of this node's
188     children.
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     },
204     /**
205     Returns this node's depth.
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.
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         }
219         var depth  = 0,
220             parent = this.parent;
222         while (parent) {
223             depth += 1;
224             parent = parent.parent;
225         }
227         return depth;
228     },
230     /**
231     Removes all children from this node. The removed children will still be
232     reusable unless the `destroy` option is truthy.
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     },
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.
256     Traversal will stop as soon as a truthy value is returned from the callback.
258     See `Tree#traverseNode()` for more details on how depth-first traversal
259     works.
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.
270         @param {Tree.Node} callback.node Node being traversed.
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     },
281     /**
282     Returns `true` if this node has one or more child nodes.
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     },
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.
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     },
304     /**
305     Returns the numerical index of the given child node, or `-1` if the node is
306     not a child of this node.
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;
316         if (this._isIndexStale) {
317             this._reindex();
318         }
320         index = this._indexMap[node.id];
322         return typeof index === 'undefined' ? -1 : index;
323     },
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.
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.
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.
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.
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     },
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.
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         }
368         return !!(this.parent && this.parent.isInTree());
369     },
371     /**
372     Returns `true` if this node is the root of the tree.
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     },
382     /**
383     Returns this node's next sibling, or `undefined` if this node is the last
384     child.
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     },
396     /**
397     Prepends a node or array of nodes at the beginning of this node's children.
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.
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     },
416     /**
417     Returns this node's previous sibling, or `undefined` if this node is the
418     first child
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     },
430     /**
431     Removes this node from its parent node.
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     },
450     /**
451     Returns the total number of nodes contained within this node, including all
452     descendants of this node's children.
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;
463         for (var i = 0; i < len; i++) {
464             total += children[i].size();
465         }
467         return total;
468     },
470     /**
471     Serializes this node to an object suitable for use in JSON.
473     @method toJSON
474     @return {Object} Serialized node object.
475     **/
476     toJSON: function () {
477         var obj   = {},
478             state = this.state,
479             i, key, len;
481         // Do nothing if this node is marked as destroyed.
482         if (state.destroyed) {
483             return null;
484         }
486         // Serialize properties explicitly marked as serializable.
487         for (i = 0, len = this._serializable.length; i < len; i++) {
488             key = this._serializable[i];
490             if (key in this) {
491                 obj[key] = this[key];
492             }
493         }
495         // Serialize child nodes.
496         if (this.canHaveChildren) {
497             obj.children = [];
499             for (i = 0, len = this.children.length; i < len; i++) {
500                 obj.children.push(this.children[i].toJSON());
501             }
502         }
504         return obj;
505     },
507     /**
508     Performs a depth-first traversal of this node, passing it and each of its
509     descendants to the specified _callback_.
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.
516     Since traversal is depth-first, that means nodes are traversed like this:
518                 1
519               / | \
520              2  8  9
521             / \     \
522            3   7    10
523          / | \      / \
524         4  5  6    11 12
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.
534         @param {Tree.Node} callback.node Node being traversed.
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     },
544     // -- Protected Methods ----------------------------------------------------
545     _reindex: function () {
546         var children = this.children,
547             indexMap = {},
548             i, len;
550         for (i = 0, len = children.length; i < len; i++) {
551             indexMap[children[i].id] = i;
552         }
554         this._indexMap     = indexMap;
555         this._isIndexStale = false;
556     }
559 Y.namespace('Tree').Node = TreeNode;
562 }, '3.13.0');