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('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.13.0', {"requires": ["model-list"]});