8
0

node.js 4.4 KB

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