~michael.nelson/ubuntu-webcatalog/1267731-import-sca-apps-error

« back to all changes in this revision

Viewing changes to src/webcatalog/static/yui/3.10.3/build/lazy-model-list/lazy-model-list.js

  • Committer: Tarmac
  • Author(s): Stephen Stewart
  • Date: 2013-06-26 09:19:32 UTC
  • mfrom: (184.1.4 ubuntu-global-nav)
  • Revision ID: tarmac-20130626091932-8urtuli368k8p7ds
[r=beuno,jonas-drange] add ubuntu global nav to apps.ubuntu.com

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
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/
 
6
*/
 
7
 
 
8
YUI.add('lazy-model-list', function (Y, NAME) {
 
9
 
 
10
/**
 
11
Provides the LazyModelList class, which is a ModelList subclass that manages
 
12
plain objects instead of fully instantiated model instances.
 
13
 
 
14
@module app
 
15
@submodule lazy-model-list
 
16
@since 3.6.0
 
17
**/
 
18
 
 
19
/**
 
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.
 
24
 
 
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
 
29
instance.
 
30
 
 
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:
 
33
 
 
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')`.
 
38
 
 
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.
 
42
 
 
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.
 
47
 
 
48
  * Custom `idAttribute` fields are not supported.
 
49
 
 
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.
 
53
 
 
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).
 
60
 
 
61
@class LazyModelList
 
62
@extends ModelList
 
63
@constructor
 
64
@since 3.6.0
 
65
**/
 
66
 
 
67
var AttrProto = Y.Attribute.prototype,
 
68
    GlobalEnv = YUI.namespace('Env.Model'),
 
69
    Lang      = Y.Lang,
 
70
    YArray    = Y.Array,
 
71
 
 
72
    EVT_ADD   = 'add',
 
73
    EVT_ERROR = 'error',
 
74
    EVT_RESET = 'reset';
 
75
 
 
76
Y.LazyModelList = Y.Base.create('lazyModelList', Y.ModelList, [], {
 
77
    // -- Lifecycle ------------------------------------------------------------
 
78
    initializer: function () {
 
79
        this.after('*:change', this._afterModelChange);
 
80
    },
 
81
 
 
82
    // -- Public Methods -------------------------------------------------------
 
83
 
 
84
    /**
 
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()`.
 
88
 
 
89
    If no model or model index is specified, all cached models in this list will
 
90
    be freed.
 
91
 
 
92
    Note: Specifying an index is faster than specifying a model instance, since
 
93
    the latter requires an `indexOf()` call.
 
94
 
 
95
    @method free
 
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.
 
98
    @chainable
 
99
    @see revive()
 
100
    **/
 
101
    free: function (model) {
 
102
        var index;
 
103
 
 
104
        if (model) {
 
105
            index = Lang.isNumber(model) ? model : this.indexOf(model);
 
106
 
 
107
            if (index >= 0) {
 
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.
 
112
                //
 
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];
 
117
            }
 
118
        } else {
 
119
            this._models = [];
 
120
        }
 
121
 
 
122
        return this;
 
123
    },
 
124
 
 
125
    /**
 
126
    Overrides ModelList#get() to return a map of property values rather than
 
127
    performing attribute lookups.
 
128
 
 
129
    @method get
 
130
    @param {String} name Property name.
 
131
    @return {String[]} Array of property values.
 
132
    @see ModelList.get()
 
133
    **/
 
134
    get: function (name) {
 
135
        if (this.attrAdded(name)) {
 
136
            return AttrProto.get.apply(this, arguments);
 
137
        }
 
138
 
 
139
        return YArray.map(this._items, function (item) {
 
140
            return item[name];
 
141
        });
 
142
    },
 
143
 
 
144
    /**
 
145
    Overrides ModelList#getAsHTML() to return a map of HTML-escaped property
 
146
    values rather than performing attribute lookups.
 
147
 
 
148
    @method getAsHTML
 
149
    @param {String} name Property name.
 
150
    @return {String[]} Array of HTML-escaped property values.
 
151
    @see ModelList.getAsHTML()
 
152
    **/
 
153
    getAsHTML: function (name) {
 
154
        if (this.attrAdded(name)) {
 
155
            return Y.Escape.html(AttrProto.get.apply(this, arguments));
 
156
        }
 
157
 
 
158
        return YArray.map(this._items, function (item) {
 
159
            return Y.Escape.html(item[name]);
 
160
        });
 
161
    },
 
162
 
 
163
    /**
 
164
    Overrides ModelList#getAsURL() to return a map of URL-encoded property
 
165
    values rather than performing attribute lookups.
 
166
 
 
167
    @method getAsURL
 
168
    @param {String} name Property name.
 
169
    @return {String[]} Array of URL-encoded property values.
 
170
    @see ModelList.getAsURL()
 
171
    **/
 
172
    getAsURL: function (name) {
 
173
        if (this.attrAdded(name)) {
 
174
            return encodeURIComponent(AttrProto.get.apply(this, arguments));
 
175
        }
 
176
 
 
177
        return YArray.map(this._items, function (item) {
 
178
            return encodeURIComponent(item[name]);
 
179
        });
 
180
    },
 
181
 
 
182
    /**
 
183
    Returns the index of the given object or Model instance in this
 
184
    LazyModelList.
 
185
 
 
186
    @method indexOf
 
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()
 
190
    **/
 
191
    indexOf: function (model) {
 
192
        return YArray.indexOf(model && model._isYUIModel ?
 
193
            this._models : this._items, model);
 
194
    },
 
195
 
 
196
    /**
 
197
    Overrides ModelList#reset() to work with plain objects.
 
198
 
 
199
    @method reset
 
200
    @param {Object[]|Model[]|ModelList} [models] Models to add.
 
201
    @param {Object} [options] Options.
 
202
    @chainable
 
203
    @see ModelList.reset()
 
204
    **/
 
205
    reset: function (items, options) {
 
206
        items || (items  = []);
 
207
        options || (options = {});
 
208
 
 
209
        var facade = Y.merge({src: 'reset'}, options);
 
210
 
 
211
        // Convert `items` into an array of plain objects, since we don't want
 
212
        // model instances.
 
213
        items = items._isYUIModelList ? items.map(this._modelToObject) :
 
214
            YArray.map(items, this._modelToObject);
 
215
 
 
216
        facade.models = items;
 
217
 
 
218
        if (options.silent) {
 
219
            this._defResetFn(facade);
 
220
        } else {
 
221
            // Sort the items before firing the reset event.
 
222
            if (this.comparator) {
 
223
                items.sort(Y.bind(this._sort, this));
 
224
            }
 
225
 
 
226
            this.fire(EVT_RESET, facade);
 
227
        }
 
228
 
 
229
        return this;
 
230
    },
 
231
 
 
232
    /**
 
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.
 
236
 
 
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
 
240
    Model instance.
 
241
 
 
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.
 
244
 
 
245
    @method revive
 
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
 
249
        will be returned.
 
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
 
252
        list.
 
253
    @see free()
 
254
    **/
 
255
    revive: function (item) {
 
256
        var i, len, models;
 
257
 
 
258
        if (item || item === 0) {
 
259
            return this._revive(Lang.isNumber(item) ? item :
 
260
                this.indexOf(item));
 
261
        } else {
 
262
            models = [];
 
263
 
 
264
            for (i = 0, len = this._items.length; i < len; i++) {
 
265
                models.push(this._revive(i));
 
266
            }
 
267
 
 
268
            return models;
 
269
        }
 
270
    },
 
271
 
 
272
    /**
 
273
    Overrides ModelList#toJSON() to use toArray() instead, since it's more
 
274
    efficient for LazyModelList.
 
275
 
 
276
    @method toJSON
 
277
    @return {Object[]} Array of objects.
 
278
    @see ModelList.toJSON()
 
279
    **/
 
280
    toJSON: function () {
 
281
        return this.toArray();
 
282
    },
 
283
 
 
284
    // -- Protected Methods ----------------------------------------------------
 
285
 
 
286
    /**
 
287
    Overrides ModelList#add() to work with plain objects.
 
288
 
 
289
    @method _add
 
290
    @param {Object|Model} item Object or model to add.
 
291
    @param {Object} [options] Options.
 
292
    @return {Object} Added item.
 
293
    @protected
 
294
    @see ModelList._add()
 
295
    **/
 
296
    _add: function (item, options) {
 
297
        var facade;
 
298
 
 
299
        options || (options = {});
 
300
 
 
301
        // If the item is a model instance, convert it to a plain object.
 
302
        item = this._modelToObject(item);
 
303
 
 
304
        // Ensure that the item has a clientId.
 
305
        if (!('clientId' in item)) {
 
306
            item.clientId = this._generateClientId();
 
307
        }
 
308
 
 
309
        if (this._isInList(item)) {
 
310
            this.fire(EVT_ERROR, {
 
311
                error: 'Model is already in the list.',
 
312
                model: item,
 
313
                src  : 'add'
 
314
            });
 
315
 
 
316
            return;
 
317
        }
 
318
 
 
319
        facade = Y.merge(options, {
 
320
            index: 'index' in options ? options.index : this._findIndex(item),
 
321
            model: item
 
322
        });
 
323
 
 
324
        options.silent ? this._defAddFn(facade) : this.fire(EVT_ADD, facade);
 
325
 
 
326
        return item;
 
327
    },
 
328
 
 
329
    /**
 
330
    Overrides ModelList#clear() to support `this._models`.
 
331
 
 
332
    @method _clear
 
333
    @protected
 
334
    @see ModelList.clear()
 
335
    **/
 
336
    _clear: function () {
 
337
        YArray.each(this._models, this._detachList, this);
 
338
 
 
339
        this._clientIdMap = {};
 
340
        this._idMap       = {};
 
341
        this._items       = [];
 
342
        this._models      = [];
 
343
    },
 
344
 
 
345
    /**
 
346
    Generates an ad-hoc clientId for a non-instantiated Model.
 
347
 
 
348
    @method _generateClientId
 
349
    @return {String} Unique clientId.
 
350
    @protected
 
351
    **/
 
352
    _generateClientId: function () {
 
353
        GlobalEnv.lastId || (GlobalEnv.lastId = 0);
 
354
        return this.model.NAME + '_' + (GlobalEnv.lastId += 1);
 
355
    },
 
356
 
 
357
    /**
 
358
    Returns `true` if the given item is in this list, `false` otherwise.
 
359
 
 
360
    @method _isInList
 
361
    @param {Object} item Plain object item.
 
362
    @return {Boolean} `true` if the item is in this list, `false` otherwise.
 
363
    @protected
 
364
    **/
 
365
    _isInList: function (item) {
 
366
        return !!(('clientId' in item && this._clientIdMap[item.clientId]) ||
 
367
                ('id' in item && this._idMap[item.id]));
 
368
    },
 
369
 
 
370
    /**
 
371
    Converts a Model instance into a plain object. If _model_ is not a Model
 
372
    instance, it will be returned as is.
 
373
 
 
374
    This method differs from Model#toJSON() in that it doesn't delete the
 
375
    `clientId` property.
 
376
 
 
377
    @method _modelToObject
 
378
    @param {Model|Object} model Model instance to convert.
 
379
    @return {Object} Plain object.
 
380
    @protected
 
381
    **/
 
382
    _modelToObject: function (model) {
 
383
        if (model._isYUIModel) {
 
384
            model = model.getAttrs();
 
385
            delete model.destroyed;
 
386
            delete model.initialized;
 
387
        }
 
388
 
 
389
        return model;
 
390
    },
 
391
 
 
392
    /**
 
393
    Overrides ModelList#_remove() to convert Model instances to indices
 
394
    before removing to ensure consistency in the `remove` event facade.
 
395
 
 
396
    @method _remove
 
397
    @param {Object|Model} item Object or model to remove.
 
398
    @param {Object} [options] Options.
 
399
    @return {Object} Removed object.
 
400
    @protected
 
401
    **/
 
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);
 
408
        }
 
409
 
 
410
        return Y.ModelList.prototype._remove.call(this, item, options);
 
411
    },
 
