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

Commit d20d660

Browse files
author
Piotr Jasiun
authored
Merge pull request #1333 from ckeditor/t/ckeditor5-core/120
Feature: Introduced decorable DataController#init metohd. Closes ckeditor/ckeditor5-core#120.
2 parents a4f3dad + 160af76 commit d20d660

File tree

2 files changed

+99
-2
lines changed

2 files changed

+99
-2
lines changed

src/controller/datacontroller.js

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import mix from '@ckeditor/ckeditor5-utils/src/mix';
1111
import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
12+
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
1213

1314
import Mapper from '../conversion/mapper';
1415

@@ -100,6 +101,8 @@ export default class DataController {
100101
this.upcastDispatcher.on( 'text', convertText(), { priority: 'lowest' } );
101102
this.upcastDispatcher.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
102103
this.upcastDispatcher.on( 'documentFragment', convertToModelFragment(), { priority: 'lowest' } );
104+
105+
this.decorate( 'init' );
103106
}
104107

105108
/**
@@ -170,9 +173,42 @@ export default class DataController {
170173
return viewDocumentFragment;
171174
}
172175

176+
/**
177+
* Sets initial input data parsed by the {@link #processor data processor} and
178+
* converted by the {@link #upcastDispatcher view-to-model converters}.
179+
* Initial data can be set only to document that {@link module:engine/model/document~Document#version} is equal 0.
180+
*
181+
* **Note** This method is {@link module:utils/observablemixin~ObservableMixin#decorate decorated} which is
182+
* used by e.g. collaborative editing plugin that syncs remote data on init.
183+
*
184+
* @fires init
185+
* @param {String} data Input data.
186+
* @param {String} [rootName='main'] Root name.
187+
*/
188+
init( data, rootName = 'main' ) {
189+
if ( this.model.document.version ) {
190+
/**
191+
* Cannot set initial data to not empty {@link module:engine/model/document~Document}.
192+
* Initial data should be set once, during {@link module:core/editor/editor~Editor} initialization,
193+
* when the {@link module:engine/model/document~Document#version} is equal 0.
194+
*
195+
* @error datacontroller-init-document-not-empty
196+
*/
197+
throw new CKEditorError( 'datacontroller-init-document-not-empty: Trying to set initial data to not empty document.' );
198+
}
199+
200+
const modelRoot = this.model.document.getRoot( rootName );
201+
202+
this.model.enqueueChange( 'transparent', writer => {
203+
writer.insert( this.parse( data, modelRoot ), modelRoot );
204+
} );
205+
}
206+
173207
/**
174208
* Sets input data parsed by the {@link #processor data processor} and
175209
* converted by the {@link #upcastDispatcher view-to-model converters}.
210+
* This method can be used any time to replace existing editor data by the new one without clearing the
211+
* {@link module:engine/model/document~Document#history document history}.
176212
*
177213
* This method also creates a batch with all the changes applied. If all you need is to parse data, use
178214
* the {@link #parse} method.
@@ -185,8 +221,6 @@ export default class DataController {
185221
const modelRoot = this.model.document.getRoot( rootName );
186222

187223
this.model.enqueueChange( 'transparent', writer => {
188-
// Clearing selection is a workaround for ticket #569 (LiveRange loses position after removing data from document).
189-
// After fixing it this code should be removed.
190224
writer.setSelection( null );
191225
writer.removeSelectionAttribute( this.model.document.selection.getAttributeKeys() );
192226

@@ -237,6 +271,13 @@ export default class DataController {
237271
* Removes all event listeners set by the DataController.
238272
*/
239273
destroy() {}
274+
275+
/**
276+
* Event fired by decorated {@link #init} method.
277+
* See {@link module:utils/observablemixin~ObservableMixin.decorate} for more information and samples.
278+
*
279+
* @event init
280+
*/
240281
}
241282

242283
mix( DataController, ObservableMixin );

tests/controller/datacontroller.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Model from '../../src/model/model';
77
import Range from '../../src/model/range';
88
import DataController from '../../src/controller/datacontroller';
99
import HtmlDataProcessor from '../../src/dataprocessor/htmldataprocessor';
10+
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
1011

1112
import ModelDocumentFragment from '../../src/model/documentfragment';
1213
import ViewDocumentFragment from '../../src/view/documentfragment';
@@ -152,6 +153,61 @@ describe( 'DataController', () => {
152153
} );
153154
} );
154155

156+
describe( 'init()', () => {
157+
it( 'should be decorated', () => {
158+
const spy = sinon.spy();
159+
160+
data.on( 'init', spy );
161+
162+
data.init( 'foo bar' );
163+
164+
sinon.assert.calledWithExactly( spy, sinon.match.any, [ 'foo bar' ] );
165+
} );
166+
167+
it( 'should throw an error when document data is already initialized', () => {
168+
data.init( '<p>Foo</p>' );
169+
170+
expect( () => {
171+
data.init( '<p>Bar</p>' );
172+
} ).to.throw(
173+
CKEditorError,
174+
'datacontroller-init-document-not-empty: Trying to set initial data to not empty document.'
175+
);
176+
} );
177+
178+
it( 'should set data to default main root', () => {
179+
schema.extend( '$text', { allowIn: '$root' } );
180+
data.init( 'foo' );
181+
182+
expect( getData( model, { withoutSelection: true } ) ).to.equal( 'foo' );
183+
} );
184+
185+
it( 'should get root name as a parameter', () => {
186+
schema.extend( '$text', { allowIn: '$root' } );
187+
data.init( 'foo', 'title' );
188+
189+
expect( getData( model, { withoutSelection: true, rootName: 'title' } ) ).to.equal( 'foo' );
190+
} );
191+
192+
it( 'should create a batch', () => {
193+
schema.extend( '$text', { allowIn: '$root' } );
194+
data.init( 'foo' );
195+
196+
expect( count( modelDocument.history.getDeltas() ) ).to.equal( 1 );
197+
} );
198+
199+
it( 'should cause firing change event', () => {
200+
const spy = sinon.spy();
201+
202+
schema.extend( '$text', { allowIn: '$root' } );
203+
model.document.on( 'change', spy );
204+
205+
data.init( 'foo' );
206+
207+
expect( spy.calledOnce ).to.be.true;
208+
} );
209+
} );
210+
155211
describe( 'set()', () => {
156212
it( 'should set data to default main root', () => {
157213
schema.extend( '$text', { allowIn: '$root' } );

0 commit comments

Comments
 (0)