2
YUI 3.10.3 (build 2fb5187)
3
Copyright 2013 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
8
YUI.add('lazy-model-list', function (Y, NAME) {
11
Provides the LazyModelList class, which is a ModelList subclass that manages
12
plain objects instead of fully instantiated model instances.
15
@submodule lazy-model-list
20
LazyModelList is a subclass of ModelList that maintains a list of plain
21
JavaScript objects rather than a list of Model instances. This makes it
22
well-suited for managing large amounts of data (on the order of thousands of
23
items) that would tend to bog down a vanilla ModelList.
25
The API presented by LazyModelList is the same as that of ModelList, except that
26
in every case where ModelList would provide a Model instance, LazyModelList
27
provides a plain JavaScript object. LazyModelList also provides a `revive()`
28
method that can convert the plain object at a given index into a full Model
31
Since the items stored in a LazyModelList are plain objects and not full Model
32
instances, there are a few caveats to be aware of:
34
* Since items are plain objects and not Model instances, they contain
35
properties rather than Model attributes. To retrieve a property, use
36
`item.foo` rather than `item.get('foo')`. To set a property, use
37
`item.foo = 'bar'` rather than `item.set('foo', 'bar')`.
39
* Model attribute getters and setters aren't supported, since items in the
40
LazyModelList are stored and manipulated as plain objects with simple
41
properties rather than YUI attributes.
43
* Changes made to the plain object version of an item will not trigger or
44
bubble up Model `change` events. However, once an item is revived into a
45
full Model using the `revive()` method, changes to that Model instance
46
will trigger and bubble change events as expected.
48
* Custom `idAttribute` fields are not supported.
50
* `id` and `clientId` properties _are_ supported. If an item doesn't have a
51
`clientId` property, one will be generated automatically when the item is
52
added to a LazyModelList.
54
LazyModelList is generally much more memory efficient than ModelList when
55
managing large numbers of items, and adding/removing items is significantly
56
faster. However, the tradeoff is that LazyModelList is only well-suited for
57
storing very simple items without complex attributes, and consumers must
58
explicitly revive items into full Model instances as needed (this is not done
59
transparently for performance reasons).
67
var AttrProto = Y.Attribute.prototype,
68
GlobalEnv = YUI.namespace('Env.Model'),
76
Y.LazyModelList = Y.Base.create('lazyModelList', Y.ModelList, [], {
77
// -- Lifecycle ------------------------------------------------------------
78
initializer: function () {
79
this.after('*:change', this._afterModelChange);
82
// -- Public Methods -------------------------------------------------------
85
Deletes the specified model from the model cache to release memory. The
86
model won't be destroyed or removed from the list, just freed from the
87
cache; it can still be instantiated again using `revive()`.
89
If no model or model index is specified, all cached models in this list will
92
Note: Specifying an index is faster than specifying a model instance, since
93
the latter requires an `indexOf()` call.
96
@param {Model|Number} [model] Model or index of the model to free. If not
97
specified, all instantiated models in this list will be freed.
101
free: function (model) {
105
index = Lang.isNumber(model) ? model : this.indexOf(model);
108
// We don't detach the model because it's not being removed from
109
// the list, just being freed from memory. If something else
110
// still holds a reference to it, it may still bubble events to
111
// the list, but that's okay.
113
// `this._models` is a sparse array, which ensures that the
114
// indices of models and items match even if we don't have model
115
// instances for all items.
116
delete this._models[index];
126
Overrides ModelList#get() to return a map of property values rather than
127
performing attribute lookups.
130
@param {String} name Property name.
131
@return {String[]} Array of property values.
134
get: function (name) {
135
if (this.attrAdded(name)) {
136
return AttrProto.get.apply(this, arguments);
139
return YArray.map(this._items, function (item) {
145
Overrides ModelList#getAsHTML() to return a map of HTML-escaped property
146
values rather than performing attribute lookups.
149
@param {String} name Property name.
150
@return {String[]} Array of HTML-escaped property values.
151
@see ModelList.getAsHTML()
153
getAsHTML: function (name) {
154
if (this.attrAdded(name)) {
155
return Y.Escape.html(AttrProto.get.apply(this, arguments));
158
return YArray.map(this._items, function (item) {
159
return Y.Escape.html(item[name]);
164
Overrides ModelList#getAsURL() to return a map of URL-encoded property
165
values rather than performing attribute lookups.
168
@param {String} name Property name.
169
@return {String[]} Array of URL-encoded property values.
170
@see ModelList.getAsURL()
172
getAsURL: function (name) {
173
if (this.attrAdded(name)) {
174
return encodeURIComponent(AttrProto.get.apply(this, arguments));
177
return YArray.map(this._items, function (item) {
178
return encodeURIComponent(item[name]);
183
Returns the index of the given object or Model instance in this
187
@param {Model|Object} needle The object or Model instance to search for.
188
@return {Number} Item index, or `-1` if not found.
189
@see ModelList.indexOf()
191
indexOf: function (model) {
192
return YArray.indexOf(model && model._isYUIModel ?
193
this._models : this._items, model);
197
Overrides ModelList#reset() to work with plain objects.
200
@param {Object[]|Model[]|ModelList} [models] Models to add.
201
@param {Object} [options] Options.
203
@see ModelList.reset()
205
reset: function (items, options) {
206
items || (items = []);
207
options || (options = {});
209
var facade = Y.merge({src: 'reset'}, options);
211
// Convert `items` into an array of plain objects, since we don't want
213
items = items._isYUIModelList ? items.map(this._modelToObject) :
214
YArray.map(items, this._modelToObject);
216
facade.models = items;
218
if (options.silent) {
219
this._defResetFn(facade);
221
// Sort the items before firing the reset event.
222
if (this.comparator) {
223
items.sort(Y.bind(this._sort, this));
226
this.fire(EVT_RESET, facade);
233
Revives an item (or all items) into a full Model instance. The _item_
234
argument may be the index of an object in this list, an actual object (which
235
must exist in the list), or may be omitted to revive all items in the list.
237
Once revived, Model instances are attached to this list and cached so that
238
reviving them in the future doesn't require another Model instantiation. Use
239
the `free()` method to explicitly uncache and detach a previously revived
242
Note: Specifying an index rather than an object will be faster, since
243
objects require an `indexOf()` lookup in order to retrieve the index.
246
@param {Number|Object} [item] Index of the object to revive, or the object
247
itself. If an object, that object must exist in this list. If not
248
specified, all items in the list will be revived and an array of models
250
@return {Model|Model[]|null} Revived Model instance, array of revived Model
251
instances, or `null` if the given index or object was not found in this
255
revive: function (item) {
258
if (item || item === 0) {
259
return this._revive(Lang.isNumber(item) ? item :
264
for (i = 0, len = this._items.length; i < len; i++) {
265
models.push(this._revive(i));
273
Overrides ModelList#toJSON() to use toArray() instead, since it's more
274
efficient for LazyModelList.
277
@return {Object[]} Array of objects.
278
@see ModelList.toJSON()
280
toJSON: function () {
281
return this.toArray();
284
// -- Protected Methods ----------------------------------------------------
287
Overrides ModelList#add() to work with plain objects.
290
@param {Object|Model} item Object or model to add.
291
@param {Object} [options] Options.
292
@return {Object} Added item.
294
@see ModelList._add()
296
_add: function (item, options) {
299
options || (options = {});
301
// If the item is a model instance, convert it to a plain object.
302
item = this._modelToObject(item);
304
// Ensure that the item has a clientId.
305
if (!('clientId' in item)) {
306
item.clientId = this._generateClientId();
309
if (this._isInList(item)) {
310
this.fire(EVT_ERROR, {
311
error: 'Model is already in the list.',
319
facade = Y.merge(options, {
320
index: 'index' in options ? options.index : this._findIndex(item),
324
options.silent ? this._defAddFn(facade) : this.fire(EVT_ADD, facade);
330
Overrides ModelList#clear() to support `this._models`.
334
@see ModelList.clear()
336
_clear: function () {
337
YArray.each(this._models, this._detachList, this);
339
this._clientIdMap = {};
346
Generates an ad-hoc clientId for a non-instantiated Model.
348
@method _generateClientId
349
@return {String} Unique clientId.
352
_generateClientId: function () {
353
GlobalEnv.lastId || (GlobalEnv.lastId = 0);
354
return this.model.NAME + '_' + (GlobalEnv.lastId += 1);
358
Returns `true` if the given item is in this list, `false` otherwise.
361
@param {Object} item Plain object item.
362
@return {Boolean} `true` if the item is in this list, `false` otherwise.
365
_isInList: function (item) {
366
return !!(('clientId' in item && this._clientIdMap[item.clientId]) ||
367
('id' in item && this._idMap[item.id]));
371
Converts a Model instance into a plain object. If _model_ is not a Model
372
instance, it will be returned as is.
374
This method differs from Model#toJSON() in that it doesn't delete the
377
@method _modelToObject
378
@param {Model|Object} model Model instance to convert.
379
@return {Object} Plain object.
382
_modelToObject: function (model) {
383
if (model._isYUIModel) {
384
model = model.getAttrs();
385
delete model.destroyed;
386
delete model.initialized;
393
Overrides ModelList#_remove() to convert Model instances to indices
394
before removing to ensure consistency in the `remove` event facade.
397
@param {Object|Model} item Object or model to remove.
398
@param {Object} [options] Options.
399
@return {Object} Removed object.
402
_remove: function (item, options) {
403
// If the given item is a model instance, turn it into an index before
404
// calling the parent _remove method, since we only want to deal with
405
// the plain object version.
406
if (item._isYUIModel) {
407
item = this.indexOf(item);
410
return Y.ModelList.prototype._remove.call(this, item, options);
414
Revives a single model at the specified index and returns it. This is the
415
underlying implementation for `revive()`.
418
@param {Number} index Index of the item to revive.
419
@return {Model} Revived model.
422
_revive: function (index) {
429
item = this._items[index];
435
model = this._models[index];
438
model = new this.model(item);
440
// The clientId attribute is read-only, but revived models should
441
// have the same clientId as the original object, so we need to set
443
model._set('clientId', item.clientId);
445
this._attachList(model);
446
this._models[index] = model;
452
// -- Event Handlers -------------------------------------------------------
455
Handles `change` events on revived models and updates the original objects
458
@method _afterModelChange
459
@param {EventFacade} e
462
_afterModelChange: function (e) {
463
var changed = e.changed,
464
item = this._clientIdMap[e.target.get('clientId')],
468
for (name in changed) {
469
if (changed.hasOwnProperty(name)) {
470
item[name] = changed[name].newVal;
476
// -- Default Event Handlers -----------------------------------------------
479
Overrides ModelList#_defAddFn() to support plain objects.
482
@param {EventFacade} e
485
_defAddFn: function (e) {
488
this._clientIdMap[item.clientId] = item;
490
if (Lang.isValue(item.id)) {
491
this._idMap[item.id] = item;
494
this._items.splice(e.index, 0, item);
498
Overrides ModelList#_defRemoveFn() to support plain objects.
501
@param {EventFacade} e
504
_defRemoveFn: function (e) {
507
model = this._models[index];
509
delete this._clientIdMap[item.clientId];
512
delete this._idMap[item.id];
516
this._detachList(model);
519
this._items.splice(index, 1);
520
this._models.splice(index, 1);
525
}, '3.10.3', {"requires": ["model-list"]});