Browse Source

Merge pull request #41 from ckeditor/t/26

T/26: Review our approach to MVVM.
Piotrek Koszuliński 9 years ago
parent
commit
88d644dacb
28 changed files with 539 additions and 265 deletions
  1. 35 0
      packages/ckeditor5-ui/src/bindings/stickytoolbar.js
  2. 6 13
      packages/ckeditor5-ui/src/bindings/toolbar.js
  3. 29 0
      packages/ckeditor5-ui/src/bindings/toolbarbindingsmixin.js
  4. 16 16
      packages/ckeditor5-ui/src/editableui/editableui.js
  5. 37 4
      packages/ckeditor5-ui/src/editableui/editableuiview.js
  6. 13 7
      packages/ckeditor5-ui/src/editableui/inline/inlineeditableuiview.js
  7. 8 11
      packages/ckeditor5-ui/src/editorui/boxed/boxededitorui.js
  8. 7 7
      packages/ckeditor5-ui/src/editorui/boxed/boxededitoruiview.js
  9. 12 5
      packages/ckeditor5-ui/src/editorui/editorui.js
  10. 6 5
      packages/ckeditor5-ui/src/editorui/editoruiview.js
  11. 57 0
      packages/ckeditor5-ui/src/iconmanager/iconmanager.js
  12. 55 0
      packages/ckeditor5-ui/src/iconmanager/iconmanagerview.js
  13. 0 12
      packages/ckeditor5-ui/src/iconmanagermodel.jsdoc
  14. 0 36
      packages/ckeditor5-ui/src/iconmanagerview.js
  15. 1 1
      packages/ckeditor5-ui/src/template.js
  16. 27 11
      packages/ckeditor5-ui/src/view.js
  17. 31 0
      packages/ckeditor5-ui/tests/bindings/stickytoolbar.js
  18. 5 38
      packages/ckeditor5-ui/tests/bindings/toolbar.js
  19. 68 0
      packages/ckeditor5-ui/tests/bindings/toolbarbindingsmixin.js
  20. 0 6
      packages/ckeditor5-ui/tests/boxededitorui/boxededitorui.js
  21. 1 2
      packages/ckeditor5-ui/tests/boxededitorui/boxededitoruiview.js
  22. 28 29
      packages/ckeditor5-ui/tests/editableui/editableui.js
  23. 19 16
      packages/ckeditor5-ui/tests/editableui/editableuiview.js
  24. 10 9
      packages/ckeditor5-ui/tests/editableui/inline/inlineeditableuiview.js
  25. 1 2
      packages/ckeditor5-ui/tests/editorui/editoruiview.js
  26. 32 0
      packages/ckeditor5-ui/tests/iconmanager/iconmanager.js
  27. 8 4
      packages/ckeditor5-ui/tests/iconmanager/iconmanagerview.js
  28. 27 31
      packages/ckeditor5-ui/tests/view.js

+ 35 - 0
packages/ckeditor5-ui/src/bindings/stickytoolbar.js

@@ -0,0 +1,35 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import mix from '../../utils/mix.js';
+import ToolbarBindingsMixin from './toolbarbindingsmixin.js';
+import BaseStickyToolbar from '../toolbar/sticky/stickytoolbar.js';
+
+/**
+ * The editor sticky toolbar controller class.
+ *
+ * See {@link ui.stickyToolbar.StickyToolbar}.
+ *
+ * @memberOf ui.bindings
+ * @extends ui.stickyToolbar.StickyToolbar
+ */
+export default class StickyToolbar extends BaseStickyToolbar {
+	/**
+	 * Creates an instance of {@link ui.bindings.StickyToolbar} class.
+	 *
+	 * @param {ui.stickyToolbar.StickyToolbarModel} model Model of this sticky toolbar.
+	 * @param {ui.View} view View of this sticky toolbar.
+	 * @param {ckeditor5.Editor} editor
+	 */
+	constructor( model, view, editor ) {
+		super( model, view );
+
+		this.editor = editor;
+	}
+}
+
+mix( StickyToolbar, ToolbarBindingsMixin );

+ 6 - 13
packages/ckeditor5-ui/src/bindings/toolbar.js

@@ -5,15 +5,18 @@
 
 'use strict';
 
+import mix from '../../utils/mix.js';
+import ToolbarBindingsMixin from './toolbarbindingsmixin.js';
 import BaseToolbar from '../toolbar/toolbar.js';
 
 /**
  * The editor toolbar controller class.
  *
+ * See {@link ui.toolbar.Toolbar}.
+ *
  * @memberOf ui.bindings
  * @extends ui.toolbar.Toolbar
  */
-
 export default class Toolbar extends BaseToolbar {
 	/**
 	 * Creates a new toolbar instance.
@@ -27,16 +30,6 @@ export default class Toolbar extends BaseToolbar {
 
 		this.editor = editor;
 	}
-
-	/**
-	 * Adds buttons to the toolbar. Buttons are taken from the {@link ui.editorUI.EditorUI#featureComponents}
-	 * factory.
-	 *
-	 * @param {String[]} buttons The name of the buttons to add to the toolbar.
-	 */
-	addButtons( buttons ) {
-		for ( let button of buttons ) {
-			this.add( 'buttons', this.editor.ui.featureComponents.create( button ) );
-		}
-	}
 }
+
+mix( Toolbar, ToolbarBindingsMixin );

+ 29 - 0
packages/ckeditor5-ui/src/bindings/toolbarbindingsmixin.js

