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

Commit 0fb4295

Browse files
author
Piotr Jasiun
authored
Merge pull request #1627 from ckeditor/t/1626
Other: Added support for handling data in multiple roots in `DataController`. Closes #1626. BREAKING CHANGE: The second parameter (`rootName`) from `DataController#init()` method has been removed. To initialize data on a root different than default one an object with `rootName` - `data` pair should be passed. BREAKING CHANGE: The second parameter (`rootName`) from `DataController#set()` method has been removed. To set data on a root different than default one an object with `rootName` - `data` pair should be passed.
2 parents de43698 + d10c072 commit 0fb4295

File tree

2 files changed

+175
-18
lines changed

2 files changed

+175
-18
lines changed

src/controller/datacontroller.js

Lines changed: 107 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,21 @@ export default class DataController {
113113
* @returns {String} Output data.
114114
*/
115115
get( rootName = 'main' ) {
116+
if ( !this._checkIfRootsExists( [ rootName ] ) ) {
117+
/**
118+
* Cannot get data from a non-existing root. This error is thrown when {@link #get DataController#get() method}
119+
* is called with non-existent root name. For example, if there is an editor instance with only `main` root,
120+
* calling {@link #get} like:
121+
*
122+
* data.get( 'root2' );
123+
*
124+
* will throw this error.
125+
*
126+
* @error datacontroller-get-non-existent-root
127+
*/
128+
throw new CKEditorError( 'datacontroller-get-non-existent-root: Attempting to get data from a non-existing root.' );
129+
}
130+
116131
// Get model range.
117132
return this.stringify( this.model.document.getRoot( rootName ) );
118133
}
@@ -181,12 +196,20 @@ export default class DataController {
181196
* **Note** This method is {@link module:utils/observablemixin~ObservableMixin#decorate decorated} which is
182197
* used by e.g. collaborative editing plugin that syncs remote data on init.
183198
*
199+
* When data is passed as a string it is initialized on a default `main` root:
200+
*
201+
* dataController.init( '<p>Foo</p>' ); // Initializes data on the `main` root.
202+
*
203+
* To initialize data on a different root or multiple roots at once, object containing `rootName` - `data` pairs should be passed:
204+
*
205+
* dataController.init( { main: '<p>Foo</p>', title: '<h1>Bar</h1>' } ); // Initializes data on the `main` and `title` roots.
206+
*
184207
* @fires init
185-
* @param {String} data Input data.
186-
* @param {String} [rootName='main'] Root name.
208+
* @param {String|Object.<String,String>} data Input data as a string or an object containing `rootName` - `data`
209+
* pairs to initialize data on multiple roots at once.
187210
* @returns {Promise} Promise that is resolved after the data is set on the editor.
188211
*/
189-
init( data, rootName = 'main' ) {
212+
init( data ) {
190213
if ( this.model.document.version ) {
191214
/**
192215
* Cannot set initial data to not empty {@link module:engine/model/document~Document}.
@@ -198,10 +221,33 @@ export default class DataController {
198221
throw new CKEditorError( 'datacontroller-init-document-not-empty: Trying to set initial data to not empty document.' );
199222
}
200223

201-
const modelRoot = this.model.document.getRoot( rootName );
224+
let initialData = {};
225+
if ( typeof data === 'string' ) {
226+
initialData.main = data; // Default root is 'main'. To initiate data on a different root, object should be passed.
227+
} else {
228+
initialData = data;
229+
}
230+
231+
if ( !this._checkIfRootsExists( Object.keys( initialData ) ) ) {
232+
/**
233+
* Cannot init data on a non-existing root. This error is thrown when {@link #init DataController#init() method}
234+
* is called with non-existent root name. For example, if there is an editor instance with only `main` root,
235+
* calling {@link #init} like:
236+
*
237+
* data.init( { main: '<p>Foo</p>', root2: '<p>Bar</p>' } );
238+
*
239+
* will throw this error.
240+
*
241+
* @error datacontroller-init-non-existent-root
242+
*/
243+
throw new CKEditorError( 'datacontroller-init-non-existent-root: Attempting to init data on a non-existing root.' );
244+
}
202245

203246
this.model.enqueueChange( 'transparent', writer => {
204-
writer.insert( this.parse( data, modelRoot ), modelRoot, 0 );
247+
for ( const rootName of Object.keys( initialData ) ) {
248+
const modelRoot = this.model.document.getRoot( rootName );
249+
writer.insert( this.parse( initialData[ rootName ], modelRoot ), modelRoot, 0 );
250+
}
205251
} );
206252

207253
return Promise.resolve();
@@ -216,19 +262,51 @@ export default class DataController {
216262
* This method also creates a batch with all the changes applied. If all you need is to parse data, use
217263
* the {@link #parse} method.
218264
*
219-
* @param {String} data Input data.
220-
* @param {String} [rootName='main'] Root name.
265+
* When data is passed as a string it is set on a default `main` root:
266+
*
267+
* dataController.set( '<p>Foo</p>' ); // Sets data on the `main` root.
268+
*
269+
* To set data on a different root or multiple roots at once, object containing `rootName` - `data` pairs should be passed:
270+
*
271+
* dataController.set( { main: '<p>Foo</p>', title: '<h1>Bar</h1>' } ); // Sets data on the `main` and `title` roots.
272+
*
273+
* @param {String|Object.<String,String>} data Input data as a string or an object containing `rootName` - `data`
274+
* pairs to set data on multiple roots at once.
221275
*/
222-
set( data, rootName = 'main' ) {
223-
// Save to model.
224-
const modelRoot = this.model.document.getRoot( rootName );
276+
set( data ) {
277+
let newData = {};
278+
if ( typeof data === 'string' ) {
279+
newData.main = data; // Default root is 'main'. To set data on a different root, object should be passed.
280+
} else {
281+
newData = data;
282+
}
283+
284+
if ( !this._checkIfRootsExists( Object.keys( newData ) ) ) {
285+
/**
286+
* Cannot set data on a non-existing root. This error is thrown when {@link #set DataController#set() method}
287+
* is called with non-existent root name. For example, if there is an editor instance with only `main` root,
288+
* calling {@link #set} like:
289+
*
290+
* data.set( { main: '<p>Foo</p>', root2: '<p>Bar</p>' } );
291+
*
292+
* will throw this error.
293+
*
294+
* @error datacontroller-set-non-existent-root
295+
*/
296+
throw new CKEditorError( 'datacontroller-set-non-existent-root: Attempting to set data on a non-existing root.' );
297+
}
225298

226299
this.model.enqueueChange( 'transparent', writer => {
227300
writer.setSelection( null );
228301
writer.removeSelectionAttribute( this.model.document.selection.getAttributeKeys() );
229302

230-
writer.remove( writer.createRangeIn( modelRoot ) );
231-
writer.insert( this.parse( data, modelRoot ), modelRoot, 0 );
303+
for ( const rootName of Object.keys( newData ) ) {
304+
// Save to model.
305+
const modelRoot = this.model.document.getRoot( rootName );
306+
307+
writer.remove( writer.createRangeIn( modelRoot ) );
308+
writer.insert( this.parse( newData[ rootName ], modelRoot ), modelRoot, 0 );
309+
}
232310
} );
233311
}
234312

@@ -275,6 +353,23 @@ export default class DataController {
275353
*/
276354
destroy() {}
277355

356+
/**
357+
* Checks if all provided root names are existing editor roots.
358+
*
359+
* @private
360+
* @param {Array.<String>} rootNames Root names to check.
361+
* @returns {Boolean} Whether all provided root names are existing editor roots.
362+
*/
363+
_checkIfRootsExists( rootNames ) {
364+
for ( const rootName of rootNames ) {
365+
if ( !this.model.document.getRootNames().includes( rootName ) ) {
366+
return false;
367+
}
368+
}
369+
370+
return true;
371+
}
372+
278373
/**
279374
* Event fired by decorated {@link #init} method.
280375
* See {@link module:utils/observablemixin~ObservableMixin.decorate} for more information and samples.

tests/controller/datacontroller.js

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,17 @@ describe( 'DataController', () => {
178178
expect( getData( model, { withoutSelection: true } ) ).to.equal( 'foo' );
179179
} );
180180

181+
it( 'should set data to multiple roots at once', () => {
182+
schema.extend( '$text', { allowIn: '$root' } );
183+
data.init( { main: 'bar', title: 'baz' } );
184+
185+
expect( getData( model, { withoutSelection: true } ) ).to.equal( 'bar' );
186+
expect( getData( model, { withoutSelection: true, rootName: 'title' } ) ).to.equal( 'baz' );
187+
} );
188+
181189
it( 'should get root name as a parameter', () => {
182190
schema.extend( '$text', { allowIn: '$root' } );
183-
data.init( 'foo', 'title' );
191+
data.init( { title: 'foo' } );
184192

185193
expect( getData( model, { withoutSelection: true, rootName: 'title' } ) ).to.equal( 'foo' );
186194
} );
@@ -210,6 +218,28 @@ describe( 'DataController', () => {
210218

211219
return promise;
212220
} );
221+
222+
it( 'should throw an error when non-existent root is used (single)', () => {
223+
expect( () => {
224+
data.init( { nonexistent: '<p>Bar</p>' } );
225+
} ).to.throw(
226+
CKEditorError,
227+
'datacontroller-init-non-existent-root: Attempting to init data on a non-existing root.'
228+
);
229+
} );
230+
231+
it( 'should throw an error when non-existent root is used (one of many)', () => {
232+
schema.extend( '$text', { allowIn: '$root' } );
233+
234+
expect( () => {
235+
data.init( { main: 'bar', nonexistent: '<p>Bar</p>' } );
236+
} ).to.throw(
237+
CKEditorError,
238+
'datacontroller-init-non-existent-root: Attempting to init data on a non-existing root.'
239+
);
240+
241+
expect( getData( model, { withoutSelection: true } ) ).to.equal( '' );
242+
} );
213243
} );
214244

215245
describe( 'set()', () => {
@@ -240,8 +270,8 @@ describe( 'DataController', () => {
240270

241271
it( 'should get root name as a parameter', () => {
242272
schema.extend( '$text', { allowIn: '$root' } );
243-
data.set( 'foo', 'main' );
244-
data.set( 'Bar', 'title' );
273+
data.set( 'foo' );
274+
data.set( { title: 'Bar' } );
245275

246276
expect( getData( model, { withoutSelection: true, rootName: 'main' } ) ).to.equal( 'foo' );
247277
expect( getData( model, { withoutSelection: true, rootName: 'title' } ) ).to.equal( 'Bar' );
@@ -252,7 +282,7 @@ describe( 'DataController', () => {
252282
it( 'should parse given data before set in a context of correct root', () => {
253283
schema.extend( '$text', { allowIn: '$title', disallowIn: '$root' } );
254284
data.set( 'foo', 'main' );
255-
data.set( 'Bar', 'title' );
285+
data.set( { title: 'Bar' } );
256286

257287
expect( getData( model, { withoutSelection: true, rootName: 'main' } ) ).to.equal( '' );
258288
expect( getData( model, { withoutSelection: true, rootName: 'title' } ) ).to.equal( 'Bar' );
@@ -265,14 +295,37 @@ describe( 'DataController', () => {
265295
it( 'should allow setting empty data', () => {
266296
schema.extend( '$text', { allowIn: '$root' } );
267297

268-
data.set( 'foo', 'title' );
298+
data.set( { title: 'foo' } );
269299

270300
expect( getData( model, { withoutSelection: true, rootName: 'title' } ) ).to.equal( 'foo' );
271301

272-
data.set( '', 'title' );
302+
data.set( { title: '' } );
273303

274304
expect( getData( model, { withoutSelection: true, rootName: 'title' } ) ).to.equal( '' );
275305
} );
306+
307+
it( 'should throw an error when non-existent root is used (single)', () => {
308+
expect( () => {
309+
data.set( { nonexistent: '<p>Bar</p>' } );
310+
} ).to.throw(
311+
CKEditorError,
312+
'datacontroller-set-non-existent-root: Attempting to set data on a non-existing root.'
313+
);
314+
} );
315+
316+
it( 'should throw an error when non-existent root is used (one of many) without touching any roots data', () => {
317+
schema.extend( '$text', { allowIn: '$root' } );
318+
data.set( 'foo' );
319+
320+
expect( () => {
321+
data.set( { main: 'bar', nonexistent: '<p>Bar</p>' } );
322+
} ).to.throw(
323+
CKEditorError,
324+
'datacontroller-set-non-existent-root: Attempting to set data on a non-existing root.'
325+
);
326+
327+
expect( getData( model, { withoutSelection: true } ) ).to.equal( 'foo' );
328+
} );
276329
} );
277330

278331
describe( 'get()', () => {
@@ -343,6 +396,15 @@ describe( 'DataController', () => {
343396
expect( data.get( 'main' ) ).to.equal( '<p>foo</p>' );
344397
expect( data.get( 'title' ) ).to.equal( 'Bar' );
345398
} );
399+
400+
it( 'should throw an error when non-existent root is used', () => {
401+
expect( () => {
402+
data.get( 'nonexistent' );
403+
} ).to.throw(
404+
CKEditorError,
405+
'datacontroller-get-non-existent-root: Attempting to get data from a non-existing root.'
406+
);
407+
} );
346408
} );
347409

348410
describe( 'stringify()', () => {

0 commit comments

Comments
 (0)