412
 
 
413
    /**
 
414
    Revives a single model at the specified index and returns it. This is the
 
415
    underlying implementation for `revive()`.
 
416
 
 
417
    @method _revive
 
418
    @param {Number} index Index of the item to revive.
 
419
    @return {Model} Revived model.
 
420
    @protected
 
421
    **/
 
422
    _revive: function (index) {
 
423
        var item, model;
 
424
 
 
425
        if (index < 0) {
 
426
            return null;
 
427
        }
 
428
 
 
429
        item = this._items[index];
 
430
 
 
431
        if (!item) {
 
432
            return null;
 
433
        }
 
434
 
 
435
        model = this._models[index];
 
436
 
 
437
        if (!model) {
 
438
            model = new this.model(item);
 
439
 
 
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
 
442
            // it manually.
 
443
            model._set('clientId', item.clientId);
 
444
 
 
445
            this._attachList(model);
 
446
            this._models[index] = model;
 
447
        }
 
448
 
 
449
        return model;
 
450
    },
 
451
 
 
452
    // -- Event Handlers -------------------------------------------------------
 
453
 
 
454
    /**
 
455
    Handles `change` events on revived models and updates the original objects
 
456
    with the changes.
 
457
 
 
458
    @method _afterModelChange
 
459
    @param {EventFacade} e
 
460
    @protected
 
461
    **/
 
