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"]});