Explorar o código

Feature: Added a possibility to initialize collection values through the constructor.

Ported from https://github.com/ckeditor/ckeditor5-utils/pull/309 (1aac49db1ca9ea914f9fc44f00e295474faf3ed2).
Marek Lewandowski %!s(int64=6) %!d(string=hai) anos
pai
achega
fcfc30edff

+ 87 - 3
packages/ckeditor5-utils/src/collection.js

@@ -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.
 		 *
@@ -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.
@@ -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.
 	 *

+ 35 - 9
packages/ckeditor5-utils/tests/collection.js

@@ -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 );
+			} );
 		} );
 	} );