document.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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 Writer from './writer.js';
  9. import DomConverter from './domconverter.js';
  10. import RootEditableElement from './rooteditableelement.js';
  11. import { injectQuirksHandling } from './filler.js';
  12. import log from '../../utils/log.js';
  13. import mix from '../../utils/mix.js';
  14. import ObservableMixin from '../../utils/observablemixin.js';
  15. /**
  16. * Document class creates an abstract layer over the content editable area.
  17. * It combines the actual tree of view elements, tree of DOM elements,
  18. * {@link engine.view.DomConverter DOM Converter}, {@link engine.view.Renderer renderer} and all
  19. * {@link engine.view.Observer observers}.
  20. *
  21. * If you want to only transform the tree of view elements to the DOM elements you can use the
  22. * {@link engine.view.DomConverter DomConverter}.
  23. *
  24. * @memberOf engine.view
  25. * @mixes utils.EmitterMixin
  26. */
  27. export default class Document {
  28. /**
  29. * Creates a Document instance.
  30. */
  31. constructor() {
  32. /**
  33. * Roots of the DOM tree. Map on the `HTMLElement`s with roots names as keys.
  34. *
  35. * @readonly
  36. * @member {Map} engine.view.Document#domRoots
  37. */
  38. this.domRoots = new Map();
  39. /**
  40. * Selection done on this document.
  41. *
  42. * @readonly
  43. * @member {engine.view.Selection} engine.view.Document#selection
  44. */
  45. this.selection = new Selection();
  46. /**
  47. * Tree View writer.
  48. *
  49. * @readonly
  50. * @member {engine.view.Writer} engine.view.Document#writer
  51. */
  52. this.writer = new Writer();
  53. /**
  54. * Instance of the {@link engine.view.DomConverter domConverter} use by
  55. * {@link engine.view.Document#renderer renderer} and {@link engine.view.observer.Observer observers}.
  56. *
  57. * @readonly
  58. * @member {engine.view.DomConverter} engine.view.Document#domConverter
  59. */
  60. this.domConverter = new DomConverter();
  61. /**
  62. * Roots of the view tree. Map of the {engine.view.Element view elements} with roots names as keys.
  63. *
  64. * @readonly
  65. * @member {Map} engine.view.Document#roots
  66. */
  67. this.roots = new Map();
  68. /**
  69. * True if document is focused.
  70. *
  71. * This property is updated by the {@link engine.view.obsever.FocusObserver}.
  72. * If the {@link engine.view.obsever.FocusObserver} is disabled this property will not change.
  73. *
  74. * @readonly
  75. * @observable
  76. * @member {Boolean} engine.view.Document#isFocused
  77. */
  78. this.set( 'isFocused', false );
  79. /**
  80. * {@link engine.view.EditableElement EditableElement} which is containing selection. It can be `null` if
  81. * there is no selection found.
  82. *
  83. * @readonly
  84. * @observable
  85. * @member {engine.view.EditableElement|null} engine.view.Document#selectedEditable
  86. */
  87. this.set( 'selectedEditable', this.selection.getEditableElement() );
  88. this.selection.on( 'change', () => this.selectedEditable = this.selection.getEditableElement() );
  89. /**
  90. * Instance of the {@link engine.view.Document#renderer renderer}.
  91. *
  92. * @readonly
  93. * @member {engine.view.Renderer} engine.view.Document#renderer
  94. */
  95. this.renderer = new Renderer( this.domConverter, this.selection );
  96. this.renderer.bind( 'isFocused' ).to( this, 'isFocused' );
  97. /**
  98. * Map of registered {@link engine.view.Observer observers}.
  99. *
  100. * @private
  101. * @member {Map.<Function, engine.view.Observer>} engine.view.Document#_observers
  102. */
  103. this._observers = new Map();
  104. injectQuirksHandling( this );
  105. }
  106. /**
  107. * Creates observer of the given type if not yet created, {@link engine.view.Observer#enable enables} it
  108. * and {@link engine.view.observer.Observer#observe attaches} to all existing and future
  109. * {@link engine.view.Document#domRoots DOM roots}.
  110. *
  111. * Note: Observers are recognized by their constructor (classes). A single observer will be instantiated and used only
  112. * when registered for the first time. This means that features and other components can register a single observer
  113. * multiple times without caring whether it has been already added or not.
  114. *
  115. * @param {Function} Observer The constructor of an observer to add.
  116. * Should create an instance inheriting from {@link engine.view.observer.Observer}.
  117. * @returns {engine.view.observer.Observer} Added observer instance.
  118. */
  119. addObserver( Observer ) {
  120. let observer = this._observers.get( Observer );
  121. if ( observer ) {
  122. return observer;
  123. }
  124. observer = new Observer( this );
  125. this._observers.set( Observer, observer );
  126. for ( let [ name, domElement ] of this.domRoots ) {
  127. observer.observe( domElement, name );
  128. }
  129. observer.enable();
  130. return observer;
  131. }
  132. /**
  133. * Returns observer of the given type or `undefined` if such observer has not been added yet.
  134. *
  135. * @param {Function} Observer The constructor of an observer to get.
  136. * @returns {engine.view.observer.Observer|undefined} Observer instance or undefined.
  137. */
  138. getObserver( Observer ) {
  139. return this._observers.get( Observer );
  140. }
  141. /**
  142. * Creates a {@link engine.view.Document#roots view root element}.
  143. *
  144. * If the DOM element is passed as a first parameter it will be automatically
  145. * {@link engine.view.Document#attachDomRoot attached}:
  146. *
  147. * document.createRoot( document.querySelector( 'div#editor' ) ); // Will call document.attachDomRoot.
  148. *
  149. * However, if the string is passed, then only the view element will be created and the DOM element have to be
  150. * attached separately:
  151. *
  152. * document.createRoot( 'body' );
  153. * document.attachDomRoot( document.querySelector( 'body#editor' ) );
  154. *
  155. * @param {Element|String} domRoot DOM root element or the tag name of view root element if the DOM element will be
  156. * attached later.
  157. * @param {String} [name='main'] Name of the root.
  158. * @returns {engine.view.RootEditableElement} The created view root element.
  159. */
  160. createRoot( domRoot, name = 'main' ) {
  161. const rootTag = typeof domRoot == 'string' ? domRoot : domRoot.tagName;
  162. const viewRoot = new RootEditableElement( this, rootTag, name );
  163. this.roots.set( name, viewRoot );
  164. // Mark changed nodes in the renderer.
  165. viewRoot.on( 'change:children', ( evt, node ) => this.renderer.markToSync( 'children', node ) );
  166. viewRoot.on( 'change:attributes', ( evt, node ) => this.renderer.markToSync( 'attributes', node ) );
  167. viewRoot.on( 'change:text', ( evt, node ) => this.renderer.markToSync( 'text', node ) );
  168. if ( this.domConverter.isElement( domRoot ) ) {
  169. this.attachDomRoot( domRoot, name );
  170. }
  171. return viewRoot;
  172. }
  173. /**
  174. * Attaches DOM root element to the view element and enable all observers on that element. This method also
  175. * {@link engine.view.Renderer#markToSync mark element} to be synchronized with the view what means that all child
  176. * nodes will be removed and replaced with content of the view root.
  177. *
  178. * Note that {@link engine.view.Document#createRoot} will call this method automatically if the DOM element is
  179. * passed to it.
  180. *
  181. * @param {Element|String} domRoot DOM root element.
  182. * @param {String} [name='main'] Name of the root.
  183. */
  184. attachDomRoot( domRoot, name = 'main' ) {
  185. const viewRoot = this.getRoot( name );
  186. this.domRoots.set( name, domRoot );
  187. this.domConverter.bindElements( domRoot, viewRoot );
  188. this.renderer.markToSync( 'children', viewRoot );
  189. this.renderer.domDocuments.add( domRoot.ownerDocument );
  190. for ( let observer of this._observers.values() ) {
  191. observer.observe( domRoot, name );
  192. }
  193. }
  194. /**
  195. * Gets a {@link engine.view.Document#roots view root element} with the specified name. If the name is not
  196. * specific "main" root is returned.
  197. *
  198. * @param {String} [name='main'] Name of the root.
  199. * @returns {engine.view.RootEditableElement} The view root element with the specified name.
  200. */
  201. getRoot( name = 'main' ) {
  202. return this.roots.get( name );
  203. }
  204. /**
  205. * Gets DOM root element.
  206. *
  207. * @param {String} [name='main'] Name of the root.
  208. * @returns {Element} DOM root element instance.
  209. */
  210. getDomRoot( name = 'main' ) {
  211. return this.domRoots.get( name );
  212. }
  213. /**
  214. * Renders all changes. In order to avoid triggering the observers (e.g. mutations) all observers are disabled
  215. * before rendering and re-enabled after that.
  216. */
  217. render() {
  218. this.disableObservers();
  219. this.renderer.render();
  220. this.enableObservers();
  221. }
  222. /**
  223. * Focuses document. It will focus {@link engine.view.EditableElement EditableElement} that is currently having
  224. * selection inside.
  225. */
  226. focus() {
  227. if ( !this.isFocused ) {
  228. const editable = this.selectedEditable;
  229. if ( editable ) {
  230. this.domConverter.focus( editable );
  231. this.render();
  232. } else {
  233. /**
  234. * Before focusing view document, selection should be placed inside one of the view's editables.
  235. * Normally its selection will be converted from model document (which have default selection), but
  236. * when using view document on its own, we need to manually place selection before focusing it.
  237. *
  238. * @error view-focus-no-selection
  239. */
  240. log.warn( 'view-focus-no-selection: There is no selection in any editable to focus.' );
  241. }
  242. }
  243. }
  244. /**
  245. * Disables all added observers.
  246. */
  247. disableObservers() {
  248. for ( let observer of this._observers.values() ) {
  249. observer.disable();
  250. }
  251. }
  252. /**
  253. * Enables all added observers.
  254. */
  255. enableObservers() {
  256. for ( let observer of this._observers.values() ) {
  257. observer.enable();
  258. }
  259. }
  260. }
  261. mix( Document, ObservableMixin );
  262. /**
  263. * Enum representing type of the change.
  264. *
  265. * Possible values:
  266. *
  267. * * `children` - for child list changes,
  268. * * `attributes` - for element attributes changes,
  269. * * `text` - for text nodes changes.
  270. *
  271. * @typedef {String} engine.view.ChangeType
  272. */