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

Commit 09cebc6

Browse files
authored
Merge pull request #73 from ckeditor/t/72
Feature: Editor can now be created with initial data passed to the `create()` method. Closes #72. BREAKING CHANGE: The `ClassicEditor#element` property was renamed to `ClassicEditor#sourceElement`. See ckeditor/ckeditor5-core#64.
2 parents ae98cfd + 6df6ba9 commit 09cebc6

File tree

6 files changed

+207
-20
lines changed

6 files changed

+207
-20
lines changed

src/classiceditor.js

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import ClassicEditorUIView from './classiceditoruiview';
1717
import ElementReplacer from '@ckeditor/ckeditor5-utils/src/elementreplacer';
1818
import getDataFromElement from '@ckeditor/ckeditor5-utils/src/dom/getdatafromelement';
1919
import mix from '@ckeditor/ckeditor5-utils/src/mix';
20+
import isElement from '@ckeditor/ckeditor5-utils/src/lib/lodash/isElement';
2021

2122
/**
2223
* The {@glink builds/guides/overview#classic-editor classic editor} implementation.
@@ -53,23 +54,26 @@ export default class ClassicEditor extends Editor {
5354
* {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`} method instead.
5455
*
5556
* @protected
56-
* @param {HTMLElement} element The DOM element that will be the source for the created editor.
57-
* The data will be loaded from it and loaded back to it once the editor is destroyed.
57+
* @param {HTMLElement|String} sourceElementOrData The DOM element that will be the source for the created editor
58+
* or editor's initial data. For more information see
59+
* {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`}.
5860
* @param {module:core/editor/editorconfig~EditorConfig} config The editor configuration.
5961
*/
60-
constructor( element, config ) {
62+
constructor( sourceElementOrData, config ) {
6163
super( config );
6264

65+
if ( isElement( sourceElementOrData ) ) {
66+
this.sourceElement = sourceElementOrData;
67+
}
68+
6369
/**
64-
* The element replacer instance used to hide the editor element.
70+
* The element replacer instance used to hide the editor's source element.
6571
*
6672
* @protected
6773
* @member {module:utils/elementreplacer~ElementReplacer}
6874
*/
6975
this._elementReplacer = new ElementReplacer();
7076

71-
this.element = element;
72-
7377
this.data.processor = new HtmlDataProcessor();
7478

7579
this.model.document.createRoot();
@@ -79,15 +83,25 @@ export default class ClassicEditor extends Editor {
7983
attachToForm( this );
8084
}
8185

86+
/**
87+
* @inheritDoc
88+
*/
89+
get element() {
90+
return this.ui.view.element;
91+
}
92+
8293
/**
8394
* Destroys the editor instance, releasing all resources used by it.
8495
*
85-
* Updates the original editor element with the data.
96+
* Updates the editor's source element with the data.
8697
*
8798
* @returns {Promise}
8899
*/
89100
destroy() {
90-
this.updateElement();
101+
if ( this.sourceElement ) {
102+
this.updateSourceElement();
103+
}
104+
91105
this._elementReplacer.restore();
92106
this.ui.destroy();
93107

@@ -128,25 +142,72 @@ export default class ClassicEditor extends Editor {
128142
* console.error( err.stack );
129143
* } );
130144
*
131-
* @param {HTMLElement} element The DOM element that will be the source for the created editor.
132-
* The data will be loaded from it and loaded back to it once the editor is destroyed.
145+
* Creating instance when using initial data instead of a DOM element:
146+
*
147+
* import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
148+
* import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
149+
* import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
150+
* import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
151+
* import ...
152+
*
153+
* ClassicEditor
154+
* .create( '<p>Hello world!</p>', {
155+
* plugins: [ Essentials, Bold, Italic, ... ],
156+
* toolbar: [ 'bold', 'italic', ... ]
157+
* } )
158+
* .then( editor => {
159+
* console.log( 'Editor was initialized', editor );
160+
*
161+
* // Initial data was provided so `editor.element` needs to be added manually to the DOM.
162+
* document.body.appendChild( editor.element );
163+
* } )
164+
* .catch( err => {
165+
* console.error( err.stack );
166+
* } );
167+
*
168+
* @param {HTMLElement|String} sourceElementOrData The DOM element that will be the source for the created editor
169+
* or editor's initial data.
170+
*
171+
* If a source element is passed, then its contents will be automatically
172+
* {@link module:editor-classic/classiceditor~ClassicEditor#setData loaded} to the editor on startup
173+
* and the {@link module:core/editor/editorwithui~EditorWithUI#element editor element} will replace the passed element in the DOM
174+
* (the original one will be hidden and editor will be injected next to it).
175+
*
176+
* Moreover, the data will be set back to the source element once the editor is destroyed and
177+
* (if the element is a `<textarea>`) when a form in which this element is contained is submitted (which ensures
178+
* automatic integration with native web forms).
179+
*
180+
* If a data is passed, a detached editor will be created. It means that you need to insert it into the DOM manually
181+
* (by accessing the {@link module:editor-classic/classiceditor~ClassicEditor#element `editor.element`} property).
182+
*
183+
* See the examples above to learn more.
184+
*
133185
* @param {module:core/editor/editorconfig~EditorConfig} config The editor configuration.
134186
* @returns {Promise} A promise resolved once the editor is ready.
135187
* The promise returns the created {@link module:editor-classic/classiceditor~ClassicEditor} instance.
136188
*/
137-
static create( element, config ) {
189+
static create( sourceElementOrData, config ) {
138190
return new Promise( resolve => {
139-
const editor = new this( element, config );
191+
const editor = new this( sourceElementOrData, config );
140192

141193
resolve(
142194
editor.initPlugins()
143195
.then( () => editor.ui.init() )
144196
.then( () => {
145-
editor._elementReplacer.replace( element, editor.ui.view.element );
197+
if ( isElement( sourceElementOrData ) ) {
198+
editor._elementReplacer.replace( sourceElementOrData, editor.element );
199+
}
200+
146201
editor.fire( 'uiReady' );
147202
} )
148203
.then( () => editor.editing.view.attachDomRoot( editor.ui.view.editableElement ) )
149-
.then( () => editor.data.init( getDataFromElement( element ) ) )
204+
.then( () => {
205+
const initialData = isElement( sourceElementOrData ) ?
206+
getDataFromElement( sourceElementOrData ) :
207+
sourceElementOrData;
208+
209+
return editor.data.init( initialData );
210+
} )
150211
.then( () => {
151212
editor.fire( 'dataReady' );
152213
editor.fire( 'ready' );

tests/classiceditor.js

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,21 @@ describe( 'ClassicEditor', () => {
4646
} );
4747

4848
it( 'has a Data Interface', () => {
49-
testUtils.isMixed( ClassicEditor, DataApiMixin );
49+
expect( testUtils.isMixed( ClassicEditor, DataApiMixin ) ).to.true;
5050
} );
5151

5252
it( 'has a Element Interface', () => {
53-
testUtils.isMixed( ClassicEditor, ElementApiMixin );
53+
expect( testUtils.isMixed( ClassicEditor, ElementApiMixin ) ).to.true;
5454
} );
5555

5656
it( 'creates main root element', () => {
5757
expect( editor.model.document.getRoot( 'main' ) ).to.instanceof( RootElement );
5858
} );
5959

60+
it( 'contains the source element as #sourceElement property', () => {
61+
expect( editor.sourceElement ).to.equal( editorElement );
62+
} );
63+
6064
it( 'handles form element', () => {
6165
const form = document.createElement( 'form' );
6266
const textarea = document.createElement( 'textarea' );
@@ -88,6 +92,14 @@ describe( 'ClassicEditor', () => {
8892
} );
8993
} );
9094

95+
it( 'allows to pass data to the constructor', () => {
96+
return ClassicEditor.create( '<p>Hello world!</p>', {
97+
plugins: [ Paragraph ]
98+
} ).then( editor => {
99+
expect( editor.getData() ).to.equal( '<p>Hello world!</p>' );
100+
} );
101+
} );
102+
91103
describe( 'ui', () => {
92104
it( 'creates the UI using BoxedEditorUI classes', () => {
93105
expect( editor.ui ).to.be.instanceof( ClassicEditorUI );
@@ -137,6 +149,18 @@ describe( 'ClassicEditor', () => {
137149
} );
138150
} );
139151

152+
it( 'should have undefined the #sourceElement if editor was initialized with data', () => {
153+
return ClassicEditor
154+
.create( '<p>Foo.</p>', {
155+
plugins: [ Paragraph, Bold ]
156+
} )
157+
.then( newEditor => {
158+
expect( newEditor.sourceElement ).to.be.undefined;
159+
160+
return newEditor.destroy();
161+
} );
162+
} );
163+
140164
describe( 'ui', () => {
141165
it( 'inserts editor UI next to editor element', () => {
142166
expect( editor.ui.view.element.previousSibling ).to.equal( editorElement );
@@ -145,6 +169,20 @@ describe( 'ClassicEditor', () => {
145169
it( 'attaches editable UI as view\'s DOM root', () => {
146170
expect( editor.editing.view.getDomRoot() ).to.equal( editor.ui.view.editable.element );
147171
} );
172+
173+
it( 'editor.element points to the editor\'s UI when editor was initialized on the DOM element', () => {
174+
expect( editor.element ).to.equal( editor.ui.view.element );
175+
} );
176+
177+
it( 'editor.element points to the editor\'s UI when editor was initialized with data', () => {
178+
return ClassicEditor.create( '<p>Hello world!</p>', {
179+
plugins: [ Paragraph ]
180+
} ).then( editor => {
181+
expect( editor.element ).to.equal( editor.ui.view.element );
182+
183+
return editor.destroy();
184+
} );
185+
} );
148186
} );
149187
} );
150188

@@ -243,12 +281,29 @@ describe( 'ClassicEditor', () => {
243281
} );
244282
} );
245283

284+
it( 'does not update the source element if editor was initialized with data', () => {
285+
return ClassicEditor
286+
.create( '<p>Foo.</p>', {
287+
plugins: [ Paragraph, Bold ]
288+
} )
289+
.then( newEditor => {
290+
const spy = sinon.stub( newEditor, 'updateSourceElement' );
291+
292+
return newEditor.destroy()
293+
.then( () => {
294+
expect( spy.called ).to.be.false;
295+
296+
spy.restore();
297+
} );
298+
} );
299+
} );
300+
246301
it( 'restores the editor element', () => {
247-
expect( editor.element.style.display ).to.equal( 'none' );
302+
expect( editor.sourceElement.style.display ).to.equal( 'none' );
248303

249304
return editor.destroy()
250305
.then( () => {
251-
expect( editor.element.style.display ).to.equal( '' );
306+
expect( editor.sourceElement.style.display ).to.equal( '' );
252307
} );
253308
} );
254309
} );
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<p>
2+
<button id="destroyEditors">Destroy editors</button>
3+
<button id="initEditor">Init editor</button>
4+
</p>
5+
6+
<div class="container"></div>
7+
8+
<style>
9+
body {
10+
width: 10000px;
11+
height: 10000px;
12+
}
13+
14+
.container {
15+
padding: 20px;
16+
width: 500px;
17+
}
18+
</style>

tests/manual/classiceditor-data.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md.
4+
*/
5+
6+
/* globals console:false, document, window */
7+
8+
import ClassicEditor from '../../src/classiceditor';
9+
import Enter from '@ckeditor/ckeditor5-enter/src/enter';
10+
import Typing from '@ckeditor/ckeditor5-typing/src/typing';
11+
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
12+
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
13+
import Undo from '@ckeditor/ckeditor5-undo/src/undo';
14+
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
15+
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
16+
17+
window.editors = [];
18+
let counter = 1;
19+
20+
const container = document.querySelector( '.container' );
21+
22+
function initEditor() {
23+
ClassicEditor
24+
.create( `<h2>Hello world! #${ counter }</h2><p>This is an editor instance.</p>`, {
25+
plugins: [ Enter, Typing, Paragraph, Undo, Heading, Bold, Italic ],
26+
toolbar: [ 'heading', '|', 'bold', 'italic', 'undo', 'redo' ]
27+
} )
28+
.then( editor => {
29+
counter += 1;
30+
window.editors.push( editor );
31+
container.appendChild( editor.element );
32+
} )
33+
.catch( err => {
34+
console.error( err.stack );
35+
} );
36+
}
37+
38+
function destroyEditors() {
39+
window.editors.forEach( editor => {
40+
editor.destroy()
41+
.then( () => {
42+
editor.element.remove();
43+
} );
44+
} );
45+
window.editors = [];
46+
counter = 1;
47+
}
48+
49+
document.getElementById( 'initEditor' ).addEventListener( 'click', initEditor );
50+
document.getElementById( 'destroyEditors' ).addEventListener( 'click', destroyEditors );

tests/manual/classiceditor-data.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
1. Click "Init editor".
2+
2. New editor instance should be appended to the document with initial data in it. You can create more than one editor.
3+
3. After clicking "Destroy editor" all editors should be removed from the document.

tests/manual/classiceditor.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
1. Click "Init editor".
22
2. Expected:
33
* Framed editor should be created.
4-
* Original element should disappear.
4+
* Source element should disappear.
55
* There should be a toolbar with "Bold", "Italic", "Undo" and "Redo" buttons.
66
3. Click "Destroy editor".
77
4. Expected:
88
* Editor should be destroyed.
9-
* Original element should be visible.
9+
* Source element should be visible.
1010
* The element should contain its data (updated).
1111
* The 'ck-body region' should be removed.
1212

0 commit comments

Comments
 (0)