[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 YUI 3.17.2 (build 9c3c78e) 3 Copyright 2014 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.17.2', {"requires": ["model-list"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |