Skip to content

Commit

Permalink
Merge pull request #7644 from ckeditor/i/7627
Browse files Browse the repository at this point in the history
Feature: Introduced the `Collection.addMany()` method for adding multiple items in a single call. Closes #7627.

Feature: Introduced the `Collection.change` event. See #7627.
  • Loading branch information
jodator authored Jul 20, 2020
2 parents 4dbec54 + e13db78 commit a1f0efd
Show file tree
Hide file tree
Showing 3 changed files with 427 additions and 64 deletions.
20 changes: 5 additions & 15 deletions packages/ckeditor5-font/src/documentcolorcollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export default class DocumentColorCollection extends Collection {
* @member {Boolean} #isEmpty
*/
this.set( 'isEmpty', true );

this.on( 'change', () => {
this.set( 'isEmpty', this.length === 0 );
} );
}

/**
Expand All @@ -44,6 +48,7 @@ export default class DocumentColorCollection extends Collection {
* @param {Number} [index] The position of the item in the collection. The item
* is pushed to the collection when `index` is not specified.
* @fires add
* @fires change
*/
add( item, index ) {
if ( this.find( element => element.color === item.color ) ) {
Expand All @@ -52,21 +57,6 @@ export default class DocumentColorCollection extends Collection {
}

super.add( item, index );

this.set( 'isEmpty', false );
}

/**
* @inheritdoc
*/
remove( subject ) {
const ret = super.remove( subject );

if ( this.length === 0 ) {
this.set( 'isEmpty', true );
}

return ret;
}

/**
Expand Down
165 changes: 116 additions & 49 deletions packages/ckeditor5-utils/src/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,24 @@ export default class Collection {
* @param {Number} [index] The position of the item in the collection. The item
* is pushed to the collection when `index` not specified.
* @fires add
* @fires change
*/
add( item, index ) {
const itemId = this._getItemIdBeforeAdding( item );
return this.addMany( [ item ], index );
}

// TODO: Use ES6 default function argument.
/**
* Adds multiple items into the collection.
*
* Any item not containing an id will get an automatically generated one.
*
* @chainable
* @param {Iterable.<Object>} item
* @param {Number} [index] The position of the insertion. Items will be appended if no `index` is specified.
* @fires add
* @fires change
*/
addMany( items, index ) {
if ( index === undefined ) {
index = this._items.length;
} else if ( index > this._items.length || index < 0 ) {
Expand All @@ -195,11 +208,22 @@ export default class Collection {
throw new CKEditorError( 'collection-add-item-invalid-index', this );
}

this._items.splice( index, 0, item );
for ( let offset = 0; offset < items.length; offset++ ) {
const item = items[ offset ];
const itemId = this._getItemIdBeforeAdding( item );
const currentItemIndex = index + offset;

this._itemMap.set( itemId, item );
this._items.splice( currentItemIndex, 0, item );
this._itemMap.set( itemId, item );

this.fire( 'add', item, index );
this.fire( 'add', item, currentItemIndex );
}

this.fire( 'change', {
added: items,
removed: [],
index
} );

return this;
}
Expand Down Expand Up @@ -271,52 +295,16 @@ export default class Collection {
* @param {Object|Number|String} subject The item to remove, its id or index in the collection.
* @returns {Object} The removed item.
* @fires remove
* @fires change
*/
remove( subject ) {
let index, id, item;
let itemDoesNotExist = false;
const idProperty = this._idProperty;

if ( typeof subject == 'string' ) {
id = subject;
item = this._itemMap.get( id );
itemDoesNotExist = !item;

if ( item ) {
index = this._items.indexOf( item );
}
} else if ( typeof subject == 'number' ) {
index = subject;
item = this._items[ index ];
itemDoesNotExist = !item;

if ( item ) {
id = item[ idProperty ];
}
} else {
item = subject;
id = item[ idProperty ];
index = this._items.indexOf( item );
itemDoesNotExist = ( index == -1 || !this._itemMap.get( id ) );
}
const [ item, index ] = this._remove( subject );

if ( itemDoesNotExist ) {
/**
* Item not found.
*
* @error collection-remove-404
*/
throw new CKEditorError( 'collection-remove-404: Item not found.', this );
}

this._items.splice( index, 1 );
this._itemMap.delete( id );

const externalItem = this._bindToInternalToExternalMap.get( item );
this._bindToInternalToExternalMap.delete( item );
this._bindToExternalToInternalMap.delete( externalItem );

this.fire( 'remove', item, index );
this.fire( 'change', {
added: [],
removed: [ item ],
index
} );

return item;
}
Expand Down Expand Up @@ -363,16 +351,27 @@ export default class Collection {
/**
* Removes all items from the collection and destroys the binding created using
* {@link #bindTo}.
*
* @fires remove
* @fires change
*/
clear() {
if ( this._bindToCollection ) {
this.stopListening( this._bindToCollection );
this._bindToCollection = null;
}

const removedItems = Array.from( this._items );

while ( this.length ) {
this.remove( 0 );
this._remove( 0 );
}

this.fire( 'change', {
added: [],
removed: removedItems,
index: 0
} );
}

/**
Expand Down Expand Up @@ -664,6 +663,65 @@ export default class Collection {
return itemId;
}

/**
* Core {@link #remove} method implementation shared in other functions.
*
* In contrast this method **does not** fire the {@link #event:change} event.
*
* @private
* @param {Object} subject The item to remove, its id or index in the collection.
* @returns {Array} Returns an array with the removed item and its index.
* @fires remove
*/
_remove( subject ) {
let index, id, item;
let itemDoesNotExist = false;
const idProperty = this._idProperty;

if ( typeof subject == 'string' ) {
id = subject;
item = this._itemMap.get( id );
itemDoesNotExist = !item;

if ( item ) {
index = this._items.indexOf( item );
}
} else if ( typeof subject == 'number' ) {
index = subject;
item = this._items[ index ];
itemDoesNotExist = !item;

if ( item ) {
id = item[ idProperty ];
}
} else {
item = subject;
id = item[ idProperty ];
index = this._items.indexOf( item );
itemDoesNotExist = ( index == -1 || !this._itemMap.get( id ) );
}

if ( itemDoesNotExist ) {
/**
* Item not found.
*
* @error collection-remove-404
*/
throw new CKEditorError( 'collection-remove-404: Item not found.', this );
}

this._items.splice( index, 1 );
this._itemMap.delete( id );

const externalItem = this._bindToInternalToExternalMap.get( item );
this._bindToInternalToExternalMap.delete( item );
this._bindToExternalToInternalMap.delete( externalItem );

this.fire( 'remove', item, index );

return [ item, index ];
}

/**
* Iterable interface.
*
Expand All @@ -680,6 +738,15 @@ export default class Collection {
* @param {Object} item The added item.
*/

/**
* Fired when the collection was changed due to adding or removing items.
*
* @event change
* @param {Iterable.<Object>} added A list of added items.
* @param {Iterable.<Object>} removed A list of removed items.
* @param {Number} index An index where the addition or removal occurred.
*/

/**
* Fired when an item is removed from the collection.
*
Expand Down
Loading

0 comments on commit a1f0efd

Please sign in to comment.