collection.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module utils/collection
  7. */
  8. import EmitterMixin from './emittermixin';
  9. import CKEditorError from './ckeditorerror';
  10. import uid from './uid';
  11. import isIterable from './isiterable';
  12. import mix from './mix';
  13. /**
  14. * Collections are ordered sets of objects. Items in the collection can be retrieved by their indexes
  15. * in the collection (like in an array) or by their ids.
  16. *
  17. * If an object without an `id` property is being added to the collection, the `id` property will be generated
  18. * automatically. Note that the automatically generated id is unique only within this single collection instance.
  19. *
  20. * By default an item in the collection is identified by its `id` property. The name of the identifier can be
  21. * configured through the constructor of the collection.
  22. *
  23. * @mixes module:utils/emittermixin~EmitterMixin
  24. */
  25. export default class Collection {
  26. /**
  27. * Creates a new Collection instance.
  28. *
  29. * You can provide an array of initial items the collection will be created with:
  30. *
  31. * const collection = new Collection( [ { id: 'John' }, { id: 'Mike' } ] );
  32. *
  33. * console.log( collection.get( 0 ) ); // -> { id: 'John' }
  34. * console.log( collection.get( 1 ) ); // -> { id: 'Mike' }
  35. * console.log( collection.get( 'Mike' ) ); // -> { id: 'Mike' }
  36. *
  37. * Or you can first create a collection and then add new items using the {@link #add} method:
  38. *
  39. * const collection = new Collection();
  40. *
  41. * collection.add( { id: 'John' } );
  42. * console.log( collection.get( 0 ) ); // -> { id: 'John' }
  43. *
  44. * Whatever option you choose, you can always pass a configuration object as the last argument
  45. * of the constructor:
  46. *
  47. * const emptyCollection = new Collection( { idProperty: 'name' } );
  48. * emptyCollection.add( { name: 'John' } );
  49. * console.log( collection.get( 'John' ) ); // -> { name: 'John' }
  50. *
  51. * const nonEmptyCollection = new Collection( [ { name: 'John' } ], { idProperty: 'name' } );
  52. * nonEmptyCollection.add( { name: 'George' } );
  53. * console.log( collection.get( 'George' ) ); // -> { name: 'George' }
  54. *
  55. * @param {Iterable.<Object>|Object} initialItemsOrOptions The initial items of the collection or
  56. * the options object.
  57. * @param {Object} [options={}] The options object, when the first argument is an array of initial items.
  58. * @param {String} [options.idProperty='id'] The name of the property which is used to identify an item.
  59. * Items that do not have such a property will be assigned one when added to the collection.
  60. */
  61. constructor( initialItemsOrOptions = {}, options = {} ) {
  62. const hasInitialItems = isIterable( initialItemsOrOptions );
  63. if ( !hasInitialItems ) {
  64. options = initialItemsOrOptions;
  65. }
  66. /**
  67. * The internal list of items in the collection.
  68. *
  69. * @private
  70. * @member {Object[]}
  71. */
  72. this._items = [];
  73. /**
  74. * The internal map of items in the collection.
  75. *
  76. * @private
  77. * @member {Map}
  78. */
  79. this._itemMap = new Map();
  80. /**
  81. * The name of the property which is considered to identify an item.
  82. *
  83. * @private
  84. * @member {String}
  85. */
  86. this._idProperty = options.idProperty || 'id';
  87. /**
  88. * A helper mapping external items of a bound collection ({@link #bindTo})
  89. * and actual items of this collection. It provides information
  90. * necessary to properly remove items bound to another collection.
  91. *
  92. * See {@link #_bindToInternalToExternalMap}.
  93. *
  94. * @protected
  95. * @member {WeakMap}
  96. */
  97. this._bindToExternalToInternalMap = new WeakMap();
  98. /**
  99. * A helper mapping items of this collection to external items of a bound collection
  100. * ({@link #bindTo}). It provides information necessary to manage the bindings, e.g.
  101. * to avoid loops in two–way bindings.
  102. *
  103. * See {@link #_bindToExternalToInternalMap}.
  104. *
  105. * @protected
  106. * @member {WeakMap}
  107. */
  108. this._bindToInternalToExternalMap = new WeakMap();
  109. /**
  110. * Stores indexes of skipped items from bound external collection.
  111. *
  112. * @private
  113. * @member {Array}
  114. */
  115. this._skippedIndexesFromExternal = [];
  116. // Set the initial content of the collection (if provided in the constructor).
  117. if ( hasInitialItems ) {
  118. for ( const item of initialItemsOrOptions ) {
  119. this._items.push( item );
  120. this._itemMap.set( this._getItemIdBeforeAdding( item ), item );
  121. }
  122. }
  123. /**
  124. * A collection instance this collection is bound to as a result
  125. * of calling {@link #bindTo} method.
  126. *
  127. * @protected
  128. * @member {module:utils/collection~Collection} #_bindToCollection
  129. */
  130. }
  131. /**
  132. * The number of items available in the collection.
  133. *
  134. * @member {Number} #length
  135. */
  136. get length() {
  137. return this._items.length;
  138. }
  139. /**
  140. * Returns the first item from the collection or null when collection is empty.
  141. *
  142. * @returns {Object|null} The first item or `null` if collection is empty.
  143. */
  144. get first() {
  145. return this._items[ 0 ] || null;
  146. }
  147. /**
  148. * Returns the last item from the collection or null when collection is empty.
  149. *
  150. * @returns {Object|null} The last item or `null` if collection is empty.
  151. */
  152. get last() {
  153. return this._items[ this.length - 1 ] || null;
  154. }
  155. /**
  156. * Adds an item into the collection.
  157. *
  158. * If the item does not have an id, then it will be automatically generated and set on the item.
  159. *
  160. * @chainable
  161. * @param {Object} item
  162. * @param {Number} [index] The position of the item in the collection. The item
  163. * is pushed to the collection when `index` not specified.
  164. * @fires add
  165. */
  166. add( item, index ) {
  167. let itemId;
  168. const idProperty = this._idProperty;
  169. if ( ( idProperty in item ) ) {
  170. itemId = item[ idProperty ];
  171. if ( typeof itemId != 'string' ) {
  172. /**
  173. * This item's id should be a string.
  174. *
  175. * @error collection-add-invalid-id
  176. */
  177. throw new CKEditorError( 'collection-add-invalid-id', this );
  178. }
  179. if ( this.get( itemId ) ) {
  180. /**
  181. * This item already exists in the collection.
  182. *
  183. * @error collection-add-item-already-exists
  184. */
  185. throw new CKEditorError( 'collection-add-item-already-exists', this );
  186. }
  187. } else {
  188. item[ idProperty ] = itemId = uid();
  189. }
  190. // TODO: Use ES6 default function argument.
  191. if ( index === undefined ) {
  192. index = this._items.length;
  193. } else if ( index > this._items.length || index < 0 ) {
  194. /**
  195. * The index number has invalid value.
  196. *
  197. * @error collection-add-item-bad-index
  198. */
  199. throw new CKEditorError( 'collection-add-item-invalid-index', this );
  200. }
  201. this._items.splice( index, 0, item );
  202. this._itemMap.set( itemId, item );
  203. this.fire( 'add', item, index );
  204. return this;
  205. }
  206. /**
  207. * Gets item by its id or index.
  208. *
  209. * @param {String|Number} idOrIndex The item id or index in the collection.
  210. * @returns {Object|null} The requested item or `null` if such item does not exist.
  211. */
  212. get( idOrIndex ) {
  213. let item;
  214. if ( typeof idOrIndex == 'string' ) {
  215. item = this._itemMap.get( idOrIndex );
  216. } else if ( typeof idOrIndex == 'number' ) {
  217. item = this._items[ idOrIndex ];
  218. } else {
  219. /**
  220. * Index or id must be given.
  221. *
  222. * @error collection-get-invalid-arg
  223. */
  224. throw new CKEditorError( 'collection-get-invalid-arg: Index or id must be given.', this );
  225. }
  226. return item || null;
  227. }
  228. /**
  229. * Returns a boolean indicating whether the collection contains an item.
  230. *
  231. * @param {Object|String} itemOrId The item or its id in the collection.
  232. * @returns {Boolean} `true` if the collection contains the item, `false` otherwise.
  233. */
  234. has( itemOrId ) {
  235. if ( typeof itemOrId == 'string' ) {
  236. return this._itemMap.has( itemOrId );
  237. } else { // Object
  238. const idProperty = this._idProperty;
  239. const id = itemOrId[ idProperty ];
  240. return this._itemMap.has( id );
  241. }
  242. }
  243. /**
  244. * Gets index of item in the collection.
  245. * When item is not defined in the collection then index will be equal -1.
  246. *
  247. * @param {Object|String} itemOrId The item or its id in the collection.
  248. * @returns {Number} Index of given item.
  249. */
  250. getIndex( itemOrId ) {
  251. let item;
  252. if ( typeof itemOrId == 'string' ) {
  253. item = this._itemMap.get( itemOrId );
  254. } else {
  255. item = itemOrId;
  256. }
  257. return this._items.indexOf( item );
  258. }
  259. /**
  260. * Removes an item from the collection.
  261. *
  262. * @param {Object|Number|String} subject The item to remove, its id or index in the collection.
  263. * @returns {Object} The removed item.
  264. * @fires remove
  265. */
  266. remove( subject ) {
  267. let index, id, item;
  268. let itemDoesNotExist = false;
  269. const idProperty = this._idProperty;
  270. if ( typeof subject == 'string' ) {
  271. id = subject;
  272. item = this._itemMap.get( id );
  273. itemDoesNotExist = !item;
  274. if ( item ) {
  275. index = this._items.indexOf( item );
  276. }
  277. } else if ( typeof subject == 'number' ) {
  278. index = subject;
  279. item = this._items[ index ];
  280. itemDoesNotExist = !item;
  281. if ( item ) {
  282. id = item[ idProperty ];
  283. }
  284. } else {
  285. item = subject;
  286. id = item[ idProperty ];
  287. index = this._items.indexOf( item );
  288. itemDoesNotExist = ( index == -1 || !this._itemMap.get( id ) );
  289. }
  290. if ( itemDoesNotExist ) {
  291. /**
  292. * Item not found.
  293. *
  294. * @error collection-remove-404
  295. */
  296. throw new CKEditorError( 'collection-remove-404: Item not found.', this );
  297. }
  298. this._items.splice( index, 1 );
  299. this._itemMap.delete( id );
  300. const externalItem = this._bindToInternalToExternalMap.get( item );
  301. this._bindToInternalToExternalMap.delete( item );
  302. this._bindToExternalToInternalMap.delete( externalItem );
  303. this.fire( 'remove', item, index );
  304. return item;
  305. }
  306. /**
  307. * Executes the callback for each item in the collection and composes an array or values returned by this callback.
  308. *
  309. * @param {Function} callback
  310. * @param {Object} callback.item
  311. * @param {Number} callback.index
  312. * @param {Object} ctx Context in which the `callback` will be called.
  313. * @returns {Array} The result of mapping.
  314. */
  315. map( callback, ctx ) {
  316. return this._items.map( callback, ctx );
  317. }
  318. /**
  319. * Finds the first item in the collection for which the `callback` returns a true value.
  320. *
  321. * @param {Function} callback
  322. * @param {Object} callback.item
  323. * @param {Number} callback.index
  324. * @param {Object} ctx Context in which the `callback` will be called.
  325. * @returns {Object} The item for which `callback` returned a true value.
  326. */
  327. find( callback, ctx ) {
  328. return this._items.find( callback, ctx );
  329. }
  330. /**
  331. * Returns an array with items for which the `callback` returned a true value.
  332. *
  333. * @param {Function} callback
  334. * @param {Object} callback.item
  335. * @param {Number} callback.index
  336. * @param {Object} ctx Context in which the `callback` will be called.
  337. * @returns {Object[]} The array with matching items.
  338. */
  339. filter( callback, ctx ) {
  340. return this._items.filter( callback, ctx );
  341. }
  342. /**
  343. * Removes all items from the collection and destroys the binding created using
  344. * {@link #bindTo}.
  345. */
  346. clear() {
  347. if ( this._bindToCollection ) {
  348. this.stopListening( this._bindToCollection );
  349. this._bindToCollection = null;
  350. }
  351. while ( this.length ) {
  352. this.remove( 0 );
  353. }
  354. }
  355. /**
  356. * Binds and synchronizes the collection with another one.
  357. *
  358. * The binding can be a simple factory:
  359. *
  360. * class FactoryClass {
  361. * constructor( data ) {
  362. * this.label = data.label;
  363. * }
  364. * }
  365. *
  366. * const source = new Collection( { idProperty: 'label' } );
  367. * const target = new Collection();
  368. *
  369. * target.bindTo( source ).as( FactoryClass );
  370. *
  371. * source.add( { label: 'foo' } );
  372. * source.add( { label: 'bar' } );
  373. *
  374. * console.log( target.length ); // 2
  375. * console.log( target.get( 1 ).label ); // 'bar'
  376. *
  377. * source.remove( 0 );
  378. * console.log( target.length ); // 1
  379. * console.log( target.get( 0 ).label ); // 'bar'
  380. *
  381. * or the factory driven by a custom callback:
  382. *
  383. * class FooClass {
  384. * constructor( data ) {
  385. * this.label = data.label;
  386. * }
  387. * }
  388. *
  389. * class BarClass {
  390. * constructor( data ) {
  391. * this.label = data.label;
  392. * }
  393. * }
  394. *
  395. * const source = new Collection( { idProperty: 'label' } );
  396. * const target = new Collection();
  397. *
  398. * target.bindTo( source ).using( ( item ) => {
  399. * if ( item.label == 'foo' ) {
  400. * return new FooClass( item );
  401. * } else {
  402. * return new BarClass( item );
  403. * }
  404. * } );
  405. *
  406. * source.add( { label: 'foo' } );
  407. * source.add( { label: 'bar' } );
  408. *
  409. * console.log( target.length ); // 2
  410. * console.log( target.get( 0 ) instanceof FooClass ); // true
  411. * console.log( target.get( 1 ) instanceof BarClass ); // true
  412. *
  413. * or the factory out of property name:
  414. *
  415. * const source = new Collection( { idProperty: 'label' } );
  416. * const target = new Collection();
  417. *
  418. * target.bindTo( source ).using( 'label' );
  419. *
  420. * source.add( { label: { value: 'foo' } } );
  421. * source.add( { label: { value: 'bar' } } );
  422. *
  423. * console.log( target.length ); // 2
  424. * console.log( target.get( 0 ).value ); // 'foo'
  425. * console.log( target.get( 1 ).value ); // 'bar'
  426. *
  427. * It's possible to skip specified items by returning falsy value:
  428. *
  429. * const source = new Collection();
  430. * const target = new Collection();
  431. *
  432. * target.bindTo( source ).using( item => {
  433. * if ( item.hidden ) {
  434. * return null;
  435. * }
  436. *
  437. * return item;
  438. * } );
  439. *
  440. * source.add( { hidden: true } );
  441. * source.add( { hidden: false } );
  442. *
  443. * console.log( source.length ); // 2
  444. * console.log( target.length ); // 1
  445. *
  446. * **Note**: {@link #clear} can be used to break the binding.
  447. *
  448. * @param {module:utils/collection~Collection} externalCollection A collection to be bound.
  449. * @returns {Object}
  450. * @returns {module:utils/collection~CollectionBindToChain} The binding chain object.
  451. */
  452. bindTo( externalCollection ) {
  453. if ( this._bindToCollection ) {
  454. /**
  455. * The collection cannot be bound more than once.
  456. *
  457. * @error collection-bind-to-rebind
  458. */
  459. throw new CKEditorError( 'collection-bind-to-rebind: The collection cannot be bound more than once.', this );
  460. }
  461. this._bindToCollection = externalCollection;
  462. return {
  463. as: Class => {
  464. this._setUpBindToBinding( item => new Class( item ) );
  465. },
  466. using: callbackOrProperty => {
  467. if ( typeof callbackOrProperty == 'function' ) {
  468. this._setUpBindToBinding( item => callbackOrProperty( item ) );
  469. } else {
  470. this._setUpBindToBinding( item => item[ callbackOrProperty ] );
  471. }
  472. }
  473. };
  474. }
  475. /**
  476. * Finalizes and activates a binding initiated by {#bindTo}.
  477. *
  478. * @protected
  479. * @param {Function} factory A function which produces collection items.
  480. */
  481. _setUpBindToBinding( factory ) {
  482. const externalCollection = this._bindToCollection;
  483. // Adds the item to the collection once a change has been done to the external collection.
  484. //
  485. // @private
  486. const addItem = ( evt, externalItem, index ) => {
  487. const isExternalBoundToThis = externalCollection._bindToCollection == this;
  488. const externalItemBound = externalCollection._bindToInternalToExternalMap.get( externalItem );
  489. // If an external collection is bound to this collection, which makes it a 2–way binding,
  490. // and the particular external collection item is already bound, don't add it here.
  491. // The external item has been created **out of this collection's item** and (re)adding it will
  492. // cause a loop.
  493. if ( isExternalBoundToThis && externalItemBound ) {
  494. this._bindToExternalToInternalMap.set( externalItem, externalItemBound );
  495. this._bindToInternalToExternalMap.set( externalItemBound, externalItem );
  496. } else {
  497. const item = factory( externalItem );
  498. // When there is no item we need to remember skipped index first and then we can skip this item.
  499. if ( !item ) {
  500. this._skippedIndexesFromExternal.push( index );
  501. return;
  502. }
  503. // Lets try to put item at the same index as index in external collection
  504. // but when there are a skipped items in one or both collections we need to recalculate this index.
  505. let finalIndex = index;
  506. // When we try to insert item after some skipped items from external collection we need
  507. // to include this skipped items and decrease index.
  508. //
  509. // For the following example:
  510. // external -> [ 'A', 'B - skipped for internal', 'C - skipped for internal' ]
  511. // internal -> [ A ]
  512. //
  513. // Another item is been added at the end of external collection:
  514. // external.add( 'D' )
  515. // external -> [ 'A', 'B - skipped for internal', 'C - skipped for internal', 'D' ]
  516. //
  517. // We can't just add 'D' to internal at the same index as index in external because
  518. // this will produce empty indexes what is invalid:
  519. // internal -> [ 'A', empty, empty, 'D' ]
  520. //
  521. // So we need to include skipped items and decrease index
  522. // internal -> [ 'A', 'D' ]
  523. for ( const skipped of this._skippedIndexesFromExternal ) {
  524. if ( index > skipped ) {
  525. finalIndex--;
  526. }
  527. }
  528. // We need to take into consideration that external collection could skip some items from
  529. // internal collection.
  530. //
  531. // For the following example:
  532. // internal -> [ 'A', 'B - skipped for external', 'C - skipped for external' ]
  533. // external -> [ A ]
  534. //
  535. // Another item is been added at the end of external collection:
  536. // external.add( 'D' )
  537. // external -> [ 'A', 'D' ]
  538. //
  539. // We need to include skipped items and place new item after them:
  540. // internal -> [ 'A', 'B - skipped for external', 'C - skipped for external', 'D' ]
  541. for ( const skipped of externalCollection._skippedIndexesFromExternal ) {
  542. if ( finalIndex >= skipped ) {
  543. finalIndex++;
  544. }
  545. }
  546. this._bindToExternalToInternalMap.set( externalItem, item );
  547. this._bindToInternalToExternalMap.set( item, externalItem );
  548. this.add( item, finalIndex );
  549. // After adding new element to internal collection we need update indexes
  550. // of skipped items in external collection.
  551. for ( let i = 0; i < externalCollection._skippedIndexesFromExternal.length; i++ ) {
  552. if ( finalIndex <= externalCollection._skippedIndexesFromExternal[ i ] ) {
  553. externalCollection._skippedIndexesFromExternal[ i ]++;
  554. }
  555. }
  556. }
  557. };
  558. // Load the initial content of the collection.
  559. for ( const externalItem of externalCollection ) {
  560. addItem( null, externalItem, externalCollection.getIndex( externalItem ) );
  561. }
  562. // Synchronize the with collection as new items are added.
  563. this.listenTo( externalCollection, 'add', addItem );
  564. // Synchronize the with collection as new items are removed.
  565. this.listenTo( externalCollection, 'remove', ( evt, externalItem, index ) => {
  566. const item = this._bindToExternalToInternalMap.get( externalItem );
  567. if ( item ) {
  568. this.remove( item );
  569. }
  570. // After removing element from external collection we need update/remove indexes
  571. // of skipped items in internal collection.
  572. this._skippedIndexesFromExternal = this._skippedIndexesFromExternal.reduce( ( result, skipped ) => {
  573. if ( index < skipped ) {
  574. result.push( skipped - 1 );
  575. }
  576. if ( index > skipped ) {
  577. result.push( skipped );
  578. }
  579. return result;
  580. }, [] );
  581. } );
  582. }
  583. _getItemIdBeforeAdding( item ) {
  584. const idProperty = this._idProperty;
  585. let itemId;
  586. if ( ( idProperty in item ) ) {
  587. itemId = item[ idProperty ];
  588. if ( typeof itemId != 'string' ) {
  589. /**
  590. * This item's id should be a string.
  591. *
  592. * @error collection-add-invalid-id
  593. * @param {Object} item The item being added to the collection.
  594. * @param {module:utils/collection~Collection} collection The collection the item is added to.
  595. */
  596. throw new CKEditorError( 'collection-add-invalid-id', {
  597. item,
  598. collection: this,
  599. } );
  600. }
  601. if ( this.get( itemId ) ) {
  602. /**
  603. * This item already exists in the collection.
  604. *
  605. * @error collection-add-item-already-exists
  606. * @param {Object} item The item being added to the collection.
  607. * @param {module:utils/collection~Collection} collection The collection the item is added to.
  608. */
  609. throw new CKEditorError( 'collection-add-item-already-exists', {
  610. item,
  611. collection: this
  612. } );
  613. }
  614. } else {
  615. item[ idProperty ] = itemId = uid();
  616. }
  617. return itemId;
  618. }
  619. /**
  620. * Iterable interface.
  621. *
  622. * @returns {Iterable.<*>}
  623. */
  624. [ Symbol.iterator ]() {
  625. return this._items[ Symbol.iterator ]();
  626. }
  627. /**
  628. * Fired when an item is added to the collection.
  629. *
  630. * @event add
  631. * @param {Object} item The added item.
  632. */
  633. /**
  634. * Fired when an item is removed from the collection.
  635. *
  636. * @event remove
  637. * @param {Object} item The removed item.
  638. * @param {Number} index Index from which item was removed.
  639. */
  640. }
  641. mix( Collection, EmitterMixin );
  642. /**
  643. * An object returned by the {@link module:utils/collection~Collection#bindTo `bindTo()`} method
  644. * providing functions that specify the type of the binding.
  645. *
  646. * See the {@link module:utils/collection~Collection#bindTo `bindTo()`} documentation for examples.
  647. *
  648. * @interface module:utils/collection~CollectionBindToChain
  649. */
  650. /**
  651. * Creates a callback or a property binding.
  652. *
  653. * @method #using
  654. * @param {Function|String} callbackOrProperty When the function is passed, it should return
  655. * the collection items. When the string is provided, the property value is used to create the bound collection items.
  656. */
  657. /**
  658. * Creates the class factory binding in which items of the source collection are passed to
  659. * the constructor of the specified class.
  660. *
  661. * @method #as
  662. * @param {Function} Class The class constructor used to create instances in the factory.
  663. */