462
    _afterModelChange: function (e) {
 
463
        var changed = e.changed,
 
464
            item    = this._clientIdMap[e.target.get('clientId')],
 
465
            name;
 
466
 
 
467
        if (item) {
 
468
            for (name in changed) {
 
469
                if (changed.hasOwnProperty(name)) {
 
470
                    item[name] = changed[name].newVal;
 
471
                }
 
472
            }
 
473
        }
 
474
    },
 
475
 
 
476
    // -- Default Event Handlers -----------------------------------------------
 
477
 
 
478
    /**
 
479
    Overrides ModelList#_defAddFn() to support plain objects.
 
480
 
 
481
    @method _defAddFn
 
482
    @param {EventFacade} e
 
483
    @protected
 
484
    **/
 
485
    _defAddFn: function (e) {
 
486
        var item = e.model;
 
487
 
 
488
        this._clientIdMap[item.clientId] = item;
 
489
 
 
490
        if (Lang.isValue(item.id)) {
 
491
            this._idMap[item.id] = item;
 
492
        }
 
493
 
 
494
        this._items.splice(e.index, 0, item);
 
495
    },
 
496
 
 
497
    /**
 
498
    Overrides ModelList#_defRemoveFn() to support plain objects.
 
499
 
 
500
    @method _defRemoveFn
 
501
    @param {EventFacade} e
 
502
    @protected
 
503
    **/
 
504
    _defRemoveFn: function (e) {
 
505
        var index = e.index,
 
506
            item  = e.model,
 
507
            model = this._models[index];
 
508
 
 
509
        delete this._clientIdMap[item.clientId];
 
510
 
 
511
        if ('id' in item) {
 
512
            delete this._idMap[item.id];
 
513
        }
 
514
 
 
515
        if (model) {
 
516
            this._detachList(model);
 
517
        }
 
518
 
 
519
        this._items.splice(index, 1);
 
520
        this._models.splice(index, 1);
 
521
    }
 
522
});
 
523
 
 
524
 
 
525
}, '3.10.3', {"requires": ["model-list"]});