node.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. import clone from '../lib/lodash/clone.js';
  7. import utils from '../utils.js';
  8. import CKEditorError from '../ckeditorerror.js';
  9. /**
  10. * Tree model node. This is an abstract class for other classes representing different nodes in Tree Model.
  11. *
  12. * @memberOf core.treeModel
  13. */
  14. export default class Node {
  15. /**
  16. * Creates a tree node.
  17. *
  18. * This is an abstract class, so this constructor should not be used directly.
  19. *
  20. * @param {Iterable|Object} attrs Iterable collection of attributes.
  21. * @abstract
  22. */
  23. constructor( attrs ) {
  24. /**
  25. * Parent element. Null by default. Set by {@link core.treeModel.Element#insertChildren}.
  26. *
  27. * @readonly
  28. * @member {core.treeModel.Element|null} core.treeModel.Node#parent
  29. */
  30. this.parent = null;
  31. /**
  32. * List of attributes set on this node.
  33. *
  34. * **Note:** It is **important** that attributes of nodes already attached to the document must be changed
  35. * only by an {@link core.treeModel.operation.AttributeOperation}. Do not set attributes of such nodes
  36. * using {@link core.treeModel.Node} methods.
  37. *
  38. * @protected
  39. * @member {Map} core.treeModel.Node#_attrs
  40. */
  41. this._attrs = utils.toMap( attrs );
  42. }
  43. /**
  44. * Depth of the node, which equals to total number of its parents.
  45. *
  46. * @readonly
  47. * @type {Number}
  48. */
  49. get depth() {
  50. let depth = 0;
  51. let parent = this.parent;
  52. while ( parent ) {
  53. depth++;
  54. parent = parent.parent;
  55. }
  56. return depth;
  57. }
  58. /**
  59. * Nodes next sibling or `null` if it is the last child.
  60. *
  61. * @readonly
  62. * @type {core.treeModel.Node|null}
  63. */
  64. get nextSibling() {
  65. const index = this.getIndex();
  66. return ( index !== null && this.parent.getChild( index + 1 ) ) || null;
  67. }
  68. /**
  69. * Nodes previous sibling or null if it is the last child.
  70. *
  71. * @readonly
  72. * @type {core.treeModel.Node|null}
  73. */
  74. get previousSibling() {
  75. const index = this.getIndex();
  76. return ( index !== null && this.parent.getChild( index - 1 ) ) || null;
  77. }
  78. /**
  79. * The top parent for the node. If node has no parent it is the root itself.
  80. *
  81. * @readonly
  82. * @type {Number}
  83. */
  84. get root() {
  85. let root = this;
  86. while ( root.parent ) {
  87. root = root.parent;
  88. }
  89. return root;
  90. }
  91. /**
  92. * Index of the node in the parent element or null if the node has no parent.
  93. *
  94. * Throws error if the parent element does not contain this node.
  95. *
  96. * @returns {Number|Null} Index of the node in the parent element or null if the node has not parent.
  97. */
  98. getIndex() {
  99. let pos;
  100. if ( !this.parent ) {
  101. return null;
  102. }
  103. if ( ( pos = this.parent.getChildIndex( this ) ) == -1 ) {
  104. /**
  105. * The node's parent does not contain this node.
  106. *
  107. * @error node-not-found-in-parent
  108. */
  109. throw new CKEditorError( 'node-not-found-in-parent: The node\'s parent does not contain this node.' );
  110. }
  111. return pos;
  112. }
  113. /**
  114. * Gets path to the node. For example if the node is the second child of the first child of the root then the path
  115. * will be `[ 1, 2 ]`. This path can be used as a parameter of {@link core.treeModel.Position}.
  116. *
  117. * @returns {Number[]} The path.
  118. */
  119. getPath() {
  120. const path = [];
  121. let node = this;
  122. while ( node.parent ) {
  123. path.unshift( node.getIndex() );
  124. node = node.parent;
  125. }
  126. return path;
  127. }
  128. /**
  129. * Checks if the node has an attribute for given key.
  130. *
  131. * @param {String} key Key of attribute to check.
  132. * @returns {Boolean} `true` if attribute with given key is set on node, `false` otherwise.
  133. */
  134. hasAttribute( key ) {
  135. return this._attrs.has( key );
  136. }
  137. /**
  138. * Gets an attribute value for given key or undefined if that attribute is not set on node.
  139. *
  140. * @param {String} key Key of attribute to look for.
  141. * @returns {*} Attribute value or null.
  142. */
  143. getAttribute( key ) {
  144. return this._attrs.get( key );
  145. }
  146. /**
  147. * Returns iterator that iterates over this node attributes.
  148. *
  149. * @returns {Iterable.<*>}
  150. */
  151. getAttributes() {
  152. return this._attrs[ Symbol.iterator ]();
  153. }
  154. /**
  155. * Custom toJSON method to solve child-parent circular dependencies.
  156. *
  157. * @returns {Object} Clone of this object with the parent property replaced with its name.
  158. */
  159. toJSON() {
  160. const json = clone( this );
  161. // Due to circular references we need to remove parent reference.
  162. json.parent = this.parent ? this.parent.name : null;
  163. return json;
  164. }
  165. }