Explorar el Código

Code refactoring and extended documentation in Template class and tests.

Aleksander Nowodzinski hace 10 años
padre
commit
d7ffd5e2ab
Se han modificado 2 ficheros con 294 adiciones y 236 borrados
  1. 137 81
      packages/ckeditor5-engine/src/ui/template.js
  2. 157 155
      packages/ckeditor5-engine/tests/ui/template.js

+ 137 - 81
packages/ckeditor5-engine/src/ui/template.js

@@ -36,112 +36,163 @@ CKEDITOR.define( () => {
 		 * @returns {HTMLElement}
 		 */
 		render() {
-			return renderElement( this.def );
+			return this._renderElement( this.def );
 		}
-	}
 
-	function getTextUpdater() {
-		return ( el, value ) => el.innerHTML = value;
-	}
-
-	function getAttributeUpdater( attr ) {
-		return ( el, value ) => el.setAttribute( attr, value );
-	}
-
-	function renderElement( def ) {
-		if ( !def ) {
-			return null;
-		}
+		/**
+		 * Renders an element from definition.
+		 *
+		 * @protected
+		 * @param {TemplateDefinition} def Definition of an element.
+		 * @returns {HTMLElement} A rendered element.
+		 */
+		_renderElement( def ) {
+			if ( !def ) {
+				return null;
+			}
 
-		const el = document.createElement( def.tag );
+			const el = document.createElement( def.tag );
 
-		// Set the text first.
-		renderElementText( def, el );
+			// Set the text first.
+			this._renderElementText( def, el );
 
-		// Set attributes.
-		renderElementAttributes( def, el );
+			// Set attributes.
+			this._renderElementAttributes( def, el );
 
-		// Invoke children recursively.
-		renderElementChildren( def, el );
+			// Invoke children recursively.
+			this._renderElementChildren( def, el );
 
-		// Activate DOM binding for event listeners.
-		activateElementListeners( def, el );
+			// Activate DOM binding for event listeners.
+			this._activateElementListeners( def, el );
 
-		return el;
-	}
+			return el;
+		}
 
-	function renderElementText( def, el ) {
-		if ( def.text ) {
-			if ( typeof def.text == 'function' ) {
-				def.text( el, getTextUpdater() );
-			} else {
-				el.innerHTML = def.text;
+		/**
+		 * Renders element text content from definition.
+		 *
+		 * @protected
+		 * @param {TemplateDefinition} def Definition of an element.
+		 * @param {HTMLElement} el Element which is rendered.
+		 */
+		_renderElementText( def, el ) {
+			if ( def.text ) {
+				if ( typeof def.text == 'function' ) {
+					def.text( el, getTextUpdater() );
+				} else {
+					el.innerHTML = def.text;
+				}
 			}
 		}
-	}
-
-	function renderElementAttributes( def, el ) {
-		let value;
-		let attr;
 
-		for ( attr in def.attrs ) {
-			value = def.attrs[ attr ];
+		/**
+		 * Renders element attributes from definition.
+		 *
+		 * @protected
+		 * @param {TemplateDefinition} def Definition of an element.
+		 * @param {HTMLElement} el Element which is rendered.
+		 */
+		_renderElementAttributes( def, el ) {
+			let attr, value;
 
-			// Attribute bound directly to the model.
-			if ( typeof value == 'function' ) {
-				value( el, getAttributeUpdater( attr ) );
-			}
+			for ( attr in def.attrs ) {
+				value = def.attrs[ attr ];
 
-			// Explicit attribute definition (string).
-			else {
-				// Attribute can be an array, i.e. classes.
-				if ( Array.isArray( value ) ) {
-					value = value.join( ' ' );
+				// Attribute bound directly to the model.
+				if ( typeof value == 'function' ) {
+					value( el, getAttributeUpdater( attr ) );
 				}
 
-				el.setAttribute( attr, value );
-			}
-		}
-	}
+				// Explicit attribute definition (string).
+				else {
+					// Attribute can be an array, i.e. classes.
+					if ( Array.isArray( value ) ) {
+						value = value.join( ' ' );
+					}
 
-	function renderElementChildren( def, el ) {
-		let child;
-
-		if ( def.children ) {
-			for ( child of def.children ) {
-				el.appendChild( renderElement( child ) );
+					el.setAttribute( attr, value );
+				}
 			}
 		}
-	}
 
-	function activateElementListeners( def, el ) {
-		if ( def.on ) {
-			for ( let l in def.on ) {
-				let domEvtDef = l.split( '@' );
-				let name, selector;
+		/**
+		 * Recursively renders element children from definition by
+		 * calling {@link #_renderElement}.
+		 *
+		 * @protected
+		 * @param {TemplateDefinition} def Definition of an element.
+		 * @param {HTMLElement} el Element which is rendered.
+		 */
+		_renderElementChildren( def, el ) {
+			let child;
 
-				if ( domEvtDef.length == 2 ) {
-					name = domEvtDef[ 0 ];
-					selector = domEvtDef[ 1 ];
-				} else {
-					name = l;
-					selector = null;
+			if ( def.children ) {
+				for ( child of def.children ) {
+					el.appendChild( this._renderElement( child ) );
 				}
+			}
+		}
 
-				if ( Array.isArray( def.on[ l ] ) ) {
-					def.on[ l ].map( i => i( el, name, selector ) );
-				} else {
-					def.on[ l ]( el, name, selector );
+		/**
+		 * Activates element `on` listeners passed in element definition.
+		 *
+		 * @protected
+		 * @param {TemplateDefinition} def Definition of an element.
+		 * @param {HTMLElement} el Element which is rendered.
+		 */
+		_activateElementListeners( def, el ) {
+			if ( def.on ) {
+				let l, domEvtDef, name, selector;
+
+				for ( l in def.on ) {
+					domEvtDef = l.split( '@' );
+
+					if ( domEvtDef.length == 2 ) {
+						name = domEvtDef[ 0 ];
+						selector = domEvtDef[ 1 ];
+					} else {
+						name = l;
+						selector = null;
+					}
+
+					if ( Array.isArray( def.on[ l ] ) ) {
+						def.on[ l ].map( i => i( el, name, selector ) );
+					} else {
+						def.on[ l ]( el, name, selector );
+					}
 				}
 			}
 		}
 	}
 
+	/**
+	 * Returns a function which, when called in the context of HTMLElement,
+	 * it updates `innerHTML` property of that element with given value.
+	 *
+	 * @protected
+	 * @param {Function}
+	 */
+	function getTextUpdater() {
+		return ( el, value ) => el.innerHTML = value;
+	}
+
+	/**
+	 * Returns a function which, when called in the context of HTMLElement,
+	 * it updates element's attribute with given value.
+	 *
+	 * @protected
+	 * @param {String} attr A name of the attribute to be updated.
+	 * @param {Function}
+	 */
+	function getAttributeUpdater( attr ) {
+		return ( el, value ) => el.setAttribute( attr, value );
+	}
+
 	return Template;
 } );
 
 /**
- * The virtual class representing an argument of the {@link Template} constructor.
+ * Definition of {@link Template}.
  *
  *		{
  *			tag: 'p',
@@ -163,15 +214,20 @@ CKEDITOR.define( () => {
  *				...
  *			},
  *			on: {
- *				w: 'a'
- *				x: [ 'b', 'c', callback ],
- *				'y@selector': 'd',
- *				'z@selector': [ 'e', 'f', callback ],
+ *				event1: 'a'
+ *				event2: [ 'b', 'c', callback ],
+ *				'event3@selector': 'd',
+ *				'event4@selector': [ 'e', 'f', callback ],
  *				...
  *			},
  *			text: 'abc'
  *		}
  *
- * @abstract
- * @class TemplateDefinition
- */
+ * @typedef TemplateDefinition
+ * @type Object
+ * @property {String} tag
+ * @property {Array} [children]
+ * @property {Object} [attrs]
+ * @property {String} [text]
+ * @property {Object} [on]
+ */

+ 157 - 155
packages/ckeditor5-engine/tests/ui/template.js

@@ -12,184 +12,186 @@ const modules = bender.amd.require( 'ckeditor', 'ui/view', 'ui/template' );
 let Template;
 
 bender.tools.createSinonSandbox();
-beforeEach( createClassReferences );
 
-describe( 'constructor', () => {
-	it( 'accepts the definition', () => {
-		let def = {
-			tag: 'p'
-		};
+describe( 'Template', () => {
+	beforeEach( createClassReferences );
 
-		expect( new Template( def ).def ).to.equal( def );
-	} );
-} );
+	describe( 'constructor', () => {
+		it( 'accepts the definition', () => {
+			let def = {
+				tag: 'p'
+			};
 
-describe( 'render', () => {
-	it( 'returns null when no definition', () => {
-		expect( new Template().render() ).to.be.null;
+			expect( new Template( def ).def ).to.equal( def );
+		} );
 	} );
 
-	it( 'creates an element', () => {
-		let el = new Template( {
-			tag: 'p',
-			attrs: {
-				'class': [ 'a', 'b' ],
-				x: 'bar'
-			},
-			text: 'foo'
-		} ).render();
+	describe( 'render', () => {
+		it( 'returns null when no definition', () => {
+			expect( new Template().render() ).to.be.null;
+		} );
+
+		it( 'creates an element', () => {
+			let el = new Template( {
+				tag: 'p',
+				attrs: {
+					'class': [ 'a', 'b' ],
+					x: 'bar'
+				},
+				text: 'foo'
+			} ).render();
+
+			expect( el ).to.be.instanceof( HTMLElement );
+			expect( el.parentNode ).to.be.null;
+			expect( el.outerHTML ).to.be.equal( '<p class="a b" x="bar">foo</p>' );
+		} );
+
+		it( 'creates element\'s children', () => {
+			let el = new Template( {
+				tag: 'p',
+				attrs: {
+					a: 'A'
+				},
+				children: [
+					{
+						tag: 'b',
+						text: 'B'
+					},
+					{
+						tag: 'i',
+						text: 'C',
+						children: [
+							{
+								tag: 'b',
+								text: 'D'
+							}
+						]
+					}
+				]
+			} ).render();
 
-		expect( el ).to.be.instanceof( HTMLElement );
-		expect( el.parentNode ).to.be.null();
+			expect( el.outerHTML ).to.be.equal( '<p a="A"><b>B</b><i>C<b>D</b></i></p>' );
+		} );
 
-		expect( el.outerHTML ).to.be.equal( '<p class="a b" x="bar">foo</p>' );
-	} );
+		describe( 'callback', () => {
+			it( 'works for attributes', () => {
+				let spy1 = bender.sinon.spy();
+				let spy2 = bender.sinon.spy();
 
-	it( 'creates element\'s children', () => {
-		let el = new Template( {
-			tag: 'p',
-			attrs: {
-				a: 'A'
-			},
-			children: [
-				{
-					tag: 'b',
-					text: 'B'
-				},
-				{
-					tag: 'i',
-					text: 'C',
+				let el = new Template( {
+					tag: 'p',
+					attrs: {
+						'class': spy1
+					},
 					children: [
 						{
-							tag: 'b',
-							text: 'D'
+							tag: 'span',
+							attrs: {
+								id: spy2
+							}
 						}
 					]
-				}
-			]
-		} ).render();
+				} ).render();
 
-		expect( el.outerHTML ).to.be.equal( '<p a="A"><b>B</b><i>C<b>D</b></i></p>' );
-	} );
-} );
+				sinon.assert.calledWithExactly( spy1, el, sinon.match.func );
+				sinon.assert.calledWithExactly( spy2, el.firstChild, sinon.match.func );
 
-describe( 'callback value', () => {
-	it( 'works for attributes', () => {
-		let spy1 = bender.sinon.spy();
-		let spy2 = bender.sinon.spy();
-
-		let el = new Template( {
-			tag: 'p',
-			attrs: {
-				'class': spy1
-			},
-			children: [
-				{
-					tag: 'span',
-					attrs: {
-						id: spy2
-					}
-				}
-			]
-		} ).render();
+				spy1.firstCall.args[ 1 ]( el, 'foo' );
+				spy2.firstCall.args[ 1 ]( el.firstChild, 'bar' );
 
-		sinon.assert.calledWithExactly( spy1, el, sinon.match.func );
-		sinon.assert.calledWithExactly( spy2, el.firstChild, sinon.match.func );
+				expect( el.outerHTML ).to.be.equal( '<p class="foo"><span id="bar"></span></p>' );
+			} );
 
-		spy1.firstCall.args[ 1 ]( el, 'foo' );
-		spy2.firstCall.args[ 1 ]( el.firstChild, 'bar' );
+			it( 'works for "text" property', () => {
+				let spy1 = bender.sinon.spy();
+				let spy2 = bender.sinon.spy();
 
-		expect( el.outerHTML ).to.be.equal( '<p class="foo"><span id="bar"></span></p>' );
-	} );
+				let el = new Template( {
+					tag: 'p',
+					text: spy1,
+					children: [
+						{
+							tag: 'span',
+							text: spy2
+						}
+					]
+				} ).render();
 
-	it( 'works for "text" property', () => {
-		let spy1 = bender.sinon.spy();
-		let spy2 = bender.sinon.spy();
-
-		let el = new Template( {
-			tag: 'p',
-			text: spy1,
-			children: [
-				{
-					tag: 'span',
-					text: spy2
-				}
-			]
-		} ).render();
-
-		sinon.assert.calledWithExactly( spy1, el, sinon.match.func );
-		sinon.assert.calledWithExactly( spy2, el.firstChild, sinon.match.func );
-
-		spy2.firstCall.args[ 1 ]( el.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>' );
-	} );
+				sinon.assert.calledWithExactly( spy1, el, sinon.match.func );
+				sinon.assert.calledWithExactly( spy2, el.firstChild, sinon.match.func );
 
-	it( 'works for "on" property', () => {
-		let spy1 = bender.sinon.spy();
-		let spy2 = bender.sinon.spy();
-		let spy3 = bender.sinon.spy();
-		let spy4 = bender.sinon.spy();
-
-		let el = new Template( {
-			tag: 'p',
-			children: [
-				{
-					tag: 'span',
-					on: {
-						bar: spy2
-					}
-				}
-			],
-			on: {
-				foo: spy1,
-				baz: [ spy3, spy4 ]
-			}
-		} ).render();
-
-		sinon.assert.calledWithExactly( spy1, el, 'foo', null );
-		sinon.assert.calledWithExactly( spy2, el.firstChild, 'bar', null );
-		sinon.assert.calledWithExactly( spy3, el, 'baz', null );
-		sinon.assert.calledWithExactly( spy4, el, 'baz', null );
-	} );
+				spy2.firstCall.args[ 1 ]( el.firstChild, 'bar' );
+				expect( el.outerHTML ).to.be.equal( '<p><span>bar</span></p>' );
 
-	it( 'works for "on" property with selectors', () => {
-		let spy1 = bender.sinon.spy();
-		let spy2 = bender.sinon.spy();
-		let spy3 = bender.sinon.spy();
-		let spy4 = bender.sinon.spy();
-
-		let el = new Template( {
-			tag: 'p',
-			children: [
-				{
-					tag: 'span',
-					attrs: {
-						'id': 'x'
+				spy1.firstCall.args[ 1 ]( el, 'foo' );
+				expect( el.outerHTML ).to.be.equal( '<p>foo</p>' );
+			} );
+
+			it( 'works for "on" property', () => {
+				let spy1 = bender.sinon.spy();
+				let spy2 = bender.sinon.spy();
+				let spy3 = bender.sinon.spy();
+				let spy4 = bender.sinon.spy();
+
+				let el = new Template( {
+					tag: 'p',
+					children: [
+						{
+							tag: 'span',
+							on: {
+								bar: spy2
+							}
+						}
+					],
+					on: {
+						foo: spy1,
+						baz: [ spy3, spy4 ]
 					}
-				},
-				{
-					tag: 'span',
-					attrs: {
-						'class': 'y'
-					},
+				} ).render();
+
+				sinon.assert.calledWithExactly( spy1, el, 'foo', null );
+				sinon.assert.calledWithExactly( spy2, el.firstChild, 'bar', null );
+				sinon.assert.calledWithExactly( spy3, el, 'baz', null );
+				sinon.assert.calledWithExactly( spy4, el, 'baz', null );
+			} );
+
+			it( 'works for "on" property with selectors', () => {
+				let spy1 = bender.sinon.spy();
+				let spy2 = bender.sinon.spy();
+				let spy3 = bender.sinon.spy();
+				let spy4 = bender.sinon.spy();
+
+				let el = new Template( {
+					tag: 'p',
+					children: [
+						{
+							tag: 'span',
+							attrs: {
+								'id': 'x'
+							}
+						},
+						{
+							tag: 'span',
+							attrs: {
+								'class': 'y'
+							},
+							on: {
+								'bar@p': spy2
+							}
+						},
+					],
 					on: {
-						'bar@p': spy2
+						'foo@span': spy1,
+						'baz@.y': [ spy3, spy4 ]
 					}
-				},
-			],
-			on: {
-				'foo@span': spy1,
-				'baz@.y': [ spy3, spy4 ]
-			}
-		} ).render();
-
-		sinon.assert.calledWithExactly( spy1, el, 'foo', 'span' );
-		sinon.assert.calledWithExactly( spy2, el.lastChild, 'bar', 'p' );
-		sinon.assert.calledWithExactly( spy3, el, 'baz', '.y' );
-		sinon.assert.calledWithExactly( spy4, el, 'baz', '.y' );
+				} ).render();
+
+				sinon.assert.calledWithExactly( spy1, el, 'foo', 'span' );
+				sinon.assert.calledWithExactly( spy2, el.lastChild, 'bar', 'p' );
+				sinon.assert.calledWithExactly( spy3, el, 'baz', '.y' );
+				sinon.assert.calledWithExactly( spy4, el, 'baz', '.y' );
+			} );
+		} );
 	} );
 } );