Przeglądaj źródła

Added DocumentFragment support treeView.DomConvereter.

Szymon Kupś 9 lat temu
rodzic
commit
036a55f102

+ 64 - 0
packages/ckeditor5-engine/src/treeview/domconverter.js

@@ -7,6 +7,7 @@
 
 import ViewText from './text.js';
 import ViewElement from './element.js';
+import ViewDocumentFragment from './documentfragment.js';
 
 /**
  * DomConverter is a set of tools to do transformations between DOM nodes and view nodes. It also handles
@@ -64,6 +65,19 @@ export default class DomConverter {
 	}
 
 	/**
+	 * Binds DOM and View document fragments, so it will be possible to get corresponding document fragments using
+	 * {@link engine.treeView.DomConverter#getCorrespondingViewDocumentFragment} and
+	 * {@link engine.treeView.DomConverter#getCorrespondingDomDocumentFragment.
+	 *
+	 * @param {DocumentFragment} domFragment DOM document fragment to bind.
+	 * @param {engine.treeView.DocumentFragment} viewFragment View document fragment to bind.
+	 */
+	bindDocumentFragments( domFragment, viewFragment ) {
+		this._domToViewMapping.set( domFragment, viewFragment );
+		this._viewToDomMapping.set( viewFragment, domFragment );
+	}
+
+	/**
 	 * Compares DOM and View nodes. Elements are same when they are bound. Text nodes are same when they have the same
 	 * text data. Nodes need to have corresponding types. In all other cases nodes are different.
 	 *
@@ -103,6 +117,24 @@ export default class DomConverter {
 
 		if ( viewNode instanceof ViewText ) {
 			return domDocument.createTextNode( viewNode.data );
+		} else if ( viewNode instanceof  ViewDocumentFragment ) {
+			if ( this.getCorrespondingDom( viewNode ) ) {
+				return this.getCorrespondingDom( viewNode );
+			}
+
+			const domFragment = domDocument.createDocumentFragment();
+
+			if ( options.bind ) {
+				this.bindDocumentFragments( domFragment, viewNode );
+			}
+
+			if ( options.withChildren || options.withChildren === undefined ) {
+				for ( let childView of viewNode.getChildren() ) {
+					domFragment.appendChild( this.viewToDom( childView, domDocument, options ) );
+				}
+			}
+
+			return domFragment;
 		} else {
 			if ( this.getCorrespondingDom( viewNode ) ) {
 				return this.getCorrespondingDom( viewNode );
@@ -145,6 +177,26 @@ export default class DomConverter {
 
 		if ( domNode instanceof Text ) {
 			return new ViewText( domNode.data );
+		} else if ( domNode instanceof DocumentFragment ) {
+			if ( this.getCorrespondingView( domNode ) ) {
+				return this.getCorrespondingView( domNode );
+			}
+
+			const viewFragment = new ViewDocumentFragment();
+
+			if ( options.bind ) {
+				this.bindDocumentFragments( domNode, viewFragment );
+			}
+
+			if ( options.withChildren || options.withChildren === undefined ) {
+				for ( let i = 0, len = domNode.childNodes.length; i < len; i++ ) {
+					let domChild = domNode.childNodes[ i ];
+
+					viewFragment.appendChildren( this.domToView( domChild, options ) );
+				}
+			}
+
+			return viewFragment;
 		} else {
 			if ( this.getCorrespondingView( domNode ) ) {
 				return this.getCorrespondingView( domNode );
@@ -184,6 +236,8 @@ export default class DomConverter {
 	getCorrespondingView( domNode ) {
 		if ( domNode instanceof HTMLElement ) {
 			return this.getCorrespondingViewElement( domNode );
+		} else if ( domNode instanceof DocumentFragment ) {
+			return this.getCorrespondingViewDocumentFragment( domNode );
 		} else {
 			return this.getCorrespondingViewText( domNode );
 		}
@@ -200,6 +254,10 @@ export default class DomConverter {
 		return this._domToViewMapping.get( domElement );
 	}
 
+	getCorrespondingViewDocumentFragment( domFragment ) {
+		return this._domToViewMapping.get( domFragment );
+	}
+
 	/**
 	 * Gets corresponding text node. Text nodes are not {@link engine.treeView.DomConverter#bindElements bound},
 	 * corresponding text node is returned based on the sibling or parent.
@@ -254,6 +312,8 @@ export default class DomConverter {
 	getCorrespondingDom( viewNode ) {
 		if ( viewNode instanceof ViewElement ) {
 			return this.getCorrespondingDomElement( viewNode );
+		} else if ( viewNode instanceof ViewDocumentFragment ) {
+			return this.getCorrespondingDomDocumentFragment( viewNode );
 		} else {
 			return this.getCorrespondingDomText( viewNode );
 		}
@@ -270,6 +330,10 @@ export default class DomConverter {
 		return this._viewToDomMapping.get( viewElement );
 	}
 
+	getCorrespondingDomDocumentFragment( viewDocumentFragment ) {
+		return this._viewToDomMapping.get( viewDocumentFragment );
+	}
+
 	/**
 	 * Gets corresponding text node. Text nodes are not {@link engine.treeView.DomConverter#bindElements bound},
 	 * corresponding text node is returned based on the sibling or parent.

+ 153 - 0
packages/ckeditor5-engine/tests/treeview/domconverter.js

@@ -11,6 +11,7 @@ import utils from '/ckeditor5/utils/utils.js';
 import ViewElement from '/ckeditor5/engine/treeview/element.js';
 import ViewText from '/ckeditor5/engine/treeview/text.js';
 import DomConverter from '/ckeditor5/engine/treeview/domconverter.js';
+import ViewDocumentFragment from '/ckeditor5/engine/treeview/documentfragment.js';
 
 describe( 'DomConverter', () => {
 	let converter;
@@ -31,6 +32,18 @@ describe( 'DomConverter', () => {
 		} );
 	} );
 
+	describe( 'bindDocumentFragments', () => {
+		it( 'should bind document fragments', () => {
+			const domFragment = document.createDocumentFragment();
+			const viewFragment = new ViewDocumentFragment();
+
+			converter.bindDocumentFragments( domFragment, viewFragment );
+
+			expect( converter.getCorrespondingView( domFragment ) ).to.equal( viewFragment );
+			expect( converter.getCorrespondingDom( viewFragment ) ).to.equal( domFragment );
+		} );
+	} );
+
 	describe( 'compareNodes', () => {
 		it( 'should return false for nodes not matching types', () => {
 			const domElement = document.createElement( 'p' );
@@ -138,6 +151,56 @@ describe( 'DomConverter', () => {
 			expect( viewP.getChildCount() ).to.equal( 0 );
 			expect( converter.getCorrespondingDom( viewP ) ).to.not.equal( domP );
 		} );
+
+		it( 'should create view document fragment from DOM document fragment', () => {
+			const domImg = document.createElement( 'img' );
+			const domText = document.createTextNode( 'foo' );
+			const domFragment = document.createDocumentFragment();
+
+			domFragment.appendChild( domImg );
+			domFragment.appendChild( domText );
+
+			const viewFragment = converter.domToView( domFragment, { bind: true } );
+
+			expect( viewFragment ).to.be.an.instanceof( ViewDocumentFragment );
+			expect( viewFragment.getChildCount() ).to.equal( 2 );
+			expect( viewFragment.getChild( 0 ).name ).to.equal( 'img' );
+			expect( viewFragment.getChild( 1 ).data ).to.equal( 'foo' );
+
+			expect( converter.getCorrespondingDom( viewFragment ) ).to.equal( domFragment );
+			expect( converter.getCorrespondingDom( viewFragment.getChild( 0 ) ) ).to.equal( domFragment.childNodes[ 0 ] );
+		} );
+
+		it( 'should create view document fragment from DOM document fragment without children', () => {
+			const domImg = document.createElement( 'img' );
+			const domText = document.createTextNode( 'foo' );
+			const domFragment = document.createDocumentFragment();
+
+			domFragment.appendChild( domImg );
+			domFragment.appendChild( domText );
+
+			const viewImg = new ViewElement( 'img' );
+
+			converter.bindElements( domImg, viewImg );
+
+			const viewFragment = converter.domToView( domFragment, { withChildren: false } );
+
+			expect( viewFragment ).to.be.an.instanceof( ViewDocumentFragment );
+
+			expect( viewFragment.getChildCount() ).to.equal( 0 );
+			expect( converter.getCorrespondingDom( viewFragment ) ).to.not.equal( domFragment );
+		} );
+
+		it( 'should return already bind document fragment', () => {
+			const domFragment = document.createDocumentFragment();
+			const viewFragment = new ViewDocumentFragment();
+
+			converter.bindDocumentFragments( domFragment, viewFragment );
+
+			const viewFragment2 = converter.domToView( domFragment );
+
+			expect( viewFragment2 ).to.equal( viewFragment );
+		} );
 	} );
 
 	describe( 'viewToDom', () => {
@@ -222,6 +285,56 @@ describe( 'DomConverter', () => {
 			expect( domP.childNodes.length ).to.equal( 0 );
 			expect( converter.getCorrespondingView( domP ) ).not.to.equal( viewP );
 		} );
+
+		it( 'should create DOM document fragment from view document fragment and bind elements', () => {
+			const viewImg = new ViewElement( 'img' );
+			const viewText = new ViewText( 'foo' );
+			const viewFragment = new ViewDocumentFragment();
+
+			viewFragment.appendChildren( viewImg );
+			viewFragment.appendChildren( viewText );
+
+			const domFragment = converter.viewToDom( viewFragment, document, { bind: true } );
+
+			expect( domFragment ).to.be.an.instanceof( DocumentFragment );
+			expect( domFragment.childNodes.length ).to.equal( 2 );
+			expect( domFragment.childNodes[ 0 ].tagName ).to.equal( 'IMG' );
+			expect( domFragment.childNodes[ 1 ].data ).to.equal( 'foo' );
+
+			expect( converter.getCorrespondingView( domFragment ) ).to.equal( viewFragment );
+			expect( converter.getCorrespondingView( domFragment.childNodes[ 0 ] ) ).to.equal( viewFragment.getChild( 0 ) );
+		} );
+
+		it( 'should create DOM document fragment from view document without children', () => {
+			const viewImg = new ViewElement( 'img' );
+			const viewText = new ViewText( 'foo' );
+			const viewFragment = new ViewDocumentFragment();
+
+			viewFragment.appendChildren( viewImg );
+			viewFragment.appendChildren( viewText );
+
+			const domImg = document.createElement( 'img' );
+
+			converter.bindElements( domImg, viewImg );
+
+			const domFragment = converter.viewToDom( viewFragment, document, { withChildren: false } );
+
+			expect( domFragment ).to.be.an.instanceof( DocumentFragment );
+
+			expect( domFragment.childNodes.length ).to.equal( 0 );
+			expect( converter.getCorrespondingView( domFragment ) ).not.to.equal( viewFragment );
+		} );
+
+		it( 'should return already bind document fragment', () => {
+			const domFragment = document.createDocumentFragment();
+			const viewFragment = new ViewDocumentFragment();
+
+			converter.bindDocumentFragments( domFragment, viewFragment );
+
+			const domFragment2 = converter.viewToDom( viewFragment );
+
+			expect( domFragment2 ).to.equal( domFragment );
+		} );
 	} );
 
 	describe( 'getCorrespondingView', () => {
@@ -247,6 +360,15 @@ describe( 'DomConverter', () => {
 
 			expect( converter.getCorrespondingView( domText ) ).to.equal( viewText );
 		} );
+
+		it( 'should return corresponding view document fragment', () => {
+			const domFragment = document.createDocumentFragment();
+			const viewFragment = converter.domToView( domFragment );
+
+			converter.bindElements( domFragment, viewFragment );
+
+			expect( converter.getCorrespondingView( domFragment ) ).to.equal( viewFragment );
+		} );
 	} );
 
 	describe( 'getCorrespondingViewElement', () => {
@@ -260,6 +382,17 @@ describe( 'DomConverter', () => {
 		} );
 	} );
 
+	describe( 'getCorrespondingViewDocumentFragment', () => {
+		it( 'should return corresponding view document fragment', () => {
+			const domFragment = document.createDocumentFragment();
+			const viewFragment = converter.domToView( domFragment );
+
+			converter.bindElements( domFragment, viewFragment );
+
+			expect( converter.getCorrespondingViewDocumentFragment( domFragment ) ).to.equal( viewFragment );
+		} );
+	} );
+
 	describe( 'getCorrespondingViewText', () => {
 		it( 'should return corresponding view text based on sibling', () => {
 			const domImg = document.createElement( 'img' );
@@ -356,6 +489,15 @@ describe( 'DomConverter', () => {
 
 			expect( converter.getCorrespondingDom( viewText ) ).to.equal( domText );
 		} );
+
+		it( 'should return corresponding DOM document fragment', () => {
+			const domFragment = document.createDocumentFragment();
+			const viewFragment = new ViewDocumentFragment();
+
+			converter.bindElements( domFragment, viewFragment );
+
+			expect( converter.getCorrespondingDom( viewFragment ) ).to.equal( domFragment );
+		} );
 	} );
 
 	describe( 'getCorrespondingDomElement', () => {
@@ -369,6 +511,17 @@ describe( 'DomConverter', () => {
 		} );
 	} );
 
+	describe( 'getCorrespondingDomDocumentFragment', () => {
+		it( 'should return corresponding DOM document fragment', () => {
+			const domFragment = document.createDocumentFragment();
+			const viewFragment = new ViewDocumentFragment();
+
+			converter.bindElements( domFragment, viewFragment );
+
+			expect( converter.getCorrespondingDomDocumentFragment( viewFragment ) ).to.equal( domFragment );
+		} );
+	} );
+
 	describe( 'getCorrespondingDomText', () => {
 		it( 'should return corresponding DOM text based on sibling', () => {
 			const domImg = document.createElement( 'img' );