diff --git a/src/ballooneditor.js b/src/ballooneditor.js index f52d5b7..146fda8 100644 --- a/src/ballooneditor.js +++ b/src/ballooneditor.js @@ -18,6 +18,7 @@ import DataApiMixin from '@ckeditor/ckeditor5-core/src/editor/utils/dataapimixin import ElementApiMixin from '@ckeditor/ckeditor5-core/src/editor/utils/elementapimixin'; import attachToForm from '@ckeditor/ckeditor5-core/src/editor/utils/attachtoform'; import mix from '@ckeditor/ckeditor5-utils/src/mix'; +import isElement from '@ckeditor/ckeditor5-utils/src/lib/lodash/isElement'; /** * The {@glink builds/guides/overview#balloon-editor balloon editor} implementation (Medium-like editor). @@ -54,14 +55,17 @@ export default class BalloonEditor extends Editor { * {@link module:editor-balloon/ballooneditor~BalloonEditor.create `BalloonEditor.create()`} method instead. * * @protected - * @param {HTMLElement} element The DOM element that will be the source for the created editor - * (on which the editor will be initialized). + * @param {HTMLElement|String} sourceElementOrData The DOM element that will be the source for the created editor + * (on which the editor will be initialized) or initial data for the editor. For more information see + * {@link module:editor-balloon/ballooneditor~BalloonEditor.create `BalloonEditor.create()`}. * @param {module:core/editor/editorconfig~EditorConfig} config The editor configuration. */ - constructor( element, config ) { + constructor( sourceElementOrData, config ) { super( config ); - this.element = element; + if ( isElement( sourceElementOrData ) ) { + this.sourceElement = sourceElementOrData; + } this.config.get( 'plugins' ).push( BalloonToolbar ); this.config.define( 'balloonToolbar', this.config.get( 'toolbar' ) ); @@ -70,11 +74,18 @@ export default class BalloonEditor extends Editor { this.model.document.createRoot(); - this.ui = new BalloonEditorUI( this, new BalloonEditorUIView( this.locale, element ) ); + this.ui = new BalloonEditorUI( this, new BalloonEditorUIView( this.locale, this.sourceElement ) ); attachToForm( this ); } + /** + * @inheritDoc + */ + get element() { + return this.ui.view.editable.element; + } + /** * Destroys the editor instance, releasing all resources used by it. * @@ -90,7 +101,11 @@ export default class BalloonEditor extends Editor { this.ui.destroy(); return super.destroy() - .then( () => setDataInElement( this.element, data ) ); + .then( () => { + if ( this.sourceElement ) { + setDataInElement( this.sourceElement, data ); + } + } ); } /** @@ -127,15 +142,45 @@ export default class BalloonEditor extends Editor { * console.error( err.stack ); * } ); * - * @param {HTMLElement} element The DOM element that will be the source for the created editor - * (on which the editor will be initialized). + * Creating instance when using initial data instead of a DOM element: + * + * import BalloonEditor from '@ckeditor/ckeditor5-editor-balloon/src/ballooneditor'; + * import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials'; + * import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold'; + * import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic'; + * import ... + * + * BalloonEditor + * .create( '

Hello world!

', { + * plugins: [ Essentials, Bold, Italic, ... ], + * toolbar: [ 'bold', 'italic', ... ] + * } ) + * .then( editor => { + * console.log( 'Editor was initialized', editor ); + * + * // Initial data was provided so `editor.element` needs to be added manually to the DOM. + * document.body.appendChild( editor.element ); + * } ) + * .catch( err => { + * console.error( err.stack ); + * } ); + * + * @param {HTMLElement|String} sourceElementOrData The DOM element that will be the source for the created editor + * (on which the editor will be initialized) or initial data for the editor. + * + * If a source element is passed, then its contents will be automatically + * {@link module:editor-classic/ballooneditor~BalloonEditor#setData loaded} to the editor on startup and the element + * itself will be used as the editor's editable element. + * + * If data is provided, then `editor.element` will be created automatically and needs to be added + * to the DOM manually. * @param {module:core/editor/editorconfig~EditorConfig} config The editor configuration. * @returns {Promise} A promise resolved once the editor is ready. * The promise returns the created {@link module:editor-balloon/ballooneditor~BalloonEditor} instance. */ - static create( element, config ) { + static create( sourceElementOrData, config ) { return new Promise( resolve => { - const editor = new this( element, config ); + const editor = new this( sourceElementOrData, config ); resolve( editor.initPlugins() @@ -143,7 +188,13 @@ export default class BalloonEditor extends Editor { editor.ui.init(); editor.fire( 'uiReady' ); } ) - .then( () => editor.data.init( getDataFromElement( element ) ) ) + .then( () => { + const initialData = isElement( sourceElementOrData ) ? + getDataFromElement( sourceElementOrData ) : + sourceElementOrData; + + return editor.data.init( initialData ); + } ) .then( () => { editor.fire( 'dataReady' ); editor.fire( 'ready' ); diff --git a/src/ballooneditoruiview.js b/src/ballooneditoruiview.js index c125bbd..52208c3 100644 --- a/src/ballooneditoruiview.js +++ b/src/ballooneditoruiview.js @@ -20,6 +20,9 @@ export default class BalloonEditorUIView extends EditorUIView { * Creates an instance of the balloon editor UI view. * * @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance. + * @param {HTMLElement} [editableElement] The editable element. If not specified, the + * {@link module:ui/editableui/editableuiview~EditableUIView} + * will create it. Otherwise, the existing element will be used. */ constructor( locale, editableElement ) { super( locale ); diff --git a/tests/ballooneditor.js b/tests/ballooneditor.js index c67526b..7dc0d1b 100644 --- a/tests/ballooneditor.js +++ b/tests/ballooneditor.js @@ -102,6 +102,34 @@ describe( 'BalloonEditor', () => { } ); } ); } ); + + it( 'allows to pass data to the constructor', () => { + return BalloonEditor.create( '

Hello world!

', { + plugins: [ Paragraph ] + } ).then( editor => { + expect( editor.getData() ).to.equal( '

Hello world!

' ); + } ); + } ); + + it( 'should have undefined the #sourceElement if editor was initialized with data', () => { + return BalloonEditor + .create( '

Foo.

', { + plugins: [ Paragraph, Bold ] + } ) + .then( newEditor => { + expect( newEditor.sourceElement ).to.be.undefined; + + return newEditor.destroy(); + } ); + } ); + + it( 'editor.element should contain the whole editor (with UI) element', () => { + return BalloonEditor.create( '

Hello world!

', { + plugins: [ Paragraph ] + } ).then( editor => { + expect( editor.editing.view.getDomRoot() ).to.equal( editor.element ); + } ); + } ); } ); describe( 'create()', () => { @@ -128,7 +156,10 @@ describe( 'BalloonEditor', () => { } ); it( 'attaches editable UI as view\'s DOM root', () => { - expect( editor.editing.view.getDomRoot() ).to.equal( editor.ui.view.editable.element ); + const domRoot = editor.editing.view.getDomRoot(); + + expect( domRoot ).to.equal( editor.element ); + expect( domRoot ).to.equal( editor.ui.view.editable.element ); } ); it( 'creates the UI using BalloonEditorUI classes', () => { @@ -276,5 +307,13 @@ describe( 'BalloonEditor', () => { .to.equal( '

a

b' ); } ); } ); + + it( 'should not throw an error if editor was initialized with the data', () => { + return BalloonEditor + .create( '

Foo.

', { + plugins: [ Paragraph, Bold ] + } ) + .then( newEditor => newEditor.destroy() ); + } ); } ); } ); diff --git a/tests/manual/ballooneditor-data.html b/tests/manual/ballooneditor-data.html new file mode 100644 index 0000000..cc32d05 --- /dev/null +++ b/tests/manual/ballooneditor-data.html @@ -0,0 +1,19 @@ +

+ + +

+ +
+ + diff --git a/tests/manual/ballooneditor-data.js b/tests/manual/ballooneditor-data.js new file mode 100644 index 0000000..74b5d56 --- /dev/null +++ b/tests/manual/ballooneditor-data.js @@ -0,0 +1,43 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals console:false, document, window */ + +import BalloonEditor from '../../src/ballooneditor'; +import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset'; + +window.editors = []; +const container = document.querySelector( '.container' ); +let counter = 1; + +function initEditor() { + BalloonEditor + .create( `

Editor ${ counter }

This is an editor instance.

`, { + plugins: [ ArticlePluginSet ], + toolbar: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'undo', 'redo' ] + } ) + .then( editor => { + counter += 1; + window.editors.push( editor ); + container.appendChild( editor.element ); + } ) + .catch( err => { + console.error( err.stack ); + } ); +} + +function destroyEditors() { + window.editors.forEach( editor => { + editor.destroy() + .then( () => { + editor.element.remove(); + } ); + } ); + window.editors = []; + counter = 1; +} + +document.getElementById( 'initEditor' ).addEventListener( 'click', initEditor ); +document.getElementById( 'destroyEditors' ).addEventListener( 'click', destroyEditors ); diff --git a/tests/manual/ballooneditor-data.md b/tests/manual/ballooneditor-data.md new file mode 100644 index 0000000..1effe92 --- /dev/null +++ b/tests/manual/ballooneditor-data.md @@ -0,0 +1,3 @@ +1. Click "Init editor". +2. New editor instance should be appended to the document with initial data in it. You can create more than one editor. +3. After clicking "Destroy editors" all editors should be removed from the document.