[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/model-list/ -> model-list-debug.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('model-list', function (Y, NAME) {
   9  
  10  /**
  11  Provides an API for managing an ordered list of Model instances.
  12  
  13  @module app
  14  @submodule model-list
  15  @since 3.4.0
  16  **/
  17  
  18  /**
  19  Provides an API for managing an ordered list of Model instances.
  20  
  21  In addition to providing convenient `add`, `create`, `reset`, and `remove`
  22  methods for managing the models in the list, ModelLists are also bubble targets
  23  for events on the model instances they contain. This means, for example, that
  24  you can add several models to a list, and then subscribe to the `*:change` event
  25  on the list to be notified whenever any model in the list changes.
  26  
  27  ModelLists also maintain sort order efficiently as models are added and removed,
  28  based on a custom `comparator` function you may define (if no comparator is
  29  defined, models are sorted in insertion order).
  30  
  31  @class ModelList
  32  @extends Base
  33  @uses ArrayList
  34  @constructor
  35  @param {Object} config Config options.
  36      @param {Model|Model[]|ModelList|Object|Object[]} config.items Model
  37          instance, array of model instances, or ModelList to add to this list on
  38          init. The `add` event will not be fired for models added on init.
  39  @since 3.4.0
  40  **/
  41  
  42  var AttrProto = Y.Attribute.prototype,
  43      Lang      = Y.Lang,
  44      YArray    = Y.Array,
  45  
  46      /**
  47      Fired when a model is added to the list.
  48  
  49      Listen to the `on` phase of this event to be notified before a model is
  50      added to the list. Calling `e.preventDefault()` during the `on` phase will
  51      prevent the model from being added.
  52  
  53      Listen to the `after` phase of this event to be notified after a model has
  54      been added to the list.
  55  
  56      @event add
  57      @param {Model} model The model being added.
  58      @param {Number} index The index at which the model will be added.
  59      @preventable _defAddFn
  60      **/
  61      EVT_ADD = 'add',
  62  
  63      /**
  64      Fired when a model is created or updated via the `create()` method, but
  65      before the model is actually saved or added to the list. The `add` event
  66      will be fired after the model has been saved and added to the list.
  67  
  68      @event create
  69      @param {Model} model The model being created/updated.
  70      @since 3.5.0
  71      **/
  72      EVT_CREATE = 'create',
  73  
  74      /**
  75      Fired when an error occurs, such as when an attempt is made to add a
  76      duplicate model to the list, or when a sync layer response can't be parsed.
  77  
  78      @event error
  79      @param {Any} error Error message, object, or exception generated by the
  80        error. Calling `toString()` on this should result in a meaningful error
  81        message.
  82      @param {String} src Source of the error. May be one of the following (or any
  83        custom error source defined by a ModelList subclass):
  84  
  85        * `add`: Error while adding a model (probably because it's already in the
  86           list and can't be added again). The model in question will be provided
  87           as the `model` property on the event facade.
  88        * `parse`: An error parsing a JSON response. The response in question will
  89           be provided as the `response` property on the event facade.
  90        * `remove`: Error while removing a model (probably because it isn't in the
  91          list and can't be removed). The model in question will be provided as
  92          the `model` property on the event facade.
  93      **/
  94      EVT_ERROR = 'error',
  95  
  96      /**
  97      Fired after models are loaded from a sync layer.
  98  
  99      @event load
 100      @param {Object} parsed The parsed version of the sync layer's response to
 101          the load request.
 102      @param {Mixed} response The sync layer's raw, unparsed response to the load
 103          request.
 104      @since 3.5.0
 105      **/
 106      EVT_LOAD = 'load',
 107  
 108      /**
 109      Fired when a model is removed from the list.
 110  
 111      Listen to the `on` phase of this event to be notified before a model is
 112      removed from the list. Calling `e.preventDefault()` during the `on` phase
 113      will prevent the model from being removed.
 114  
 115      Listen to the `after` phase of this event to be notified after a model has
 116      been removed from the list.
 117  
 118      @event remove
 119      @param {Model} model The model being removed.
 120      @param {Number} index The index of the model being removed.
 121      @preventable _defRemoveFn
 122      **/
 123      EVT_REMOVE = 'remove',
 124  
 125      /**
 126      Fired when the list is completely reset via the `reset()` method or sorted
 127      via the `sort()` method.
 128  
 129      Listen to the `on` phase of this event to be notified before the list is
 130      reset. Calling `e.preventDefault()` during the `on` phase will prevent
 131      the list from being reset.
 132  
 133      Listen to the `after` phase of this event to be notified after the list has
 134      been reset.
 135  
 136      @event reset
 137      @param {Model[]} models Array of the list's new models after the reset.
 138      @param {String} src Source of the event. May be either `'reset'` or
 139        `'sort'`.
 140      @preventable _defResetFn
 141      **/
 142      EVT_RESET = 'reset';
 143  
 144  function ModelList() {
 145      ModelList.superclass.constructor.apply(this, arguments);
 146  }
 147  
 148  Y.ModelList = Y.extend(ModelList, Y.Base, {
 149      // -- Public Properties ----------------------------------------------------
 150  
 151      /**
 152      The `Model` class or subclass of the models in this list.
 153  
 154      The class specified here will be used to create model instances
 155      automatically based on attribute hashes passed to the `add()`, `create()`,
 156      and `reset()` methods.
 157  
 158      You may specify the class as an actual class reference or as a string that
 159      resolves to a class reference at runtime (the latter can be useful if the
 160      specified class will be loaded lazily).
 161  
 162      @property model
 163      @type Model|String
 164      @default Y.Model
 165      **/
 166      model: Y.Model,
 167  
 168      // -- Protected Properties -------------------------------------------------
 169  
 170      /**
 171      Total hack to allow us to identify ModelList instances without using
 172      `instanceof`, which won't work when the instance was created in another
 173      window or YUI sandbox.
 174  
 175      @property _isYUIModelList
 176      @type Boolean
 177      @default true
 178      @protected
 179      @since 3.5.0
 180      **/
 181      _isYUIModelList: true,
 182  
 183      // -- Lifecycle Methods ----------------------------------------------------
 184      initializer: function (config) {
 185          config || (config = {});
 186  
 187          var model = this.model = config.model || this.model;
 188  
 189          if (typeof model === 'string') {
 190              // Look for a namespaced Model class on `Y`.
 191              this.model = Y.Object.getValue(Y, model.split('.'));
 192  
 193              if (!this.model) {
 194                  Y.error('ModelList: Model class not found: ' + model);
 195              }
 196          }
 197  
 198          this.publish(EVT_ADD,    {defaultFn: this._defAddFn});
 199          this.publish(EVT_RESET,  {defaultFn: this._defResetFn});
 200          this.publish(EVT_REMOVE, {defaultFn: this._defRemoveFn});
 201  
 202          this.after('*:idChange', this._afterIdChange);
 203  
 204          this._clear();
 205  
 206          if (config.items) {
 207              this.add(config.items, {silent: true});
 208          }
 209      },
 210  
 211      destructor: function () {
 212          this._clear();
 213      },
 214  
 215      // -- Public Methods -------------------------------------------------------
 216  
 217      /**
 218      Adds the specified model or array of models to this list. You may also pass
 219      another ModelList instance, in which case all the models in that list will
 220      be added to this one as well.
 221  
 222      @example
 223  
 224          // Add a single model instance.
 225          list.add(new Model({foo: 'bar'}));
 226  
 227          // Add a single model, creating a new instance automatically.
 228          list.add({foo: 'bar'});
 229  
 230          // Add multiple models, creating new instances automatically.
 231          list.add([
 232              {foo: 'bar'},
 233              {baz: 'quux'}
 234          ]);
 235  
 236          // Add all the models in another ModelList instance.
 237          list.add(otherList);
 238  
 239      @method add
 240      @param {Model|Model[]|ModelList|Object|Object[]} models Model or array of
 241          models to add. May be existing model instances or hashes of model
 242          attributes, in which case new model instances will be created from the
 243          hashes. You may also pass a ModelList instance to add all the models it
 244          contains.
 245      @param {Object} [options] Data to be mixed into the event facade of the
 246          `add` event(s) for the added models.
 247  
 248          @param {Number} [options.index] Index at which to insert the added
 249              models. If not specified, the models will automatically be inserted
 250              in the appropriate place according to the current sort order as
 251              dictated by the `comparator()` method, if any.
 252          @param {Boolean} [options.silent=false] If `true`, no `add` event(s)
 253              will be fired.
 254  
 255      @return {Model|Model[]} Added model or array of added models.
 256      **/
 257      add: function (models, options) {
 258          var isList = models._isYUIModelList;
 259  
 260          if (isList || Lang.isArray(models)) {
 261              return YArray.map(isList ? models.toArray() : models, function (model, index) {
 262                  var modelOptions = options || {};
 263  
 264                  // When an explicit insertion index is specified, ensure that
 265                  // the index is increased by one for each subsequent item in the
 266                  // array.
 267                  if ('index' in modelOptions) {
 268                      modelOptions = Y.merge(modelOptions, {
 269                          index: modelOptions.index + index
 270                      });
 271                  }
 272  
 273                  return this._add(model, modelOptions);
 274              }, this);
 275          } else {
 276              return this._add(models, options);
 277          }
 278      },
 279  
 280      /**
 281      Define this method to provide a function that takes a model as a parameter
 282      and returns a value by which that model should be sorted relative to other
 283      models in this list.
 284  
 285      By default, no comparator is defined, meaning that models will not be sorted
 286      (they'll be stored in the order they're added).
 287  
 288      @example
 289          var list = new Y.ModelList({model: Y.Model});
 290  
 291          list.comparator = function (model) {
 292              return model.get('id'); // Sort models by id.
 293          };
 294  
 295      @method comparator
 296      @param {Model} model Model being sorted.
 297      @return {Number|String} Value by which the model should be sorted relative
 298        to other models in this list.
 299      **/
 300  
 301      // comparator is not defined by default
 302  
 303      /**
 304      Creates or updates the specified model on the server, then adds it to this
 305      list if the server indicates success.
 306  
 307      @method create
 308      @param {Model|Object} model Model to create. May be an existing model
 309        instance or a hash of model attributes, in which case a new model instance
 310        will be created from the hash.
 311      @param {Object} [options] Options to be passed to the model's `sync()` and
 312          `set()` methods and mixed into the `create` and `add` event facades.
 313        @param {Boolean} [options.silent=false] If `true`, no `add` event(s) will
 314            be fired.
 315      @param {Function} [callback] Called when the sync operation finishes.
 316        @param {Error} callback.err If an error occurred, this parameter will
 317          contain the error. If the sync operation succeeded, _err_ will be
 318          falsy.
 319        @param {Any} callback.response The server's response.
 320      @return {Model} Created model.
 321      **/
 322      create: function (model, options, callback) {
 323          var self = this;
 324  
 325          // Allow callback as second arg.
 326          if (typeof options === 'function') {
 327              callback = options;
 328              options  = {};
 329          }
 330  
 331          options || (options = {});
 332  
 333          if (!model._isYUIModel) {
 334              model = new this.model(model);
 335          }
 336  
 337          self.fire(EVT_CREATE, Y.merge(options, {
 338              model: model
 339          }));
 340  
 341          return model.save(options, function (err) {
 342              if (!err) {
 343                  self.add(model, options);
 344              }
 345  
 346              if (callback) {
 347                  callback.apply(null, arguments);
 348              }
 349          });
 350      },
 351  
 352      /**
 353      Executes the supplied function on each model in this list.
 354  
 355      By default, the callback function's `this` object will refer to the model
 356      currently being iterated. Specify a `thisObj` to override the `this` object
 357      if desired.
 358  
 359      Note: Iteration is performed on a copy of the internal array of models, so
 360      it's safe to delete a model from the list during iteration.
 361  
 362      @method each
 363      @param {Function} callback Function to execute on each model.
 364          @param {Model} callback.model Model instance.
 365          @param {Number} callback.index Index of the current model.
 366          @param {ModelList} callback.list The ModelList being iterated.
 367      @param {Object} [thisObj] Object to use as the `this` object when executing
 368          the callback.
 369      @chainable
 370      @since 3.6.0
 371      **/
 372      each: function (callback, thisObj) {
 373          var items = this._items.concat(),
 374              i, item, len;
 375  
 376          for (i = 0, len = items.length; i < len; i++) {
 377              item = items[i];
 378              callback.call(thisObj || item, item, i, this);
 379          }
 380  
 381          return this;
 382      },
 383  
 384      /**
 385      Executes the supplied function on each model in this list. Returns an array
 386      containing the models for which the supplied function returned a truthy
 387      value.
 388  
 389      The callback function's `this` object will refer to this ModelList. Use
 390      `Y.bind()` to bind the `this` object to another object if desired.
 391  
 392      @example
 393  
 394          // Get an array containing only the models whose "enabled" attribute is
 395          // truthy.
 396          var filtered = list.filter(function (model) {
 397              return model.get('enabled');
 398          });
 399  
 400          // Get a new ModelList containing only the models whose "enabled"
 401          // attribute is truthy.
 402          var filteredList = list.filter({asList: true}, function (model) {
 403              return model.get('enabled');
 404          });
 405  
 406      @method filter
 407      @param {Object} [options] Filter options.
 408          @param {Boolean} [options.asList=false] If truthy, results will be
 409              returned as a new ModelList instance rather than as an array.
 410  
 411      @param {Function} callback Function to execute on each model.
 412          @param {Model} callback.model Model instance.
 413          @param {Number} callback.index Index of the current model.
 414          @param {ModelList} callback.list The ModelList being filtered.
 415  
 416      @return {Model[]|ModelList} Array of models for which the callback function
 417          returned a truthy value (empty if it never returned a truthy value). If
 418          the `options.asList` option is truthy, a new ModelList instance will be
 419          returned instead of an array.
 420      @since 3.5.0
 421      */
 422      filter: function (options, callback) {
 423          var filtered = [],
 424              items    = this._items,
 425              i, item, len, list;
 426  
 427          // Allow options as first arg.
 428          if (typeof options === 'function') {
 429              callback = options;
 430              options  = {};
 431          }
 432  
 433          for (i = 0, len = items.length; i < len; ++i) {
 434              item = items[i];
 435  
 436              if (callback.call(this, item, i, this)) {
 437                  filtered.push(item);
 438              }
 439          }
 440  
 441          if (options.asList) {
 442              list = new this.constructor({model: this.model});
 443  
 444              if (filtered.length) {
 445                  list.add(filtered, {silent: true});
 446              }
 447  
 448              return list;
 449          } else {
 450              return filtered;
 451          }
 452      },
 453  
 454      /**
 455      If _name_ refers to an attribute on this ModelList instance, returns the
 456      value of that attribute. Otherwise, returns an array containing the values
 457      of the specified attribute from each model in this list.
 458  
 459      @method get
 460      @param {String} name Attribute name or object property path.
 461      @return {Any|Any[]} Attribute value or array of attribute values.
 462      @see Model.get()
 463      **/
 464      get: function (name) {
 465          if (this.attrAdded(name)) {
 466              return AttrProto.get.apply(this, arguments);
 467          }
 468  
 469          return this.invoke('get', name);
 470      },
 471  
 472      /**
 473      If _name_ refers to an attribute on this ModelList instance, returns the
 474      HTML-escaped value of that attribute. Otherwise, returns an array containing
 475      the HTML-escaped values of the specified attribute from each model in this
 476      list.
 477  
 478      The values are escaped using `Escape.html()`.
 479  
 480      @method getAsHTML
 481      @param {String} name Attribute name or object property path.
 482      @return {String|String[]} HTML-escaped value or array of HTML-escaped
 483        values.
 484      @see Model.getAsHTML()
 485      **/
 486      getAsHTML: function (name) {
 487          if (this.attrAdded(name)) {
 488              return Y.Escape.html(AttrProto.get.apply(this, arguments));
 489          }
 490  
 491          return this.invoke('getAsHTML', name);
 492      },
 493  
 494      /**
 495      If _name_ refers to an attribute on this ModelList instance, returns the
 496      URL-encoded value of that attribute. Otherwise, returns an array containing
 497      the URL-encoded values of the specified attribute from each model in this
 498      list.
 499  
 500      The values are encoded using the native `encodeURIComponent()` function.
 501  
 502      @method getAsURL
 503      @param {String} name Attribute name or object property path.
 504      @return {String|String[]} URL-encoded value or array of URL-encoded values.
 505      @see Model.getAsURL()
 506      **/
 507      getAsURL: function (name) {
 508          if (this.attrAdded(name)) {
 509              return encodeURIComponent(AttrProto.get.apply(this, arguments));
 510          }
 511  
 512          return this.invoke('getAsURL', name);
 513      },
 514  
 515      /**
 516      Returns the model with the specified _clientId_, or `null` if not found.
 517  
 518      @method getByClientId
 519      @param {String} clientId Client id.
 520      @return {Model} Model, or `null` if not found.
 521      **/
 522      getByClientId: function (clientId) {
 523          return this._clientIdMap[clientId] || null;
 524      },
 525  
 526      /**
 527      Returns the model with the specified _id_, or `null` if not found.
 528  
 529      Note that models aren't expected to have an id until they're saved, so if
 530      you're working with unsaved models, it may be safer to call
 531      `getByClientId()`.
 532  
 533      @method getById
 534      @param {String|Number} id Model id.
 535      @return {Model} Model, or `null` if not found.
 536      **/
 537      getById: function (id) {
 538          return this._idMap[id] || null;
 539      },
 540  
 541      /**
 542      Calls the named method on every model in the list. Any arguments provided
 543      after _name_ will be passed on to the invoked method.
 544  
 545      @method invoke
 546      @param {String} name Name of the method to call on each model.
 547      @param {Any} [args*] Zero or more arguments to pass to the invoked method.
 548      @return {Any[]} Array of return values, indexed according to the index of
 549        the model on which the method was called.
 550      **/
 551      invoke: function (name /*, args* */) {
 552          var args = [this._items, name].concat(YArray(arguments, 1, true));
 553          return YArray.invoke.apply(YArray, args);
 554      },
 555  
 556      /**
 557      Returns the model at the specified _index_.
 558  
 559      @method item
 560      @param {Number} index Index of the model to fetch.
 561      @return {Model} The model at the specified index, or `undefined` if there
 562        isn't a model there.
 563      **/
 564  
 565      // item() is inherited from ArrayList.
 566  
 567      /**
 568      Loads this list of models from the server.
 569  
 570      This method delegates to the `sync()` method to perform the actual load
 571      operation, which is an asynchronous action. Specify a _callback_ function to
 572      be notified of success or failure.
 573  
 574      If the load operation succeeds, a `reset` event will be fired.
 575  
 576      @method load
 577      @param {Object} [options] Options to be passed to `sync()` and to
 578        `reset()` when adding the loaded models. It's up to the custom sync
 579        implementation to determine what options it supports or requires, if any.
 580      @param {Function} [callback] Called when the sync operation finishes.
 581        @param {Error} callback.err If an error occurred, this parameter will
 582          contain the error. If the sync operation succeeded, _err_ will be
 583          falsy.
 584        @param {Any} callback.response The server's response. This value will
 585          be passed to the `parse()` method, which is expected to parse it and
 586          return an array of model attribute hashes.
 587      @chainable
 588      **/
 589      load: function (options, callback) {
 590          var self = this;
 591  
 592          // Allow callback as only arg.
 593          if (typeof options === 'function') {
 594              callback = options;
 595              options  = {};
 596          }
 597  
 598          options || (options = {});
 599  
 600          this.sync('read', options, function (err, response) {
 601              var facade = {
 602                      options : options,
 603                      response: response
 604                  },
 605  
 606                  parsed;
 607  
 608              if (err) {
 609                  facade.error = err;
 610                  facade.src   = 'load';
 611  
 612                  self.fire(EVT_ERROR, facade);
 613              } else {
 614                  // Lazy publish.
 615                  if (!self._loadEvent) {
 616                      self._loadEvent = self.publish(EVT_LOAD, {
 617                          preventable: false
 618                      });
 619                  }
 620  
 621                  parsed = facade.parsed = self._parse(response);
 622  
 623                  self.reset(parsed, options);
 624                  self.fire(EVT_LOAD, facade);
 625              }
 626  
 627              if (callback) {
 628                  callback.apply(null, arguments);
 629              }
 630          });
 631  
 632          return this;
 633      },
 634  
 635      /**
 636      Executes the specified function on each model in this list and returns an
 637      array of the function's collected return values.
 638  
 639      @method map
 640      @param {Function} fn Function to execute on each model.
 641        @param {Model} fn.model Current model being iterated.
 642        @param {Number} fn.index Index of the current model in the list.
 643        @param {Model[]} fn.models Array of models being iterated.
 644      @param {Object} [thisObj] `this` object to use when calling _fn_.
 645      @return {Array} Array of return values from _fn_.
 646      **/
 647      map: function (fn, thisObj) {
 648          return YArray.map(this._items, fn, thisObj);
 649      },
 650  
 651      /**
 652      Called to parse the _response_ when the list is loaded from the server.
 653      This method receives a server _response_ and is expected to return an array
 654      of model attribute hashes.
 655  
 656      The default implementation assumes that _response_ is either an array of
 657      attribute hashes or a JSON string that can be parsed into an array of
 658      attribute hashes. If _response_ is a JSON string and either `Y.JSON` or the
 659      native `JSON` object are available, it will be parsed automatically. If a
 660      parse error occurs, an `error` event will be fired and the model will not be
 661      updated.
 662  
 663      You may override this method to implement custom parsing logic if necessary.
 664  
 665      @method parse
 666      @param {Any} response Server response.
 667      @return {Object[]} Array of model attribute hashes.
 668      **/
 669      parse: function (response) {
 670          if (typeof response === 'string') {
 671              try {
 672                  return Y.JSON.parse(response) || [];
 673              } catch (ex) {
 674                  this.fire(EVT_ERROR, {
 675                      error   : ex,
 676                      response: response,
 677                      src     : 'parse'
 678                  });
 679  
 680                  return null;
 681              }
 682          }
 683  
 684          return response || [];
 685      },
 686  
 687      /**
 688      Removes the specified model or array of models from this list. You may also
 689      pass another ModelList instance to remove all the models that are in both
 690      that instance and this instance, or pass numerical indices to remove the
 691      models at those indices.
 692  
 693      @method remove
 694      @param {Model|Model[]|ModelList|Number|Number[]} models Models or indices of
 695          models to remove.
 696      @param {Object} [options] Data to be mixed into the event facade of the
 697          `remove` event(s) for the removed models.
 698  
 699          @param {Boolean} [options.silent=false] If `true`, no `remove` event(s)
 700              will be fired.
 701  
 702      @return {Model|Model[]} Removed model or array of removed models.
 703      **/
 704      remove: function (models, options) {
 705          var isList = models._isYUIModelList;
 706  
 707          if (isList || Lang.isArray(models)) {
 708              // We can't remove multiple models by index because the indices will
 709              // change as we remove them, so we need to get the actual models
 710              // first.
 711              models = YArray.map(isList ? models.toArray() : models, function (model) {
 712                  if (Lang.isNumber(model)) {
 713                      return this.item(model);
 714                  }
 715  
 716                  return model;
 717              }, this);
 718  
 719              return YArray.map(models, function (model) {
 720                  return this._remove(model, options);
 721              }, this);
 722          } else {
 723              return this._remove(models, options);
 724          }
 725      },
 726  
 727      /**
 728      Completely replaces all models in the list with those specified, and fires a
 729      single `reset` event.
 730  
 731      Use `reset` when you want to add or remove a large number of items at once
 732      with less overhead, and without firing `add` or `remove` events for each
 733      one.
 734  
 735      @method reset
 736      @param {Model[]|ModelList|Object[]} [models] Models to add. May be existing
 737          model instances or hashes of model attributes, in which case new model
 738          instances will be created from the hashes. If a ModelList is passed, all
 739          the models in that list will be added to this list. Calling `reset()`
 740          without passing in any models will clear the list.
 741      @param {Object} [options] Data to be mixed into the event facade of the
 742          `reset` event.
 743  
 744          @param {Boolean} [options.silent=false] If `true`, no `reset` event will
 745              be fired.
 746  
 747      @chainable
 748      **/
 749      reset: function (models, options) {
 750          models  || (models  = []);
 751          options || (options = {});
 752  
 753          var facade = Y.merge({src: 'reset'}, options);
 754  
 755          if (models._isYUIModelList) {
 756              models = models.toArray();
 757          } else {
 758              models = YArray.map(models, function (model) {
 759                  return model._isYUIModel ? model : new this.model(model);
 760              }, this);
 761          }
 762  
 763          facade.models = models;
 764  
 765          if (options.silent) {
 766              this._defResetFn(facade);
 767          } else {
 768              // Sort the models before firing the reset event.
 769              if (this.comparator) {
 770                  models.sort(Y.bind(this._sort, this));
 771              }
 772  
 773              this.fire(EVT_RESET, facade);
 774          }
 775  
 776          return this;
 777      },
 778  
 779      /**
 780      Executes the supplied function on each model in this list, and stops
 781      iterating if the callback returns `true`.
 782  
 783      By default, the callback function's `this` object will refer to the model
 784      currently being iterated. Specify a `thisObj` to override the `this` object
 785      if desired.
 786  
 787      Note: Iteration is performed on a copy of the internal array of models, so
 788      it's safe to delete a model from the list during iteration.
 789  
 790      @method some
 791      @param {Function} callback Function to execute on each model.
 792          @param {Model} callback.model Model instance.
 793          @param {Number} callback.index Index of the current model.
 794          @param {ModelList} callback.list The ModelList being iterated.
 795      @param {Object} [thisObj] Object to use as the `this` object when executing
 796          the callback.
 797      @return {Boolean} `true` if the callback returned `true` for any item,
 798          `false` otherwise.
 799      @since 3.6.0
 800      **/
 801      some: function (callback, thisObj) {
 802          var items = this._items.concat(),
 803              i, item, len;
 804  
 805          for (i = 0, len = items.length; i < len; i++) {
 806              item = items[i];
 807  
 808              if (callback.call(thisObj || item, item, i, this)) {
 809                  return true;
 810              }
 811          }
 812  
 813          return false;
 814      },
 815  
 816      /**
 817      Forcibly re-sorts the list.
 818  
 819      Usually it shouldn't be necessary to call this method since the list
 820      maintains its sort order when items are added and removed, but if you change
 821      the `comparator` function after items are already in the list, you'll need
 822      to re-sort.
 823  
 824      @method sort
 825      @param {Object} [options] Data to be mixed into the event facade of the
 826          `reset` event.
 827        @param {Boolean} [options.silent=false] If `true`, no `reset` event will
 828            be fired.
 829        @param {Boolean} [options.descending=false] If `true`, the sort is
 830            performed in descending order.
 831      @chainable
 832      **/
 833      sort: function (options) {
 834          if (!this.comparator) {
 835              return this;
 836          }
 837  
 838          var models = this._items.concat(),
 839              facade;
 840  
 841          options || (options = {});
 842  
 843          models.sort(Y.rbind(this._sort, this, options));
 844  
 845          facade = Y.merge(options, {
 846              models: models,
 847              src   : 'sort'
 848          });
 849  
 850          if (options.silent) {
 851              this._defResetFn(facade);
 852          } else {
 853              this.fire(EVT_RESET, facade);
 854          }
 855  
 856          return this;
 857      },
 858  
 859      /**
 860      Override this method to provide a custom persistence implementation for this
 861      list. The default method just calls the callback without actually doing
 862      anything.
 863  
 864      This method is called internally by `load()` and its implementations relies
 865      on the callback being called. This effectively means that when a callback is
 866      provided, it must be called at some point for the class to operate correctly.
 867  
 868      @method sync
 869      @param {String} action Sync action to perform. May be one of the following:
 870  
 871        * `create`: Store a list of newly-created models for the first time.
 872        * `delete`: Delete a list of existing models.
 873        * `read`  : Load a list of existing models.
 874        * `update`: Update a list of existing models.
 875  
 876        Currently, model lists only make use of the `read` action, but other
 877        actions may be used in future versions.
 878  
 879      @param {Object} [options] Sync options. It's up to the custom sync
 880        implementation to determine what options it supports or requires, if any.
 881      @param {Function} [callback] Called when the sync operation finishes.
 882        @param {Error} callback.err If an error occurred, this parameter will
 883          contain the error. If the sync operation succeeded, _err_ will be
 884          falsy.
 885        @param {Any} [callback.response] The server's response. This value will
 886          be passed to the `parse()` method, which is expected to parse it and
 887          return an array of model attribute hashes.
 888      **/
 889      sync: function (/* action, options, callback */) {
 890          var callback = YArray(arguments, 0, true).pop();
 891  
 892          if (typeof callback === 'function') {
 893              callback();
 894          }
 895      },
 896  
 897      /**
 898      Returns an array containing the models in this list.
 899  
 900      @method toArray
 901      @return {Model[]} Array containing the models in this list.
 902      **/
 903      toArray: function () {
 904          return this._items.concat();
 905      },
 906  
 907      /**
 908      Returns an array containing attribute hashes for each model in this list,
 909      suitable for being passed to `Y.JSON.stringify()`.
 910  
 911      Under the hood, this method calls `toJSON()` on each model in the list and
 912      pushes the results into an array.
 913  
 914      @method toJSON
 915      @return {Object[]} Array of model attribute hashes.
 916      @see Model.toJSON()
 917      **/
 918      toJSON: function () {
 919          return this.map(function (model) {
 920              return model.toJSON();
 921          });
 922      },
 923  
 924      // -- Protected Methods ----------------------------------------------------
 925  
 926      /**
 927      Adds the specified _model_ if it isn't already in this list.
 928  
 929      If the model's `clientId` or `id` matches that of a model that's already in
 930      the list, an `error` event will be fired and the model will not be added.
 931  
 932      @method _add
 933      @param {Model|Object} model Model or object to add.
 934      @param {Object} [options] Data to be mixed into the event facade of the
 935          `add` event for the added model.
 936        @param {Boolean} [options.silent=false] If `true`, no `add` event will be
 937            fired.
 938      @return {Model} The added model.
 939      @protected
 940      **/
 941      _add: function (model, options) {
 942          var facade, id;
 943  
 944          options || (options = {});
 945  
 946          if (!model._isYUIModel) {
 947              model = new this.model(model);
 948          }
 949  
 950          id = model.get('id');
 951  
 952          if (this._clientIdMap[model.get('clientId')]
 953                  || (Lang.isValue(id) && this._idMap[id])) {
 954  
 955              this.fire(EVT_ERROR, {
 956                  error: 'Model is already in the list.',
 957                  model: model,
 958                  src  : 'add'
 959              });
 960  
 961              return;
 962          }
 963  
 964          facade = Y.merge(options, {
 965              index: 'index' in options ? options.index : this._findIndex(model),
 966              model: model
 967          });
 968  
 969          if (options.silent) {
 970              this._defAddFn(facade);
 971          } else {
 972              this.fire(EVT_ADD, facade);
 973          }
 974  
 975          return model;
 976      },
 977  
 978      /**
 979      Adds this list as a bubble target for the specified model's events.
 980  
 981      @method _attachList
 982      @param {Model} model Model to attach to this list.
 983      @protected
 984      **/
 985      _attachList: function (model) {
 986          // Attach this list and make it a bubble target for the model.
 987          model.lists.push(this);
 988          model.addTarget(this);
 989      },
 990  
 991      /**
 992      Clears all internal state and the internal list of models, returning this
 993      list to an empty state. Automatically detaches all models in the list.
 994  
 995      @method _clear
 996      @protected
 997      **/
 998      _clear: function () {
 999          YArray.each(this._items, this._detachList, this);
1000  
1001          this._clientIdMap = {};
1002          this._idMap       = {};
1003          this._items       = [];
1004      },
1005  
1006      /**
1007      Compares the value _a_ to the value _b_ for sorting purposes. Values are
1008      assumed to be the result of calling a model's `comparator()` method. You can
1009      override this method to implement custom sorting logic, such as a descending
1010      sort or multi-field sorting.
1011  
1012      @method _compare
1013      @param {Mixed} a First value to compare.
1014      @param {Mixed} b Second value to compare.
1015      @return {Number} `-1` if _a_ should come before _b_, `0` if they're
1016          equivalent, `1` if _a_ should come after _b_.
1017      @protected
1018      @since 3.5.0
1019      **/
1020      _compare: function (a, b) {
1021          return a < b ? -1 : (a > b ? 1 : 0);
1022      },
1023  
1024      /**
1025      Removes this list as a bubble target for the specified model's events.
1026  
1027      @method _detachList
1028      @param {Model} model Model to detach.
1029      @protected
1030      **/
1031      _detachList: function (model) {
1032          var index = YArray.indexOf(model.lists, this);
1033  
1034          if (index > -1) {
1035              model.lists.splice(index, 1);
1036              model.removeTarget(this);
1037          }
1038      },
1039  
1040      /**
1041      Returns the index at which the given _model_ should be inserted to maintain
1042      the sort order of the list.
1043  
1044      @method _findIndex
1045      @param {Model} model The model being inserted.
1046      @return {Number} Index at which the model should be inserted.
1047      @protected
1048      **/
1049      _findIndex: function (model) {
1050          var items = this._items,
1051              max   = items.length,
1052              min   = 0,
1053              item, middle, needle;
1054  
1055          if (!this.comparator || !max) {
1056              return max;
1057          }
1058  
1059          needle = this.comparator(model);
1060  
1061          // Perform an iterative binary search to determine the correct position
1062          // based on the return value of the `comparator` function.
1063          while (min < max) {
1064              middle = (min + max) >> 1; // Divide by two and discard remainder.
1065              item   = items[middle];
1066  
1067              if (this._compare(this.comparator(item), needle) < 0) {
1068                  min = middle + 1;
1069              } else {
1070                  max = middle;
1071              }
1072          }
1073  
1074          return min;
1075      },
1076  
1077      /**
1078      Calls the public, overrideable `parse()` method and returns the result.
1079  
1080      Override this method to provide a custom pre-parsing implementation. This
1081      provides a hook for custom persistence implementations to "prep" a response
1082      before calling the `parse()` method.
1083  
1084      @method _parse
1085      @param {Any} response Server response.
1086      @return {Object[]} Array of model attribute hashes.
1087      @protected
1088      @see ModelList.parse()
1089      @since 3.7.0
1090      **/
1091      _parse: function (response) {
1092          return this.parse(response);
1093      },
1094  
1095      /**
1096      Removes the specified _model_ if it's in this list.
1097  
1098      @method _remove
1099      @param {Model|Number} model Model or index of the model to remove.
1100      @param {Object} [options] Data to be mixed into the event facade of the
1101          `remove` event for the removed model.
1102        @param {Boolean} [options.silent=false] If `true`, no `remove` event will
1103            be fired.
1104      @return {Model} Removed model.
1105      @protected
1106      **/
1107      _remove: function (model, options) {
1108          var index, facade;
1109  
1110          options || (options = {});
1111  
1112          if (Lang.isNumber(model)) {
1113              index = model;
1114              model = this.item(index);
1115          } else {
1116              index = this.indexOf(model);
1117          }
1118  
1119          if (index === -1 || !model) {
1120              this.fire(EVT_ERROR, {
1121                  error: 'Model is not in the list.',
1122                  index: index,
1123                  model: model,
1124                  src  : 'remove'
1125              });
1126  
1127              return;
1128          }
1129  
1130          facade = Y.merge(options, {
1131              index: index,
1132              model: model
1133          });
1134  
1135          if (options.silent) {
1136              this._defRemoveFn(facade);
1137          } else {
1138              this.fire(EVT_REMOVE, facade);
1139          }
1140  
1141          return model;
1142      },
1143  
1144      /**
1145      Array sort function used by `sort()` to re-sort the models in the list.
1146  
1147      @method _sort
1148      @param {Model} a First model to compare.
1149      @param {Model} b Second model to compare.
1150      @param {Object} [options] Options passed from `sort()` function.
1151          @param {Boolean} [options.descending=false] If `true`, the sort is
1152            performed in descending order.
1153      @return {Number} `-1` if _a_ is less than _b_, `0` if equal, `1` if greater
1154        (for ascending order, the reverse for descending order).
1155      @protected
1156      **/
1157      _sort: function (a, b, options) {
1158          var result = this._compare(this.comparator(a), this.comparator(b));
1159  
1160          // Early return when items are equal in their sort comparison.
1161          if (result === 0) {
1162              return result;
1163          }
1164  
1165          // Flips sign when the sort is to be peformed in descending order.
1166          return options && options.descending ? -result : result;
1167      },
1168  
1169      // -- Event Handlers -------------------------------------------------------
1170  
1171      /**
1172      Updates the model maps when a model's `id` attribute changes.
1173  
1174      @method _afterIdChange
1175      @param {EventFacade} e
1176      @protected
1177      **/
1178      _afterIdChange: function (e) {
1179          var newVal  = e.newVal,
1180              prevVal = e.prevVal,
1181              target  = e.target;
1182  
1183          if (Lang.isValue(prevVal)) {
1184              if (this._idMap[prevVal] === target) {
1185                  delete this._idMap[prevVal];
1186              } else {
1187                  // The model that changed isn't in this list. Probably just a
1188                  // bubbled change event from a nested Model List.
1189                  return;
1190              }
1191          } else {
1192              // The model had no previous id. Verify that it exists in this list
1193              // before continuing.
1194              if (this.indexOf(target) === -1) {
1195                  return;
1196              }
1197          }
1198  
1199          if (Lang.isValue(newVal)) {
1200              this._idMap[newVal] = target;
1201          }
1202      },
1203  
1204      // -- Default Event Handlers -----------------------------------------------
1205  
1206      /**
1207      Default event handler for `add` events.
1208  
1209      @method _defAddFn
1210      @param {EventFacade} e
1211      @protected
1212      **/
1213      _defAddFn: function (e) {
1214          var model = e.model,
1215              id    = model.get('id');
1216  
1217          this._clientIdMap[model.get('clientId')] = model;
1218  
1219          if (Lang.isValue(id)) {
1220              this._idMap[id] = model;
1221          }
1222  
1223          this._attachList(model);
1224          this._items.splice(e.index, 0, model);
1225      },
1226  
1227      /**
1228      Default event handler for `remove` events.
1229  
1230      @method _defRemoveFn
1231      @param {EventFacade} e
1232      @protected
1233      **/
1234      _defRemoveFn: function (e) {
1235          var model = e.model,
1236              id    = model.get('id');
1237  
1238          this._detachList(model);
1239          delete this._clientIdMap[model.get('clientId')];
1240  
1241          if (Lang.isValue(id)) {
1242              delete this._idMap[id];
1243          }
1244  
1245          this._items.splice(e.index, 1);
1246      },
1247  
1248      /**
1249      Default event handler for `reset` events.
1250  
1251      @method _defResetFn
1252      @param {EventFacade} e
1253      @protected
1254      **/
1255      _defResetFn: function (e) {
1256          // When fired from the `sort` method, we don't need to clear the list or
1257          // add any models, since the existing models are sorted in place.
1258          if (e.src === 'sort') {
1259              this._items = e.models.concat();
1260              return;
1261          }
1262  
1263          this._clear();
1264  
1265          if (e.models.length) {
1266              this.add(e.models, {silent: true});
1267          }
1268      }
1269  }, {
1270      NAME: 'modelList'
1271  });
1272  
1273  Y.augment(ModelList, Y.ArrayList);
1274  
1275  
1276  }, '3.17.2', {"requires": ["array-extras", "array-invoke", "arraylist", "base-build", "escape", "json-parse", "model"]});


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