treeview.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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 Selection from './selection.js';
  7. import Renderer from './renderer.js';
  8. import DomConverter from './domconverter.js';
  9. import Writer from './writer.js';
  10. import ViewText from './text.js';
  11. import mix from '../../utils/mix.js';
  12. import EmitterMixin from '../../utils/emittermixin.js';
  13. import { keyNames } from '../../utils/keyboard.js';
  14. /**
  15. * TreeView class creates an abstract layer over the content editable area.
  16. * It combines the actual tree of view elements, tree of DOM elements,
  17. * {@link engine.treeView.DomConverter DOM Converter}, {@link engine.treeView.Renderer renderer} and all
  18. * {@link engine.treeView.Observer observers}.
  19. *
  20. * If you want to only transform the tree of view elements to the DOM elements you can use the
  21. * {@link engine.treeView.DomConverter DomConverter}.
  22. *
  23. * @memberOf engine.treeView
  24. * @mixes utils.EmitterMixin
  25. */
  26. export default class TreeView {
  27. /**
  28. * Creates a TreeView instance.
  29. */
  30. constructor() {
  31. /**
  32. * Roots of the DOM tree. Map on the `HTMLElement`s with roots names as keys.
  33. *
  34. * @readonly
  35. * @member {Map} engine.treeView.TreeView#domRoots
  36. */
  37. this.domRoots = new Map();
  38. this.selection = new Selection();
  39. /**
  40. * Tree View writer.
  41. *
  42. * @readonly
  43. * @member {engine.treeView.Writer} engine.treeView.TreeView#writer
  44. */
  45. this.writer = new Writer();
  46. /**
  47. * Instance of the {@link engine.treeView.DomConverter domConverter} use by
  48. * {@link engine.treeView.TreeView#renderer renderer} and {@link engine.treeView.observer.Observer observers}.
  49. *
  50. * @readonly
  51. * @member {engine.treeView.DomConverter} engine.treeView.TreeView#domConverter
  52. */
  53. this.domConverter = new DomConverter();
  54. /**
  55. * Roots of the view tree. Map of the {engine.treeView.Element view elements} with roots names as keys.
  56. *
  57. * @readonly
  58. * @member {Map} engine.treeView.TreeView#viewRoots
  59. */
  60. this.viewRoots = new Map();
  61. /**
  62. * Instance of the {@link engine.treeView.TreeView#renderer renderer}.
  63. *
  64. * @readonly
  65. * @member {engine.treeView.Renderer} engine.treeView.TreeView#renderer
  66. */
  67. this.renderer = new Renderer( this.domConverter, this.selection );
  68. /**
  69. * Set of registered {@link engine.treeView.Observer observers}.
  70. *
  71. * @private
  72. * @member {Set.<engine.treeView.Observer>} engine.treeView.TreeView_#observers
  73. */
  74. this._observers = new Map();
  75. this.on( 'keydown', ( evt, data ) => {
  76. if ( data.keyCode != keyNames.arrowleft ) {
  77. return;
  78. }
  79. if ( !this.renderer._isInlineFillerAtSelection() ) {
  80. return;
  81. }
  82. const selectionPosition = this.selection.getFirstPosition();
  83. if ( selectionPosition.parent instanceof ViewText && selectionPosition.offset > 0 ) {
  84. return;
  85. }
  86. // Damn iframe! I can not use global window, so element -> document -> window -> selection
  87. const domSelection = data.domTarget.ownerDocument.defaultView.getSelection();
  88. const domParent = domSelection.getRangeAt( 0 ).startContainer;
  89. const domRange = new Range();
  90. domRange.setStart( domParent, 0 );
  91. domRange.collapse( true );
  92. domSelection.removeAllRanges();
  93. domSelection.addRange( domRange );
  94. } );
  95. }
  96. /**
  97. * Creates observer of the given type if not yet created, {@link engine.treeView.Observer#enable enables} it
  98. * and {@link engine.treeView.observer.Observer#observe attaches} to all existing and future
  99. * {@link engine.treeView.TreeView#domRoots DOM roots}.
  100. *
  101. * Note: Observers are recognized by their constructor (classes). A single observer will be instantiated and used only
  102. * when registered for the first time. This means that features and other components can register a single observer
  103. * multiple times without caring whether it has been already added or not.
  104. *
  105. * @param {Function} Observer The constructor of an observer to add.
  106. * Should create an instance inheriting from {@link engine.treeView.observer.Observer}.
  107. */
  108. addObserver( Observer ) {
  109. if ( this._observers.has( Observer ) ) {
  110. return;
  111. }
  112. const observer = new Observer( this );
  113. this._observers.set( Observer, observer );
  114. for ( let [ name, domElement ] of this.domRoots ) {
  115. observer.observe( domElement, name );
  116. }
  117. observer.enable();
  118. }
  119. getObserver( Observer ) {
  120. return this._observers.get( Observer );
  121. }
  122. /**
  123. * Creates a root for the HTMLElement. It adds elements to {@link engine.treeView.TreeView#domRoots} and
  124. * {@link engine.treeView.TreeView#viewRoots}.
  125. *
  126. * The constructor copies the element name and attributes to create the
  127. * root of the view, but does not copy its children. This means that while
  128. * {@link engine.treeView.TreeView#render rendering}, the whole content of this
  129. * root element will be removed but the root name and attributes will be preserved.
  130. *
  131. * @param {HTMLElement} domRoot DOM element in which the tree view should do change.
  132. * @param {String} name Name of the root.
  133. * @returns {engine.treeView.element} The created view root element.
  134. */
  135. createRoot( domRoot, name ) {
  136. const viewRoot = this.domConverter.domToView( domRoot, { bind: true, withChildren: false } );
  137. viewRoot.setTreeView( this );
  138. // Mark changed nodes in the renderer.
  139. viewRoot.on( 'change', ( evt, type, node ) => {
  140. this.renderer.markToSync( type, node );
  141. } );
  142. this.renderer.markToSync( 'CHILDREN', viewRoot );
  143. this.domRoots.set( name, domRoot );
  144. this.viewRoots.set( name, viewRoot );
  145. for ( let observer of this._observers.values() ) {
  146. observer.observe( domRoot, name );
  147. }
  148. return viewRoot;
  149. }
  150. /**
  151. * Renders all changes. In order to avoid triggering the observers (e.g. mutations) all observers all detached
  152. * before rendering and reattached after that.
  153. */
  154. render() {
  155. for ( let observer of this._observers.values() ) {
  156. observer.disable();
  157. }
  158. this.renderer.render();
  159. for ( let observer of this._observers.values() ) {
  160. observer.enable();
  161. }
  162. }
  163. }
  164. mix( TreeView, EmitterMixin );
  165. /**
  166. * Enum representing type of the change.
  167. *
  168. * Possible values:
  169. *
  170. * * `CHILDREN` - for child list changes,
  171. * * `ATTRIBUTES` - for element attributes changes,
  172. * * `TEXT` - for text nodes changes.
  173. *
  174. * @typedef {String} engine.treeView.ChangeType
  175. */