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/
8 YUI.add('tree-node', function (Y, NAME) {
10 /*jshint expr:true, onevar:false */
13 Provides the `Tree.Node` class, which represents a tree node contained in a
14 `Tree` data structure.
21 Represents a tree node in a `Tree` data structure.
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`.
50 function TreeNode(tree, config) {
51 config || (config = {});
53 this.id = this._yuid = config.id || this.id || Y.guid('treeNode-');
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;
66 // Mix in arbitrary properties on the config object, but don't overwrite any
67 // existing properties of this node.
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;
77 TreeNode.prototype = {
78 // -- Public Properties ----------------------------------------------------
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
92 @property {Boolean} canHaveChildren
96 Child nodes contained within this node.
98 @property {Tree.Node[]} children
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
114 Unique id for this node.
116 @property {String} id
121 Parent node of this node, or `undefined` if this is an unattached node or
124 @property {Tree.Node} parent
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
139 The Tree instance with which this node is associated.
141 @property {Tree} tree
145 // -- Protected Properties -------------------------------------------------
148 Mapping of child node ids to indices.
150 @property {Object} _indexMap
155 Flag indicating whether the `_indexMap` is stale and needs to be rebuilt.
157 @property {Boolean} _isIndexStale
164 Simple way to type-check that this is an instance of Tree.Node.
166 @property {Boolean} _isYUITreeNode
170 _isYUITreeNode: true,
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
179 @property {String[]} _serializable
182 _serializable: ['canHaveChildren', 'data', 'id', 'state'],
184 // -- Public Methods -------------------------------------------------------
187 Appends the given tree node or array of nodes to the end of this node's
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
195 @param {Object} [options] Options.
196 @param {Boolean} [options.silent=false] If `true`, the `add` event will
198 @return {Tree.Node|Tree.Node[]} Node or array of nodes that were appended.
200 append: function (node, options) {
201 return this.tree.appendNode(this, node, options);
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.
211 @return {Number} This node's depth.
220 parent = this.parent;
224 parent = parent.parent;
231 Removes all children from this node. The removed children will still be
232 reusable unless the `destroy` option is truthy.
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
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.
247 empty: function (options) {
248 return this.tree.emptyNode(this, options);
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
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
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
277 find: function (options, callback, thisObj) {
278 return this.tree.findNode(this, options, callback, thisObj);
282 Returns `true` if this node has one or more child nodes.
285 @return {Boolean} `true` if this node has one or more child nodes, `false`
288 hasChildren: function () {
289 return !!this.children.length;
293 Returns the numerical index of this node within its parent node, or `-1` if
294 this node doesn't have a parent node.
297 @return {Number} Index of this node within its parent node, or `-1` if this
298 node doesn't have a parent node.
301 return this.parent ? this.parent.indexOf(this) : -1;
305 Returns the numerical index of the given child node, or `-1` if the node is
306 not a child of this node.
309 @param {Tree.Node} node Child node.
310 @return {Number} Index of the child, or `-1` if the node is not a child of
313 indexOf: function (node) {
316 if (this._isIndexStale) {
320 index = this._indexMap[node.id];
322 return typeof index === 'undefined' ? -1 : index;
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.
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
342 @param {Boolean} [options.silent=false] If `true`, the `add` event will
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.
351 insert: function (node, options) {
352 return this.tree.insertNode(this, node, options);
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.
360 @return {Boolean} `true` if this node has been inserted into a tree, `false`
363 isInTree: function () {
364 if (this.tree && this.tree.rootNode === this) {
368 return !!(this.parent && this.parent.isInTree());
372 Returns `true` if this node is the root of the tree.
375 @return {Boolean} `true` if this node is the root of the tree, `false`
378 isRoot: function () {
379 return !!(this.tree && this.tree.rootNode === this);
383 Returns this node's next sibling, or `undefined` if this node is the last
387 @return {Tree.Node} This node's next sibling, or `undefined` if this node is
392 return this.parent.children[this.index() + 1];
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.
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
407 @param {Object} [options] Options.
408 @param {Boolean} [options.silent=false] If `true`, the `add` event will
410 @return {Tree.Node|Tree.Node[]} Node or array of nodes that were prepended.
412 prepend: function (node, options) {
413 return this.tree.prependNode(this, node, options);
417 Returns this node's previous sibling, or `undefined` if this node is the
421 @return {Tree.Node} This node's previous sibling, or `undefined` if this
422 node is the first child.
424 previous: function () {
426 return this.parent.children[this.index() - 1];
431 Removes this node from its parent node.
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
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.
446 remove: function (options) {
447 return this.tree.removeNode(this, options);
451 Returns the total number of nodes contained within this node, including all
452 descendants of this node's children.
455 @return {Number} Total number of nodes contained within this node, including
459 var children = this.children,
460 len = children.length,
463 for (var i = 0; i < len; i++) {
464 total += children[i].size();
471 Serializes this node to an object suitable for use in JSON.
474 @return {Object} Serialized node object.
476 toJSON: function () {
481 // Do nothing if this node is marked as destroyed.
482 if (state.destroyed) {
486 // Serialize properties explicitly marked as serializable.
487 for (i = 0, len = this._serializable.length; i < len; i++) {
488 key = this._serializable[i];
491 obj[key] = this[key];
495 // Serialize child nodes.
496 if (this.canHaveChildren) {
499 for (i = 0, len = this.children.length; i < len; i++) {
500 obj.children.push(this.children[i].toJSON());
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:
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
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`.
540 traverse: function (options, callback, thisObj) {
541 return this.tree.traverseNode(this, options, callback, thisObj);
544 // -- Protected Methods ----------------------------------------------------
545 _reindex: function () {
546 var children = this.children,
550 for (i = 0, len = children.length; i < len; i++) {
551 indexMap[children[i].id] = i;
554 this._indexMap = indexMap;
555 this._isIndexStale = false;
559 Y.namespace('Tree').Node = TreeNode;