瀏覽代碼

Implemented automated registration of child View instances refered in View#template.

Aleksander Nowodzinski 9 年之前
父節點
當前提交
93f10f4b62
共有 2 個文件被更改,包括 95 次插入18 次删除
  1. 53 12
      packages/ckeditor5-ui/src/view.js
  2. 42 6
      packages/ckeditor5-ui/tests/view.js

+ 53 - 12
packages/ckeditor5-ui/src/view.js

@@ -145,6 +145,8 @@ export default class View {
 			return null;
 		}
 
+		this._addTemplateChildren();
+
 		return ( this._element = this.template.render() );
 	}
 
@@ -223,22 +225,17 @@ export default class View {
 	 *				this.childA = new SomeChildView( locale );
 	 *				this.childB = new SomeChildView( locale );
 	 *
+	 *				this.template = new Template( { tag: 'p' } );
+	 *
 	 *				// Register children.
 	 *				this.addChildren( [ this.childA, this.childB ] );
+	 *			}
 	 *
-	 *				this.template = new Template( {
-	 *					tag: 'p',
-	 *
-	 *					children: [
-	 *						// This is where the `childA` will render.
-	 *						this.childA,
-	 *
-	 *						{ tag: 'b' },
+	 *			init() {
+	 *				this.element.appendChild( this.childA.element );
+	 *				this.element.appendChild( this.childB.element );
 	 *
-	 *						// This is where the `childB` will render.
-	 *						this.childB
-	 *					]
-	 *				} );
+	 *				return super.init();
 	 *			}
 	 *		}
 	 *
@@ -249,6 +246,28 @@ export default class View {
 	 *			document.body.appendChild( view.element );
 	 *		} );
 	 *
+	 * **Note**: There's no need to add child views if they're used in the
+	 * {@link #template} explicitly:
+	 *
+	 *		class SampleView extends View {
+	 *			constructor( locale ) {
+	 *				super( locale );
+	 *
+	 *				this.childA = new SomeChildView( locale );
+	 *				this.childB = new SomeChildView( locale );
+	 *
+	 *				this.template = new Template( {
+	 *					tag: 'p',
+	 *
+ 	 *					// These children will be added automatically. There's no
+ 	 *					// need to call {@link #addChildren} for any of them.
+	 *					children: [ this.childA, this.childB ]
+	 *				} );
+	 *			}
+	 *
+	 *			...
+	 *		}
+	 *
 	 * @param {module:ui/view~View|Iterable.<module:ui/view~View>} children Children views to be registered.
 	 */
 	addChildren( children ) {
@@ -309,6 +328,28 @@ export default class View {
 
 		return Promise.all( promises );
 	}
+
+	/**
+	 * Recursively traverses {@link #template} in search of {@link module:ui/view~View}
+	 * instances and automatically registers them using {@link #addChildren} method.
+	 *
+	 * @protected
+	 */
+	_addTemplateChildren() {
+		const search = ( def ) => {
+			if ( def.children ) {
+				for ( let defOrView of def.children ) {
+					if ( defOrView instanceof View ) {
+						this.addChildren( defOrView );
+					} else {
+						search( defOrView );
+					}
+				}
+			}
+		};
+
+		search( this.template );
+	}
 }
 
 mix( View, DomEmmiterMixin );

+ 42 - 6
packages/ckeditor5-ui/tests/view.js

@@ -61,7 +61,7 @@ describe( 'View', () => {
 		} );
 	} );
 
-	describe( 'createCollection', () => {
+	describe( 'createCollection()', () => {
 		beforeEach( () => {
 			setTestViewClass();
 			setTestViewInstance();
@@ -81,7 +81,7 @@ describe( 'View', () => {
 		} );
 	} );
 
-	describe( 'addChildren', () => {
+	describe( 'addChildren()', () => {
 		beforeEach( () => {
 			setTestViewClass();
 			setTestViewInstance();
@@ -192,6 +192,44 @@ describe( 'View', () => {
 		it( 'is null when there is no template', () => {
 			expect( new View().element ).to.be.null;
 		} );
+
+		it( 'registers child views found in the template', () => {
+			const view = new View();
+			const viewA = new View();
+			const viewB = new View();
+			const viewC = new View();
+
+			viewA.template = new Template( { tag: 'a' } );
+			viewB.template = new Template( { tag: 'b' } );
+			viewC.template = new Template( { tag: 'c' } );
+
+			view.template = new Template( {
+				tag: 'div',
+				children: [
+					viewA,
+					viewB,
+					{
+						tag: 'p',
+						children: [
+							viewC
+						]
+					},
+					{
+						text: 'foo'
+					}
+				]
+			} );
+
+			expect( view._unboundChildren ).to.have.length( 0 );
+
+			// Render the view.
+			view.element;
+
+			expect( view._unboundChildren ).to.have.length( 3 );
+			expect( view._unboundChildren.get( 0 ) ).to.equal( viewA );
+			expect( view._unboundChildren.get( 1 ) ).to.equal( viewB );
+			expect( view._unboundChildren.get( 2 ) ).to.equal( viewC );
+		} );
 	} );
 
 	describe( 'destroy()', () => {
@@ -216,8 +254,8 @@ describe( 'View', () => {
 		it( 'clears #_unboundChildren', () => {
 			const cached = view._unboundChildren;
 
-			view.addChildren( new View(), new View() );
-			expect( cached ).to.have.length.above( 1 );
+			view.addChildren( [ new View(), new View() ] );
+			expect( cached ).to.have.length.above( 2 );
 
 			return view.destroy().then( () => {
 				expect( cached ).to.have.length( 0 );
@@ -350,6 +388,4 @@ function createViewWithChildren() {
 	} );
 
 	setTestViewInstance();
-
-	view.addChildren( childA, childB );
 }