@ckeditor/ckeditor5-ui
Version:
The UI framework and standard UI library of CKEditor 5.
130 lines (129 loc) • 5.6 kB
JavaScript
/**
* @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/editorui/bodycollection
*/
/* globals document */
import Template from '../template.js';
import ViewCollection from '../viewcollection.js';
import { createElement } from '@ckeditor/ckeditor5-utils';
/**
* This is a special {@link module:ui/viewcollection~ViewCollection} dedicated to elements that are detached from the DOM structure of
* the editor, like floating panels, floating toolbars, dialogs, etc.
*
* The body collection is available under the {@link module:ui/editorui/editoruiview~EditorUIView#body `editor.ui.view.body`} property.
* Any plugin can add a {@link module:ui/view~View view} to this collection.
*
* All views added to a body collection render in a dedicated DOM container (`<div class="ck ck-body ...">...</div>`). All body collection
* containers render in a common shared (`<div class="ck-body-wrapper">...</div>`) in the DOM to limit the pollution of
* the `<body>` element. The resulting DOM structure is as follows:
*
* ```html
* <body>
* <!-- Content of the webpage... -->
*
* <!-- The shared wrapper for all body collection containers. -->
* <div class="ck-body-wrapper">
* <!-- The container of the first body collection instance. -->
* <div class="ck ck-body ...">
* <!-- View elements belonging to the first body collection -->
* </div>
*
* <!-- The container of the second body collection instance. -->
* <div class="ck ck-body ...">...</div>
*
* <!-- More body collection containers for the rest of instances... -->
* </div>
* </body>
* ```
*
* By default, the {@link module:ui/editorui/editoruiview~EditorUIView `editor.ui.view`} manages the life cycle of the
* {@link module:ui/editorui/editoruiview~EditorUIView#body `editor.ui.view.body`} collection, attaching and detaching it
* when the editor gets created or {@link module:core/editor/editor~Editor#destroy destroyed}.
*
* # Custom body collection instances
*
* Even though most editor instances come with a built-in body collection
* ({@link module:ui/editorui/editoruiview~EditorUIView#body `editor.ui.view.body`}), you can create your own instance of this
* class if you need to control their life cycle.
*
* The life cycle of a custom body collection must be handled manually by the developer using the dedicated API:
* * A body collection will render itself automatically in the DOM as soon as you call {@link ~BodyCollection#attachToDom}.
* * Calling {@link ~BodyCollection#detachFromDom} will remove the collection from the DOM.
*
* **Note**: The shared collection wrapper (`<div class="ck-body-wrapper">...</div>`) gets automatically removed from DOM when the
* last body collection is {@link ~BodyCollection#detachFromDom detached} and does not require any special handling.
*/
export default class BodyCollection extends ViewCollection {
/**
* The {@link module:core/editor/editor~Editor#locale editor's locale} instance.
* See the view {@link module:ui/view~View#locale locale} property.
*/
locale;
/**
* The element holding elements of the body collection.
*/
_bodyCollectionContainer;
/**
* The wrapper element that holds all of the {@link #_bodyCollectionContainer} elements.
*/
static _bodyWrapper;
/**
* Creates a new instance of the {@link module:ui/editorui/bodycollection~BodyCollection}.
*
* @param locale The {@link module:core/editor/editor~Editor editor's locale} instance.
* @param initialItems The initial items of the collection.
*/
constructor(locale, initialItems = []) {
super(initialItems);
this.locale = locale;
}
/**
* The element holding elements of the body collection.
*/
get bodyCollectionContainer() {
return this._bodyCollectionContainer;
}
/**
* Attaches the body collection to the DOM body element. You need to execute this method to render the content of
* the body collection.
*/
attachToDom() {
this._bodyCollectionContainer = new Template({
tag: 'div',
attributes: {
class: [
'ck',
'ck-reset_all',
'ck-body',
'ck-rounded-corners'
],
dir: this.locale.uiLanguageDirection,
role: 'application'
},
children: this
}).render();
// Create a shared wrapper if there were none or the previous one got disconnected from DOM.
if (!BodyCollection._bodyWrapper || !BodyCollection._bodyWrapper.isConnected) {
BodyCollection._bodyWrapper = createElement(document, 'div', { class: 'ck-body-wrapper' });
document.body.appendChild(BodyCollection._bodyWrapper);
}
BodyCollection._bodyWrapper.appendChild(this._bodyCollectionContainer);
}
/**
* Detaches the collection from the DOM structure. Use this method when you do not need to use the body collection
* anymore to clean-up the DOM structure.
*/
detachFromDom() {
super.destroy();
if (this._bodyCollectionContainer) {
this._bodyCollectionContainer.remove();
}
if (BodyCollection._bodyWrapper && !BodyCollection._bodyWrapper.childElementCount) {
BodyCollection._bodyWrapper.remove();
delete BodyCollection._bodyWrapper;
}
}
}