UNPKG

@ckeditor/ckeditor5-ui

Version:

The UI framework and standard UI library of CKEditor 5.

423 lines (422 loc) • 13.2 kB
/** * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ /** * @module ui/view */ import ViewCollection from './viewcollection.js'; import Template, { type BindChain, type TemplateDefinition } from './template.js'; import { Collection, type DecoratedMethodEvent, type Locale, type LocaleTranslate } from '@ckeditor/ckeditor5-utils'; import '../theme/globals/globals.css'; declare const View_base: import("@ckeditor/ckeditor5-utils").Mixed<{ new (): import("@ckeditor/ckeditor5-utils").Observable; prototype: import("@ckeditor/ckeditor5-utils").Observable; }, import("@ckeditor/ckeditor5-utils").DomEmitter>; /** * 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#registerChild aggregate} children in * {@link module:ui/view~View#createCollection collections} and manage the life cycle of DOM * listeners e.g. by handling rendering and destruction. * * See the {@link module:ui/template~TemplateDefinition} syntax to learn more about shaping view * elements, attributes and listeners. * * ```ts * class SampleView extends View { * constructor( locale ) { * super( locale ); * * const bind = this.bindTemplate; * * // Views define their interface (state) using observable attributes. * this.set( 'elementClass', 'bar' ); * * this.setTemplate( { * tag: 'p', * * // The element of the view can be defined with its children. * children: [ * 'Hello', * { * tag: 'b', * children: [ 'world!' ] * } * ], * attributes: { * 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' ) * } * } ); * } * } * * const view = new SampleView( locale ); * * view.render(); * * // Append <p class="foo bar">Hello<b>world</b></p> to the <body> * 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!' ); * } ); * ``` */ export default class View<TElement extends HTMLElement = HTMLElement> extends /* #__PURE__ */ View_base { /** * An HTML element of the view. `null` until {@link #render rendered} * from the {@link #template}. * * ```ts * class SampleView extends View { * constructor() { * super(); * * // A template instance the #element will be created from. * this.setTemplate( { * tag: 'p' * * // ... * } ); * } * } * * const view = new SampleView(); * * // Renders the #template. * view.render(); * * // Append the HTML element of the view to <body>. * document.body.appendChild( view.element ); * ``` * * **Note**: The element of the view can also be assigned directly: * * ```ts * view.element = document.querySelector( '#my-container' ); * ``` */ element: TElement | null; /** * Set `true` when the view has already been {@link module:ui/view~View#render rendered}. * * @readonly */ isRendered: boolean; /** * A set of tools to localize the user interface. * * Also see {@link module:core/editor/editor~Editor#locale}. * * @readonly */ locale: Locale | undefined; /** * Shorthand for {@link module:utils/locale~Locale#t}. * * Note: If {@link #locale} instance hasn't been passed to the view this method may not * be available. * * @see module:utils/locale~Locale#t */ t: LocaleTranslate | undefined; /** * Template of this view. It provides the {@link #element} representing * the view in DOM, which is {@link #render rendered}. */ template?: Template; viewUid?: string; /** * Collections registered with {@link #createCollection}. */ protected _viewCollections: Collection<ViewCollection>; /** * A collection of view instances, which have been added directly * into the {@link module:ui/template~Template#children}. */ protected _unboundChildren: ViewCollection; /** * Cached {@link module:ui/template~BindChain bind chain} object created by the * {@link #template}. See {@link #bindTemplate}. */ private _bindTemplate?; /** * Creates an instance of the {@link module:ui/view~View} class. * * Also see {@link #render}. * * @param locale The localization services instance. */ constructor(locale?: Locale); /** * 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. * * ```ts * class SampleView extends View { * constructor( locale ) { * super( locale ); * * 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.setTemplate( { * 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' ) * } * } ); * } * } * ``` */ get bindTemplate(): BindChain<this>; /** * Creates a new collection of views, which can be used as * {@link module:ui/template~Template#children} of this view. * * ```ts * class SampleView extends View { * constructor( locale ) { * super( locale ); * * const child = new ChildView( locale ); * this.items = this.createCollection( [ child ] ); * * this.setTemplate( { * tag: 'p', * * // `items` collection will render here. * children: this.items * } ); * } * } * * const view = new SampleView( locale ); * view.render(); * * // It will append <p><child#element></p> to the <body>. * document.body.appendChild( view.element ); * ``` * * @param views Initial views of the collection. * @returns A new collection of view instances. */ createCollection<T extends View = View>(views?: Iterable<T>): ViewCollection<T>; /** * Registers a new child view under the view instance. Once registered, a child * view is managed by its parent, including {@link #render rendering} * and {@link #destroy destruction}. * * To revert this, use {@link #deregisterChild}. * * ```ts * class SampleView extends View { * constructor( locale ) { * super( locale ); * * this.childA = new SomeChildView( locale ); * this.childB = new SomeChildView( locale ); * * this.setTemplate( { tag: 'p' } ); * * // Register the children. * this.registerChild( [ this.childA, this.childB ] ); * } * * render() { * super.render(); * * this.element.appendChild( this.childA.element ); * this.element.appendChild( this.childB.element ); * } * } * * const view = new SampleView( locale ); * * view.render(); * * // Will append <p><childA#element><b></b><childB#element></p>. * document.body.appendChild( view.element ); * ``` * * **Note**: There's no need to add child views if they're already referenced in the * {@link #template}: * * ```ts * class SampleView extends View { * constructor( locale ) { * super( locale ); * * this.childA = new SomeChildView( locale ); * this.childB = new SomeChildView( locale ); * * this.setTemplate( { * tag: 'p', * * // These children will be added automatically. There's no * // need to call {@link #registerChild} for any of them. * children: [ this.childA, this.childB ] * } ); * } * * // ... * } * ``` * * @param children Children views to be registered. */ registerChild(children: View | Iterable<View>): void; /** * The opposite of {@link #registerChild}. Removes a child view from this view instance. * Once removed, the child is no longer managed by its parent, e.g. it can safely * become a child of another parent view. * * @see #registerChild * @param children Child views to be removed. */ deregisterChild(children: View | Iterable<View>): void; /** * Sets the {@link #template} of the view with with given definition. * * A shorthand for: * * ```ts * view.setTemplate( definition ); * ``` * * @param definition Definition of view's template. */ setTemplate(definition: TemplateDefinition): void; /** * {@link module:ui/template~Template.extend Extends} the {@link #template} of the view with * with given definition. * * A shorthand for: * * ```ts * Template.extend( view.template, definition ); * ``` * * **Note**: Is requires the {@link #template} to be already set. See {@link #setTemplate}. * * @param definition Definition which extends the {@link #template}. */ extendTemplate(definition: Partial<TemplateDefinition>): void; /** * Recursively renders the view. * * Once the view is rendered: * * the {@link #element} becomes an HTML element out of {@link #template}, * * the {@link #isRendered} flag is set `true`. * * **Note**: The children of the view: * * defined directly in the {@link #template} * * residing in collections created by the {@link #createCollection} method, * * and added by {@link #registerChild} * are also rendered in the process. * * In general, `render()` method 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 view is rendered. To allow an early customization of the view (e.g. by its parent), * such references should be done in `render()`. * * ```ts * class SampleView extends View { * constructor() { * this.setTemplate( { * // ... * } ); * }, * * render() { * // View#element becomes available. * super.render(); * * // The "scroll" listener depends on #element. * this.listenTo( window, '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; * } * } ); * } * } * * const view = new SampleView(); * * // Let's customize the view before it gets rendered. * view.extendTemplate( { * attributes: { * class: [ * 'additional-class' * ] * } * } ); * * // Late rendering allows customization of the view. * view.render(); * ``` */ render(): void; /** * Recursively destroys the view instance and child views added by {@link #registerChild} 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(): void; } /** * Event fired by the {@link module:ui/view~View#render} method. Actual rendering is executed as a listener to * this event with the default priority. * * See {@link module:utils/observablemixin~Observable#decorate} for more information and samples. * * @eventName ~View#render */ export type UIViewRenderEvent = DecoratedMethodEvent<View, 'render'>; export {};