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

Commit

Permalink
Feature: Added a possibility to initialize collection values through …
Browse files Browse the repository at this point in the history
…the constructor.

Ported from #309 (1aac49d).
  • Loading branch information
mlewand committed Feb 19, 2020
1 parent d38350c commit 11b4eb7
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 12 deletions.
90 changes: 87 additions & 3 deletions src/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,45 @@ export default class Collection {
/**
* Creates a new Collection instance.
*
* @param {Object} [options={}] The options object.
* @param {String} [options.idProperty='id'] The name of the property which is considered to identify an item.
* You can provide an array of initial items the collection will be created with:
*
* const collection = new Collection( [ { id: 'John' }, { id: 'Mike' } ] );
*
* console.log( collection.get( 0 ) ); // -> { id: 'John' }
* console.log( collection.get( 1 ) ); // -> { id: 'Mike' }
* console.log( collection.get( 'Mike' ) ); // -> { id: 'Mike' }
*
* Or you can first create a collection and then add new items using the {@link #add} method:
*
* const collection = new Collection();
*
* collection.add( { id: 'John' } );
* console.log( collection.get( 0 ) ); // -> { id: 'John' }
*
* Whatever option you choose, you can always pass a configuration object as the last argument
* of the constructor:
*
* const emptyCollection = new Collection( { idProperty: 'name' } );
* emptyCollection.add( { name: 'John' } );
* console.log( collection.get( 'John' ) ); // -> { name: 'John' }
*
* const nonEmptyCollection = new Collection( [ { name: 'John' } ], { idProperty: 'name' } );
* nonEmptyCollection.add( { name: 'George' } );
* console.log( collection.get( 'George' ) ); // -> { name: 'George' }
*
* @param {Array.<Object>|Object} initialItemsOrOptions The initial items of the collection or
* the options object.
* @param {Object} [options={}] The options object, when the first argument is an array of initial items.
* @param {String} [options.idProperty='id'] The name of the property which is used to identify an item.
* Items that do not have such a property will be assigned one when added to the collection.
*/
constructor( options = {} ) {
constructor( initialItemsOrOptions = {}, options = {} ) {
const hasInitialItems = initialItemsOrOptions instanceof Array;

if ( !hasInitialItems ) {
options = initialItemsOrOptions;
}

/**
* The internal list of items in the collection.
*
Expand Down Expand Up @@ -88,6 +123,14 @@ export default class Collection {
*/
this._skippedIndexesFromExternal = [];

// Set the initial content of the collection (if provided in the constructor).
if ( hasInitialItems ) {
for ( const item of initialItemsOrOptions ) {
this._items.push( item );
this._itemMap.set( this._getItemIdBeforeAdding( item ), item );
}
}

/**
* A collection instance this collection is bound to as a result
* of calling {@link #bindTo} method.
Expand Down Expand Up @@ -604,6 +647,47 @@ export default class Collection {
} );
}

_getItemIdBeforeAdding( item ) {
const idProperty = this._idProperty;
let itemId;

if ( ( idProperty in item ) ) {
itemId = item[ idProperty ];

if ( typeof itemId != 'string' ) {
/**
* This item's id should be a string.
*
* @error collection-add-invalid-id
* @param {Object} item The item being added to the collection.
* @param {module:utils/collection~Collection} collection The collection the item is added to.
*/
throw new CKEditorError( 'collection-add-invalid-id', {
item,
collection: this,
} );
}

if ( this.get( itemId ) ) {
/**
* This item already exists in the collection.
*
* @error collection-add-item-already-exists
* @param {Object} item The item being added to the collection.
* @param {module:utils/collection~Collection} collection The collection the item is added to.
*/
throw new CKEditorError( 'collection-add-item-already-exists', {
item,
collection: this
} );
}
} else {
item[ idProperty ] = itemId = uid();
}

return itemId;
}

/**
* Iterable interface.
*
Expand Down
44 changes: 35 additions & 9 deletions tests/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,44 @@ describe( 'Collection', () => {
} );

describe( 'constructor()', () => {
it( 'allows to change the id property used by the collection', () => {
const item1 = { id: 'foo', name: 'xx' };
const item2 = { id: 'foo', name: 'yy' };
const collection = new Collection( { idProperty: 'name' } );

collection.add( item1 );
collection.add( item2 );
it( 'allows setting initial collection items', () => {
const item1 = getItem( 'foo' );
const item2 = getItem( 'bar' );
const collection = new Collection( [ item1, item2 ] );

expect( collection ).to.have.length( 2 );

expect( collection.get( 'xx' ) ).to.equal( item1 );
expect( collection.remove( 'yy' ) ).to.equal( item2 );
expect( collection.get( 0 ) ).to.equal( item1 );
expect( collection.get( 1 ) ).to.equal( item2 );
expect( collection.get( 'foo' ) ).to.equal( item1 );
expect( collection.get( 'bar' ) ).to.equal( item2 );
} );

describe( 'options', () => {
it( 'allow to change the id property used by the collection', () => {
const item1 = { id: 'foo', name: 'xx' };
const item2 = { id: 'foo', name: 'yy' };
const collection = new Collection( { idProperty: 'name' } );

collection.add( item1 );
collection.add( item2 );

expect( collection ).to.have.length( 2 );

expect( collection.get( 'xx' ) ).to.equal( item1 );
expect( collection.remove( 'yy' ) ).to.equal( item2 );
} );

it( 'allow to change the id property used by the collection (initial items)', () => {
const item1 = { id: 'foo', name: 'xx' };
const item2 = { id: 'foo', name: 'yy' };
const collection = new Collection( [ item1, item2 ], { idProperty: 'name' } );

expect( collection ).to.have.length( 2 );

expect( collection.get( 'xx' ) ).to.equal( item1 );
expect( collection.remove( 'yy' ) ).to.equal( item2 );
} );
} );
} );

Expand Down

0 comments on commit 11b4eb7

Please sign in to comment.