element.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. import Node from './node.js';
  7. import utils from '../utils.js';
  8. import isPlainObject from '../lib/lodash/isPlainObject.js';
  9. /**
  10. * Tree view element.
  11. *
  12. * @memberOf core.treeView
  13. * @extends core.treeView.Node
  14. */
  15. export default class Element extends Node {
  16. /**
  17. * Creates a tree view element.
  18. *
  19. * Attributes can be passes in various formats:
  20. *
  21. * new Element( 'div', { 'class': 'editor', 'contentEditable': 'true' } ); // object
  22. * new Element( 'div', [ [ 'class', 'editor' ], [ 'contentEditable', 'true' ] ] ); // map-like iterator
  23. * new Element( 'div', mapOfAttributes ); // map
  24. *
  25. * @param {String} name Node name.
  26. * @param {Object|Iterable} [attrs] Collection of attributes.
  27. * @param {core.treeView.Node|Iterable.<core.treeView.Node>} [children] List of nodes to be inserted into created element.
  28. */
  29. constructor( name, attrs, children ) {
  30. super();
  31. /**
  32. * Name of the element.
  33. *
  34. * @readonly
  35. * @member {String} core.treeView.Element#name
  36. */
  37. this.name = name;
  38. /**
  39. * Map of attributes, where attributes names are keys and attributes values are values.
  40. *
  41. * @protected
  42. * @member {Map} core.treeView.Element#_attrs
  43. */
  44. if ( isPlainObject( attrs ) ) {
  45. this._attrs = utils.objectToMap( attrs );
  46. } else {
  47. this._attrs = new Map( attrs );
  48. }
  49. /**
  50. * Array of child nodes.
  51. *
  52. * @protected
  53. * @member {Array.<core.treeView.Node>} core.treeView.Element#_children
  54. */
  55. this._children = [];
  56. if ( children ) {
  57. this.insertChildren( 0, children );
  58. }
  59. }
  60. /**
  61. * Clones provided element.
  62. *
  63. * @param {Boolean} deep If set to `true` clones element and all its children recursively. When set to `false`,
  64. * element will be cloned without any children.
  65. * @returns {Element} Clone of this element.
  66. */
  67. clone( deep ) {
  68. const childrenClone = [];
  69. if ( deep ) {
  70. for ( let child of this.getChildren() ) {
  71. childrenClone.push( child.clone( deep ) );
  72. }
  73. }
  74. return new Element( this.name, this._attrs, childrenClone );
  75. }
  76. /**
  77. * {@link core.treeView.Element#insert Insert} a child node or a list of child nodes at the end of this node and sets
  78. * the parent of these nodes to this element.
  79. *
  80. * @fires core.treeView.Node#change
  81. * @param {core.treeView.Node|Iterable.<core.treeView.Node>} nodes Node or the list of nodes to be inserted.
  82. * @returns {Number} Number of appended nodes.
  83. */
  84. appendChildren( nodes ) {
  85. return this.insertChildren( this.getChildCount(), nodes );
  86. }
  87. /**
  88. * Gets child at the given index.
  89. *
  90. * @param {Number} index Index of child.
  91. * @returns {core.treeView.Node} Child node.
  92. */
  93. getChild( index ) {
  94. return this._children[ index ];
  95. }
  96. /**
  97. * Gets the number of element's children.
  98. *
  99. * @returns {Number} The number of element's children.
  100. */
  101. getChildCount() {
  102. return this._children.length;
  103. }
  104. /**
  105. * Gets index of the given child node. Returns `-1` if child node is not found.
  106. *
  107. * @param {core.treeView.Node} node Child node.
  108. * @returns {Number} Index of the child node.
  109. */
  110. getChildIndex( node ) {
  111. return this._children.indexOf( node );
  112. }
  113. /**
  114. * Gets child nodes iterator.
  115. *
  116. * @returns {Iterable.<core.treeView.Node>} Child nodes iterator.
  117. */
  118. getChildren() {
  119. return this._children[ Symbol.iterator ]();
  120. }
  121. /**
  122. * Returns an iterator that contains the keys for attributes.
  123. *
  124. * @returns {Iterator.<String>} Keys for attributes.
  125. */
  126. getAttributeKeys() {
  127. return this._attrs.keys();
  128. }
  129. /**
  130. * Gets attribute by key.
  131. *
  132. * @param {String} key Attribute key.
  133. * @returns {String} Attribute value.
  134. */
  135. getAttribute( key ) {
  136. return this._attrs.get( key );
  137. }
  138. /**
  139. * Returns a boolean indicating whether an attribute with the specified key exists in the element.
  140. *
  141. * @param {String} key Attribute key.
  142. * @returns {Boolean} `true` if attribute with the specified key exists in the element, false otherwise.
  143. */
  144. hasAttribute( key ) {
  145. return this._attrs.has( key );
  146. }
  147. /**
  148. * Adds or overwrite attribute with a specified key and value.
  149. *
  150. * @param {String} key Attribute key.
  151. * @param {String} value Attribute value.
  152. * @fires core.treeView.Node#change
  153. */
  154. setAttribute( key, value ) {
  155. this._fireChange( 'ATTRIBUTES', this );
  156. this._attrs.set( key, value );
  157. }
  158. /**
  159. * Inserts a child node or a list of child nodes on the given index and sets the parent of these nodes to
  160. * this element.
  161. *
  162. * @param {Number} index Position where nodes should be inserted.
  163. * @param {core.treeView.Node|Iterable.<core.treeView.Node>} nodes Node or the list of nodes to be inserted.
  164. * @fires core.treeView.Node#change
  165. * @returns {Number} Number of inserted nodes.
  166. */
  167. insertChildren( index, nodes ) {
  168. this._fireChange( 'CHILDREN', this );
  169. let count = 0;
  170. if ( !utils.isIterable( nodes ) ) {
  171. nodes = [ nodes ];
  172. }
  173. for ( let node of nodes ) {
  174. node.parent = this;
  175. this._children.splice( index, 0, node );
  176. index++;
  177. count++;
  178. }
  179. return count;
  180. }
  181. /**
  182. * Removes attribute from the element.
  183. *
  184. * @param {String} key Attribute key.
  185. * @returns {Boolean} Returns true if an attribute existed and has been removed.
  186. * @fires core.treeView.Node#change
  187. */
  188. removeAttribute( key ) {
  189. this._fireChange( 'ATTRIBUTES', this );
  190. return this._attrs.delete( key );
  191. }
  192. /**
  193. * Removes number of child nodes starting at the given index and set the parent of these nodes to `null`.
  194. *
  195. * @param {Number} index Number of the first node to remove.
  196. * @param {Number} [number] Number of nodes to remove.
  197. * @returns {Array.<core.treeView.Node>} The array of removed nodes.
  198. * @fires core.treeView.Node#change
  199. */
  200. removeChildren( index, number ) {
  201. this._fireChange( 'CHILDREN', this );
  202. if ( typeof number === 'undefined' ) {
  203. number = 1;
  204. }
  205. for ( let i = index; i < index + number; i++ ) {
  206. this._children[ i ].parent = null;
  207. }
  208. return this._children.splice( index, number );
  209. }
  210. /**
  211. * Checks if this element is similar to other element.
  212. * Both elements should have the same name and attributes to be considered as similar. Two similar elements
  213. * can contain different set of children nodes.
  214. *
  215. * @param {Element} otherElement
  216. * @returns {Boolean}
  217. */
  218. isSimilar( otherElement ) {
  219. if ( !( otherElement instanceof Element ) ) {
  220. return false;
  221. }
  222. // If exactly the same Element is provided - return true immediately.
  223. if ( this === otherElement ) {
  224. return true;
  225. }
  226. // Check name and attributes.
  227. if ( this.name != otherElement.name ) {
  228. return false;
  229. }
  230. const thisNodeAttrKeys = this.getAttributeKeys();
  231. const otherNodeAttrKeys = otherElement.getAttributeKeys();
  232. let count = 0;
  233. for ( let key of thisNodeAttrKeys ) {
  234. if ( this.getAttribute( key ) !== otherElement.getAttribute( key ) ) {
  235. return false;
  236. }
  237. count++;
  238. }
  239. if ( count != utils.count( otherNodeAttrKeys ) ) {
  240. return false;
  241. }
  242. return true;
  243. }
  244. }