瀏覽代碼

Added core UI API to the repository.

Aleksander Nowodzinski 10 年之前
父節點
當前提交
8ef83a8f0d
共有 3 個文件被更改,包括 291 次插入0 次删除
  1. 42 0
      packages/ckeditor5-ui/src/ui/region.js
  2. 135 0
      packages/ckeditor5-ui/src/ui/view.js
  3. 114 0
      packages/ckeditor5-ui/tests/ui/view.js

+ 42 - 0
packages/ckeditor5-ui/src/ui/region.js

@@ -0,0 +1,42 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+CKEDITOR.define( [ 'Collection', 'Model' ], function( Collection, Model ) {
+	class Region extends Model {
+		/**
+		 * Creates an instance of the {@link Region} class.
+		 *
+		 * @param {String} name The name of the Region.
+		 * @param {HTMLElement} [el] The element used for this region.
+		 * @constructor
+		 */
+		constructor( name, el ) {
+			super();
+
+			/**
+			 * The name of the region.
+			 */
+			this.name = name;
+
+			/**
+			 * The element of the region.
+			 */
+			this.el = el;
+
+			/**
+			 * Views which belong to the region.
+			 */
+			this.views = new Collection();
+
+			this.views.on( 'add', ( evt, view ) => this.el && this.el.appendChild( view.el ) );
+		};
+
+		destroy() {};
+	}
+
+	return Region;
+} );

+ 135 - 0
packages/ckeditor5-ui/src/ui/view.js

@@ -0,0 +1,135 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+CKEDITOR.define( [ 'Collection', 'Model' ], function( Collection, Model ) {
+	class View extends Model {
+		/**
+		 * Creates an instance of the {@link View} class.
+		 *
+		 * @param {Model} mode (View)Model of this View.
+		 * @constructor
+		 */
+		constructor( model ) {
+			super();
+
+			/**
+			 * Model of this view.
+			 */
+			this.model = new Model( model );
+
+			/**
+			 * Regions which belong to this view.
+			 */
+			this.regions = new Collection();
+
+			this.regions.on( 'add', ( evt, region ) => this.el.appendChild( region.el ) );
+		};
+
+		/**
+		 * Element of this view.
+		 *
+		 * @property el
+		 */
+		get el() {
+			if ( this._el ) {
+				return this._el;
+			}
+
+			return this._el = this.render();
+		};
+
+		/**
+		 * Binds a property of the model to a specific listener that
+		 * updates the view when the property changes.
+		 *
+		 * @param {Model} model Model to which the property is bound to.
+		 * @param {String} property Property name in the model.
+		 * @param {Function} [callback] Callback function executed on property change in model.
+		 * @constructor
+		 */
+		bind( model, property, callback ) {
+			return function( el, attr ) {
+				// TODO: Use ES6 default arguments syntax.
+				var changeCallback = callback || setAttribute;
+
+				function setAttribute( el, value ) {
+					el.setAttribute( attr, value );
+				}
+
+				function executeCallback( el, value ) {
+					var result = changeCallback( el, value );
+
+					if ( typeof result != 'undefined' ) {
+						setAttribute( el, result );
+					}
+				}
+
+				// Execute callback when the property changes.
+				model.on( 'change:' + property, ( evt, value ) => executeCallback( el, value ) );
+
+				// Set the initial state of the view.
+				executeCallback( el, model[ property ] );
+			};
+		};
+
+		/**
+		 * Renders {@link el} using {@link template}.
+		 *
+		 * @param {Object} [def] Template definition to be rendered.
+		 * @returns HTMLElement {@link el} ready to be injected into DOM.
+		 */
+		render( template ) {
+			// TODO: Use ES6 default arguments syntax.
+			template = template || this.template;
+
+			if ( !template ) {
+				return null;
+			}
+
+			var el = document.createElement( template.tag ),
+				attr, value;
+
+			// Set the text first.
+			if ( template.text ) {
+				el.innerHTML = template.text;
+			}
+
+			// Set attributes.
+			for ( attr in template.attributes ) {
+				value = template.attributes[ attr ];
+
+				// Attribute bound directly to the model.
+				if ( typeof value == 'function' ) {
+					value( el, attr );
+				}
+
+				// Explicit attribute definition (string).
+				else {
+					// Attribute can be an array, i.e. classes.
+					if ( Array.isArray( value ) ) {
+						value = value.join( ' ' );
+					}
+
+					el.setAttribute( attr, value );
+				}
+			}
+
+			// Invoke children recursively.
+			if ( template.children ) {
+				for ( let child of template.children ) {
+					el.appendChild( this.render( child ) );
+				}
+			}
+
+			return el;
+		}
+
+		destroy() {};
+	}
+
+	return View;
+} );

