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