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

Commit

Permalink
Merge 146f1c8 into de43698
Browse files Browse the repository at this point in the history
  • Loading branch information
f1ames committed Jan 11, 2019
2 parents de43698 + 146f1c8 commit 8959ce2
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 20 deletions.
123 changes: 109 additions & 14 deletions src/controller/datacontroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ export default class DataController {
* @returns {String} Output data.
*/
get( rootName = 'main' ) {
if ( !this._checkIfRootsExists( [ rootName ] ) ) {
/**
* Cannot get data from a non-existing root. This error is thrown when {@link #get DataController#get() method}
* is called with non-existent root name. For example, if there is an editor instance with only `main` root,
* calling {@link #get} like:
*
* data.get( 'root2' );
*
* will throw this error.
*
* @error datacontroller-get-non-existent-root
*/
throw new CKEditorError( 'datacontroller-get-non-existent-root: Attempting to get data from a non-existing root.' );
}

// Get model range.
return this.stringify( this.model.document.getRoot( rootName ) );
}
Expand Down Expand Up @@ -181,12 +196,20 @@ export default class DataController {
* **Note** This method is {@link module:utils/observablemixin~ObservableMixin#decorate decorated} which is
* used by e.g. collaborative editing plugin that syncs remote data on init.
*
* When data is passed as a string it is initialized on a default `main` root:
*
* dataController.init( '<p>Foo</p>' ); // Initializes data on the `main` root.
*
* To initialize data on a different root or multiple roots at once, object containing `rootName` - `data` pairs should be passed:
*
* dataController.init( { main: '<p>Foo</p>', title: '<h1>Bar</h1>' } ); // Initializes data on the `main` and `title` roots.
*
* @fires init
* @param {String} data Input data.
* @param {String} [rootName='main'] Root name.
* @param {String|Object.<String,String>} data Input data as a string or an object containing `rootName` - `data`
* pairs to initialize data on multiple roots at once.
* @returns {Promise} Promise that is resolved after the data is set on the editor.
*/
init( data, rootName = 'main' ) {
init( data ) {
if ( this.model.document.version ) {
/**
* Cannot set initial data to not empty {@link module:engine/model/document~Document}.
Expand All @@ -198,10 +221,33 @@ export default class DataController {
throw new CKEditorError( 'datacontroller-init-document-not-empty: Trying to set initial data to not empty document.' );
}

const modelRoot = this.model.document.getRoot( rootName );
let initialData = {};
if ( typeof data === 'string' ) {
initialData.main = data; // Default root is 'main'. To initiate data on a different root, object should be passed.
} else {
initialData = data;
}

if ( !this._checkIfRootsExists( Object.keys( initialData ) ) ) {
/**
* Cannot init data on a non-existing root. This error is thrown when {@link #init DataController#init() method}
* is called with non-existent root name. For example, if there is an editor instance with only `main` root,
* calling {@link #init} like:
*
* data.init( { main: '<p>Foo</p>', root2: '<p>Bar</p>' } );
*
* will throw this error.
*
* @error datacontroller-init-non-existent-root
*/
throw new CKEditorError( 'datacontroller-init-non-existent-root: Attempting to init data on a non-existing root.' );
}

this.model.enqueueChange( 'transparent', writer => {
writer.insert( this.parse( data, modelRoot ), modelRoot, 0 );
for ( const rootName of Object.keys( initialData ) ) {
const modelRoot = this.model.document.getRoot( rootName );
writer.insert( this.parse( initialData[ rootName ], modelRoot ), modelRoot, 0 );
}
} );

return Promise.resolve();
Expand All @@ -216,19 +262,51 @@ export default class DataController {
* This method also creates a batch with all the changes applied. If all you need is to parse data, use
* the {@link #parse} method.
*
* @param {String} data Input data.
* @param {String} [rootName='main'] Root name.
* When data is passed as a string it is set on a default `main` root:
*
* dataController.set( '<p>Foo</p>' ); // Sets data on the `main` root.
*
* To set data on a different root or multiple roots at once, object containing `rootName` - `data` pairs should be passed:
*
* dataController.set( { main: '<p>Foo</p>', title: '<h1>Bar</h1>' } ); // Sets data on the `main` and `title` roots.
*
* @param {String|Object.<String,String>} data Input data as a string or an object containing `rootName` - `data`
* pairs to set data on multiple roots at once.
*/
set( data, rootName = 'main' ) {
// Save to model.
const modelRoot = this.model.document.getRoot( rootName );
set( data ) {
let newData = {};
if ( typeof data === 'string' ) {
newData.main = data; // Default root is 'main'. To set data on a different root, object should be passed.
} else {
newData = data;
}

if ( !this._checkIfRootsExists( Object.keys( newData ) ) ) {
/**
* Cannot set data on a non-existing root. This error is thrown when {@link #set DataController#set() method}
* is called with non-existent root name. For example, if there is an editor instance with only `main` root,
* calling {@link #set} like:
*
* data.set( { main: '<p>Foo</p>', root2: '<p>Bar</p>' } );
*
* will throw this error.
*
* @error datacontroller-set-non-existent-root
*/
throw new CKEditorError( 'datacontroller-set-non-existent-root: Attempting to set data on a non-existing root.' );
}

this.model.enqueueChange( 'transparent', writer => {
writer.setSelection( null );
writer.removeSelectionAttribute( this.model.document.selection.getAttributeKeys() );
for ( const rootName of Object.keys( newData ) ) {
// Save to model.
const modelRoot = this.model.document.getRoot( rootName );

writer.setSelection( null );
writer.removeSelectionAttribute( this.model.document.selection.getAttributeKeys() );

writer.remove( writer.createRangeIn( modelRoot ) );
writer.insert( this.parse( data, modelRoot ), modelRoot, 0 );
writer.remove( writer.createRangeIn( modelRoot ) );
writer.insert( this.parse( newData[ rootName ], modelRoot ), modelRoot, 0 );
}
} );
}

Expand Down Expand Up @@ -275,6 +353,23 @@ export default class DataController {
*/
destroy() {}

/**
* Checks if all provided root names are existing editor roots.
*
* @private
* @param {Array.<String>} rootNames Root names to check.
* @returns {Boolean} Whether all provided root names are existing editor roots.
*/
_checkIfRootsExists( rootNames ) {
for ( const rootName of rootNames ) {
if ( !this.model.document.getRootNames().includes( rootName ) ) {
return false;
}
}

return true;
}

/**
* Event fired by decorated {@link #init} method.
* See {@link module:utils/observablemixin~ObservableMixin.decorate} for more information and samples.
Expand Down
74 changes: 68 additions & 6 deletions tests/controller/datacontroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,17 @@ describe( 'DataController', () => {
expect( getData( model, { withoutSelection: true } ) ).to.equal( 'foo' );
} );

it( 'should set data to multiple roots at once', () => {
schema.extend( '$text', { allowIn: '$root' } );
data.init( { main: 'bar', title: 'baz' } );

expect( getData( model, { withoutSelection: true } ) ).to.equal( 'bar' );
expect( getData( model, { withoutSelection: true, rootName: 'title' } ) ).to.equal( 'baz' );
} );

it( 'should get root name as a parameter', () => {
schema.extend( '$text', { allowIn: '$root' } );
data.init( 'foo', 'title' );
data.init( { title: 'foo' } );

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

return promise;
} );

it( 'should throw an error when non-existent root is used (single)', () => {
expect( () => {
data.init( { nonexistent: '<p>Bar</p>' } );
} ).to.throw(
CKEditorError,
'datacontroller-init-non-existent-root: Attempting to init data on a non-existing root.'
);
} );

it( 'should throw an error when non-existent root is used (one of many)', () => {
schema.extend( '$text', { allowIn: '$root' } );

expect( () => {
data.init( { main: 'bar', nonexistent: '<p>Bar</p>' } );
} ).to.throw(
CKEditorError,
'datacontroller-init-non-existent-root: Attempting to init data on a non-existing root.'
);

expect( getData( model, { withoutSelection: true } ) ).to.equal( '' );
} );
} );

describe( 'set()', () => {
Expand Down Expand Up @@ -240,8 +270,8 @@ describe( 'DataController', () => {

it( 'should get root name as a parameter', () => {
schema.extend( '$text', { allowIn: '$root' } );
data.set( 'foo', 'main' );
data.set( 'Bar', 'title' );
data.set( 'foo' );
data.set( { title: 'Bar' } );

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

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

data.set( 'foo', 'title' );
data.set( { title: 'foo' } );

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

data.set( '', 'title' );
data.set( { title: '' } );

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

it( 'should throw an error when non-existent root is used (single)', () => {
expect( () => {
data.set( { nonexistent: '<p>Bar</p>' } );
} ).to.throw(
CKEditorError,
'datacontroller-set-non-existent-root: Attempting to set data on a non-existing root.'
);
} );

it( 'should throw an error when non-existent root is used (one of many) without touching any roots data', () => {
schema.extend( '$text', { allowIn: '$root' } );
data.set( 'foo' );

expect( () => {
data.set( { main: 'bar', nonexistent: '<p>Bar</p>' } );
} ).to.throw(
CKEditorError,
'datacontroller-set-non-existent-root: Attempting to set data on a non-existing root.'
);

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

describe( 'get()', () => {
Expand Down Expand Up @@ -343,6 +396,15 @@ describe( 'DataController', () => {
expect( data.get( 'main' ) ).to.equal( '<p>foo</p>' );
expect( data.get( 'title' ) ).to.equal( 'Bar' );
} );

it( 'should throw an error when non-existent root is used', () => {
expect( () => {
data.get( 'nonexistent' );
} ).to.throw(
CKEditorError,
'datacontroller-get-non-existent-root: Attempting to get data from a non-existing root.'
);
} );
} );

describe( 'stringify()', () => {
Expand Down

0 comments on commit 8959ce2

Please sign in to comment.