8
0

document.js 11 KB

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