Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #19 from ckeditor/t/18
Browse files Browse the repository at this point in the history
Feature: Editor can now be created with initial data passed to the `create()` method. Closes #18.

BREAKING CHANGE: The `BalloonEditor#element` property was renamed to `BalloonEditor#sourceElement`
  • Loading branch information
Reinmar authored Jul 3, 2018
2 parents 03af1c0 + 14009af commit 48c265c
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 12 deletions.
73 changes: 62 additions & 11 deletions src/ballooneditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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' ) );
Expand All @@ -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.
*
Expand All @@ -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 );
}
} );
}

/**
Expand Down Expand Up @@ -127,23 +142,59 @@ 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( '<p>Hello world!</p>', {
* 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()
.then( () => {
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' );
Expand Down
3 changes: 3 additions & 0 deletions src/ballooneditoruiview.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
41 changes: 40 additions & 1 deletion tests/ballooneditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,34 @@ describe( 'BalloonEditor', () => {
} );
} );
} );

it( 'allows to pass data to the constructor', () => {
return BalloonEditor.create( '<p>Hello world!</p>', {
plugins: [ Paragraph ]
} ).then( editor => {
expect( editor.getData() ).to.equal( '<p>Hello world!</p>' );
} );
} );

it( 'should have undefined the #sourceElement if editor was initialized with data', () => {
return BalloonEditor
.create( '<p>Foo.</p>', {
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( '<p>Hello world!</p>', {
plugins: [ Paragraph ]
} ).then( editor => {
expect( editor.editing.view.getDomRoot() ).to.equal( editor.element );
} );
} );
} );

describe( 'create()', () => {
Expand All @@ -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', () => {
Expand Down Expand Up @@ -276,5 +307,13 @@ describe( 'BalloonEditor', () => {
.to.equal( '<p>a</p><heading>b</heading>' );
} );
} );

it( 'should not throw an error if editor was initialized with the data', () => {
return BalloonEditor
.create( '<p>Foo.</p>', {
plugins: [ Paragraph, Bold ]
} )
.then( newEditor => newEditor.destroy() );
} );
} );
} );
19 changes: 19 additions & 0 deletions tests/manual/ballooneditor-data.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<p>
<button id="destroyEditors">Destroy editors</button>
<button id="initEditor">Init editor</button>
</p>

<div class="container"></div>

<style>
body {
width: 10000px;
height: 10000px;
}


.container {
padding: 20px;
width: 500px;
}
</style>
43 changes: 43 additions & 0 deletions tests/manual/ballooneditor-data.js
Original file line number Diff line number Diff line change
@@ -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( `<h2>Editor ${ counter }</h2><p>This is an editor instance.</p>`, {
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 );
3 changes: 3 additions & 0 deletions tests/manual/ballooneditor-data.md
Original file line number Diff line number Diff line change
@@ -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.

0 comments on commit 48c265c

Please sign in to comment.