[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/lazy-model-list/ -> lazy-model-list.js (source)

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1