document.js 10 KB

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