API Docs for: 3.17.2
Show:

File: tree/js/tree-node.js

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