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

Allow to pass initial data to the editor constructor #73

Merged
merged 16 commits into from
Jul 3, 2018
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 62 additions & 11 deletions src/classiceditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import ClassicEditorUIView from './classiceditoruiview';
import ElementReplacer from '@ckeditor/ckeditor5-utils/src/elementreplacer';
import getDataFromElement from '@ckeditor/ckeditor5-utils/src/dom/getdatafromelement';
import mix from '@ckeditor/ckeditor5-utils/src/mix';
import isElement from '@ckeditor/ckeditor5-utils/src/lib/lodash/isElement';

/**
* The {@glink builds/guides/overview#classic-editor classic editor} implementation.
Expand Down Expand Up @@ -53,13 +54,18 @@ export default class ClassicEditor extends Editor {
* {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`} method instead.
*
* @protected
* @param {HTMLElement} element The DOM element that will be the source for the created editor.
* The data will be loaded from it and loaded back to it once the editor is destroyed.
* @param {HTMLElement|String} elementOrData The DOM element that will be the source for the created editor
* or editor's initial data. For more information see
* {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`}.
* @param {module:core/editor/editorconfig~EditorConfig} config The editor configuration.
*/
constructor( element, config ) {
constructor( elementOrData, config ) {
super( config );

if ( isElement( elementOrData ) ) {
this.element = elementOrData;
}

/**
* The element replacer instance used to hide the editor element.
*
Expand All @@ -68,8 +74,6 @@ export default class ClassicEditor extends Editor {
*/
this._elementReplacer = new ElementReplacer();

this.element = element;

this.data.processor = new HtmlDataProcessor();

this.model.document.createRoot();
Expand Down Expand Up @@ -128,25 +132,72 @@ export default class ClassicEditor extends Editor {
* console.error( err.stack );
* } );
*
* @param {HTMLElement} element The DOM element that will be the source for the created editor.
* The data will be loaded from it and loaded back to it once the editor is destroyed.
* Creating instance when using initial data instead of a DOM element:
*
* import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
* 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 ...
*
* ClassicEditor
* .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} elementOrData The DOM element that will be the source for the created editor
* or editor's initial data.
*
* If an element is passed, then it contents will be automatically
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its contents

* {@link module:editor-classic/classiceditor~ClassicEditor#setData loaded} to the editor on startup
* and the editor element will replace the passed element in the DOM (the original one will be hidden and editor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"editor element" -> link to the new property

* will be injected next to it).
*
* Moreover, the data will be set back to the original element once the editor is destroyed and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

original => source

* (if the element is a `<textarea>`) when a form in which this element is contained is submitted (which ensures
* automatic integration with native web forms).
*
* If a data is passed, a detached editor will be created. It means that you need to insert it into the DOM manually
* (by accessing the {@link module:editor-classic/classiceditor~ClassicEditor#element `editor.element`} property).
*
* See the examples above to learn more.
* @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-classic/classiceditor~ClassicEditor} instance.
*/
static create( element, config ) {
static create( elementOrData, config ) {
return new Promise( resolve => {
const editor = new this( element, config );
const editor = new this( elementOrData, config );

resolve(
editor.initPlugins()
.then( () => editor.ui.init() )
.then( () => {
editor._elementReplacer.replace( element, editor.ui.view.element );
if ( isElement( elementOrData ) ) {
editor._elementReplacer.replace( elementOrData, editor.ui.view.element );
}

editor.fire( 'uiReady' );
} )
.then( () => editor.editing.view.attachDomRoot( editor.ui.view.editableElement ) )
.then( () => editor.data.init( getDataFromElement( element ) ) )
.then( () => {
Copy link
Member

@Reinmar Reinmar Jun 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be easier to read if it was written like this:

const initialData = isElement(  sourceElementOrData ) ? getDataFromElement( sourceElementOrData ) : sourceElementOrData;

editor.data.init( initialData );

Copy link
Member

@Reinmar Reinmar Jun 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point is – what differs these two scenarios isn't how or whether you call editor.data.init(). It's how you retrieve this data and from where.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the code you committed isn't equal to what I proposed:

editor.data.init(
    isElement( sourceElementOrData ) ? getDataFromElement( sourceElementOrData ) : sourceElementOrData
);

In this case, we lose the information what do we call init() with. It makes reading the code harder.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, editor.data.init() may return a promise so this change breaks the promise chain – make sure to add a test for that.

if ( editor.element ) {
editor.data.init( getDataFromElement( editor.element ) );
} else {
editor.data.init( elementOrData );
editor.element = editor.ui.view.element;
}
} )
.then( () => {
editor.fire( 'dataReady' );
editor.fire( 'ready' );
Expand Down
16 changes: 16 additions & 0 deletions tests/classiceditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,22 @@ describe( 'ClassicEditor', () => {
} );
} );

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

it( 'editor.element should be equal to editor.ui.view.element when data is passed', () => {
return ClassicEditor.create( '<p>Hello world!</p>', {
plugins: [ Paragraph ]
} ).then( editor => {
expect( editor.element ).to.equal( editor.ui.view.element );
} );
} );

describe( 'ui', () => {
it( 'creates the UI using BoxedEditorUI classes', () => {
expect( editor.ui ).to.be.instanceof( ClassicEditorUI );
Expand Down
18 changes: 18 additions & 0 deletions tests/manual/classiceditor-data.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<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>
50 changes: 50 additions & 0 deletions tests/manual/classiceditor-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

/* globals console:false, document, window */

import ClassicEditor from '../../src/classiceditor';
import Enter from '@ckeditor/ckeditor5-enter/src/enter';
import Typing from '@ckeditor/ckeditor5-typing/src/typing';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Undo from '@ckeditor/ckeditor5-undo/src/undo';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';

window.editors = [];
let counter = 1;

const container = document.querySelector( '.container' );

function initEditor() {
ClassicEditor
.create( `<h2>Hello world! #${ counter }</h2><p>This is an editor instance.</p>`, {
plugins: [ Enter, Typing, Paragraph, Undo, Heading, Bold, Italic ],
toolbar: [ 'heading', '|', 'bold', 'italic', '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/classiceditor-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 editor" all editors should be removed from the document.