@@ -0,0 +1,29 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+/**
+ * Mixin that injects the common Toolbar–like bindings.
+ *
+ * See {@link ui.bindings.Toolbar}.
+ *
+ * @mixin ui.bindings.ToolbarBindingsMixin
+ */
+const ToolbarBindingsMixin = {
+	/**
+	 * Adds buttons to the toolbar. Buttons are taken from the {@link ui.editorUI.EditorUI#featureComponents}
+	 * factory.
+	 *
+	 * @param {String[]} buttons The name of the buttons to add to the toolbar.
+	 */
+	addButtons( buttons ) {
+		for ( let button of buttons ) {
+			this.add( 'buttons', this.editor.ui.featureComponents.create( button ) );
+		}
+	}
+};
+
+export default ToolbarBindingsMixin;

+ 16 - 16
packages/ckeditor5-ui/src/editableui/editableui.js

@@ -6,21 +6,29 @@
 'use strict';
 
 import Controller from '../controller.js';
-import Model from '../model.js';
 
 /**
+ * The editable UI controller class. It glues the engine editable
+ * {@link engine.view.RootEditableElement} with the UI.
+ *
+ *		// An instance of EditableUI.
+ *		new EditableUI( editor, editable, new EditableUIView() );
+ *
+ * See {@link ui.editableUI.EditableUIView}.
+ *
  * @memberOf ui.editableUI
  * @extends ui.Controller
  */
 export default class EditableUI extends Controller {
 	/**
-	 * Creates a new instance of the Editable class.
+	 * Creates an instance of {@link ui.editableUI.EditableUI} class.
 	 *
-	 * @param {ckeditor5.Editor} editor The editor instance.
-	 * @param {engine.view.RootEditableElement} editable The editable element.
+	 * @param {engine.view.RootEditableElement} editable The editable element (in the engine).
+	 * @param {ui.View} [view] An instance of EditableUIView.
+	 * @param {ckeditor5.Editor} [editor] The editor instance.
 	 */
-	constructor( editor, editable ) {
-		super();
+	constructor( editable, view, editor ) {
+		super( editable, view );
 
 		/**
 		 * The editor instance.
@@ -30,15 +38,7 @@ export default class EditableUI extends Controller {
 		 */
 		this.editor = editor;
 
-		/**
-		 * The model for the view.
-		 *
-		 * @readonly
-		 * @member {ui.Model} ui.editableUI.EditableUI#viewModel
-		 */
-		this.viewModel = new Model();
-
-		this.viewModel.bind( 'isReadOnly', 'isFocused' ).to( editable );
-		this.viewModel.set( 'name', editable.rootName );
+		view.model.bind( 'isReadOnly', 'isFocused' ).to( editable );
+		view.model.set( 'name', editable.rootName );
 	}
 }

+ 37 - 4
packages/ckeditor5-ui/src/editableui/editableuiview.js

@@ -9,20 +9,23 @@ import View from '../view.js';
 import Template from '../template.js';
 
 /**
+ * The editable UI view class.
+ *
+ * See {@link ui.editableUI.EditableUI}.
+ *
  * @memberOf ui.editableUI
  * @extends ui.View
  */
 export default class EditableUIView extends View {
 	/**
-	 * Creates an instance of the EditableUIView class.
+	 * Creates an instance of {@link ui.editableUI.EditableUIView} class.
 	 *
-	 * @param {utils.Observable} model (View)Model of this View.
 	 * @param {utils.Locale} [locale] The {@link ckeditor5.Editor#locale editor's locale} instance.
 	 * @param {HTMLElement} [editableElement] The editable element. If not specified, this view
 	 * should create it. Otherwise, the existing element should be used.
 	 */
-	constructor( model, locale, editableElement ) {
-		super( model, locale );
+	constructor( locale, editableElement ) {
+		super( locale );
 
 		const bind = this.bind;
 
@@ -47,6 +50,12 @@ export default class EditableUIView extends View {
 		 * @readonly
 		 * @member {HTMLElement} ui.editableUI.EditableUIView#editableElement
 		 */
+
+		/**
+		 * Model of this editable UI view.
+		 *
+		 * @member {ui.editableUI.EditableUIViewModel} ui.editableUI.EditableUIView#model
+		 */
 	}
 
 	/**
@@ -69,3 +78,27 @@ export default class EditableUIView extends View {
 		this.editableElement.contentEditable = false;
 	}
 }
+
+/**
+ * The editable UI view {@link ui.Model} interface.
+ *
+ * @interface ui.editableUI.EditableUIViewModel
+ */
+
+/**
+ * Controls whether the editable is writable or not.
+ *
+ * @member {Boolean} ui.editableUI.EditableUIViewModel#isReadOnly
+ */
+
+/**
+ * Controls whether the editable is focused, i.e. the user is typing in it.
+ *
+ * @member {Boolean} ui.editableUI.EditableUIViewModel#isFocused
+ */
+
+/**
+ * The name of the editable UI view.
+ *
+ * @member {String} ui.editableUI.EditableUIViewModel#name
+ */

+ 13 - 7
packages/ckeditor5-ui/src/editableui/inline/inlineeditableuiview.js

@@ -9,7 +9,9 @@ import EditableUIView from '../../editableui/editableuiview.js';
 import Template from '../../template.js';
 
 /**
- * The class implementing an inline {@link ui.editableUI.EditableUIView}.
+ * The inline editable UI class implementing an inline {@link ui.editableUI.EditableUIView}.
+ *
+ * See {@link ui.editableUI.EditableUI}, {@link ui.editableUI.EditableUIView}.
  *
  * @memberOf ui.editableUI.inline
  * @extends ui.editableUI.EditableUIView
@@ -18,21 +20,25 @@ export default class InlineEditableUIView extends EditableUIView {
 	/**
 	 * Creates an instance of the InlineEditableUIView class.
 	 *
-	 * @param {utils.Observable} model (View)Model of this view.
 	 * @param {utils.Locale} [locale] The {@link ckeditor5.Editor#locale editor's locale} instance.
 	 * @param {HTMLElement} [editableElement] The editable element. If not specified, the {@link EditableUIView}
 	 * should create it. Otherwise, the existing element should be used.
 	 */
-	constructor( model, locale, editableElement ) {
-		super( model, locale, editableElement );
+	constructor( locale, editableElement ) {
+		super( locale, editableElement );
+
+		const bind = this.bind;
+		const t = this.t;
 
-		const label = this.t( 'Rich Text Editor, %0', [ this.model.name ] );
+		const getLabel = ( value ) => {
+			return t( 'Rich Text Editor, %0', [ value ] );
+		};
 
 		Template.extend( this.template, {
 			attributes: {
 				role: 'textbox',
-				'aria-label': label,
-				title: label,
+				'aria-label': bind.to( 'name', getLabel ),
+				title: bind.to( 'name', getLabel ),
 				class: 'ck-editor__editable_inline'
 			}
 		} );

+ 8 - 11
packages/ckeditor5-ui/src/editorui/boxed/boxededitorui.js

@@ -9,15 +9,20 @@ import EditorUI from '../../../ui/editorui/editorui.js';
 import ControllerCollection from '../../../ui/controllercollection.js';
 
 /**
- * Boxed editor UI. The class representing classic editor interface, which contains of a toolbar and editable are,
- * closed within a box.
+ * The boxed editor UI controller class. This class controls an editor interface
+ * consisting of a toolbar and an editable area, enclosed within a box.
+ *
+ *		// An instance of BoxedEditorUI.
+ *		new BoxedEditorUI( editor );
+ *
+ * See {@link ui.editorUI.boxed.BoxedEditorUIView}.
  *
  * @member ui.editorUI.boxed
  * @extends ui.editorUI.EditorUI
  */
 export default class BoxedEditorUI extends EditorUI {
 	/**
-	 * Creates a BoxedEditorUI instance.
+	 * Creates a boxed editor UI instance.
 	 *
 	 * @param {ckeditor5.Editor} editor
 	 */
@@ -49,12 +54,4 @@ export default class BoxedEditorUI extends EditorUI {
 		 */
 		this.set( 'height', config.get( 'ui.height' ) );
 	}
-
-	/**
-	 * @readonly
-	 * @property {ui.Model} viewModel
-	 */
-	get viewModel() {
-		return this;
-	}
 }

+ 7 - 7
packages/ckeditor5-ui/src/editorui/boxed/boxededitoruiview.js

@@ -10,20 +10,20 @@ import uid from '../../../utils/uid.js';
 import Template from '../../template.js';
 
 /**
- * Boxed editor UI view.
+ * The boxed editor UI view class. This class represents an editor interface
+ * consisting of a toolbar and an editable area, enclosed within a box.
+ *
+ * See {@link ui.editorUI.boxed.BoxedEditorUI}.
  *
  * @member ui.editorUI.boxed
  * @extends ui.editorUI.EditorUIView
  */
 export default class BoxedEditorUIView extends EditorUIView {
 	/**
-	 * Creates a BoxedEditorUIView instance.
-	 *
-	 * @param {utils.Observable} model (View)Model of this view.
-	 * @param {utils.Locale} [locale] The {@link ckeditor5.Editor#locale editor's locale} instance.
+	 * @inheritDoc
 	 */
-	constructor( model, locale ) {
-		super( model, locale );
+	constructor( locale ) {
+		super( locale );
 
 		const t = this.t;
 		const ariaLabelUid = uid();

+ 12 - 5
packages/ckeditor5-ui/src/editorui/editorui.js

@@ -9,12 +9,19 @@ import Controller from '../controller.js';
 import ControllerCollection from '../controllercollection.js';
 import ComponentFactory from '../componentfactory.js';
 import ObservableMixin from '../../utils/observablemixin.js';
-import IconManagerView from '../iconmanagerview.js';
+import IconManager from '../iconmanager/iconmanager.js';
+import IconManagerView from '../iconmanager/iconmanagerview.js';
 import iconManagerModel from '../../../theme/iconmanagermodel.js';
 import mix from '../../utils/mix.js';
 
 /**
- * Base class for the editor main view controllers.
+ * The editor UI controller class. It's a base class for the editor
+ * main view controllers.
+ *
+ *		// An instance of EditorUI.
+ *		new EditorUI( editor );
+ *
+ * See {@link ui.editorUI.EditorUIView}, {@link ui.iconManager.IconManager}.
  *
  * @memberOf ui.editorUI
  * @extends ui.Controller
@@ -22,7 +29,7 @@ import mix from '../../utils/mix.js';
  */
 export default class EditorUI extends Controller {
 	/**
-	 * Creates an EditorUI instance.
+	 * Creates an instance of {@link ui.editorUI.EditorUI} class.
 	 *
 	 * @param {ckeditor5.Editor} editor
 	 */
@@ -56,7 +63,7 @@ export default class EditorUI extends Controller {
 	}
 
 	/**
-	 * Adds IconManager into DOM.
+	 * Injects the {@link ui.iconManager.IconManager} into DOM.
 	 *
 	 * @protected
 	 */
@@ -70,7 +77,7 @@ export default class EditorUI extends Controller {
 		this.icons = iconManagerModel.icons;
 
 		this.collections.get( 'body' ).add(
-			new Controller( iconManagerModel, new IconManagerView( iconManagerModel ) )
+			new IconManager( iconManagerModel, new IconManagerView() )
 		);
 	}
 }

+ 6 - 5
packages/ckeditor5-ui/src/editorui/editoruiview.js

@@ -9,20 +9,21 @@ import View from '../view.js';
 import Template from '../template.js';
 
 /**
- * Base class for the editor main views.
+ * The editor UI view class. Base class for the editor main views.
+ *
+ * See {@link ui.editorUI.EditorUI}.
  *
  * @memberOf ui.editorUI
  * @extends ui.View
  */
 export default class EditorUIView extends View {
 	/**
-	 * Creates a EditorUIView instance.
+	 * Creates an instance of the editor UI view class.
 	 *
-	 * @param {utils.Observable} model (View)Model of this view.
 	 * @param {utils.Locale} [locale] The {@link ckeditor5.Editor#locale editor's locale} instance.
 	 */
-	constructor( model, locale ) {
-		super( model, locale );
+	constructor( locale ) {
+		super( locale );
 
 		this._createBodyRegion();
 

+ 57 - 0
packages/ckeditor5-ui/src/iconmanager/iconmanager.js

@@ -0,0 +1,57 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import Controller from '../controller.js';
+
+/**
+ * The icon manager controller class. It provides SVG icons, which then can
+ * be used by {@link ui.icon.Icon} component and similar.
+ *
+ *		const model = new Model( {
+ *			icons: [ 'bold', 'italic', ... ],
+ *			sprite: '...' // SVG sprite
+ *		} );
+ *
+ *		// An instance of IconManager.
+ *		new IconManager( model, new IconManagerView() );
+ *
+ * See {@link ui.iconManager.IconManagerView}.
+ *
+ * @memberOf ui.iconManager
+ * @extends ui.Controller
+ */
+export default class IconManager extends Controller {
+	/**
+	 * Creates an instance of {@link ui.iconManager.IconManager} class.
+	 *
+	 * @param {ui.iconManager.IconManagerModel} model Model of this IconManager.
+	 * @param {ui.View} view View of this IconManager.
+	 */
+	constructor( model, view ) {
+		super( model, view );
+
+		view.model.bind( 'sprite' ).to( model );
+	}
+}
+
+/**
+ * The icon manager component {@link ui.Model} interface.
+ *
+ * @interface ui.iconManager.IconManagerModel
+ */
+
+/**
+ * An array of icon names which are brought by the {@link ui.iconManager.IconManagerModel#sprite}.
+ *
+ * @member {Array.<String>} ui.iconManager.IconManagerModel#icons
+ */
+
+/**
+ * The actual SVG (HTML) of the icons to be injected in DOM.
+ *
+ * @member {String} ui.iconManager.IconManagerModel#sprite
+ */

+ 55 - 0
packages/ckeditor5-ui/src/iconmanager/iconmanagerview.js

@@ -0,0 +1,55 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import View from '../view.js';
+import Template from '../template.js';
+
+/**
+ * The icon manager view class.
+ *
+ * See {@link ui.iconManager.IconManager}.
+ *
+ * @memberOf ui.iconManager
+ * @extends ui.View
+ */
+export default class IconManagerView extends View {
+	constructor( locale ) {
+		super( locale );
+
+		this.template = new Template( {
+			tag: 'svg',
+			ns: 'http://www.w3.org/2000/svg',
+			attributes: {
+				class: 'ck-icon-manager-sprite'
+			}
+		} );
+
+		/**
+		 * Model of this icon manager view.
+		 *
+		 * @member {ui.iconManager.IconManagerViewModel} ui.iconManager.IconManagerView#model
+		 */
+	}
+
+	init() {
+		this.element.innerHTML = this.model.sprite;
+
+		return super.init();
+	}
+}
+
+/**
+ * The icon manager view {@link ui.Model} interface.
+ *
+ * @interface ui.iconManager.IconManagerViewModel
+ */
+
+/**
+ * The actual SVG (HTML) of the icons to be injected in DOM.
+ *
+ * @member {String} ui.iconManager.IconManagerViewModel#sprite
+ */

+ 0 - 12
packages/ckeditor5-ui/src/iconmanagermodel.jsdoc

@@ -1,12 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-/**
- * Provides a Model containing icon sprite for {@link ui.iconManager.IconManagerView}.
- *
- * @class IconManagerModel
- * @memberOf ui.iconManager
- * @extends ui.Model
- */

+ 0 - 36
packages/ckeditor5-ui/src/iconmanagerview.js

@@ -1,36 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-'use strict';
-
-import View from './view.js';
-import Template from './template.js';
-
-/**
- * Icon manager view using {@link ui.iconManager.IconManagerModel}.
- *
- * @memberOf ui.iconManager
- * @extends ui.View
- */
-
-export default class IconManagerView extends View {
-	constructor( model, locale ) {
-		super( model, locale );
-
-		this.template = new Template( {
-			tag: 'svg',
-			ns: 'http://www.w3.org/2000/svg',
-			attributes: {
-				class: 'ck-icon-manager-sprite'
-			}
-		} );
-	}
-
-	init() {
-		this.element.innerHTML = this.model.sprite;
-
-		return super.init();
-	}
-}

+ 1 - 1
packages/ckeditor5-ui/src/template.js

@@ -122,7 +122,7 @@ export default class Template {
 	 * @param {utils.ObservableMixin} observable An instance of ObservableMixin class.
 	 * @param {utils.EmitterMixin} emitter An instance of `EmitterMixin` class. It listens
 	 * to `observable` attribute changes and DOM Events, depending on the binding. Usually {@link ui.View} instance.
-	 * @returns {ui.TemplateBinding}
+	 * @returns {Object}
 	 */
 	static bind( observable, emitter ) {
 		return {

+ 27 - 11
packages/ckeditor5-ui/src/view.js

@@ -5,6 +5,7 @@
 
 'use strict';
 
+import Model from './model.js';
 import Collection from '../utils/collection.js';
 import Region from './region.js';
 import Template from './template.js';
@@ -22,16 +23,15 @@ export default class View {
 	/**
 	 * Creates an instance of the {@link ui.View} class.
 	 *
-	 * @param {ui.Model} model (View)Model of this View.
 	 * @param {utils.Locale} [locale] The {@link ckeditor5.Editor#locale editor's locale} instance.
 	 */
-	constructor( model, locale ) {
+	constructor( locale ) {
 		/**
 		 * Model of this view.
 		 *
 		 * @member {ui.Model} ui.View#model
 		 */
-		this.model = model;
+		this.model = new Model();
 
 		/**
 		 * @readonly
@@ -58,14 +58,6 @@ export default class View {
 			idProperty: 'name'
 		} );
 
-		/**
-		 * Shorthand for {@link ui.Template#bind}, bound to {@link ui.View#model}
-		 * and {@link ui.View}.
-		 *
-		 * @method ui.View#bind
-		 */
-		this.bind = Template.bind( this.model, this );
-
 		/**
 		 * Template of this view.
 		 *
@@ -86,6 +78,14 @@ export default class View {
 		 * @private
 		 * @member {HTMLElement} ui.View.#_element
 		 */
+
+		/**
+		 * Cached {@link ui.Template} binder object specific for this instance.
+		 * See {@link ui.View#bind}.
+		 *
+		 * @private
+		 * @member {Object} ui.View.#_bind
+		 */
 	}
 
 	/**
@@ -111,6 +111,22 @@ export default class View {
 		this._element = el;
 	}
 
+	/**
+	 * Shorthand for {@link ui.Template#bind}, bound to {@link ui.View#model}
+	 * and {@link ui.View} on the first access.
+	 *
+	 * Cached {@link ui.Template#bind} object is stored in {@link ui.View.#_bind}.
+	 *
+	 * @method ui.View#bind
+	 */
+	get bind() {
+		if ( this._bind ) {
+			return this._bind;
+		}
+
+		return ( this._bind = Template.bind( this.model, this ) );
+	}
+
 	/**
 	 * Initializes the view.
 	 *

+ 31 - 0
packages/ckeditor5-ui/tests/bindings/stickytoolbar.js

@@ -0,0 +1,31 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: ui, toolbar */
+
+'use strict';
+
+import Editor from '/ckeditor5/editor/editor.js';
+import Model from '/ckeditor5/ui/model.js';
+import View from '/ckeditor5/ui/view.js';
+import StickyToolbar from '/ckeditor5/ui/bindings/toolbar.js';
+
+describe( 'StickyToolbar', () => {
+	let toolbar, model, editor;
+
+	beforeEach( () => {
+		editor = new Editor();
+		model = new Model( {
+			isActive: false
+		} );
+		toolbar = new StickyToolbar( model, new View(), editor );
+	} );
+
+	describe( 'constructor', () => {
+		it( 'sets all the properties', () => {
+			expect( toolbar ).to.have.property( 'editor', editor );
+		} );
+	} );
+} );

+ 5 - 38
packages/ckeditor5-ui/tests/bindings/toolbar.js

@@ -10,17 +10,17 @@
 import Editor from '/ckeditor5/editor/editor.js';
 import Model from '/ckeditor5/ui/model.js';
 import View from '/ckeditor5/ui/view.js';
-import Controller from '/ckeditor5/ui/controller.js';
 import Toolbar from '/ckeditor5/ui/bindings/toolbar.js';
 
 describe( 'Toolbar', () => {
-	let toolbar, view, model, editor;
+	let toolbar, model, editor;
 
 	beforeEach( () => {
 		editor = new Editor();
-		model = new Model();
-		view = new View( model );
-		toolbar = new Toolbar( model, view, editor );
+		model = new Model( {
+			isActive: false
+		} );
+		toolbar = new Toolbar( model, new View(), editor );
 	} );
 
 	describe( 'constructor', () => {
@@ -28,37 +28,4 @@ describe( 'Toolbar', () => {
 			expect( toolbar ).to.have.property( 'editor', editor );
 		} );
 	} );
-
-	describe( 'addButtons', () => {
-		it( 'creates buttons for each button name', () => {
-			const createSpy = sinon.spy( () => new Controller() );
-
-			editor.ui = {
-				featureComponents: {
-					create: createSpy
-				}
-			};
-
-			toolbar.addButtons( [ 'foo', 'bar' ] );
-
-			expect( createSpy.callCount ).to.equal( 2 );
-			expect( createSpy.firstCall.calledWith( 'foo' ) ).to.be.true;
-			expect( createSpy.secondCall.calledWith( 'bar' ) ).to.be.true;
-		} );
-
-		it( 'adds created components to the collection of buttons', () => {
-			const component = new Controller();
-			const createSpy = sinon.spy( () => component );
-
-			editor.ui = {
-				featureComponents: {
-					create: createSpy
-				}
-			};
-
-			toolbar.addButtons( [ 'foo' ] );
-
-			expect( toolbar.collections.get( 'buttons' ).get( 0 ) ).to.equal( component );
-		} );
-	} );
 } );

+ 68 - 0
packages/ckeditor5-ui/tests/bindings/toolbarbindingsmixin.js

@@ -0,0 +1,68 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: ui, toolbar */
+
+'use strict';
+
+import mix from '/ckeditor5/utils/mix.js';
+import Editor from '/ckeditor5/editor/editor.js';
+import Controller from '/ckeditor5/ui/controller.js';
+import ControllerCollection from '/ckeditor5/ui/controllercollection.js';
+import ToolbarBindingsMixin from '/ckeditor5/ui/bindings/toolbarbindingsmixin.js';
+
+describe( 'ToolbarBindingsMixin', () => {
+	let mixinInstance, editor;
+
+	beforeEach( () => {
+		editor = new Editor();
+
+		class MixClass extends Controller {
+			constructor( model, view ) {
+				super( model, view );
+
+				this.collections.add( new ControllerCollection( 'buttons' ) );
+			}
+		}
+
+		mix( MixClass, ToolbarBindingsMixin );
+
+		mixinInstance = new MixClass();
+		mixinInstance.editor = editor;
+	} );
+
+	describe( 'addButtons', () => {
+		it( 'creates buttons for each button name', () => {
+			const createSpy = sinon.spy( () => new Controller() );
+
+			editor.ui = {
+				featureComponents: {
+					create: createSpy
+				}
+			};
+
+			mixinInstance.addButtons( [ 'foo', 'bar' ] );
+
+			expect( createSpy.callCount ).to.equal( 2 );
+			expect( createSpy.firstCall.calledWith( 'foo' ) ).to.be.true;
+			expect( createSpy.secondCall.calledWith( 'bar' ) ).to.be.true;
+		} );
+
+		it( 'adds created components to the collection of buttons', () => {
+			const component = new Controller();
+			const createSpy = sinon.spy( () => component );
+
+			editor.ui = {
+				featureComponents: {
+					create: createSpy
+				}
+			};
+
+			mixinInstance.addButtons( [ 'foo' ] );
+
+			expect( mixinInstance.collections.get( 'buttons' ).get( 0 ) ).to.equal( component );
+		} );
+	} );
+} );

+ 0 - 6
packages/ckeditor5-ui/tests/boxededitorui/boxededitorui.js

@@ -33,10 +33,4 @@ describe( 'BoxedEditorUI', () => {
 			expect( boxedEditorUI.height ).to.equal( editor.config.get( 'ui.height' ) );
 		} );
 	} );
-
-	describe( 'viewModel', () => {
-		it( 'points to the BoxedEditorUI instance', () => {
-			expect( boxedEditorUI.viewModel ).to.equal( boxedEditorUI );
-		} );
-	} );
 } );

+ 1 - 2
packages/ckeditor5-ui/tests/boxededitorui/boxededitoruiview.js

@@ -6,14 +6,13 @@
 'use strict';
 
 import BoxedEditorUIView from '/ckeditor5/ui/editorui/boxed/boxededitoruiview.js';
-import Model from '/ckeditor5/ui/model.js';
 import Locale from '/ckeditor5/utils/locale.js';
 
 describe( 'BoxedEditorUIView', () => {
 	let boxedEditorUIView, element, topRegionEl, mainRegionEl;
 
 	beforeEach( () => {
-		boxedEditorUIView = new BoxedEditorUIView( new Model(), new Locale( 'en' ) );
+		boxedEditorUIView = new BoxedEditorUIView( new Locale( 'en' ) );
 		boxedEditorUIView.init();
 
 		element = boxedEditorUIView.element;

+ 28 - 29
packages/ckeditor5-ui/tests/editableui/editableui.js

@@ -9,51 +9,50 @@
 
 import StandardEditor from '/ckeditor5/editor/standardeditor.js';
 import EditableUI from '/ckeditor5/ui/editableui/editableui.js';
-import Model from '/ckeditor5/ui/model.js';
+import EditableUIView from '/ckeditor5/ui/editableui/editableuiview.js';
 import testUtils from '/tests/utils/_utils/utils.js';
 
 describe( 'EditableUI', () => {
-	let editable, editableUI, editor;
+	let editable, editableUI, editableUIView, editor;
 
 	beforeEach( () => {
 		editor = new StandardEditor();
 		editable = editor.editing.view.createRoot( document.createElement( 'div' ) );
-		editableUI = new EditableUI( editor, editable );
+		editableUIView = new EditableUIView( editor.locale );
+		editableUI = new EditableUI( editable, editableUIView, editor );
 	} );
 
 	describe( 'constructor', () => {
 		it( 'sets all properties', () => {
 			expect( editableUI.editor ).to.equal( editor );
-			expect( editableUI.viewModel ).to.be.instanceof( Model );
-		} );
-	} );
-
-	describe( 'viewModel', () => {
-		it( 'constains observable attributes', () => {
-			expect( editableUI.viewModel ).to.have.property( 'isReadOnly', false );
-			expect( editableUI.viewModel ).to.have.property( 'isFocused', false );
 		} );
 
-		it( 'binds isFocused to editable.isFocused', () => {
-			testUtils.assertBinding(
-				editableUI.viewModel,
-				{ isFocused: false },
-				[
-					[ editable, { isFocused: true } ]
-				],
-				{ isFocused: true }
-			);
+		it( 'binds editableUIView#model attributes to the editable', () => {
+			it( 'binds isFocused to editable.isFocused', () => {
+				testUtils.assertBinding(
+					editableUIView.model,
+					{ isFocused: false },
+					[
+						[ editable, { isFocused: true } ]
+					],
+					{ isFocused: true }
+				);
+			} );
+
+			it( 'binds isReadOnly to editable.isReadOnly', () => {
+				testUtils.assertBinding(
+					editableUIView.model,
+					{ isReadOnly: false },
+					[
+						[ editable, { isReadOnly: true } ]
+					],
+					{ isReadOnly: true }
+				);
+			} );
 		} );
 
-		it( 'binds isReadOnly to editable.isReadOnly', () => {
-			testUtils.assertBinding(
-				editableUI.viewModel,
-				{ isReadOnly: false },
-				[
-					[ editable, { isReadOnly: true } ]
-				],
-				{ isReadOnly: true }
-			);
+		it( 'sets editableUIView.model#name to editable#rootName', () => {
+			expect( editableUIView.model.name ).to.equal( editable.rootName );
 		} );
 	} );
 } );

+ 19 - 16
packages/ckeditor5-ui/tests/editableui/editableuiview.js

@@ -7,28 +7,30 @@
 
 'use strict';
 
+import EditableUI from '/ckeditor5/ui/editableui/editableui.js';
 import EditableUIView from '/ckeditor5/ui/editableui/editableuiview.js';
 import Model from '/ckeditor5/ui/model.js';
 import Locale from '/ckeditor5/utils/locale.js';
 
 describe( 'EditableUIView', () => {
-	let model, view, editableElement, locale;
+	let editable, view, editableElement, locale;
 
 	beforeEach( () => {
-		model = new Model( {
+		editable = new Model( {
 			isReadOnly: false,
-			isFocused: false
+			isFocused: false,
+			rootName: 'foo'
 		} );
 		locale = new Locale( 'en' );
-		view = new EditableUIView( model, locale );
+		view = new EditableUIView( locale );
 		editableElement = document.createElement( 'div' );
 
-		view.init();
+		return new EditableUI( editable, view ).init();
 	} );
 
 	describe( 'constructor', () => {
 		it( 'renders element from template when no editableElement', () => {
-			view = new EditableUIView( model, locale );
+			view = new EditableUIView( locale );
 			view.init();
 
 			expect( view.element ).to.equal( view.editableElement );
@@ -36,7 +38,7 @@ describe( 'EditableUIView', () => {
 		} );
 
 		it( 'accepts editableElement as an argument', () => {
-			view = new EditableUIView( model, locale, editableElement );
+			view = new EditableUIView( locale, editableElement );
 			view.init();
 
 			expect( view.element ).to.equal( editableElement );
@@ -52,8 +54,8 @@ describe( 'EditableUIView', () => {
 				expect( view.element.classList.contains( 'ck-focused' ) ).to.be.false;
 			} );
 
-			it( 'reacts on model.isFocused', () => {
-				model.isFocused = true;
+			it( 'reacts on editable.isFocused', () => {
+				editable.isFocused = true;
 
 				expect( view.element.classList.contains( 'ck-blurred' ) ).to.be.false;
 				expect( view.element.classList.contains( 'ck-focused' ) ).to.be.true;
@@ -65,8 +67,8 @@ describe( 'EditableUIView', () => {
 				expect( view.element.getAttribute( 'contenteditable' ) ).to.equal( 'true' );
 			} );
 
-			it( 'reacts on model.isReadOnly', () => {
-				model.isReadOnly = true;
+			it( 'reacts on editable.isReadOnly', () => {
+				editable.isReadOnly = true;
 
 				expect( view.element.getAttribute( 'contenteditable' ) ).to.equal( 'false' );
 			} );
@@ -75,14 +77,15 @@ describe( 'EditableUIView', () => {
 
 	describe( 'destroy', () => {
 		it( 'updates contentEditable property of editableElement', () => {
-			view = new EditableUIView( model, locale, editableElement );
-			view.init();
+			view = new EditableUIView( locale, editableElement );
 
-			expect( view.editableElement.contentEditable ).to.equal( 'true' );
+			return new EditableUI( editable, view ).init().then( () => {
+				expect( view.editableElement.contentEditable ).to.equal( 'true' );
 
-			view.destroy();
+				view.destroy();
 
-			expect( view.editableElement.contentEditable ).to.equal( 'false' );
+				expect( view.editableElement.contentEditable ).to.equal( 'false' );
+			} );
 		} );
 	} );
 } );

+ 10 - 9
packages/ckeditor5-ui/tests/editableui/inline/inlineeditableuiview.js

@@ -7,33 +7,34 @@
 
 'use strict';
 
+import EditableUI from '/ckeditor5/ui/editableui/editableui.js';
 import InlineEditableUIView from '/ckeditor5/ui/editableui/inline/inlineeditableuiview.js';
 import Model from '/ckeditor5/ui/model.js';
 import Locale from '/ckeditor5/utils/locale.js';
 
 describe( 'InlineEditableUIView', () => {
-	let model, view, editableElement, locale;
+	let editable, view, editableElement, locale;
 
 	beforeEach( () => {
-		model = new Model( { isEditable: true, isFocused: false, name: 'foo' } );
+		editable = new Model( {
+			isReadOnly: false,
+			isFocused: false,
+			rootName: 'foo'
+		} );
 		locale = new Locale( 'en' );
-		view = new InlineEditableUIView( model, locale );
+		view = new InlineEditableUIView( locale );
 		editableElement = document.createElement( 'div' );
 
-		return view.init();
+		return new EditableUI( editable, view ).init();
 	} );
 
 	describe( 'constructor', () => {
-		it( 'accepts model', () => {
-			expect( view.model ).to.equal( model );
-		} );
-
 		it( 'accepts locale', () => {
 			expect( view.locale ).to.equal( locale );
 		} );
 
 		it( 'accepts editableElement', () => {
-			view = new InlineEditableUIView( model, locale, editableElement );
+			view = new InlineEditableUIView( locale, editableElement );
 
 			expect( view.element ).to.equal( editableElement );
 		} );

+ 1 - 2
packages/ckeditor5-ui/tests/editorui/editoruiview.js

@@ -6,13 +6,12 @@
 'use strict';
 
 import EditorUIView from '/ckeditor5/ui/editorui/editoruiview.js';
-import Model from '/ckeditor5/ui/model.js';
 
 describe( 'EditorUIView', () => {
 	let editorUIView;
 
 	beforeEach( () => {
-		editorUIView = new EditorUIView( new Model() );
+		editorUIView = new EditorUIView();
 
 		return editorUIView.init();
 	} );

+ 32 - 0
packages/ckeditor5-ui/tests/iconmanager/iconmanager.js

@@ -0,0 +1,32 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+ /* bender-tags: ui, iconmanager */
+
+'use strict';
+
+import Model from '/ckeditor5/ui/model.js';
+import IconManager from '/ckeditor5/ui/iconmanager/iconmanager.js';
+import IconManagerView from '/ckeditor5/ui/iconmanager/iconmanagerview.js';
+
+describe( 'IconManager', () => {
+	let model, view;
+
+	beforeEach( () => {
+		view = new IconManagerView();
+		model = new Model( {
+			sprite: 'foo',
+			icons: [ 'bar' ]
+		} );
+
+		return new IconManager( model, view ).init();
+	} );
+
+	describe( 'constructor', () => {
+		it( 'binds view#model attributes to the IconManager#model', () => {
+			expect( view.model.sprite ).to.equal( model.sprite );
+		} );
+	} );
+} );

+ 8 - 4
packages/ckeditor5-ui/tests/iconmanagerview.js → packages/ckeditor5-ui/tests/iconmanager/iconmanagerview.js

@@ -8,18 +8,22 @@
 'use strict';
 
 import testUtils from '/tests/ckeditor5/_utils/utils.js';
-import IconManagerView from '/ckeditor5/ui/iconmanagerview.js';
+import IconManager from '/ckeditor5/ui/iconmanager/iconmanager.js';
+import IconManagerView from '/ckeditor5/ui/iconmanager/iconmanagerview.js';
 import Model from '/ckeditor5/ui/model.js';
 
 testUtils.createSinonSandbox();
 
 describe( 'IconManagerView', () => {
-	let view;
+	let model, view;
 
 	beforeEach( () => {
-		view = new IconManagerView( new Model( {
+		view = new IconManagerView();
+		model = new Model( {
 			sprite: 'foo'
-		} ) );
+		} );
+
+		return new IconManager( model, view ).init();
 	} );
 
 	describe( 'constructor', () => {

+ 27 - 31
packages/ckeditor5-ui/tests/view.js

@@ -13,7 +13,6 @@ import View from '/ckeditor5/ui/view.js';
 import Template from '/ckeditor5/ui/template.js';
 import Region from '/ckeditor5/ui/region.js';
 import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
-import Model from '/ckeditor5/ui/model.js';
 
 let TestView, view;
 
@@ -26,39 +25,17 @@ describe( 'View', () => {
 			setTestViewInstance();
 		} );
 
-		it( 'accepts the model', () => {
-			setTestViewInstance( { a: 'foo', b: 42 } );
-
-			expect( view.model ).to.be.an.instanceof( Model );
-			expect( view ).to.have.deep.property( 'model.a', 'foo' );
-			expect( view ).to.have.deep.property( 'model.b', 42 );
-		} );
-
 		it( 'defines basic view properties', () => {
-			const model = new Model();
-
-			view = new View( model );
+			view = new View();
 
-			expect( view.model ).to.equal( model );
 			expect( view.t ).to.be.undefined;
 			expect( view.regions.length ).to.equal( 0 );
 		} );
 
-		it( 'creates view#bind shorthand for Template binding', () => {
-			expect( view.bind.to ).to.be.a( 'function' );
-			expect( view.bind.if ).to.be.a( 'function' );
-
-			const binding = view.bind.to( 'a' );
-
-			expect( binding.observable ).to.equal( view.model );
-			expect( binding.emitter ).to.equal( view );
-		} );
-
-		it( 'defines the locale property and the t function', () => {
-			const model = new Model();
+		it( 'defines the locale property and the "t" function', () => {
 			const locale = { t() {} };
 
-			view = new View( model, locale );
+			view = new View( locale );
 
 			expect( view.locale ).to.equal( locale );
 			expect( view.t ).to.equal( locale.t );
@@ -140,6 +117,23 @@ describe( 'View', () => {
 		} );
 	} );
 
+	describe( 'bind', () => {
+		beforeEach( () => {
+			setTestViewClass();
+			setTestViewInstance();
+		} );
+
+		it( 'returns a shorthand for Template binding', () => {
+			expect( view.bind.to ).to.be.a( 'function' );
+			expect( view.bind.if ).to.be.a( 'function' );
+
+			const binding = view.bind.to( 'a' );
+
+			expect( binding.observable ).to.equal( view.model );
+			expect( binding.emitter ).to.equal( view );
+		} );
+	} );
+
 	describe( 'register', () => {
 		beforeEach( () => {
 			setTestViewClass();
@@ -223,7 +217,9 @@ describe( 'View', () => {
 		beforeEach( createViewInstanceWithTemplate );
 
 		it( 'invokes out of #template', () => {
-			setTestViewInstance( { a: 1 } );
+			setTestViewInstance();
+
+			view.model.set( 'a', 1 );
 
 			expect( view.element ).to.be.an.instanceof( HTMLElement );
 			expect( view.element.nodeName ).to.equal( 'A' );
@@ -303,8 +299,8 @@ function createViewInstanceWithTemplate() {
 
 function setTestViewClass( templateDef, regionsFn ) {
 	TestView = class V extends View {
-		constructor( model ) {
-			super( model );
+		constructor() {
+			super();
 
 			if ( templateDef ) {
 				this.template = new Template( templateDef );
@@ -317,8 +313,8 @@ function setTestViewClass( templateDef, regionsFn ) {
 	};
 }
 
-function setTestViewInstance( model ) {
-	view = new TestView( new Model( model ) );
+function setTestViewInstance() {
+	view = new TestView();
 
 	if ( view.template ) {
 		document.body.appendChild( view.element );