Skip to content

Commit ce735cc

Browse files
authored
Merge pull request #17321 from bendemboski/optimize-body-wrapper-lookup
Other (ui): Improved the performance of the `BodyCollection` DOM wrapper lookup by replacing `document.querySelector()` with a static element reference. Huge thanks to [Ben Demboski](https://github.com/bendemboski) for this contribution!
2 parents 7f3f02e + d79ed9b commit ce735cc

File tree

2 files changed

+58
-22
lines changed

2 files changed

+58
-22
lines changed

packages/ckeditor5-ui/src/editorui/bodycollection.ts

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,51 @@ import type View from '../view.js';
1616
import { createElement, type Locale } from '@ckeditor/ckeditor5-utils';
1717

1818
/**
19-
* This is a special {@link module:ui/viewcollection~ViewCollection} dedicated to elements that are detached
20-
* from the DOM structure of the editor, like panels, icons, etc.
19+
* This is a special {@link module:ui/viewcollection~ViewCollection} dedicated to elements that are detached from the DOM structure of
20+
* the editor, like floating panels, floating toolbars, dialogs, etc.
2121
*
22-
* The body collection is available in the {@link module:ui/editorui/editoruiview~EditorUIView#body `editor.ui.view.body`} property.
22+
* The body collection is available under the {@link module:ui/editorui/editoruiview~EditorUIView#body `editor.ui.view.body`} property.
2323
* Any plugin can add a {@link module:ui/view~View view} to this collection.
24-
* These views will render in a container placed directly in the `<body>` element.
25-
* The editor will detach and destroy this collection when the editor will be {@link module:core/editor/editor~Editor#destroy destroyed}.
2624
*
27-
* If you need to control the life cycle of the body collection on your own, you can create your own instance of this class.
25+
* All views added to a body collection render in a dedicated DOM container (`<div class="ck ck-body ...">...</div>`). All body collection
26+
* containers render in a common shared (`<div class="ck-body-wrapper">...</div>`) in the DOM to limit the pollution of
27+
* the `<body>` element. The resulting DOM structure is as follows:
2828
*
29-
* A body collection will render itself automatically in the DOM body element as soon as you call {@link ~BodyCollection#attachToDom}.
30-
* If you create multiple body collections, this class will create a special wrapper element in the DOM to limit the number of
31-
* elements created directly in the body and remove it when the last body collection will be
32-
* {@link ~BodyCollection#detachFromDom detached}.
29+
* ```html
30+
* <body>
31+
* <!-- Content of the webpage... -->
32+
*
33+
* <!-- The shared wrapper for all body collection containers. -->
34+
* <div class="ck-body-wrapper">
35+
* <!-- The container of the first body collection instance. -->
36+
* <div class="ck ck-body ...">
37+
* <!-- View elements belonging to the first body collection -->
38+
* </div>
39+
*
40+
* <!-- The container of the second body collection instance. -->
41+
* <div class="ck ck-body ...">...</div>
42+
*
43+
* <!-- More body collection containers for the rest of instances... -->
44+
* </div>
45+
* </body>
46+
* ```
47+
*
48+
* By default, the {@link module:ui/editorui/editoruiview~EditorUIView `editor.ui.view`} manages the life cycle of the
49+
* {@link module:ui/editorui/editoruiview~EditorUIView#body `editor.ui.view.body`} collection, attaching and detaching it
50+
* when the editor gets created or {@link module:core/editor/editor~Editor#destroy destroyed}.
51+
*
52+
* # Custom body collection instances
53+
*
54+
* Even though most editor instances come with a built-in body collection
55+
* ({@link module:ui/editorui/editoruiview~EditorUIView#body `editor.ui.view.body`}), you can create your own instance of this
56+
* class if you need to control their life cycle.
57+
*
58+
* The life cycle of a custom body collection must be handled manually by the developer using the dedicated API:
59+
* * A body collection will render itself automatically in the DOM as soon as you call {@link ~BodyCollection#attachToDom}.
60+
* * Calling {@link ~BodyCollection#detachFromDom} will remove the collection from the DOM.
61+
*
62+
* **Note**: The shared collection wrapper (`<div class="ck-body-wrapper">...</div>`) gets automatically removed from DOM when the
63+
* last body collection is {@link ~BodyCollection#detachFromDom detached} and does not require any special handling.
3364
*/
3465
export default class BodyCollection extends ViewCollection {
3566
/**
@@ -39,10 +70,15 @@ export default class BodyCollection extends ViewCollection {
3970
public readonly locale: Locale;
4071

4172
/**
42-
* The element holding elements of the body region.
73+
* The element holding elements of the body collection.
4374
*/
4475
private _bodyCollectionContainer?: HTMLElement;
4576

77+
/**
78+
* The wrapper element that holds all of the {@link #_bodyCollectionContainer} elements.
79+
*/
80+
private static _bodyWrapper?: HTMLElement;
81+
4682
/**
4783
* Creates a new instance of the {@link module:ui/editorui/bodycollection~BodyCollection}.
4884
*
@@ -56,7 +92,7 @@ export default class BodyCollection extends ViewCollection {
5692
}
5793

5894
/**
59-
* The element holding elements of the body region.
95+
* The element holding elements of the body collection.
6096
*/
6197
public get bodyCollectionContainer(): HTMLElement | undefined {
6298
return this._bodyCollectionContainer;
@@ -82,14 +118,12 @@ export default class BodyCollection extends ViewCollection {
82118
children: this
83119
} ).render() as HTMLElement;
84120

85-
let wrapper = document.querySelector( '.ck-body-wrapper' );
86-
87-
if ( !wrapper ) {
88-
wrapper = createElement( document, 'div', { class: 'ck-body-wrapper' } );
89-
document.body.appendChild( wrapper );
121+
if ( !BodyCollection._bodyWrapper ) {
122+
BodyCollection._bodyWrapper = createElement( document, 'div', { class: 'ck-body-wrapper' } );
123+
document.body.appendChild( BodyCollection._bodyWrapper );
90124
}
91125

92-
wrapper.appendChild( this._bodyCollectionContainer );
126+
BodyCollection._bodyWrapper.appendChild( this._bodyCollectionContainer );
93127
}
94128

95129
/**
@@ -103,10 +137,9 @@ export default class BodyCollection extends ViewCollection {
103137
this._bodyCollectionContainer.remove();
104138
}
105139

106-
const wrapper = document.querySelector( '.ck-body-wrapper' );
107-
108-
if ( wrapper && wrapper.childElementCount == 0 ) {
109-
wrapper.remove();
140+
if ( BodyCollection._bodyWrapper && !BodyCollection._bodyWrapper.childElementCount ) {
141+
BodyCollection._bodyWrapper.remove();
142+
delete BodyCollection._bodyWrapper;
110143
}
111144
}
112145
}

packages/ckeditor5-ui/tests/editorui/bodycollection.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe( 'BodyCollection', () => {
2626
for ( const wrapper of wrappers ) {
2727
wrapper.remove();
2828
}
29+
delete BodyCollection._bodyWrapper;
2930
} );
3031

3132
describe( 'constructor', () => {
@@ -55,6 +56,7 @@ describe( 'BodyCollection', () => {
5556

5657
expect( wrappers.length ).to.equal( 1 );
5758
expect( wrappers[ 0 ].parentNode ).to.equal( document.body );
59+
expect( BodyCollection._bodyWrapper ).to.equal( wrappers[ 0 ] );
5860

5961
const el = body.bodyCollectionContainer;
6062

@@ -111,6 +113,7 @@ describe( 'BodyCollection', () => {
111113
expect( document.querySelectorAll( '.ck-body-wrapper' ).length ).to.equal( 1 );
112114
expect( bodyElements.length ).to.equal( 2 );
113115
expect( bodyElements[ 0 ].parentNode ).to.equal( bodyElements[ 1 ].parentNode );
116+
expect( BodyCollection._bodyWrapper ).to.equal( bodyElements[ 0 ].parentNode );
114117
} );
115118

116119
it( 'should render views in proper body collections', () => {

0 commit comments

Comments
 (0)