This repository has been archived by the owner on Jun 26, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #533 from ckeditor/context
Feature: Reintroduced the concept of body collections with a focus on better management of multiple editors and support for context plugins (plugins which leave outside an editor instance). Closes ckeditor/ckeditor5#5888.
- Loading branch information
Showing
6 changed files
with
289 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
|
||
/** | ||
* @module ui/editorui/bodycollection | ||
*/ | ||
|
||
/* globals document */ | ||
|
||
import Template from '../template'; | ||
import ViewCollection from '../viewcollection'; | ||
|
||
import createElement from '@ckeditor/ckeditor5-utils/src/dom/createelement'; | ||
|
||
/** | ||
* This is a special {@link module:ui/viewcollection~ViewCollection} dedicated to elements that are detached | ||
* from the DOM structure of the editor, like panels, icons, etc. | ||
* | ||
* The body collection is available in 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. | ||
* Those views will render in a container placed directly in the `<body>` element. | ||
* The editor will detach and destroy this collection when the editor will be {@link module:core/editor/editor~Editor#destroy destroyed}. | ||
* | ||
* If you need to control the life cycle of the body collection on your own, you can create your own instance of this class. | ||
* | ||
* A body collection will render itself automatically in the DOM body element as soon as you call {@link ~BodyCollection#attachToDom}. | ||
* If you create multiple body collections this class will create a special wrapper element in the DOM to limit the number of | ||
* elements created directly in the body and remove it when the last body collection will be | ||
* {@link ~BodyCollection#detachFromDom detached}. | ||
* | ||
* @extends module:ui/viewcollection~ViewCollection | ||
*/ | ||
export default class BodyCollection extends ViewCollection { | ||
/** | ||
* Attaches the body collection to the DOM body element. You need to execute this method to render the content of | ||
* the body collection. | ||
*/ | ||
attachToDom() { | ||
/** | ||
* The element holding elements of the body region. | ||
* | ||
* @protected | ||
* @member {HTMLElement} #_bodyCollectionContainer | ||
*/ | ||
this._bodyCollectionContainer = new Template( { | ||
tag: 'div', | ||
attributes: { | ||
class: [ | ||
'ck', | ||
'ck-reset_all', | ||
'ck-body', | ||
'ck-rounded-corners' | ||
], | ||
dir: this.locale.uiLanguageDirection, | ||
}, | ||
children: this | ||
} ).render(); | ||
|
||
let wrapper = document.querySelector( '.ck-body-wrapper' ); | ||
|
||
if ( !wrapper ) { | ||
wrapper = createElement( document, 'div', { class: 'ck-body-wrapper' } ); | ||
document.body.appendChild( wrapper ); | ||
} | ||
|
||
wrapper.appendChild( this._bodyCollectionContainer ); | ||
} | ||
|
||
/** | ||
* Detach 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(); | ||
} | ||
|
||
const wrapper = document.querySelector( '.ck-body-wrapper' ); | ||
|
||
if ( wrapper && wrapper.childElementCount == 0 ) { | ||
wrapper.remove(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
|
||
/* global document */ | ||
|
||
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; | ||
import Locale from '@ckeditor/ckeditor5-utils/src/locale'; | ||
|
||
import BodyCollection from '../../src/editorui/bodycollection'; | ||
import View from '../../src/view'; | ||
|
||
describe( 'BodyCollection', () => { | ||
let locale; | ||
|
||
testUtils.createSinonSandbox(); | ||
|
||
beforeEach( () => { | ||
locale = new Locale(); | ||
} ); | ||
|
||
afterEach( () => { | ||
const wrappers = Array.from( document.querySelectorAll( '.ck-body-wrapper' ) ); | ||
|
||
for ( const wrapper of wrappers ) { | ||
wrapper.remove(); | ||
} | ||
} ); | ||
|
||
describe( 'attachToDom', () => { | ||
it( 'should create wrapper and put the collection in that wrapper', () => { | ||
const body = new BodyCollection( locale ); | ||
|
||
body.attachToDom(); | ||
|
||
const wrappers = Array.from( document.querySelectorAll( '.ck-body-wrapper' ) ); | ||
|
||
expect( wrappers.length ).to.equal( 1 ); | ||
expect( wrappers[ 0 ].parentNode ).to.equal( document.body ); | ||
|
||
const el = body._bodyCollectionContainer; | ||
|
||
expect( el.parentNode ).to.equal( wrappers[ 0 ] ); | ||
expect( el.classList.contains( 'ck' ) ).to.be.true; | ||
expect( el.classList.contains( 'ck-body' ) ).to.be.true; | ||
expect( el.classList.contains( 'ck-rounded-corners' ) ).to.be.true; | ||
expect( el.classList.contains( 'ck-reset_all' ) ).to.be.true; | ||
} ); | ||
|
||
it( 'sets the right dir attribute to the body region (LTR)', () => { | ||
const body = new BodyCollection( locale ); | ||
|
||
body.attachToDom(); | ||
|
||
const el = body._bodyCollectionContainer; | ||
|
||
expect( el.getAttribute( 'dir' ) ).to.equal( 'ltr' ); | ||
} ); | ||
|
||
it( 'sets the right dir attribute to the body region (RTL)', () => { | ||
const locale = new Locale( { uiLanguage: 'ar' } ); | ||
const body = new BodyCollection( locale ); | ||
|
||
body.attachToDom(); | ||
|
||
const el = body._bodyCollectionContainer; | ||
|
||
expect( el.getAttribute( 'dir' ) ).to.equal( 'rtl' ); | ||
} ); | ||
|
||
it( 'should put all body elements to the same wrapper', () => { | ||
const body1 = new BodyCollection( locale ); | ||
body1.attachToDom(); | ||
|
||
expect( document.querySelectorAll( '.ck-body-wrapper' ).length ).to.equal( 1 ); | ||
expect( document.querySelectorAll( '.ck-body' ).length ).to.equal( 1 ); | ||
|
||
const body2 = new BodyCollection( locale ); | ||
body2.attachToDom(); | ||
|
||
const bodyElements = document.querySelectorAll( '.ck-body' ); | ||
|
||
expect( document.querySelectorAll( '.ck-body-wrapper' ).length ).to.equal( 1 ); | ||
expect( bodyElements.length ).to.equal( 2 ); | ||
expect( bodyElements[ 0 ].parentNode ).to.equal( bodyElements[ 1 ].parentNode ); | ||
} ); | ||
|
||
it( 'should render views in proper body collections', () => { | ||
const body1 = new BodyCollection( locale ); | ||
|
||
const view1 = new View(); | ||
view1.setTemplate( { | ||
tag: 'div', | ||
attributes: { | ||
class: [ 'foo' ] | ||
} | ||
} ); | ||
|
||
// Should work if body is attached before the view is added... | ||
body1.attachToDom(); | ||
body1.add( view1 ); | ||
|
||
const body2 = new BodyCollection( locale ); | ||
|
||
const view2 = new View(); | ||
view2.setTemplate( { | ||
tag: 'div', | ||
attributes: { | ||
class: [ 'bar' ] | ||
} | ||
} ); | ||
|
||
// ...and it should work if body is attached after the view is added. | ||
body2.add( view2 ); | ||
body2.attachToDom(); | ||
|
||
const wrappers = Array.from( document.querySelectorAll( '.ck-body-wrapper' ) ); | ||
|
||
expect( wrappers.length ).to.equal( 1 ); | ||
|
||
const wrapper = wrappers[ 0 ]; | ||
const body1Element = body1._bodyCollectionContainer; | ||
const body2Element = body2._bodyCollectionContainer; | ||
|
||
expect( body1Element.parentNode ).to.equal( wrapper ); | ||
expect( body1Element.childNodes.length ).to.equal( 1 ); | ||
expect( body1Element.childNodes[ 0 ].classList.contains( 'foo' ) ).to.be.true; | ||
|
||
expect( body2Element.parentNode ).to.equal( wrapper ); | ||
expect( body2Element.childNodes.length ).to.equal( 1 ); | ||
expect( body2Element.childNodes[ 0 ].classList.contains( 'bar' ) ).to.be.true; | ||
} ); | ||
} ); | ||
|
||
describe( 'detachFromDom', () => { | ||
it( 'removes the body collection from DOM', () => { | ||
const body = new BodyCollection( locale ); | ||
|
||
body.attachToDom(); | ||
body.detachFromDom(); | ||
|
||
expect( document.querySelectorAll( '.ck-body-wrapper' ).length ).to.equal( 0 ); | ||
expect( document.querySelectorAll( '.ck-body' ).length ).to.equal( 0 ); | ||
} ); | ||
|
||
it( 'removes the multiple body collections from dom and remove the wrapper when the last is removed', () => { | ||
const body1 = new BodyCollection( locale ); | ||
body1.attachToDom(); | ||
|
||
const body2 = new BodyCollection( locale ); | ||
body2.attachToDom(); | ||
|
||
expect( document.querySelectorAll( '.ck-body-wrapper' ).length ).to.equal( 1 ); | ||
expect( document.querySelectorAll( '.ck-body' ).length ).to.equal( 2 ); | ||
|
||
body1.detachFromDom(); | ||
|
||
expect( document.querySelectorAll( '.ck-body-wrapper' ).length ).to.equal( 1 ); | ||
expect( document.querySelectorAll( '.ck-body' ).length ).to.equal( 1 ); | ||
|
||
body2.detachFromDom(); | ||
|
||
expect( document.querySelectorAll( '.ck-body-wrapper' ).length ).to.equal( 0 ); | ||
expect( document.querySelectorAll( '.ck-body' ).length ).to.equal( 0 ); | ||
} ); | ||
|
||
it( 'should not throw when be called multiple times', () => { | ||
const body = new BodyCollection( locale ); | ||
body.attachToDom(); | ||
|
||
expect( () => { | ||
body.detachFromDom(); | ||
body.detachFromDom(); | ||
} ).to.not.throw(); | ||
} ); | ||
|
||
it( 'should not throw if attachToDom was not called before', () => { | ||
const body = new BodyCollection( locale ); | ||
|
||
expect( () => { | ||
body.detachFromDom(); | ||
} ).to.not.throw(); | ||
} ); | ||
} ); | ||
} ); |
Oops, something went wrong.