瀏覽代碼

Move TemplateDefinition#text to #children.

Aleksander Nowodzinski 10 年之前
父節點
當前提交
6301c70cdf
共有 2 個文件被更改,包括 135 次插入44 次删除
  1. 71 29
      packages/ckeditor5-engine/src/ui/template.js
  2. 64 15
      packages/ckeditor5-engine/tests/ui/template.js

+ 71 - 29
packages/ckeditor5-engine/src/ui/template.js

@@ -7,6 +7,8 @@
 
 'use strict';
 
+import CKEditorError from '../ckeditorerror.js';
+
 /**
  * Basic Template class.
  *
@@ -30,31 +32,60 @@ export default class Template {
 	}
 
 	/**
-	 * Renders HTMLElement using {@link #def}.
+	 * Renders DOM Node using {@link #def}.
 	 *
 	 * @returns {HTMLElement}
 	 */
 	render() {
-		return this._renderElement( this.def, true );
+		return this._renderNode( this.def, true );
 	}
 
 	/**
-	 * Renders an element from definition.
+	 * Renders a DOM Node from definition.
 	 *
 	 * @protected
-	 * @param {TemplateDefinition} def Definition of an element.
+	 * @param {TemplateDefinition} def Definition of a Node.
 	 * @param {Boolean} intoFragment If set, children are rendered into DocumentFragment.
-	 * @returns {HTMLElement} A rendered element.
+	 * @returns {HTMLElement} A rendered Node.
 	 */