+ 114 - 0
packages/ckeditor5-ui/tests/ui/view.js

@@ -0,0 +1,114 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+var modules = bender.amd.require( 'ckeditor', 'ui/view' );
+var view;
+
+describe( 'plain view', function() {
+	beforeEach( 'Create a test view instance', function() {
+		var View = modules[ 'ui/view' ];
+
+		view = new View( {
+			a: 'foo',
+			b: 42
+		} );
+	} );
+
+	it( 'accepts the model', function() {
+		expect( view ).to.have.deep.property( 'model.a', 'foo' );
+		expect( view ).to.have.deep.property( 'model.b', 42 );
+	} );
+
+	it( 'has no default element', function() {
+		expect( view.el ).to.be.null();
+	} );
+
+	it( 'has no default template', function() {
+		expect( view.template ).to.be.undefined();
+	} );
+
+	it( 'has no default regions', function() {
+		expect( view.regions.length ).to.be.equal( 0 );
+	} );
+} );
+
+describe( 'rich view', function() {
+	beforeEach( 'Create a test view instance', function() {
+		var View = modules[ 'ui/view' ],
+			Component = class extends View {
+				constructor( model ) {
+					super( model );
+
+					this.template = {
+						tag: 'p',
+						attributes: {
+							'class': 'a',
+							x: this.bind( this.model, 'plain' )
+						},
+						text: 'b',
+						children: [
+							{
+								tag: 'b',
+								text: 'c',
+								attributes: {
+									y: this.bind( this.model, 'augmented', function( el, value ) {
+										return ( value > 0 ? 'positive' : 'negative' );
+									} )
+								}
+							},
+							{
+								tag: 'i',
+								text: 'd',
+								attributes: {
+									z: this.bind( this.model, 'custom', function( el, value ) {
+										el.innerHTML = value;
+
+										if ( value == 'foo' ) {
+											return value;
+										}
+ 									} )
+								}
+							}
+						]
+					}
+				};
+			};
+
+		view = new Component( {
+			plain: 'z',
+			augmented: 7,
+			custom: 'moo'
+		} );
+	} );
+
+	it( 'renders the element', function() {
+		expect( view.el ).to.be.instanceof( HTMLElement );
+		expect( view.el.parentNode ).to.be.null();
+
+		expect( getOuterHtml( view.el ) ).to.be.equal( '<p class="a" x="z">b<b y="positive">c</b><i>moo</i></p>' );
+	} );
+
+	it( 'reacts to changes in model', function() {
+		view.model.plain = 'x';
+		expect( getOuterHtml( view.el ) ).to.be.equal( '<p class="a" x="x">b<b y="positive">c</b><i>moo</i></p>' );
+
+		view.model.augmented = 2;
+		expect( getOuterHtml( view.el ) ).to.be.equal( '<p class="a" x="x">b<b y="positive">c</b><i>moo</i></p>' );
+
+		view.model.augmented = -2;
+		expect( getOuterHtml( view.el ) ).to.be.equal( '<p class="a" x="x">b<b y="negative">c</b><i>moo</i></p>' );
+
+		view.model.custom = 'foo';
+		expect( getOuterHtml( view.el ) ).to.be.equal( '<p class="a" x="x">b<b y="negative">c</b><i z="foo">foo</i></p>' );
+	} );
+} );
+
+function getOuterHtml( el ) {
+	var container = document.createElement( 'div' );
+	container.appendChild( el.cloneNode( 1 ) );
+	return container.innerHTML;
+}