Ver Fonte

Docs for mutation observer. Small API changes.

Piotr Jasiun há 10 anos atrás
pai
commit
9efc5481cb

+ 114 - 13
packages/ckeditor5-engine/src/treeview/observer/mutationobserver.js

@@ -7,11 +7,36 @@
 
 import Observer from './observer.js';
 
+/**
+ * Mutation observer class observe changes in the DOM, fire {@link treeview.TreeView#mutations} event, mark view elements
+ * as changed and call {@link treeview.render}. Because all mutated nodes are marked as "to be rendered" and the
+ * {@link treeview.render} is called, all changes will be reverted, unless the mutation will be handled by the
+ * {@link treeview.TreeView#mutations} event listener. It means user will see only handled changes, and the editor will
+ * block all changes which are not handled.
+ *
+ * Mutation Observer also take care of reducing number of mutations which are fired. It removes duplicates and
+ * mutations on elements which do not have corresponding view elements. Also
+ * {@link treeview.TreeView.MutatatedText text mutation} is fired only if parent element do not change child list.
+ *
+ * @class treeview.observer.MutationObserver
+ */
 export default class MutationObserver extends Observer {
+	/**
+	 * Mutation observer constructor. Note that most of the initialization is done in
+	 * {@link treeview.observer.MutationObserver#init} method.
+	 *
+	 * @constructor
+	 */
 	constructor() {
 		super();
 
-		this.config = {
+		/**
+		 * Native mutation observer config.
+		 *
+		 * @private
+		 * @type {Object}
+		 */
+		this._config = {
 			childList: true,
 			characterData: true,
 			characterDataOldValue: true,
@@ -19,41 +44,68 @@ export default class MutationObserver extends Observer {
 		};
 	}
 
-	/**
-	 * @method init
-	 * @param {treeView.TreeView}
-	 */
+	// Docs in the base class.
 	init( treeView ) {
+		/**
+		 * Referense to the {@link treeview.TreeView} object.
+		 *
+		 * @type {treeview.TreeView}
+		 */
 		this.treeView = treeView;
 
+		/**
+		 * Reference to the {@link treeview.TreeView#domRoot}.
+		 *
+		 * @type {HTMLElement}
+		 */
 		this.domRoot = treeView.domRoot;
 
+		/**
+		 * Reference to the {@link treeview.TreeView#converter}.
+		 *
+		 * @type {treeview.Converter}
+		 */
 		this.converter = treeView.converter;
 
+		/**
+		 * Reference to the {@link treeview.TreeView#renderer}.
+		 *
+		 * @type {treeview.Renderer}
+		 */
 		this.renderer = treeView.renderer;
 
+		/**
+		 * Native mutation observer.
+		 *
+		 * @private
+		 * @type {window.MutationObserver}
+		 */
 		this._mutationObserver = new window.MutationObserver( this._onMutations.bind( this ) );
 	}
 
-	/**
-	 * @method attach
-	 */
+	// Docs in the base class.
 	attach() {
-		this._mutationObserver.observe( this.domRoot, this.config );
+		this._mutationObserver.observe( this.domRoot, this._config );
 	}
 
-	/**
-	 * @method detach
-	 */
+	// Docs in the base class.
 	detach() {
 		this._mutationObserver.disconnect();
 	}
 
+	/**
+	 * Handle mutations. Deduplicate, mark view elements to sync, fire event and call render.
+	 *
+	 * @protected
+	 * @param {Array.<Object>} domMutations Array of native mutations.
+	 */
 	_onMutations( domMutations ) {
 		// Use map and set for deduplication.
 		const mutatedTexts = new Map();
 		const mutatedElements = new Set();
 
+		// Handle `childList` mutations first, so we will be able to check if the `characterData` mutation is in the
+		// element with changed structure anyway.
 		for ( let mutation of domMutations ) {
 			if ( mutation.type === 'childList' ) {
 				const element = this.converter.getCorespondingViewElement( mutation.target );
@@ -64,11 +116,14 @@ export default class MutationObserver extends Observer {
 			}
 		}
 
+		// Handle `characterData` mutations later, when we have the full list of nodes which changed structure.
 		for ( let mutation of domMutations ) {
 			if ( mutation.type === 'characterData' ) {
 				const text = this.converter.getCorespondingViewText( mutation.target );
 
 				if ( text && !mutatedElements.has( text.parent ) ) {
+					// Use text as a key, for deduplication. If there will be another mutation on the same text element
+					// we will have only one in the map.
 					mutatedTexts.set( text, {
 						type: 'text',
 						oldText: text.getText(),
@@ -79,6 +134,10 @@ export default class MutationObserver extends Observer {
 			}
 		}
 
+		// Now we build the list of mutations to fire and mark elements. We did not do it earlier to avoid marking the
+		// same node multiple times in case of duplication.
+
+		// List of mutations we will fire.
 		const viewMutations = [];
 
 		for ( let mutatedText of mutatedTexts.values() ) {
@@ -93,6 +152,7 @@ export default class MutationObserver extends Observer {
 			const viewChildren = viewElement.getChildren();
 			const newViewChildren = [];
 
+			// We want to have a list of View elements, not DOM elements.
 			for ( let i = 0; i < domChildren.length; i++ ) {
 				newViewChildren.push( this.converter.domToView( domChildren[ i ] ) );
 			}
@@ -100,7 +160,7 @@ export default class MutationObserver extends Observer {
 			this.renderer.markToSync( 'CHILDREN', viewElement );
 
 			viewMutations.push( {
-				type: 'childNodes',
+				type: 'children',
 				oldChildren: Array.from( viewChildren ),
 				newChildren: newViewChildren,
 				node: viewElement
@@ -111,4 +171,45 @@ export default class MutationObserver extends Observer {
 
 		this.treeView.render();
 	}
+
+	/**
+	 * Fired when mutation occurred. If tree view is not changed on this event, DOM will be reverter to the state before
+	 * mutation, so all changes which should be applied, should be handled on this event.
+	 *
+	 * @event mutations
+	 * @memberOf treeView.TreeView
+	 *
+	 * @param {Array.<treeview.TreeView.MutatatedText|treeview.TreeView.MutatatedChildren>} viewMutations
+	 * Array of mutations.
+	 * For mutated texts it will be {@link treeview.TreeView.MutatatedText} and for mutated elements it will be
+	 * {@link treeview.TreeView.MutatatedElement}. You can recognize the type based on the `type` property.
+	 */
+
+	/**
+	 * Mutation item for text.
+	 *
+	 * @see treeview.TreeView#mutations
+	 * @see treeview.TreeView.MutatatedChildren
+	 *
+	 * @typedef {Object} treeview.TreeView.MutatatedText
+	 *
+	 * @property {String} type For text mutations it is always 'text'.
+	 * @property {treeview.Text} node Mutated text node.
+	 * @property {String} oldText Old text.
+	 * @property {String} newText New text.
+	 */
+
+	/**
+	 * Mutation item for child nodes.
+	 *
+	 * @see treeview.TreeView#mutations
+	 * @see treeview.TreeView.MutatatedText
+	 *
+	 * @typedef {Object} treeview.TreeView.MutatatedChildren
+	 *
+	 * @property {String} type For child nodes mutations it is always 'children'.
+	 * @property {treeview.Element} node Parent of the mutated children.
+	 * @property {Array.<treeview.Node>} oldChildren Old child nodes.
+	 * @property {Array.<treeview.Node>} newChildren New child nodes.
+	 */
 }

+ 11 - 11
packages/ckeditor5-engine/src/treeview/observer/observer.js

@@ -10,14 +10,22 @@
  * and fire events on the {@link treeView.TreeView} objects.
  *
  * @abstract
- * @class treeView.Observer
+ * @class treeView.observer.Observer
  */
 export default class Observer {
+	/**
+	 * Init the observer class, cache all needed {@link treeView.TreeView} properties, create objects.
+	 * This method do not {@link treeView.observer.Observer#attach attach} listeners to the DOM.
+	 *
+	 * @method init
+	 * @param {treeView.TreeView}
+	 */
+
 	/**
 	 * Attach observers and linsters to DOM elements. This method is called when then observer is added to the
 	 * {@link treeView.TreeView} and after {@link treeView.TreeView#render rendering} to reattach observers and linsters.
 	 *
-	 * @see treeView.Observer#detach
+	 * @see treeView.observer.Observer#detach
 	 *
 	 * @method attach
 	 */
@@ -27,16 +35,8 @@ export default class Observer {
 	 * {@link treeView.TreeView#render rendering} to prevent fireing events during rendering and when the editor is
 	 * destroyed.
 	 *
-	 * @see treeView.Observer#attach
+	 * @see treeView.observer.Observer#attach
 	 *
 	 * @method detach
 	 */
-
-	/**
-	 * Init the observer class, cache all needed {@link treeView.TreeView} properties, create objects.
-	 * This method do not {@link treeView.Observer#attach attach} listeners to the DOM.
-	 *
-	 * @method init
-	 * @param {treeView.TreeView}
-	 */
 }

+ 2 - 2
packages/ckeditor5-engine/tests/treeview/observer/mutationobserver.js

@@ -59,7 +59,7 @@ describe( 'MutationObserver', () => {
 
 		expectDomEditorNotToChange();
 		expect( lastMutations.length ).to.equals( 1 );
-		expect( lastMutations[ 0 ].type ).to.equals( 'childNodes' );
+		expect( lastMutations[ 0 ].type ).to.equals( 'children' );
 		expect( lastMutations[ 0 ].node ).to.equals( treeView.viewRoot.getChild( 0 ) );
 
 		expect( lastMutations[ 0 ].newChildren.length ).to.equals( 2 );
@@ -97,7 +97,7 @@ describe( 'MutationObserver', () => {
 
 		expectDomEditorNotToChange();
 		expect( lastMutations.length ).to.equals( 1 );
-		expect( lastMutations[ 0 ].type ).to.equals( 'childNodes' );
+		expect( lastMutations[ 0 ].type ).to.equals( 'children' );
 		expect( lastMutations[ 0 ].node ).to.equals( treeView.viewRoot );
 	} );