浏览代码

Allowed adding View instances to ViewCollection before it has been initialized.

Aleksander Nowodziński 9 年之前
父节点
当前提交
f082ecbfd2

+ 3 - 17
packages/ckeditor5-ui/src/view.js

@@ -192,17 +192,9 @@ export default class View {
 		}
 
 		return Promise.resolve()
-			// Initialize child views in #_viewCollections.
+			// Initialize collections in #_viewCollections.
 			.then( () => {
-				const promises = [];
-
-				for ( let collection of this._viewCollections ) {
-					for ( let view of collection ) {
-						promises.push( view.init() );
-					}
-				}
-
-				return Promise.all( promises );
+				return Promise.all( this._viewCollections.map( c => c.init() ) );
 			} )
 			// Spread the word that this view is ready!
 			.then( () => {
@@ -218,13 +210,7 @@ export default class View {
 	destroy() {
 		this.stopListening();
 
-		let promises = [];
-
-		for ( let collection of this._viewCollections ) {
-			for ( let view of collection ) {
-				promises.push( view.destroy() );
-			}
-		}
+		const promises = this._viewCollections.map( c => c.destroy() );
 
 		this._unboundChildren.clear();
 		this._viewCollections.clear();

+ 47 - 0
packages/ckeditor5-ui/src/viewcollection.js

@@ -73,6 +73,53 @@ export default class ViewCollection extends Collection {
 	}
 
 	/**
+	 * Initializes child views by injecting {@link ui.View#element} into DOM
+	 * and calling {@link ui.View#init}.
+	 *
+	 * @returns {Promise} A Promise resolved when the initialization process is finished.
+	 */
+	init() {
+		if ( this.ready ) {
+			/**
+			 * This ViewCollection has already been initialized.
+			 *
+			 * @error ui-viewcollection-init-reinit
+			 */
+			throw new CKEditorError( 'ui-viewviewcollection-init-reinit: This ViewCollection has already been initialized.' );
+		}
+
+		// Do not render unbound children. They're already in DOM by explicit declaration
+		// in Template definition.
+		if ( !this._parentElement ) {
+			return Promise.resolve();
+		}
+
+		const promises = [];
+
+		for ( let view of this ) {
+			this._parentElement.appendChild( view.element );
+			promises.push( view.init() );
+		}
+
+		return Promise.all( promises );
+	}
+
+	/**
+	 * Destroys the view collection along with child views.
+	 *
+	 * @returns {Promise} A Promise resolved when the destruction process is finished.
+	 */
+	destroy() {
+		let promises = [];
+
+		for ( let view of this ) {
+			promises.push( view.destroy() );
+		}
+
+		return Promise.all( promises );
+	}
+
+	/**
 	 * Adds a child view to the collection. If {@link ui.ViewCollection#ready}, the child view
 	 * is also initialized when added.
 	 *

+ 15 - 23
packages/ckeditor5-ui/tests/view.js

@@ -153,21 +153,17 @@ describe( 'View', () => {
 			} );
 		} );
 
-		it( 'calls init() on all views in #_viewCollections', () => {
-			const spy = testUtils.sinon.spy( view, 'init' );
-			const spyA = testUtils.sinon.spy( childA, 'init' );
-			const spyB = testUtils.sinon.spy( childB, 'init' );
+		it( 'calls init() on all view#_viewCollections', () => {
+			const collectionA = view.createCollection();
+			const collectionB = view.createCollection();
 
-			expect( childA.ready ).to.be.false;
-			expect( childB.ready ).to.be.false;
+			const spyA = testUtils.sinon.spy( collectionA, 'init' );
+			const spyB = testUtils.sinon.spy( collectionB, 'init' );
 
 			return view.init().then( () => {
-				expect( childA.ready ).to.be.true;
-				expect( childB.ready ).to.be.true;
-
 				sinon.assert.calledOnce( spyA );
 				sinon.assert.calledOnce( spyB );
-				sinon.assert.callOrder( spy, spyA, spyB );
+				sinon.assert.callOrder( spyA, spyB );
 			} );
 		} );
 	} );
@@ -269,21 +265,17 @@ describe( 'View', () => {
 			} );
 		} );
 
-		it( 'calls destroy() on all views in #_viewCollections', () => {
-			const spy = testUtils.sinon.spy( view, 'destroy' );
-			const spyA = testUtils.sinon.spy( childA, 'destroy' );
-			const spyB = testUtils.sinon.spy( childB, 'destroy' );
+		it( 'calls destroy() on all view#_viewCollections', () => {
+			const collectionA = view.createCollection();
+			const collectionB = view.createCollection();
 
-			return view.init().then( () => {
-				expect( view.ready ).to.be.true;
-				expect( childA.ready ).to.be.true;
-				expect( childB.ready ).to.be.true;
+			const spyA = testUtils.sinon.spy( collectionA, 'destroy' );
+			const spyB = testUtils.sinon.spy( collectionB, 'destroy' );
 
-				return view.destroy().then( () => {
-					sinon.assert.calledOnce( spyA );
-					sinon.assert.calledOnce( spyB );
-					sinon.assert.callOrder( spy, spyA, spyB );
-				} );
+			return view.destroy().then( () => {
+				sinon.assert.calledOnce( spyA );
+				sinon.assert.calledOnce( spyB );
+				sinon.assert.callOrder( spyA, spyB );
 			} );
 		} );
 

+ 54 - 0
packages/ckeditor5-ui/tests/viewcollection.js

@@ -71,6 +71,60 @@ describe( 'ViewCollection', () => {
 		} );
 	} );
 
+	describe( 'init', () => {
+		it( 'should return a promise', () => {
+			expect( collection.init() ).to.be.instanceof( Promise );
+		} );
+
+		it( 'calls #init on all views in the collection', () => {
+			const viewA = new View();
+			const viewB = new View();
+
+			viewA.element = document.createElement( 'a' );
+			viewB.element = document.createElement( 'b' );
+
+			const spyA = testUtils.sinon.spy( viewA, 'init' );
+			const spyB = testUtils.sinon.spy( viewB, 'init' );
+
+			collection.setParent( document.body );
+
+			collection.add( viewA );
+			collection.add( viewB );
+
+			return collection.init().then( () => {
+				sinon.assert.calledOnce( spyA );
+				sinon.assert.calledOnce( spyB );
+				sinon.assert.callOrder( spyA, spyB );
+
+				expect( viewA.element.parentNode ).to.equal( collection._parentElement );
+				expect( viewA.element.nextSibling ).to.equal( viewB.element );
+			} );
+		} );
+	} );
+
+	describe( 'destroy', () => {
+		it( 'should return a promise', () => {
+			expect( collection.destroy() ).to.be.instanceof( Promise );
+		} );
+
+		it( 'calls #destroy on all views in the collection', () => {
+			const viewA = new View();
+			const viewB = new View();
+
+			const spyA = testUtils.sinon.spy( viewA, 'destroy' );
+			const spyB = testUtils.sinon.spy( viewB, 'destroy' );
+
+			collection.add( viewA );
+			collection.add( viewB );
+
+			return collection.destroy().then( () => {
+				sinon.assert.calledOnce( spyA );
+				sinon.assert.calledOnce( spyB );
+				sinon.assert.callOrder( spyA, spyB );
+			} );
+		} );
+	} );
+
 	describe( 'add', () => {
 		it( 'returns a promise', () => {
 			expect( collection.add( {} ) ).to.be.instanceof( Promise );