2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
8 YUI.add('model-list', function (Y, NAME) {
11 Provides an API for managing an ordered list of Model instances.
19 Provides an API for managing an ordered list of Model instances.
21 In addition to providing convenient `add`, `create`, `reset`, and `remove`
22 methods for managing the models in the list, ModelLists are also bubble targets
23 for events on the model instances they contain. This means, for example, that
24 you can add several models to a list, and then subscribe to the `*:change` event
25 on the list to be notified whenever any model in the list changes.
27 ModelLists also maintain sort order efficiently as models are added and removed,
28 based on a custom `comparator` function you may define (if no comparator is
29 defined, models are sorted in insertion order).
35 @param {Object} config Config options.
36 @param {Model|Model[]|ModelList|Object|Object[]} config.items Model
37 instance, array of model instances, or ModelList to add to this list on
38 init. The `add` event will not be fired for models added on init.
42 var AttrProto = Y.Attribute.prototype,
47 Fired when a model is added to the list.
49 Listen to the `on` phase of this event to be notified before a model is
50 added to the list. Calling `e.preventDefault()` during the `on` phase will
51 prevent the model from being added.
53 Listen to the `after` phase of this event to be notified after a model has
54 been added to the list.
57 @param {Model} model The model being added.
58 @param {Number} index The index at which the model will be added.
59 @preventable _defAddFn
64 Fired when a model is created or updated via the `create()` method, but
65 before the model is actually saved or added to the list. The `add` event
66 will be fired after the model has been saved and added to the list.
69 @param {Model} model The model being created/updated.
72 EVT_CREATE = 'create',
75 Fired when an error occurs, such as when an attempt is made to add a
76 duplicate model to the list, or when a sync layer response can't be parsed.
79 @param {Any} error Error message, object, or exception generated by the
80 error. Calling `toString()` on this should result in a meaningful error
82 @param {String} src Source of the error. May be one of the following (or any
83 custom error source defined by a ModelList subclass):
85 * `add`: Error while adding a model (probably because it's already in the
86 list and can't be added again). The model in question will be provided
87 as the `model` property on the event facade.
88 * `parse`: An error parsing a JSON response. The response in question will
89 be provided as the `response` property on the event facade.
90 * `remove`: Error while removing a model (probably because it isn't in the
91 list and can't be removed). The model in question will be provided as
92 the `model` property on the event facade.
97 Fired after models are loaded from a sync layer.
100 @param {Object} parsed The parsed version of the sync layer's response to
102 @param {Mixed} response The sync layer's raw, unparsed response to the load
109 Fired when a model is removed from the list.
111 Listen to the `on` phase of this event to be notified before a model is
112 removed from the list. Calling `e.preventDefault()` during the `on` phase
113 will prevent the model from being removed.
115 Listen to the `after` phase of this event to be notified after a model has
116 been removed from the list.
119 @param {Model} model The model being removed.
120 @param {Number} index The index of the model being removed.
121 @preventable _defRemoveFn
123 EVT_REMOVE = 'remove',
126 Fired when the list is completely reset via the `reset()` method or sorted
127 via the `sort()` method.
129 Listen to the `on` phase of this event to be notified before the list is
130 reset. Calling `e.preventDefault()` during the `on` phase will prevent
131 the list from being reset.
133 Listen to the `after` phase of this event to be notified after the list has
137 @param {Model[]} models Array of the list's new models after the reset.
138 @param {String} src Source of the event. May be either `'reset'` or
140 @preventable _defResetFn
144 function ModelList() {
145 ModelList.superclass.constructor.apply(this, arguments);
148 Y.ModelList = Y.extend(ModelList, Y.Base, {
149 // -- Public Properties ----------------------------------------------------
152 The `Model` class or subclass of the models in this list.
154 The class specified here will be used to create model instances
155 automatically based on attribute hashes passed to the `add()`, `create()`,
156 and `reset()` methods.
158 You may specify the class as an actual class reference or as a string that
159 resolves to a class reference at runtime (the latter can be useful if the
160 specified class will be loaded lazily).
168 // -- Protected Properties -------------------------------------------------
171 Total hack to allow us to identify ModelList instances without using
172 `instanceof`, which won't work when the instance was created in another
173 window or YUI sandbox.
175 @property _isYUIModelList
181 _isYUIModelList: true,
183 // -- Lifecycle Methods ----------------------------------------------------
184 initializer: function (config) {
185 config || (config = {});
187 var model = this.model = config.model || this.model;
189 if (typeof model === 'string') {
190 // Look for a namespaced Model class on `Y`.
191 this.model = Y.Object.getValue(Y, model.split('.'));
194 Y.error('ModelList: Model class not found: ' + model);
198 this.publish(EVT_ADD, {defaultFn: this._defAddFn});
199 this.publish(EVT_RESET, {defaultFn: this._defResetFn});
200 this.publish(EVT_REMOVE, {defaultFn: this._defRemoveFn});
202 this.after('*:idChange', this._afterIdChange);
207 this.add(config.items, {silent: true});
211 destructor: function () {
215 // -- Public Methods -------------------------------------------------------
218 Adds the specified model or array of models to this list. You may also pass
219 another ModelList instance, in which case all the models in that list will
220 be added to this one as well.
224 // Add a single model instance.
225 list.add(new Model({foo: 'bar'}));
227 // Add a single model, creating a new instance automatically.
228 list.add({foo: 'bar'});
230 // Add multiple models, creating new instances automatically.
236 // Add all the models in another ModelList instance.
240 @param {Model|Model[]|ModelList|Object|Object[]} models Model or array of
241 models to add. May be existing model instances or hashes of model
242 attributes, in which case new model instances will be created from the
243 hashes. You may also pass a ModelList instance to add all the models it
245 @param {Object} [options] Data to be mixed into the event facade of the
246 `add` event(s) for the added models.
248 @param {Number} [options.index] Index at which to insert the added
249 models. If not specified, the models will automatically be inserted
250 in the appropriate place according to the current sort order as
251 dictated by the `comparator()` method, if any.
252 @param {Boolean} [options.silent=false] If `true`, no `add` event(s)
255 @return {Model|Model[]} Added model or array of added models.
257 add: function (models, options) {
258 var isList = models._isYUIModelList;
260 if (isList || Lang.isArray(models)) {
261 return YArray.map(isList ? models.toArray() : models, function (model, index) {
262 var modelOptions = options || {};
264 // When an explicit insertion index is specified, ensure that
265 // the index is increased by one for each subsequent item in the
267 if ('index' in modelOptions) {
268 modelOptions = Y.merge(modelOptions, {
269 index: modelOptions.index + index
273 return this._add(model, modelOptions);
276 return this._add(models, options);
281 Define this method to provide a function that takes a model as a parameter
282 and returns a value by which that model should be sorted relative to other
285 By default, no comparator is defined, meaning that models will not be sorted
286 (they'll be stored in the order they're added).
289 var list = new Y.ModelList({model: Y.Model});
291 list.comparator = function (model) {
292 return model.get('id'); // Sort models by id.
296 @param {Model} model Model being sorted.
297 @return {Number|String} Value by which the model should be sorted relative
298 to other models in this list.
301 // comparator is not defined by default
304 Creates or updates the specified model on the server, then adds it to this
305 list if the server indicates success.
308 @param {Model|Object} model Model to create. May be an existing model
309 instance or a hash of model attributes, in which case a new model instance
310 will be created from the hash.
311 @param {Object} [options] Options to be passed to the model's `sync()` and
312 `set()` methods and mixed into the `create` and `add` event facades.
313 @param {Boolean} [options.silent=false] If `true`, no `add` event(s) will
315 @param {Function} [callback] Called when the sync operation finishes.
316 @param {Error} callback.err If an error occurred, this parameter will
317 contain the error. If the sync operation succeeded, _err_ will be
319 @param {Any} callback.response The server's response.
320 @return {Model} Created model.
322 create: function (model, options, callback) {
325 // Allow callback as second arg.
326 if (typeof options === 'function') {
331 options || (options = {});
333 if (!model._isYUIModel) {
334 model = new this.model(model);
337 self.fire(EVT_CREATE, Y.merge(options, {
341 return model.save(options, function (err) {
343 self.add(model, options);
347 callback.apply(null, arguments);
353 Executes the supplied function on each model in this list.
355 By default, the callback function's `this` object will refer to the model
356 currently being iterated. Specify a `thisObj` to override the `this` object
359 Note: Iteration is performed on a copy of the internal array of models, so
360 it's safe to delete a model from the list during iteration.
363 @param {Function} callback Function to execute on each model.
364 @param {Model} callback.model Model instance.
365 @param {Number} callback.index Index of the current model.
366 @param {ModelList} callback.list The ModelList being iterated.
367 @param {Object} [thisObj] Object to use as the `this` object when executing
372 each: function (callback, thisObj) {
373 var items = this._items.concat(),
376 for (i = 0, len = items.length; i < len; i++) {
378 callback.call(thisObj || item, item, i, this);
385 Executes the supplied function on each model in this list. Returns an array
386 containing the models for which the supplied function returned a truthy
389 The callback function's `this` object will refer to this ModelList. Use
390 `Y.bind()` to bind the `this` object to another object if desired.
394 // Get an array containing only the models whose "enabled" attribute is
396 var filtered = list.filter(function (model) {
397 return model.get('enabled');
400 // Get a new ModelList containing only the models whose "enabled"
401 // attribute is truthy.
402 var filteredList = list.filter({asList: true}, function (model) {
403 return model.get('enabled');
407 @param {Object} [options] Filter options.
408 @param {Boolean} [options.asList=false] If truthy, results will be
409 returned as a new ModelList instance rather than as an array.
411 @param {Function} callback Function to execute on each model.
412 @param {Model} callback.model Model instance.
413 @param {Number} callback.index Index of the current model.
414 @param {ModelList} callback.list The ModelList being filtered.
416 @return {Array|ModelList} Array of models for which the callback function
417 returned a truthy value (empty if it never returned a truthy value). If
418 the `options.asList` option is truthy, a new ModelList instance will be
419 returned instead of an array.
422 filter: function (options, callback) {
427 // Allow options as first arg.
428 if (typeof options === 'function') {
433 for (i = 0, len = items.length; i < len; ++i) {
436 if (callback.call(this, item, i, this)) {
441 if (options.asList) {
442 list = new this.constructor({model: this.model});
444 if (filtered.length) {
445 list.add(filtered, {silent: true});
455 If _name_ refers to an attribute on this ModelList instance, returns the
456 value of that attribute. Otherwise, returns an array containing the values
457 of the specified attribute from each model in this list.
460 @param {String} name Attribute name or object property path.
461 @return {Any|Array} Attribute value or array of attribute values.
464 get: function (name) {
465 if (this.attrAdded(name)) {
466 return AttrProto.get.apply(this, arguments);
469 return this.invoke('get', name);
473 If _name_ refers to an attribute on this ModelList instance, returns the
474 HTML-escaped value of that attribute. Otherwise, returns an array containing
475 the HTML-escaped values of the specified attribute from each model in this
478 The values are escaped using `Escape.html()`.
481 @param {String} name Attribute name or object property path.
482 @return {String|String[]} HTML-escaped value or array of HTML-escaped
484 @see Model.getAsHTML()
486 getAsHTML: function (name) {
487 if (this.attrAdded(name)) {
488 return Y.Escape.html(AttrProto.get.apply(this, arguments));
491 return this.invoke('getAsHTML', name);
495 If _name_ refers to an attribute on this ModelList instance, returns the
496 URL-encoded value of that attribute. Otherwise, returns an array containing
497 the URL-encoded values of the specified attribute from each model in this
500 The values are encoded using the native `encodeURIComponent()` function.
503 @param {String} name Attribute name or object property path.
504 @return {String|String[]} URL-encoded value or array of URL-encoded values.
505 @see Model.getAsURL()
507 getAsURL: function (name) {
508 if (this.attrAdded(name)) {
509 return encodeURIComponent(AttrProto.get.apply(this, arguments));
512 return this.invoke('getAsURL', name);
516 Returns the model with the specified _clientId_, or `null` if not found.
518 @method getByClientId
519 @param {String} clientId Client id.
520 @return {Model} Model, or `null` if not found.
522 getByClientId: function (clientId) {
523 return this._clientIdMap[clientId] || null;
527 Returns the model with the specified _id_, or `null` if not found.
529 Note that models aren't expected to have an id until they're saved, so if
530 you're working with unsaved models, it may be safer to call
534 @param {String|Number} id Model id.
535 @return {Model} Model, or `null` if not found.
537 getById: function (id) {
538 return this._idMap[id] || null;
542 Calls the named method on every model in the list. Any arguments provided
543 after _name_ will be passed on to the invoked method.
546 @param {String} name Name of the method to call on each model.
547 @param {Any} [args*] Zero or more arguments to pass to the invoked method.
548 @return {Array} Array of return values, indexed according to the index of
549 the model on which the method was called.
551 invoke: function (name /*, args* */) {
552 var args = [this._items, name].concat(YArray(arguments, 1, true));
553 return YArray.invoke.apply(YArray, args);
557 Returns the model at the specified _index_.
560 @param {Number} index Index of the model to fetch.
561 @return {Model} The model at the specified index, or `undefined` if there
565 // item() is inherited from ArrayList.
568 Loads this list of models from the server.
570 This method delegates to the `sync()` method to perform the actual load
571 operation, which is an asynchronous action. Specify a _callback_ function to
572 be notified of success or failure.
574 If the load operation succeeds, a `reset` event will be fired.
577 @param {Object} [options] Options to be passed to `sync()` and to
578 `reset()` when adding the loaded models. It's up to the custom sync
579 implementation to determine what options it supports or requires, if any.
580 @param {Function} [callback] Called when the sync operation finishes.
581 @param {Error} callback.err If an error occurred, this parameter will
582 contain the error. If the sync operation succeeded, _err_ will be
584 @param {Any} callback.response The server's response. This value will
585 be passed to the `parse()` method, which is expected to parse it and
586 return an array of model attribute hashes.
589 load: function (options, callback) {
592 // Allow callback as only arg.
593 if (typeof options === 'function') {
598 options || (options = {});
600 this.sync('read', options, function (err, response) {
612 self.fire(EVT_ERROR, facade);
615 if (!self._loadEvent) {
616 self._loadEvent = self.publish(EVT_LOAD, {
621 parsed = facade.parsed = self._parse(response);
623 self.reset(parsed, options);
624 self.fire(EVT_LOAD, facade);
628 callback.apply(null, arguments);
636 Executes the specified function on each model in this list and returns an
637 array of the function's collected return values.
640 @param {Function} fn Function to execute on each model.
641 @param {Model} fn.model Current model being iterated.
642 @param {Number} fn.index Index of the current model in the list.
643 @param {Model[]} fn.models Array of models being iterated.
644 @param {Object} [thisObj] `this` object to use when calling _fn_.
645 @return {Array} Array of return values from _fn_.
647 map: function (fn, thisObj) {
648 return YArray.map(this._items, fn, thisObj);
652 Called to parse the _response_ when the list is loaded from the server.
653 This method receives a server _response_ and is expected to return an array
654 of model attribute hashes.
656 The default implementation assumes that _response_ is either an array of
657 attribute hashes or a JSON string that can be parsed into an array of
658 attribute hashes. If _response_ is a JSON string and either `Y.JSON` or the
659 native `JSON` object are available, it will be parsed automatically. If a
660 parse error occurs, an `error` event will be fired and the model will not be
663 You may override this method to implement custom parsing logic if necessary.
666 @param {Any} response Server response.
667 @return {Object[]} Array of model attribute hashes.
669 parse: function (response) {
670 if (typeof response === 'string') {
672 return Y.JSON.parse(response) || [];
674 this.fire(EVT_ERROR, {
684 return response || [];
688 Removes the specified model or array of models from this list. You may also
689 pass another ModelList instance to remove all the models that are in both
690 that instance and this instance, or pass numerical indices to remove the
691 models at those indices.
694 @param {Model|Model[]|ModelList|Number|Number[]} models Models or indices of
696 @param {Object} [options] Data to be mixed into the event facade of the
697 `remove` event(s) for the removed models.
699 @param {Boolean} [options.silent=false] If `true`, no `remove` event(s)
702 @return {Model|Model[]} Removed model or array of removed models.
704 remove: function (models, options) {
705 var isList = models._isYUIModelList;
707 if (isList || Lang.isArray(models)) {
708 // We can't remove multiple models by index because the indices will
709 // change as we remove them, so we need to get the actual models
711 models = YArray.map(isList ? models.toArray() : models, function (model) {
712 if (Lang.isNumber(model)) {
713 return this.item(model);
719 return YArray.map(models, function (model) {
720 return this._remove(model, options);
723 return this._remove(models, options);
728 Completely replaces all models in the list with those specified, and fires a
729 single `reset` event.
731 Use `reset` when you want to add or remove a large number of items at once
732 with less overhead, and without firing `add` or `remove` events for each
736 @param {Model[]|ModelList|Object[]} [models] Models to add. May be existing
737 model instances or hashes of model attributes, in which case new model
738 instances will be created from the hashes. If a ModelList is passed, all
739 the models in that list will be added to this list. Calling `reset()`
740 without passing in any models will clear the list.
741 @param {Object} [options] Data to be mixed into the event facade of the
744 @param {Boolean} [options.silent=false] If `true`, no `reset` event will
749 reset: function (models, options) {
750 models || (models = []);
751 options || (options = {});
753 var facade = Y.merge({src: 'reset'}, options);
755 if (models._isYUIModelList) {
756 models = models.toArray();
758 models = YArray.map(models, function (model) {
759 return model._isYUIModel ? model : new this.model(model);
763 facade.models = models;
765 if (options.silent) {
766 this._defResetFn(facade);
768 // Sort the models before firing the reset event.
769 if (this.comparator) {
770 models.sort(Y.bind(this._sort, this));
773 this.fire(EVT_RESET, facade);
780 Executes the supplied function on each model in this list, and stops
781 iterating if the callback returns `true`.
783 By default, the callback function's `this` object will refer to the model
784 currently being iterated. Specify a `thisObj` to override the `this` object
787 Note: Iteration is performed on a copy of the internal array of models, so
788 it's safe to delete a model from the list during iteration.
791 @param {Function} callback Function to execute on each model.
792 @param {Model} callback.model Model instance.
793 @param {Number} callback.index Index of the current model.
794 @param {ModelList} callback.list The ModelList being iterated.
795 @param {Object} [thisObj] Object to use as the `this` object when executing
797 @return {Boolean} `true` if the callback returned `true` for any item,
801 some: function (callback, thisObj) {
802 var items = this._items.concat(),
805 for (i = 0, len = items.length; i < len; i++) {
808 if (callback.call(thisObj || item, item, i, this)) {
817 Forcibly re-sorts the list.
819 Usually it shouldn't be necessary to call this method since the list
820 maintains its sort order when items are added and removed, but if you change
821 the `comparator` function after items are already in the list, you'll need
825 @param {Object} [options] Data to be mixed into the event facade of the
827 @param {Boolean} [options.silent=false] If `true`, no `reset` event will
829 @param {Boolean} [options.descending=false] If `true`, the sort is
830 performed in descending order.
833 sort: function (options) {
834 if (!this.comparator) {
838 var models = this._items.concat(),
841 options || (options = {});
843 models.sort(Y.rbind(this._sort, this, options));
845 facade = Y.merge(options, {
850 if (options.silent) {
851 this._defResetFn(facade);
853 this.fire(EVT_RESET, facade);
860 Override this method to provide a custom persistence implementation for this
861 list. The default method just calls the callback without actually doing
864 This method is called internally by `load()` and its implementations relies
865 on the callback being called. This effectively means that when a callback is
866 provided, it must be called at some point for the class to operate correctly.
869 @param {String} action Sync action to perform. May be one of the following:
871 * `create`: Store a list of newly-created models for the first time.
872 * `delete`: Delete a list of existing models.
873 * `read` : Load a list of existing models.
874 * `update`: Update a list of existing models.
876 Currently, model lists only make use of the `read` action, but other
877 actions may be used in future versions.
879 @param {Object} [options] Sync options. It's up to the custom sync
880 implementation to determine what options it supports or requires, if any.
881 @param {Function} [callback] Called when the sync operation finishes.
882 @param {Error} callback.err If an error occurred, this parameter will
883 contain the error. If the sync operation succeeded, _err_ will be
885 @param {Any} [callback.response] The server's response. This value will
886 be passed to the `parse()` method, which is expected to parse it and
887 return an array of model attribute hashes.
889 sync: function (/* action, options, callback */) {
890 var callback = YArray(arguments, 0, true).pop();
892 if (typeof callback === 'function') {
898 Returns an array containing the models in this list.
901 @return {Array} Array containing the models in this list.
903 toArray: function () {
904 return this._items.concat();
908 Returns an array containing attribute hashes for each model in this list,
909 suitable for being passed to `Y.JSON.stringify()`.
911 Under the hood, this method calls `toJSON()` on each model in the list and
912 pushes the results into an array.
915 @return {Object[]} Array of model attribute hashes.
918 toJSON: function () {
919 return this.map(function (model) {
920 return model.toJSON();
924 // -- Protected Methods ----------------------------------------------------
927 Adds the specified _model_ if it isn't already in this list.
929 If the model's `clientId` or `id` matches that of a model that's already in
930 the list, an `error` event will be fired and the model will not be added.
933 @param {Model|Object} model Model or object to add.
934 @param {Object} [options] Data to be mixed into the event facade of the
935 `add` event for the added model.
936 @param {Boolean} [options.silent=false] If `true`, no `add` event will be
938 @return {Model} The added model.
941 _add: function (model, options) {
944 options || (options = {});
946 if (!model._isYUIModel) {
947 model = new this.model(model);
950 id = model.get('id');
952 if (this._clientIdMap[model.get('clientId')]
953 || (Lang.isValue(id) && this._idMap[id])) {
955 this.fire(EVT_ERROR, {
956 error: 'Model is already in the list.',
964 facade = Y.merge(options, {
965 index: 'index' in options ? options.index : this._findIndex(model),
969 if (options.silent) {
970 this._defAddFn(facade);
972 this.fire(EVT_ADD, facade);
979 Adds this list as a bubble target for the specified model's events.
982 @param {Model} model Model to attach to this list.
985 _attachList: function (model) {
986 // Attach this list and make it a bubble target for the model.
987 model.lists.push(this);
988 model.addTarget(this);
992 Clears all internal state and the internal list of models, returning this
993 list to an empty state. Automatically detaches all models in the list.
998 _clear: function () {
999 YArray.each(this._items, this._detachList, this);
1001 this._clientIdMap = {};
1007 Compares the value _a_ to the value _b_ for sorting purposes. Values are
1008 assumed to be the result of calling a model's `comparator()` method. You can
1009 override this method to implement custom sorting logic, such as a descending
1010 sort or multi-field sorting.
1013 @param {Mixed} a First value to compare.
1014 @param {Mixed} b Second value to compare.
1015 @return {Number} `-1` if _a_ should come before _b_, `0` if they're
1016 equivalent, `1` if _a_ should come after _b_.
1020 _compare: function (a, b) {
1021 return a < b ? -1 : (a > b ? 1 : 0);
1025 Removes this list as a bubble target for the specified model's events.
1028 @param {Model} model Model to detach.
1031 _detachList: function (model) {
1032 var index = YArray.indexOf(model.lists, this);
1035 model.lists.splice(index, 1);
1036 model.removeTarget(this);
1041 Returns the index at which the given _model_ should be inserted to maintain
1042 the sort order of the list.
1045 @param {Model} model The model being inserted.
1046 @return {Number} Index at which the model should be inserted.
1049 _findIndex: function (model) {
1050 var items = this._items,
1053 item, middle, needle;
1055 if (!this.comparator || !max) {
1059 needle = this.comparator(model);
1061 // Perform an iterative binary search to determine the correct position
1062 // based on the return value of the `comparator` function.
1064 middle = (min + max) >> 1; // Divide by two and discard remainder.
1065 item = items[middle];
1067 if (this._compare(this.comparator(item), needle) < 0) {
1078 Calls the public, overrideable `parse()` method and returns the result.
1080 Override this method to provide a custom pre-parsing implementation. This
1081 provides a hook for custom persistence implementations to "prep" a response
1082 before calling the `parse()` method.
1085 @param {Any} response Server response.
1086 @return {Object[]} Array of model attribute hashes.
1088 @see ModelList.parse()
1091 _parse: function (response) {
1092 return this.parse(response);
1096 Removes the specified _model_ if it's in this list.
1099 @param {Model|Number} model Model or index of the model to remove.
1100 @param {Object} [options] Data to be mixed into the event facade of the
1101 `remove` event for the removed model.
1102 @param {Boolean} [options.silent=false] If `true`, no `remove` event will
1104 @return {Model} Removed model.
1107 _remove: function (model, options) {
1110 options || (options = {});
1112 if (Lang.isNumber(model)) {
1114 model = this.item(index);
1116 index = this.indexOf(model);
1119 if (index === -1 || !model) {
1120 this.fire(EVT_ERROR, {
1121 error: 'Model is not in the list.',
1130 facade = Y.merge(options, {
1135 if (options.silent) {
1136 this._defRemoveFn(facade);
1138 this.fire(EVT_REMOVE, facade);
1145 Array sort function used by `sort()` to re-sort the models in the list.
1148 @param {Model} a First model to compare.
1149 @param {Model} b Second model to compare.
1150 @param {Object} [options] Options passed from `sort()` function.
1151 @param {Boolean} [options.descending=false] If `true`, the sort is
1152 performed in descending order.
1153 @return {Number} `-1` if _a_ is less than _b_, `0` if equal, `1` if greater
1154 (for ascending order, the reverse for descending order).
1157 _sort: function (a, b, options) {
1158 var result = this._compare(this.comparator(a), this.comparator(b));
1160 // Early return when items are equal in their sort comparison.
1165 // Flips sign when the sort is to be peformed in descending order.
1166 return options && options.descending ? -result : result;
1169 // -- Event Handlers -------------------------------------------------------
1172 Updates the model maps when a model's `id` attribute changes.
1174 @method _afterIdChange
1175 @param {EventFacade} e
1178 _afterIdChange: function (e) {
1179 var newVal = e.newVal,
1180 prevVal = e.prevVal,
1183 if (Lang.isValue(prevVal)) {
1184 if (this._idMap[prevVal] === target) {
1185 delete this._idMap[prevVal];
1187 // The model that changed isn't in this list. Probably just a
1188 // bubbled change event from a nested Model List.
1192 // The model had no previous id. Verify that it exists in this list
1193 // before continuing.
1194 if (this.indexOf(target) === -1) {
1199 if (Lang.isValue(newVal)) {
1200 this._idMap[newVal] = target;
1204 // -- Default Event Handlers -----------------------------------------------
1207 Default event handler for `add` events.
1210 @param {EventFacade} e
1213 _defAddFn: function (e) {
1214 var model = e.model,
1215 id = model.get('id');
1217 this._clientIdMap[model.get('clientId')] = model;
1219 if (Lang.isValue(id)) {
1220 this._idMap[id] = model;
1223 this._attachList(model);
1224 this._items.splice(e.index, 0, model);
1228 Default event handler for `remove` events.
1230 @method _defRemoveFn
1231 @param {EventFacade} e
1234 _defRemoveFn: function (e) {
1235 var model = e.model,
1236 id = model.get('id');
1238 this._detachList(model);
1239 delete this._clientIdMap[model.get('clientId')];
1241 if (Lang.isValue(id)) {
1242 delete this._idMap[id];
1245 this._items.splice(e.index, 1);
1249 Default event handler for `reset` events.
1252 @param {EventFacade} e
1255 _defResetFn: function (e) {
1256 // When fired from the `sort` method, we don't need to clear the list or
1257 // add any models, since the existing models are sorted in place.
1258 if (e.src === 'sort') {
1259 this._items = e.models.concat();
1265 if (e.models.length) {
1266 this.add(e.models, {silent: true});
1273 Y.augment(ModelList, Y.ArrayList);
1276 }, '3.13.0', {"requires": ["array-extras", "array-invoke", "arraylist", "base-build", "escape", "json-parse", "model"]});