-	_renderElement( def, intoFragment ) {
+	_renderNode( def, intoFragment ) {
 		if ( !def ) {
-			return null;
+			/**
+			 * Node definition must have either "tag" or "text" property.
+			 *
+			 * @error ui-template-wrong-syntax
+			 */
+			throw new CKEditorError( 'ui-template-wrong-syntax' );
 		}
 
-		const el = document.createElement( def.tag );
+		const isText = def.text || typeof def == 'string';
+
+		// !XOR( def.tag, isText )
+		if ( ( def.tag ? isText : !isText ) ) {
+			throw new CKEditorError( 'ui-template-wrong-syntax' );
+		}
+
+		let el;
+
+		if ( isText ) {
+			el = this._renderText( def );
+		} else {
+			el = this._renderElement( def, intoFragment );
+		}
+
+		return el;
+	}
 
-		// Set the text first.
-		this._renderElementText( def, el );
+	/**
+	 * Renders a HTMLElement from TemplateDefinition.
+	 *
+	 * @protected
+	 * @param {TemplateDefinition} def Definition of an element.
+	 * @param {Boolean} intoFragment If set, children are rendered into DocumentFragment.
+	 * @returns {HTMLElement} A rendered element.
+	 */
+	_renderElement( def, intoFragment ) {
+		const el = document.createElement( def.tag );
 
 		// Set attributes.
 		this._renderElementAttributes( def, el );
@@ -70,27 +101,39 @@ export default class Template {
 			this._renderElementChildren( def, el );
 		}
 
-		// Activate DOM binding for event listeners.
+		// Activate DOM bindings for event listeners.
 		this._activateElementListeners( def, el );
 
 		return el;
 	}
 
 	/**
-	 * Renders element text content from definition.
+	 * Renders a Text from TemplateDefinition or String.
 	 *
 	 * @protected
-	 * @param {TemplateDefinition} def Definition of an element.
-	 * @param {HTMLElement} el Element which is rendered.
+	 * @param {TemplateDefinition|String} def Definition of Text or its value.
+	 * @returns {Text} A rendered Text.
 	 */
-	_renderElementText( def, el ) {
-		if ( def.text ) {
-			if ( typeof def.text == 'function' ) {
-				def.text( el, getTextUpdater() );
-			} else {
-				el.textContent = def.text;
+	_renderText( defOrText ) {
+		const el = document.createTextNode( '' );
+
+		// Case: { text: ... }
+		if ( defOrText.text ) {
+			// Case: { text: func }, like binding
+			if ( typeof defOrText.text == 'function' ) {
+				defOrText.text( el, getTextUpdater() );
+			}
+			// Case: { text: 'foo' }
+			else {
+				el.textContent = defOrText.text;
 			}
 		}
+		// Case: 'foo'
+		else {
+			el.textContent = defOrText;
+		}
+
+		return el;
 	}
 
 	/**
@@ -132,12 +175,10 @@ export default class Template {
 	 * @param {HTMLElement} el Element which is rendered.
 	 */
 	_renderElementChildren( def, el ) {
-		let child;
-
 		if ( def.children ) {
-			for ( child of def.children ) {
-				el.appendChild( this._renderElement( child ) );
-			}
+			def.children.map( childDef => {
+				el.appendChild( this._renderNode( childDef ) );
+			} );
 		}
 	}
 
@@ -205,11 +246,13 @@ function getAttributeUpdater( attr ) {
  *				{
  *					tag: 'span',
  *					attrs: { ... },
- *					on: { ... }
+ *					children: [ ... ],
+ *					...
  *				},
  *				{
- *					...
+ *					text: 'abc'
  *				},
+ *				'def',
  *				...
  *			],
  *			attrs: {
@@ -224,8 +267,7 @@ function getAttributeUpdater( attr ) {
  *				'event3@selector': 'd',
  *				'event4@selector': [ 'e', 'f', callback ],
  *				...
- *			},
- *			text: 'abc'
+ *			}
  *		}
  *
  * @typedef TemplateDefinition

+ 64 - 15
packages/ckeditor5-engine/tests/ui/template.js

@@ -10,6 +10,7 @@
 
 import testUtils from '/tests/_utils/utils.js';
 import Template from '/ckeditor5/core/ui/template.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 testUtils.createSinonSandbox();
 
@@ -25,18 +26,31 @@ describe( 'Template', () => {
 	} );
 
 	describe( 'render', () => {
-		it( 'returns null when no definition', () => {
-			expect( new Template().render() ).to.be.null;
+		it( 'throws when wrong template definition', () => {
+			expect( () => {
+				new Template().render();
+			} ).to.throw( CKEditorError, /ui-template-wrong-syntax/ );
+
+			expect( () => {
+				new Template( {} ).render();
+			} ).to.throw( CKEditorError, /ui-template-wrong-syntax/ );
+
+			expect( () => {
+				new Template( {
+					tag: 'p',
+					text: 'foo'
+				} ).render();
+			} ).to.throw( CKEditorError, /ui-template-wrong-syntax/ );
 		} );
 
-		it( 'creates an element', () => {
+		it( 'creates a HTMLElement', () => {
 			let el = new Template( {
 				tag: 'p',
 				attrs: {
 					'class': [ 'a', 'b' ],
 					x: 'bar'
 				},
-				text: 'foo'
+				children: [ 'foo' ]
 			} ).render();
 
 			expect( el ).to.be.instanceof( HTMLElement );
@@ -44,7 +58,7 @@ describe( 'Template', () => {
 			expect( el.outerHTML ).to.be.equal( '<p class="a b" x="bar">foo</p>' );
 		} );
 
-		it( 'creates element\'s children', () => {
+		it( 'creates HTMLElement\'s children', () => {
 			let el = new Template( {
 				tag: 'p',
 				attrs: {
@@ -53,15 +67,15 @@ describe( 'Template', () => {
 				children: [
 					{
 						tag: 'b',
-						text: 'B'
+						children: [ 'B' ]
 					},
 					{
 						tag: 'i',
-						text: 'C',
 						children: [
+							'C',
 							{
 								tag: 'b',
-								text: 'D'
+								children: [ 'D' ]
 							}
 						]
 					}
@@ -71,6 +85,35 @@ describe( 'Template', () => {
 			expect( el.outerHTML ).to.be.equal( '<p a="A"><b>B</b><i>C<b>D</b></i></p>' );
 		} );
 
+		it( 'creates a Text node', () => {
+			let node = new Template( { text: 'foo' } ).render();
+
+			expect( node.nodeType ).to.be.equal( 3 );
+			expect( node.textContent ).to.be.equal( 'foo' );
+		} );
+
+		it( 'creates a child Text Node (different syntaxes)', () => {
+			let el = new Template( {
+				tag: 'p',
+				children: [
+					'foo',
+					{ text: 'bar' }
+				]
+			} ).render();
+
+			expect( el.outerHTML ).to.be.equal( '<p>foobar</p>' );
+		} );
+
+		it( 'creates multiple child Text Nodes', () => {
+			let el = new Template( {
+				tag: 'p',
+				children: [ 'a', 'b', { text: 'c' }, 'd' ]
+			} ).render();
+
+			expect( el.childNodes ).to.have.length( 4 );
+			expect( el.outerHTML ).to.be.equal( '<p>abcd</p>' );
+		} );
+
 		describe( 'callback', () => {
 			it( 'works for attributes', () => {
 				let spy1 = testUtils.sinon.spy();
@@ -106,23 +149,29 @@ describe( 'Template', () => {
 
 				let el = new Template( {
 					tag: 'p',
-					text: spy1,
 					children: [
+						{
+							text: spy1
+						},
 						{
 							tag: 'span',
-							text: spy2
+							children: [
+								{
+									text: spy2
+								}
+							]
 						}
 					]
 				} ).render();
 
-				sinon.assert.calledWithExactly( spy1, el, sinon.match.func );
-				sinon.assert.calledWithExactly( spy2, el.firstChild, sinon.match.func );
+				sinon.assert.calledWithExactly( spy1, el.firstChild, sinon.match.func );
+				sinon.assert.calledWithExactly( spy2, el.lastChild.firstChild, sinon.match.func );
 
-				spy2.firstCall.args[ 1 ]( el.firstChild, 'bar' );
+				spy2.firstCall.args[ 1 ]( el.lastChild.firstChild, 'bar' );
 				expect( el.outerHTML ).to.be.equal( '<p><span>bar</span></p>' );
 
-				spy1.firstCall.args[ 1 ]( el, 'foo' );
-				expect( el.outerHTML ).to.be.equal( '<p>foo</p>' );
+				spy1.firstCall.args[ 1 ]( el.firstChild, 'foo' );
+				expect( el.outerHTML ).to.be.equal( '<p>foo<span>bar</span></p>' );
 			} );
 
 			it( 'works for "on" property', () => {