8
0

node.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import CKEditorError from '../../utils/ckeditorerror.js';
  6. import EmitterMixin from '../../utils/emittermixin.js';
  7. import mix from '../../utils/mix.js';
  8. /**
  9. * Abstract tree view node class.
  10. *
  11. * @abstract
  12. * @memberOf engine.view
  13. */
  14. export default class Node {
  15. /**
  16. * Creates a tree view node.
  17. *
  18. * This is an abstract class, so this constructor should not be used directly.
  19. */
  20. constructor() {
  21. /**
  22. * Parent element. Null by default. Set by {@link engine.view.Element#insertChildren}.
  23. *
  24. * @readonly
  25. * @member {engine.view.Element|engine.view.DocumentFragment|null} engine.view.Node#parent
  26. */
  27. this.parent = null;
  28. }
  29. /**
  30. * Returns index of the node in the parent element or null if the node has no parent.
  31. *
  32. * Throws error if the parent element does not contain this node.
  33. *
  34. * @returns {Number|null} Index of the node in the parent element or null if the node has not parent.
  35. */
  36. getIndex() {
  37. let pos;
  38. if ( !this.parent ) {
  39. return null;
  40. }
  41. // No parent or child doesn't exist in parent's children.
  42. if ( ( pos = this.parent.getChildIndex( this ) ) == -1 ) {
  43. /**
  44. * The node's parent does not contain this node. It means that the document tree is corrupted.
  45. *
  46. * @error view-node-not-found-in-parent
  47. */
  48. throw new CKEditorError( 'view-node-not-found-in-parent: The node\'s parent does not contain this node.' );
  49. }
  50. return pos;
  51. }
  52. /**
  53. * Returns nodes next sibling or `null` if it is the last child.
  54. *
  55. * @returns {engine.view.Node|null} Nodes next sibling or `null` if it is the last child.
  56. */
  57. getNextSibling() {
  58. const index = this.getIndex();
  59. return ( index !== null && this.parent.getChild( index + 1 ) ) || null;
  60. }
  61. /**
  62. * Returns nodes previous sibling or `null` if it is the first child.
  63. *
  64. * @returns {engine.view.Node|null} Nodes previous sibling or `null` if it is the first child.
  65. */
  66. getPreviousSibling() {
  67. const index = this.getIndex();
  68. return ( index !== null && this.parent.getChild( index - 1 ) ) || null;
  69. }
  70. /**
  71. * Gets {@link engine.view.Document} reference, from the {@link engine.view.Node#getRoot root} or
  72. * returns null if the root has no reference to the {@link engine.view.Document}.
  73. *
  74. * @returns {engine.view.Document|null} View document of the node or null.
  75. */
  76. getDocument() {
  77. // Parent might be Node, null or DocumentFragment.
  78. if ( this.parent instanceof Node ) {
  79. return this.parent.getDocument();
  80. } else {
  81. return null;
  82. }
  83. }
  84. /**
  85. * Gets the top parent for the node. If node has no parent it is the root itself.
  86. *
  87. * @returns {engine.view.Node|engine.view.DocumentFragment}
  88. */
  89. getRoot() {
  90. let root = this;
  91. while ( root.parent ) {
  92. root = root.parent;
  93. }
  94. return root;
  95. }
  96. /**
  97. * Returns ancestors array of this node.
  98. *
  99. * @param {Object} options Options object.
  100. * @param {Boolean} [options.includeNode=false] When set to `true` this node will be also included in parent's array.
  101. * @param {Boolean} [options.parentFirst=false] When set to `true`, array will be sorted from node's parent to root element,
  102. * otherwise root element will be the first item in the array.
  103. * @returns {Array} Array with ancestors.
  104. */
  105. getAncestors( options = { includeNode: false, parentFirst: false } ) {
  106. const ancestors = [];
  107. let parent = options.includeNode ? this : this.parent;
  108. while ( parent ) {
  109. ancestors[ options.parentFirst ? 'push' : 'unshift' ]( parent );
  110. parent = parent.parent;
  111. }
  112. return ancestors;
  113. }
  114. /**
  115. * Removes node from parent.
  116. */
  117. remove() {
  118. this.parent.removeChildren( this.getIndex() );
  119. }
  120. /**
  121. * @param {engine.view.ChangeType} type Type of the change.
  122. * @param {engine.view.Node} node Changed node.
  123. * @fires engine.view.Node#change
  124. */
  125. _fireChange( type, node ) {
  126. this.fire( 'change:' + type, node );
  127. if ( this.parent ) {
  128. this.parent._fireChange( type, node );
  129. }
  130. }
  131. /**
  132. * Clones this node.
  133. *
  134. * @method view.Node#clone
  135. * @returns {view.Node} Clone of this node.
  136. */
  137. /**
  138. * Checks if provided node is similar to this node.
  139. *
  140. * @method view.Node#isSimilar
  141. * @returns {Boolean} True if nodes are similar.
  142. */
  143. /**
  144. * Fired when list of {@link engine.view.Element elements} children changes.
  145. *
  146. * Change event is bubbled – it is fired on all ancestors.
  147. *
  148. * @event engine.view.Node#change:children
  149. * @param {engine.view.Node} Changed node.
  150. */
  151. /**
  152. * Fired when list of {@link engine.view.Element elements} attributes changes.
  153. *
  154. * Change event is bubbled – it is fired on all ancestors.
  155. *
  156. * @event engine.view.Node#change:attributes
  157. * @param {engine.view.Node} Changed node.
  158. */
  159. /**
  160. * Fired when {@link engine.view.Text text nodes} data changes.
  161. *
  162. * Change event is bubbled – it is fired on all ancestors.
  163. *
  164. * @event engine.view.Node#change:text
  165. * @param {engine.view.Node} Changed node.
  166. */
  167. }
  168. mix( Node, EmitterMixin );