|
@@ -17,25 +17,48 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix';
|
|
|
import isIterable from '@ckeditor/ckeditor5-utils/src/isiterable';
|
|
import isIterable from '@ckeditor/ckeditor5-utils/src/isiterable';
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Basic View class.
|
|
|
|
|
|
|
+ * The basic view class, which represents an HTML element created out of a
|
|
|
|
|
+ * {@link module:ui/view~View#template}. Views are building blocks of the user interface and handle
|
|
|
|
|
+ * interaction
|
|
|
|
|
+ *
|
|
|
|
|
+ * Views {@link module:ui/view~View#addChildren aggregate} children in
|
|
|
|
|
+ * {@link module:ui/view~View#createCollection collections} and manage the life cycle of DOM
|
|
|
|
|
+ * listeners e.g. by handling initialization and destruction.
|
|
|
|
|
+ *
|
|
|
|
|
+ * See the {@link module:ui/template~TemplateDefinition} syntax to learn more about shaping view
|
|
|
|
|
+ * elements, attributes and listeners.
|
|
|
*
|
|
*
|
|
|
* class SampleView extends View {
|
|
* class SampleView extends View {
|
|
|
* constructor( locale ) {
|
|
* constructor( locale ) {
|
|
|
* super( locale );
|
|
* super( locale );
|
|
|
*
|
|
*
|
|
|
|
|
+ * const bind = this.bindTemplate;
|
|
|
|
|
+ *
|
|
|
|
|
+ * // Views define their interface (state) using observable attributes.
|
|
|
|
|
+ * this.set( 'elementClass', 'bar' );
|
|
|
|
|
+ *
|
|
|
* this.template = new Template( {
|
|
* this.template = new Template( {
|
|
|
* tag: 'p',
|
|
* tag: 'p',
|
|
|
|
|
+ *
|
|
|
|
|
+ * // The element of the view can be defined with its children.
|
|
|
* children: [
|
|
* children: [
|
|
|
* 'Hello',
|
|
* 'Hello',
|
|
|
* {
|
|
* {
|
|
|
* tag: 'b',
|
|
* tag: 'b',
|
|
|
- * children: [
|
|
|
|
|
- * 'world!'
|
|
|
|
|
- * ]
|
|
|
|
|
|
|
+ * children: [ 'world!' ]
|
|
|
* }
|
|
* }
|
|
|
* ],
|
|
* ],
|
|
|
* attributes: {
|
|
* attributes: {
|
|
|
- * class: 'foo'
|
|
|
|
|
|
|
+ * class: [
|
|
|
|
|
+ * 'foo',
|
|
|
|
|
+ *
|
|
|
|
|
+ * // Observable attributes control the state of the view in DOM.
|
|
|
|
|
+ * bind.to( 'elementClass' )
|
|
|
|
|
+ * ]
|
|
|
|
|
+ * },
|
|
|
|
|
+ * on: {
|
|
|
|
|
+ * // Views listen to DOM events and propagate them.
|
|
|
|
|
+ * click: bind.to( 'clicked' )
|
|
|
* }
|
|
* }
|
|
|
* } );
|
|
* } );
|
|
|
* }
|
|
* }
|
|
@@ -43,22 +66,35 @@ import isIterable from '@ckeditor/ckeditor5-utils/src/isiterable';
|
|
|
*
|
|
*
|
|
|
* const view = new SampleView( locale );
|
|
* const view = new SampleView( locale );
|
|
|
*
|
|
*
|
|
|
|
|
+ * // Each view must be first initialized.
|
|
|
* view.init();
|
|
* view.init();
|
|
|
*
|
|
*
|
|
|
- * // Will append <p class="foo">Hello<b>world</b></p>
|
|
|
|
|
|
|
+ * // Append <p class="foo bar">Hello<b>world</b></p> to the <body>
|
|
|
* document.body.appendChild( view.element );
|
|
* document.body.appendChild( view.element );
|
|
|
*
|
|
*
|
|
|
|
|
+ * // Change the class attribute to <p class="foo baz">Hello<b>world</b></p>
|
|
|
|
|
+ * view.elementClass = 'baz';
|
|
|
|
|
+ *
|
|
|
|
|
+ * // Respond to the "click" event in DOM by executing a custom action.
|
|
|
|
|
+ * view.on( 'clicked', () => {
|
|
|
|
|
+ * console.log( 'The view has been clicked!' );
|
|
|
|
|
+ * } );
|
|
|
|
|
+ *
|
|
|
* @mixes module:utils/observablemixin~ObservableMixin
|
|
* @mixes module:utils/observablemixin~ObservableMixin
|
|
|
*/
|
|
*/
|
|
|
export default class View {
|
|
export default class View {
|
|
|
/**
|
|
/**
|
|
|
* Creates an instance of the {@link module:ui/view~View} class.
|
|
* Creates an instance of the {@link module:ui/view~View} class.
|
|
|
*
|
|
*
|
|
|
- * @param {module:utils/locale~Locale} [locale] The {@link module:core/editor/editor~Editor editor's locale} instance.
|
|
|
|
|
|
|
+ * Also see {@link #init}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {module:utils/locale~Locale} [locale] The localization services instance.
|
|
|
*/
|
|
*/
|
|
|
constructor( locale ) {
|
|
constructor( locale ) {
|
|
|
/**
|
|
/**
|
|
|
- * A set of tools to localize the user interface. See {@link module:core/editor/editor~Editor}.
|
|
|
|
|
|
|
+ * A set of tools to localize the user interface.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Also see {@link module:core/editor/editor~Editor#locale}.
|
|
|
*
|
|
*
|
|
|
* @readonly
|
|
* @readonly
|
|
|
* @member {module:utils/locale~Locale}
|
|
* @member {module:utils/locale~Locale}
|
|
@@ -68,7 +104,8 @@ export default class View {
|
|
|
/**
|
|
/**
|
|
|
* Shorthand for {@link module:utils/locale~Locale#t}.
|
|
* Shorthand for {@link module:utils/locale~Locale#t}.
|
|
|
*
|
|
*
|
|
|
- * Note: If locale instance hasn't been passed to the view this method may not be available.
|
|
|
|
|
|
|
+ * Note: If {@link #locale} instance hasn't been passed to the view this method may not
|
|
|
|
|
+ * be available.
|
|
|
*
|
|
*
|
|
|
* @see module:utils/locale~Locale#t
|
|
* @see module:utils/locale~Locale#t
|
|
|
* @method
|
|
* @method
|
|
@@ -76,7 +113,9 @@ export default class View {
|
|
|
this.t = locale && locale.t;
|
|
this.t = locale && locale.t;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Set `true` after {@link #init}, which can be asynchronous.
|
|
|
|
|
|
|
+ * A flag set `true` after {@link #init initialization}. Because the process can be
|
|
|
|
|
+ * asynchronous, this {@link module:utils/observablemixin~Observable observable} flag allows
|
|
|
|
|
+ * deferring certain actions until it is finished.
|
|
|
*
|
|
*
|
|
|
* @readonly
|
|
* @readonly
|
|
|
* @observable
|
|
* @observable
|
|
@@ -107,21 +146,22 @@ export default class View {
|
|
|
} );
|
|
} );
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Template of this view.
|
|
|
|
|
|
|
+ * Template of this view. It provides the {@link #element} representing
|
|
|
|
|
+ * the view in DOM.
|
|
|
*
|
|
*
|
|
|
* @member {module:ui/template~Template} #template
|
|
* @member {module:ui/template~Template} #template
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Element of this view.
|
|
|
|
|
|
|
+ * An internal, cached element of this view. See {@link #element}.
|
|
|
*
|
|
*
|
|
|
* @private
|
|
* @private
|
|
|
* @member {HTMLElement} #_element
|
|
* @member {HTMLElement} #_element
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Cached {@link module:ui/template~Template} binder object specific for this instance.
|
|
|
|
|
- * See {@link #bindTemplate}.
|
|
|
|
|
|
|
+ * Cached {@link @link module:ui/template~BindChain bind chain} object created by the
|
|
|
|
|
+ * {@link #template}. See {@link #bindTemplate}.
|
|
|
*
|
|
*
|
|
|
* @private
|
|
* @private
|
|
|
* @member {Object} #_bindTemplate
|
|
* @member {Object} #_bindTemplate
|
|
@@ -129,8 +169,31 @@ export default class View {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Element of this view. The element is rendered on first reference
|
|
|
|
|
- * using {@link #template} definition.
|
|
|
|
|
|
|
+ * An HTML element of this view. The element is rendered on first reference
|
|
|
|
|
+ * by the {@link #template}:
|
|
|
|
|
+ *
|
|
|
|
|
+ * class SampleView extends View {
|
|
|
|
|
+ * constructor() {
|
|
|
|
|
+ * super();
|
|
|
|
|
+ *
|
|
|
|
|
+ * // A template instance the #element will be created from.
|
|
|
|
|
+ * this.template = new Template( {
|
|
|
|
|
+ * tag: 'p'
|
|
|
|
|
+ *
|
|
|
|
|
+ * // ...
|
|
|
|
|
+ * } );
|
|
|
|
|
+ * }
|
|
|
|
|
+ * }
|
|
|
|
|
+ *
|
|
|
|
|
+ * const view = new SampleView();
|
|
|
|
|
+ * view.init();
|
|
|
|
|
+ *
|
|
|
|
|
+ * // Renders the #template and appends the output to <body>.
|
|
|
|
|
+ * document.body.appendChild( view.element );
|
|
|
|
|
+ *
|
|
|
|
|
+ * The element of the view can also be assigned directly:
|
|
|
|
|
+ *
|
|
|
|
|
+ * view.element = document.querySelector( '#my-container' );
|
|
|
*
|
|
*
|
|
|
* @type {HTMLElement}
|
|
* @type {HTMLElement}
|
|
|
*/
|
|
*/
|
|
@@ -154,9 +217,45 @@ export default class View {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Shorthand for {@link module:ui/template~Template.bind}, bound to {@link ~View} on the first access.
|
|
|
|
|
|
|
+ * Shorthand for {@link module:ui/template~Template.bind}, a binding
|
|
|
|
|
+ * {@link module:ui/template~BindChain interface} pre–configured for the view instance.
|
|
|
|
|
+ *
|
|
|
|
|
+ * It provides {@link module:ui/template~BindChain#to `to()`} and
|
|
|
|
|
+ * {@link module:ui/template~BindChain#if `if()`} methods that initialize bindings with
|
|
|
|
|
+ * observable attributes and attach DOM listeners.
|
|
|
|
|
+ *
|
|
|
|
|
+ * class SampleView extends View {
|
|
|
|
|
+ * constructor( locale ) {
|
|
|
|
|
+ * super( locale );
|
|
|
*
|
|
*
|
|
|
- * Cached {@link module:ui/template~Template.bind} object is stored in {@link #_bindTemplate}.
|
|
|
|
|
|
|
+ * const bind = this.bindTemplate;
|
|
|
|
|
+ *
|
|
|
|
|
+ * // These {@link module:utils/observablemixin~Observable observable} attributes will control
|
|
|
|
|
+ * // the state of the view in DOM.
|
|
|
|
|
+ * this.set( {
|
|
|
|
|
+ * elementClass: 'foo',
|
|
|
|
|
+ * isEnabled: true
|
|
|
|
|
+ * } );
|
|
|
|
|
+ *
|
|
|
|
|
+ * this.template = new Template( {
|
|
|
|
|
+ * tag: 'p',
|
|
|
|
|
+ *
|
|
|
|
|
+ * attributes: {
|
|
|
|
|
+ * // The class HTML attribute will follow elementClass
|
|
|
|
|
+ * // and isEnabled view attributes.
|
|
|
|
|
+ * class: [
|
|
|
|
|
+ * bind.to( 'elementClass' )
|
|
|
|
|
+ * bind.if( 'isEnabled', 'present-when-enabled' )
|
|
|
|
|
+ * ]
|
|
|
|
|
+ * },
|
|
|
|
|
+ *
|
|
|
|
|
+ * on: {
|
|
|
|
|
+ * // The view will fire the "clicked" event upon clicking <p> in DOM.
|
|
|
|
|
+ * click: bind.to( 'clicked' )
|
|
|
|
|
+ * }
|
|
|
|
|
+ * } );
|
|
|
|
|
+ * }
|
|
|
|
|
+ * }
|
|
|
*
|
|
*
|
|
|
* @method #bindTemplate
|
|
* @method #bindTemplate
|
|
|
*/
|
|
*/
|
|
@@ -169,8 +268,8 @@ export default class View {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Creates a new collection of views, which can be used in this view instance,
|
|
|
|
|
- * e.g. as a member of {@link module:ui/template~TemplateDefinition TemplateDefinition} children.
|
|
|
|
|
|
|
+ * Creates a new collection of views, which can be used as
|
|
|
|
|
+ * {@link module:ui/template~Template#children} of this view.
|
|
|
*
|
|
*
|
|
|
* class SampleView extends View {
|
|
* class SampleView extends View {
|
|
|
* constructor( locale ) {
|
|
* constructor( locale ) {
|
|
@@ -188,16 +287,16 @@ export default class View {
|
|
|
* }
|
|
* }
|
|
|
*
|
|
*
|
|
|
* const view = new SampleView( locale );
|
|
* const view = new SampleView( locale );
|
|
|
- * const anotherView = new AnotherSampleView( locale );
|
|
|
|
|
|
|
+ * const child = new ChildView( locale );
|
|
|
*
|
|
*
|
|
|
* view.init();
|
|
* view.init();
|
|
|
*
|
|
*
|
|
|
- * // Will append <p></p>
|
|
|
|
|
|
|
+ * // It will append <p></p> to the <body>.
|
|
|
* document.body.appendChild( view.element );
|
|
* document.body.appendChild( view.element );
|
|
|
*
|
|
*
|
|
|
- * // `anotherView` becomes a child of the view, which is reflected in DOM
|
|
|
|
|
- * // <p><anotherView#element></p>
|
|
|
|
|
- * view.items.add( anotherView );
|
|
|
|
|
|
|
+ * // From now on the child is nested under its parent, which is also reflected in DOM.
|
|
|
|
|
+ * // <p><child#element></p>
|
|
|
|
|
+ * view.items.add( child );
|
|
|
*
|
|
*
|
|
|
* @returns {module:ui/viewcollection~ViewCollection} A new collection of view instances.
|
|
* @returns {module:ui/viewcollection~ViewCollection} A new collection of view instances.
|
|
|
*/
|
|
*/
|
|
@@ -210,9 +309,11 @@ export default class View {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Registers a new child view under this view instance. Once registered, a child
|
|
|
|
|
- * view is managed by its parent, including initialization ({@link #init})
|
|
|
|
|
- * and destruction ({@link #destroy}).
|
|
|
|
|
|
|
+ * Registers a new child view under the view instance. Once registered, a child
|
|
|
|
|
+ * view is managed by its parent, including {@link #init initization}
|
|
|
|
|
+ * and {@link #destroy destruction}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * To revert this, use {@link #removeChildren}.
|
|
|
*
|
|
*
|
|
|
* class SampleView extends View {
|
|
* class SampleView extends View {
|
|
|
* constructor( locale ) {
|
|
* constructor( locale ) {
|
|
@@ -223,7 +324,7 @@ export default class View {
|
|
|
*
|
|
*
|
|
|
* this.template = new Template( { tag: 'p' } );
|
|
* this.template = new Template( { tag: 'p' } );
|
|
|
*
|
|
*
|
|
|
- * // Register children.
|
|
|
|
|
|
|
+ * // Register the children.
|
|
|
* this.addChildren( [ this.childA, this.childB ] );
|
|
* this.addChildren( [ this.childA, this.childB ] );
|
|
|
* }
|
|
* }
|
|
|
*
|
|
*
|
|
@@ -239,11 +340,11 @@ export default class View {
|
|
|
*
|
|
*
|
|
|
* view.init();
|
|
* view.init();
|
|
|
*
|
|
*
|
|
|
- * // Will append <p><childA#element><b></b><childB#element></p>
|
|
|
|
|
|
|
+ * // Will append <p><childA#element><b></b><childB#element></p>.
|
|
|
* document.body.appendChild( view.element );
|
|
* document.body.appendChild( view.element );
|
|
|
*
|
|
*
|
|
|
- * **Note**: There's no need to add child views if they're used in the
|
|
|
|
|
- * {@link #template} explicitly:
|
|
|
|
|
|
|
+ * **Note**: There's no need to add child views if they're already referenced in the
|
|
|
|
|
+ * {@link #template}:
|
|
|
*
|
|
*
|
|
|
* class SampleView extends View {
|
|
* class SampleView extends View {
|
|
|
* constructor( locale ) {
|
|
* constructor( locale ) {
|
|
@@ -261,7 +362,7 @@ export default class View {
|
|
|
* } );
|
|
* } );
|
|
|
* }
|
|
* }
|
|
|
*
|
|
*
|
|
|
- * ...
|
|
|
|
|
|
|
+ * // ...
|
|
|
* }
|
|
* }
|
|
|
*
|
|
*
|
|
|
* @param {module:ui/view~View|Iterable.<module:ui/view~View>} children Children views to be registered.
|
|
* @param {module:ui/view~View|Iterable.<module:ui/view~View>} children Children views to be registered.
|
|
@@ -276,8 +377,8 @@ export default class View {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* The opposite of {@link #addChildren}. Removes a child view from this view instance.
|
|
* The opposite of {@link #addChildren}. Removes a child view from this view instance.
|
|
|
- * Once removed, the child is no longer managed by its parent, e.g. it can be safely used elsewhere,
|
|
|
|
|
- * becoming a child of another parent view.
|
|
|
|
|
|
|
+ * Once removed, the child is no longer managed by its parent, e.g. it can safely
|
|
|
|
|
+ * become a child of another parent view.
|
|
|
*
|
|
*
|
|
|
* @see #addChildren
|
|
* @see #addChildren
|
|
|
* @param {module:ui/view~View|Iterable.<module:ui/view~View>} children Child views to be removed.
|
|
* @param {module:ui/view~View|Iterable.<module:ui/view~View>} children Child views to be removed.
|
|
@@ -291,7 +392,54 @@ export default class View {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Initializes the view and child views located in {@link #_viewCollections}.
|
|
|
|
|
|
|
+ * Initializes the view and its children added by {@link #addChildren} and residing in collections
|
|
|
|
|
+ * created by the {@link #createCollection} method.
|
|
|
|
|
+ *
|
|
|
|
|
+ * In general, `init()` is the right place to keep the code which refers to the {@link #element}
|
|
|
|
|
+ * and should be executed at the very beginning of the view's life cycle. It is possible to
|
|
|
|
|
+ * {@link module:ui/template~Template.extend} the {@link #template} before the first reference of
|
|
|
|
|
+ * the {@link #element}. To allow an early customization of the view (e.g. by its parent),
|
|
|
|
|
+ * such references should be done in `init()`.
|
|
|
|
|
+ *
|
|
|
|
|
+ * class SampleView extends View {
|
|
|
|
|
+ * constructor() {
|
|
|
|
|
+ * this.template = new Template( {
|
|
|
|
|
+ * // ...
|
|
|
|
|
+ * } );
|
|
|
|
|
+ * },
|
|
|
|
|
+ *
|
|
|
|
|
+ * init() {
|
|
|
|
|
+ * super.init();
|
|
|
|
|
+ *
|
|
|
|
|
+ * function scroll() {
|
|
|
|
|
+ * // A reference to #element would render the #template and make it non-extendable.
|
|
|
|
|
+ * if ( window.scrollY > 0 ) {
|
|
|
|
|
+ * this.element.scrollLeft = 100;
|
|
|
|
|
+ * } else {
|
|
|
|
|
+ * this.element.scrollLeft = 0;
|
|
|
|
|
+ * }
|
|
|
|
|
+ * }
|
|
|
|
|
+ *
|
|
|
|
|
+ * this.listenTo( window, 'scroll', () => {
|
|
|
|
|
+ * scroll();
|
|
|
|
|
+ * } );
|
|
|
|
|
+ * }
|
|
|
|
|
+ * }
|
|
|
|
|
+ *
|
|
|
|
|
+ * const view = new SampleView();
|
|
|
|
|
+ *
|
|
|
|
|
+ * Template.extend( view.template, {
|
|
|
|
|
+ * attributes: {
|
|
|
|
|
+ * class: [
|
|
|
|
|
+ * 'additional-class'
|
|
|
|
|
+ * ]
|
|
|
|
|
+ * }
|
|
|
|
|
+ * } );
|
|
|
|
|
+ *
|
|
|
|
|
+ * // Late initialization allows customization of the view.
|
|
|
|
|
+ * view.init();
|
|
|
|
|
+ *
|
|
|
|
|
+ * Once initialized, the view becomes {@link #ready}.
|
|
|
*/
|
|
*/
|
|
|
init() {
|
|
init() {
|
|
|
if ( this.ready ) {
|
|
if ( this.ready ) {
|
|
@@ -311,7 +459,12 @@ export default class View {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Destroys the view instance and child views located in {@link #_viewCollections}.
|
|
|
|
|
|
|
+ * Recursively destroys the view instance and child views added by {@link #addChildren} and
|
|
|
|
|
+ * residing in collections created by the {@link #createCollection}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Destruction disables all event listeners:
|
|
|
|
|
+ * * created on the view, e.g. `view.on( 'event', () => {} )`,
|
|
|
|
|
+ * * defined in the {@link #template} for DOM events.
|
|
|
*/
|
|
*/
|
|
|
destroy() {
|
|
destroy() {
|
|
|
this.stopListening();
|
|
this.stopListening();
|