Răsfoiți Sursa

Render. Better code organization.

Piotr Jasiun 10 ani în urmă
părinte
comite
8720c0d756

+ 39 - 10
packages/ckeditor5-engine/src/treeview/element.js

@@ -21,10 +21,21 @@ CKEDITOR.define( [ 'utils', 'treeview/Node' ], ( utils, Node ) => {
 
 			this._children = utils.clone( children );
 
-			this.updateChildren = false;
-			this.updateAttrs = false;
+			this.domElement = null;
+		}
+
+		setDomElement( domElement ) {
+			this.domElement = domElement;
+
+			Element._domToViewMapping.set( domElement, this );
+		}
+
+		static getCorespondingElement( domElement ) {
+			return this._domToViewMapping.get( domElement );
+		}
 
-			this.DOMElement = null;
+		getChildren() {
+			return this._children[ Symbol.iterator ]();
 		}
 
 		getChild( index ) {
@@ -40,6 +51,8 @@ CKEDITOR.define( [ 'utils', 'treeview/Node' ], ( utils, Node ) => {
 		}
 
 		insertChildren( index, nodes ) {
+			this.markToSync( Node.CHILDREN_NEED_UPDATE );
+
 			if ( !utils.isIterable( nodes ) ) {
 				nodes = [ nodes ];
 			}
@@ -49,36 +62,52 @@ CKEDITOR.define( [ 'utils', 'treeview/Node' ], ( utils, Node ) => {
 
 				this._children.splice( index, 0, node );
 			}
-
-			this.updateChildList = true;
 		}
 
 		removeChildren( index, number ) {
+			this.markToSync( Node.CHILDREN_NEED_UPDATE );
+
 			for ( let i = index; i < index + number; i++ ) {
 				this._children[ i ].parent = null;
 			}
 
-			this.updateChildList = true;
-
 			return new this._children.splice( index, number );
 		}
 
+		getAttrKeys() {
+			return this._attrs.keys();
+		}
+
 		getAttr( key ) {
 			return this._attrs.get( key );
 		}
 
+		hasAttr( key ) {
+			return this._attrs.has( key );
+		}
+
 		setAttr( key, value ) {
-			this.updateAttrs = true;
+			this.markToSync( Node.ATTRIBUTES_NEED_UPDATE );
 
 			this._attrs.set( key, value );
 		}
 
-		hasAttr( key ) {
-			return this._attrs.has( key );
+		cloneDOMAttrs( element ) {
+			this.markToSync( Node.ATTRIBUTES_NEED_UPDATE );
+
+			const attrKeys = element.attributes;
+
+			for ( let key of attrKeys ) {
+				this.setAttr( key, element.getAttribute( key ) );
+			}
 		}
 
 		removeAttr( key ) {
+			this.markToSync( Node.ATTRIBUTES_NEED_UPDATE );
+
 			return this._attrs.delete( key );
 		}
 	}
+
+	Element._domToViewMapping = new WeakMap();
 } );

+ 23 - 3
packages/ckeditor5-engine/src/treeview/node.js

@@ -5,12 +5,10 @@
 
 'use strict';
 
-CKEDITOR.define( [], () => {
+CKEDITOR.define( [ 'treeview/Renderer' ], ( Renderer ) => {
 	class Node {
 		constructor() {
 			this.parent = null;
-
-			this.DOMElement = null;
 		}
 
 		getTreeView() {
@@ -52,5 +50,27 @@ CKEDITOR.define( [], () => {
 
 			return pos;
 		}
+
+		markToSync( type ) {
+			// If the node has no DOM element it is not rendered yet, its children/attributes do not need to be marked to be sync.
+			if ( !this.DOMElement ) {
+				return;
+			}
+
+			const treeView = this.getTreeView();
+
+			// If element is not attached to the Tree view it is a child of the detached subtree and will be rendered anyway with this
+			// subtree.
+			if ( !treeView ) {
+				return;
+			}
+
+			treeView.renderer.markToSync( this, type );
+		}
 	}
+
+	Node.ATTRIBUTES_NEED_UPDATE = Renderer.ATTRIBUTES_NEED_UPDATE;
+	Node.CHILDREN_NEED_UPDATE = Renderer.CHILDREN_NEED_UPDATE;
+
+	return Node;
 } );

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

@@ -23,7 +23,7 @@ CKEDITOR.define( [ 'treeview/observer/observer' ], ( Observer ) => {
 		 * @param {treeView.TreeView}
 		 */
 		init( treeView ) {
-			this.dom = treeView.dom;
+			this.domRoot = treeView.domRoot;
 
 			this._mutationObserver = new window.MutationObserver( this._onMutations.bind( this ) );
 		}
@@ -32,7 +32,7 @@ CKEDITOR.define( [ 'treeview/observer/observer' ], ( Observer ) => {
 		 * @method attach
 		 */
 		attach() {
-			this._mutationObserver.observe( this.dom, this.config );
+			this._mutationObserver.observe( this.domRoot, this.config );
 		}
 
 		/**

+ 83 - 18
packages/ckeditor5-engine/src/treeview/renderer.js

@@ -5,53 +5,118 @@
 
 'use strict';
 
-CKEDITOR.define( [], () => {
+CKEDITOR.define( [ 'utils-diff', 'treeview/element', 'treeview/text' ], ( diff, ViewElement, ViewText ) => {
 	ATTRIBUTES_NEED_UPDATE = 0;
 	CHILDREN_NEED_UPDATE = 1;
-	TEXT_NEEDS_UPDATE = 2;
 
 	class Renderer {
 		constructor( treeView ) {
 			this.view = treeView.view;
 
-			this.dom = treeView.dom;
+			this.domRoot = treeView.domRoot;
+			this.domDocument = domRoot.ownerDocument;
 
-			this.markedTexts = new Set();
 			this.markedAttrs = new Set();
 			this.markedChildren = new Set();
 		}
 
-		markNode( node, type ) {
+		markToSync( node, type ) {
 			if ( type === ATTRIBUTES_NEED_UPDATE ) {
 				this.markedAttrs.push( node );
 			} else if ( type === CHILDREN_NEED_UPDATE ) {
 				this.markedChildren.push( element );
-			} else if ( type === TEXT_NEEDS_UPDATE ) {
-				this.markedTexts.push( element );
 			}
 		}
 
 		render() {
-			this._updateTexts();
-			this._updateAttrs();
-			this._updateChildren();
-		}
+			for ( let element of this.markedAttrs ) {
+				this.updateAttrs( element );
+			}
 
-		_updateTexts() {
+			for ( let element of this.markedChildren ) {
+				this.updateChildren( element );
+			}
 
-		}
+			function updateAttrs( viewElement ) {
+				const domElement = viewElement.domElement;
+				const domAttrKeys = domElement.attributes;
+				const viewAttrKeys = viewElement.getAttrKeys();
 
-		_updateAttrs() {
-		}
+				// Add or overwrite attributes.
+				for ( let key of viewAttrKeys ) {
+					element.setAttribute( key, viewElement.getAttr( key ) );
+				}
+
+				// Remove from DOM attributes which do not exists in the view.
+				for ( let key of domAttrKeys ) {
+					if ( !viewElement.hasAttr( key ) ) {
+						element.removeAttribute( key );
+					}
+				}
+			}
+
+			function updateChildren( viewElement ) {
+				const domElement = viewElement.domElement;
+				const domChildren = domElement.childNodes;
+				const viewChildren = viewElement.getChildren();
+
+				const actions = diff( domChildren, viewChildren, compareNodes );
+
+				let i = 0;
 
-		_updateChildren() {
-			//diff
+				for ( let action of actions ) {
+					if ( action === diff.EQUAL ) {
+						i++;
+					} else if ( action === diff.INSERT ) {
+						domElement.insertBefore( viewToDom( viewChildren[ i ] ), domChildren[ i ] || null  )
+						i++;
+					} else if ( action === diff.DELETE ) {
+						domElement.removeChild( domChildren[ i ] );
+					}
+				}
+			}
+
+			function compareNodes( domNode, viewNode ) {
+				// Elements.
+				if ( domNode instanceof HTMLElement && viewNode instanceof ViewElement ) {
+					return domNode === viewNode.DOMElement
+				}
+				// Texts.
+				else if ( domNode instanceof Text && viewNode instanceof ViewText ) {
+					return domNode.data === viewNode.getText();
+				}
+
+				// Not matching types.
+				return false;
+			}
+
+			function viewToDom( view ) {
+				if ( view.domElement ) {
+					return domElement;
+				}
+
+				if ( view instanceof ViewText ) {
+					return this.domDocument.createTextNode( view.getText() );
+				} else {
+					const domElement = this.domDocument.createElement( view.name );
+					view.setDomElement( domElement );
+
+					for ( let key of view.getAttrKeys() ) {
+						element.setAttribute( key, view.getAttr( key ) );
+					}
+
+					for ( let childView of view.getChildren() ) {
+						element.appendChild( viewToDom( childView ) );
+					}
+
+					return domElement;
+				}
+			}
 		}
 	}
 
 	Renderer.ATTRIBUTES_NEED_UPDATE = ATTRIBUTES_NEED_UPDATE;
 	Renderer.CHILDREN_NEED_UPDATE = CHILDREN_NEED_UPDATE;
-	Renderer.TEXT_NEEDS_UPDATE = TEXT_NEEDS_UPDATE;
 
 	utils.extend( Document.prototype, EmitterMixin );
 

+ 11 - 1
packages/ckeditor5-engine/src/treeview/text.js

@@ -8,7 +8,17 @@
 CKEDITOR.define( [], () => {
 	class Text extends Node {
 		constructor( text ) {
-			this.text = text;
+			this._text = text;
+		}
+
+		getText() {
+			return this._text;
+		}
+
+		setText( text ) {
+			this.parent.markToSync( Node.CHILDREN_NEED_UPDATE );
+
+			this._text = text;
 		}
 	}
 } );

+ 4 - 35
packages/ckeditor5-engine/src/treeview/treeview.js

@@ -5,20 +5,19 @@
 
 'use strict';
 
-CKEDITOR.define( [ 'treeview/renderer' ], ( Renderer ) => {
+CKEDITOR.define( [ 'treeview/rootelement', 'treeview/renderer' ], ( RootElement, Renderer ) => {
 	class TreeView {
 		constructor( domElement ) {
 			/**
 			 * Root of the view
 			 */
-			this.view = domElement.clone();
+			this.viewRoot = new RootElement( this, domElement.name );
+			this.viewRoot.cloneDOMAttrs();
 
 			/**
 			 * Root of the DOM.
 			 */
-			this.dom = domElement;
-
-			this.elementsMapping = new WeakMap();
+			this.domRoot = domElement;
 
 			this.observers = new Set();
 
@@ -42,36 +41,6 @@ CKEDITOR.define( [ 'treeview/renderer' ], ( Renderer ) => {
 				observer.attach();
 			}
 		}
-
-		insertBefore( parent, newNode, referenceNode ) {
-			parent.insertBefore( newNode, referenceNode );
-			this.render.markNode( element, Renderer.CHILDREN_NEED_UPDATE );
-		}
-
-		appendChild( parent, node ) {
-			parent.appendChild( node );
-			this.render.markNode( parent, Renderer.CHILDREN_NEED_UPDATE );
-		}
-
-		removeChild( parent, child ) {
-			parent.removeChild( child );
-			this.render.markNode( parent, Renderer.CHILDREN_NEED_UPDATE );
-		}
-
-		setAttr( element, key, value ) {
-			element.setAttribute( key, value );
-			this.render.markNode( element, Renderer.ATTRIBUTES_NEED_UPDATE );
-		}
-
-		removeAttr( element, key ) {
-			element.removeAttribute( key );
-			this.render.markNode( element, Renderer.ATTRIBUTES_NEED_UPDATE );
-		}
-
-		setText( textNode, text ) {
-			textNode.data = text;
-			this.render.markNode( textNode, Renderer.TEXT_NEEDS_UPDATE );
-		}
 	}
 
 	utils.extend( Document.prototype, EmitterMixin );

+ 16 - 16
packages/ckeditor5-engine/src/utils-diff.js

@@ -7,14 +7,14 @@
 
 // the following code is based on the "O(NP) Sequence Comparison Algorithm" by Sun Wu, Udi Manber, Gene Myers, Webb Miller
 
-CKEDITOR.define( [ 'utils' ], ( utils ) => {
-	// operation types
+CKEDITOR.define( [], () => {
+	// action types
 	var INSERT = 1,
 		DELETE = -1,
 		EQUAL = 0;
 
 	/**
-	 * Calculates the difference between two arrays producing an object containing a list of operations
+	 * Calculates the difference between two arrays producing an object containing a list of actions
 	 * necessary to transform one array into another.
 	 *
 	 *		diff( 'aba', 'acca' ); // [ EQUAL, INSERT, INSERT, DELETE, EQUAL ]
@@ -30,7 +30,7 @@ CKEDITOR.define( [ 'utils' ], ( utils ) => {
 				return a === b;
 			};
 
-		// Temporary operation type statics.
+		// Temporary action type statics.
 		var _INSERT, _DELETE;
 
 		// Swapped the arrays to use the shorter one as the first one.
@@ -40,7 +40,7 @@ CKEDITOR.define( [ 'utils' ], ( utils ) => {
 			a = b;
 			b = tmp;
 
-			// We swap the operation types as well.
+			// We swap the action types as well.
 			_INSERT = DELETE;
 			_DELETE = INSERT;
 		} else {
@@ -66,17 +66,17 @@ CKEDITOR.define( [ 'utils' ], ( utils ) => {
 			// The way we should go to get further.
 			var dir = y1 > y2 ? -1 : 1;
 
-			// Clone previous operations array (if any).
+			// Clone previous actions array (if any).
 			if ( es[ k + dir ] ) {
 				es[ k ] = es[ k + dir ].slice( 0 );
 			}
 
-			// Create operations array.
+			// Create actions array.
 			if ( !es[ k ] ) {
 				es[ k ] = [];
 			}
 
-			// Push the operation.
+			// Push the action.
 			es[ k ].push( y1 > y2 ? _INSERT : _DELETE );
 
 			// Set the beginning coordinates.
@@ -87,7 +87,7 @@ CKEDITOR.define( [ 'utils' ], ( utils ) => {
 			while ( x < m && y < n && cmp( a[ x ], b[ y ] ) ) {
 				x++;
 				y++;
-				// Push no change operation.
+				// Push no change action.
 				es[ k ].push( EQUAL );
 			}
 
@@ -116,15 +116,15 @@ CKEDITOR.define( [ 'utils' ], ( utils ) => {
 			p++;
 		} while ( fp[ delta ] !== n );
 
-		// Return the final list of edit operations.
-		// We remove the first item that represents the operation for the injected nulls.
+		// Return the final list of edit actions.
+		// We remove the first item that represents the action for the injected nulls.
 		return es[ delta ].slice( 1 );
 	}
 
-	// Expose operation types.
-	utils.INSERT = INSERT;
-	utils.DELETE = DELETE;
-	utils.EQUAL = EQUAL;
+	// Expose action types.
+	diff.INSERT = INSERT;
+	diff.DELETE = DELETE;
+	diff.EQUAL = EQUAL;
 
-	return utils;
+	return diff;
 } );