[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/model/ -> model-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', function (Y, NAME) {
   9  
  10  /**
  11  Attribute-based data model with APIs for getting, setting, validating, and
  12  syncing attribute values, as well as events for being notified of model changes.
  13  
  14  @module app
  15  @submodule model
  16  @since 3.4.0
  17  **/
  18  
  19  /**
  20  Attribute-based data model with APIs for getting, setting, validating, and
  21  syncing attribute values, as well as events for being notified of model changes.
  22  
  23  In most cases, you'll want to create your own subclass of `Y.Model` and
  24  customize it to meet your needs. In particular, the `sync()` and `validate()`
  25  methods are meant to be overridden by custom implementations. You may also want
  26  to override the `parse()` method to parse non-generic server responses.
  27  
  28  @class Model
  29  @constructor
  30  @extends Base
  31  @since 3.4.0
  32  **/
  33  
  34  var GlobalEnv = YUI.namespace('Env.Model'),
  35      Lang      = Y.Lang,
  36      YArray    = Y.Array,
  37      YObject   = Y.Object,
  38  
  39      /**
  40      Fired when one or more attributes on this model are changed.
  41  
  42      @event change
  43      @param {Object} changed Hash of change information for each attribute that
  44          changed. Each item in the hash has the following properties:
  45        @param {Any} changed.newVal New value of the attribute.
  46        @param {Any} changed.prevVal Previous value of the attribute.
  47        @param {String|null} changed.src Source of the change event, if any.
  48      **/
  49      EVT_CHANGE = 'change',
  50  
  51      /**
  52      Fired when an error occurs, such as when the model doesn't validate or when
  53      a sync layer response can't be parsed.
  54  
  55      @event error
  56      @param {Any} error Error message, object, or exception generated by the
  57        error. Calling `toString()` on this should result in a meaningful error
  58        message.
  59      @param {String} src Source of the error. May be one of the following (or any
  60        custom error source defined by a Model subclass):
  61  
  62        * `load`: An error loading the model from a sync layer. The sync layer's
  63          response (if any) will be provided as the `response` property on the
  64          event facade.
  65  
  66        * `parse`: An error parsing a JSON response. The response in question will
  67          be provided as the `response` property on the event facade.
  68  
  69        * `save`: An error saving the model to a sync layer. The sync layer's
  70          response (if any) will be provided as the `response` property on the
  71          event facade.
  72  
  73        * `validate`: The model failed to validate. The attributes being validated
  74          will be provided as the `attributes` property on the event facade.
  75      **/
  76      EVT_ERROR = 'error',
  77  
  78      /**
  79      Fired after model attributes are loaded from a sync layer.
  80  
  81      @event load
  82      @param {Object} parsed The parsed version of the sync layer's response to
  83          the load request.
  84      @param {any} response The sync layer's raw, unparsed response to the load
  85          request.
  86      @since 3.5.0
  87      **/
  88      EVT_LOAD = 'load',
  89  
  90      /**
  91      Fired after model attributes are saved to a sync layer.
  92  
  93      @event save
  94      @param {Object} [parsed] The parsed version of the sync layer's response to
  95          the save request, if there was a response.
  96      @param {any} [response] The sync layer's raw, unparsed response to the save
  97          request, if there was one.
  98      @since 3.5.0
  99      **/
 100      EVT_SAVE = 'save';
 101  
 102  function Model() {
 103      Model.superclass.constructor.apply(this, arguments);
 104  }
 105  
 106  Y.Model = Y.extend(Model, Y.Base, {
 107      // -- Public Properties ----------------------------------------------------
 108  
 109      /**
 110      Hash of attributes that have changed since the last time this model was
 111      saved.
 112  
 113      @property changed
 114      @type Object
 115      @default {}
 116      **/
 117  
 118      /**
 119      Name of the attribute to use as the unique id (or primary key) for this
 120      model.
 121  
 122      The default is `id`, but if your persistence layer uses a different name for
 123      the primary key (such as `_id` or `uid`), you can specify that here.
 124  
 125      The built-in `id` attribute will always be an alias for whatever attribute
 126      name you specify here, so getting and setting `id` will always behave the
 127      same as getting and setting your custom id attribute.
 128  
 129      @property idAttribute
 130      @type String
 131      @default `'id'`
 132      **/
 133      idAttribute: 'id',
 134  
 135      /**
 136      Hash of attributes that were changed in the last `change` event. Each item
 137      in this hash is an object with the following properties:
 138  
 139        * `newVal`: The new value of the attribute after it changed.
 140        * `prevVal`: The old value of the attribute before it changed.
 141        * `src`: The source of the change, or `null` if no source was specified.
 142  
 143      @property lastChange
 144      @type Object
 145      @default {}
 146      **/
 147  
 148      /**
 149      Array of `ModelList` instances that contain this model.
 150  
 151      When a model is in one or more lists, the model's events will bubble up to
 152      those lists. You can subscribe to a model event on a list to be notified
 153      when any model in the list fires that event.
 154  
 155      This property is updated automatically when this model is added to or
 156      removed from a `ModelList` instance. You shouldn't alter it manually. When
 157      working with models in a list, you should always add and remove models using
 158      the list's `add()` and `remove()` methods.
 159  
 160      @example Subscribing to model events on a list:
 161  
 162          // Assuming `list` is an existing Y.ModelList instance.
 163          list.on('*:change', function (e) {
 164              // This function will be called whenever any model in the list
 165              // fires a `change` event.
 166              //
 167              // `e.target` will refer to the model instance that fired the
 168              // event.
 169          });
 170  
 171      @property lists
 172      @type ModelList[]
 173      @default `[]`
 174      **/
 175  
 176      // -- Protected Properties -------------------------------------------------
 177  
 178      /**
 179      This tells `Y.Base` that it should create ad-hoc attributes for config
 180      properties passed to Model's constructor. This makes it possible to
 181      instantiate a model and set a bunch of attributes without having to subclass
 182      `Y.Model` and declare all those attributes first.
 183  
 184      @property _allowAdHocAttrs
 185      @type Boolean
 186      @default true
 187      @protected
 188      @since 3.5.0
 189      **/
 190      _allowAdHocAttrs: true,
 191  
 192      /**
 193      Total hack to allow us to identify Model instances without using
 194      `instanceof`, which won't work when the instance was created in another
 195      window or YUI sandbox.
 196  
 197      @property _isYUIModel
 198      @type Boolean
 199      @default true
 200      @protected
 201      @since 3.5.0
 202      **/
 203      _isYUIModel: true,
 204  
 205      // -- Lifecycle Methods ----------------------------------------------------
 206      initializer: function (config) {
 207          this.changed    = {};
 208          this.lastChange = {};
 209          this.lists      = [];
 210      },
 211  
 212      // -- Public Methods -------------------------------------------------------
 213  
 214      /**
 215      Destroys this model instance and removes it from its containing lists, if
 216      any.
 217  
 218      The _callback_, if one is provided, will be called after the model is
 219      destroyed.
 220  
 221      If `options.remove` is `true`, then this method delegates to the `sync()`
 222      method to delete the model from the persistence layer, which is an
 223      asynchronous action. In this case, the _callback_ (if provided) will be
 224      called after the sync layer indicates success or failure of the delete
 225      operation.
 226  
 227      @method destroy
 228      @param {Object} [options] Sync options. It's up to the custom sync
 229          implementation to determine what options it supports or requires, if
 230          any.
 231        @param {Boolean} [options.remove=false] If `true`, the model will be
 232          deleted via the sync layer in addition to the instance being destroyed.
 233      @param {Function} [callback] Called after the model has been destroyed (and
 234          deleted via the sync layer if `options.remove` is `true`).
 235        @param {Error|null} callback.err If an error occurred, this parameter will
 236          contain the error. Otherwise _err_ will be `null`.
 237      @chainable
 238      **/
 239      destroy: function (options, callback) {
 240          var self = this;
 241  
 242          // Allow callback as only arg.
 243          if (typeof options === 'function') {
 244              callback = options;
 245              options  = null;
 246          }
 247  
 248          self.onceAfter('destroy', function () {
 249              function finish(err) {
 250                  if (!err) {
 251                      YArray.each(self.lists.concat(), function (list) {
 252                          list.remove(self, options);
 253                      });
 254                  }
 255  
 256                  callback && callback.apply(null, arguments);
 257              }
 258  
 259              if (options && (options.remove || options['delete'])) {
 260                  self.sync('delete', options, finish);
 261              } else {
 262                  finish();
 263              }
 264          });
 265  
 266          return Model.superclass.destroy.call(self);
 267      },
 268  
 269      /**
 270      Returns a clientId string that's unique among all models on the current page
 271      (even models in other YUI instances). Uniqueness across pageviews is
 272      unlikely.
 273  
 274      @method generateClientId
 275      @return {String} Unique clientId.
 276      **/
 277      generateClientId: function () {
 278          GlobalEnv.lastId || (GlobalEnv.lastId = 0);
 279          return this.constructor.NAME + '_' + (GlobalEnv.lastId += 1);
 280      },
 281  
 282      /**
 283      Returns the value of the specified attribute.
 284  
 285      If the attribute's value is an object, _name_ may use dot notation to
 286      specify the path to a specific property within the object, and the value of
 287      that property will be returned.
 288  
 289      @example
 290          // Set the 'foo' attribute to an object.
 291          myModel.set('foo', {
 292              bar: {
 293                  baz: 'quux'
 294              }
 295          });
 296  
 297          // Get the value of 'foo'.
 298          myModel.get('foo');
 299          // => {bar: {baz: 'quux'}}
 300  
 301          // Get the value of 'foo.bar.baz'.
 302          myModel.get('foo.bar.baz');
 303          // => 'quux'
 304  
 305      @method get
 306      @param {String} name Attribute name or object property path.
 307      @return {Any} Attribute value, or `undefined` if the attribute doesn't
 308        exist.
 309      **/
 310  
 311      // get() is defined by Y.Attribute.
 312  
 313      /**
 314      Returns an HTML-escaped version of the value of the specified string
 315      attribute. The value is escaped using `Y.Escape.html()`.
 316  
 317      @method getAsHTML
 318      @param {String} name Attribute name or object property path.
 319      @return {String} HTML-escaped attribute value.
 320      **/
 321      getAsHTML: function (name) {
 322          var value = this.get(name);
 323          return Y.Escape.html(Lang.isValue(value) ? String(value) : '');
 324      },
 325  
 326      /**
 327      Returns a URL-encoded version of the value of the specified string
 328      attribute. The value is encoded using the native `encodeURIComponent()`
 329      function.
 330  
 331      @method getAsURL
 332      @param {String} name Attribute name or object property path.
 333      @return {String} URL-encoded attribute value.
 334      **/
 335      getAsURL: function (name) {
 336          var value = this.get(name);
 337          return encodeURIComponent(Lang.isValue(value) ? String(value) : '');
 338      },
 339  
 340      /**
 341      Returns `true` if any attribute of this model has been changed since the
 342      model was last saved.
 343  
 344      New models (models for which `isNew()` returns `true`) are implicitly
 345      considered to be "modified" until the first time they're saved.
 346  
 347      @method isModified
 348      @return {Boolean} `true` if this model has changed since it was last saved,
 349        `false` otherwise.
 350      **/
 351      isModified: function () {
 352          return this.isNew() || !YObject.isEmpty(this.changed);
 353      },
 354  
 355      /**
 356      Returns `true` if this model is "new", meaning it hasn't been saved since it
 357      was created.
 358  
 359      Newness is determined by checking whether the model's `id` attribute has
 360      been set. An empty id is assumed to indicate a new model, whereas a
 361      non-empty id indicates a model that was either loaded or has been saved
 362      since it was created.
 363  
 364      @method isNew
 365      @return {Boolean} `true` if this model is new, `false` otherwise.
 366      **/
 367      isNew: function () {
 368          return !Lang.isValue(this.get('id'));
 369      },
 370  
 371      /**
 372      Loads this model from the server.
 373  
 374      This method delegates to the `sync()` method to perform the actual load
 375      operation, which is an asynchronous action. Specify a _callback_ function to
 376      be notified of success or failure.
 377  
 378      A successful load operation will fire a `load` event, while an unsuccessful
 379      load operation will fire an `error` event with the `src` value "load".
 380  
 381      If the load operation succeeds and one or more of the loaded attributes
 382      differ from this model's current attributes, a `change` event will be fired.
 383  
 384      @method load
 385      @param {Object} [options] Options to be passed to `sync()` and to `set()`
 386        when setting the loaded attributes. It's up to the custom sync
 387        implementation to determine what options it supports or requires, if any.
 388      @param {Function} [callback] Called when the sync operation finishes.
 389        @param {Error|null} callback.err If an error occurred, this parameter will
 390          contain the error. If the sync operation succeeded, _err_ will be
 391          `null`.
 392        @param {Any} callback.response The server's response. This value will
 393          be passed to the `parse()` method, which is expected to parse it and
 394          return an attribute hash.
 395      @chainable
 396      **/
 397      load: function (options, callback) {
 398          var self = this;
 399  
 400          // Allow callback as only arg.
 401          if (typeof options === 'function') {
 402              callback = options;
 403              options  = {};
 404          }
 405  
 406          options || (options = {});
 407  
 408          self.sync('read', options, function (err, response) {
 409              var facade = {
 410                      options : options,
 411                      response: response
 412                  },
 413  
 414                  parsed;
 415  
 416              if (err) {
 417                  facade.error = err;
 418                  facade.src   = 'load';
 419  
 420                  self.fire(EVT_ERROR, facade);
 421              } else {
 422                  // Lazy publish.
 423                  if (!self._loadEvent) {
 424                      self._loadEvent = self.publish(EVT_LOAD, {
 425                          preventable: false
 426                      });
 427                  }
 428  
 429                  parsed = facade.parsed = self._parse(response);
 430  
 431                  self.setAttrs(parsed, options);
 432                  self.changed = {};
 433  
 434                  self.fire(EVT_LOAD, facade);
 435              }
 436  
 437              callback && callback.apply(null, arguments);
 438          });
 439  
 440          return self;
 441      },
 442  
 443      /**
 444      Called to parse the _response_ when the model is loaded from the server.
 445      This method receives a server _response_ and is expected to return an
 446      attribute hash.
 447  
 448      The default implementation assumes that _response_ is either an attribute
 449      hash or a JSON string that can be parsed into an attribute hash. If
 450      _response_ is a JSON string and either `Y.JSON` or the native `JSON` object
 451      are available, it will be parsed automatically. If a parse error occurs, an
 452      `error` event will be fired and the model will not be updated.
 453  
 454      You may override this method to implement custom parsing logic if necessary.
 455  
 456      @method parse
 457      @param {Any} response Server response.
 458      @return {Object} Attribute hash.
 459      **/
 460      parse: function (response) {
 461          if (typeof response === 'string') {
 462              try {
 463                  return Y.JSON.parse(response);
 464              } catch (ex) {
 465                  this.fire(EVT_ERROR, {
 466                      error   : ex,
 467                      response: response,
 468                      src     : 'parse'
 469                  });
 470  
 471                  return null;
 472              }
 473          }
 474  
 475          return response;
 476      },
 477  
 478      /**
 479      Saves this model to the server.
 480  
 481      This method delegates to the `sync()` method to perform the actual save
 482      operation, which is an asynchronous action. Specify a _callback_ function to
 483      be notified of success or failure.
 484  
 485      A successful save operation will fire a `save` event, while an unsuccessful
 486      save operation will fire an `error` event with the `src` value "save".
 487  
 488      If the save operation succeeds and one or more of the attributes returned in
 489      the server's response differ from this model's current attributes, a
 490      `change` event will be fired.
 491  
 492      @method save
 493      @param {Object} [options] Options to be passed to `sync()` and to `set()`
 494        when setting synced attributes. It's up to the custom sync implementation
 495        to determine what options it supports or requires, if any.
 496      @param {Function} [callback] Called when the sync operation finishes.
 497        @param {Error|null} callback.err If an error occurred or validation
 498          failed, this parameter will contain the error. If the sync operation
 499          succeeded, _err_ will be `null`.
 500        @param {Any} callback.response The server's response. This value will
 501          be passed to the `parse()` method, which is expected to parse it and
 502          return an attribute hash.
 503      @chainable
 504      **/
 505      save: function (options, callback) {
 506          var self = this;
 507  
 508          // Allow callback as only arg.
 509          if (typeof options === 'function') {
 510              callback = options;
 511              options  = {};
 512          }
 513  
 514          options || (options = {});
 515  
 516          self._validate(self.toJSON(), function (err) {
 517              if (err) {
 518                  callback && callback.call(null, err);
 519                  return;
 520              }
 521  
 522              self.sync(self.isNew() ? 'create' : 'update', options, function (err, response) {
 523                  var facade = {
 524                          options : options,
 525                          response: response
 526                      },
 527  
 528                      parsed;
 529  
 530                  if (err) {
 531                      facade.error = err;
 532                      facade.src   = 'save';
 533  
 534                      self.fire(EVT_ERROR, facade);
 535                  } else {
 536                      // Lazy publish.
 537                      if (!self._saveEvent) {
 538                          self._saveEvent = self.publish(EVT_SAVE, {
 539                              preventable: false
 540                          });
 541                      }
 542  
 543                      if (response) {
 544                          parsed = facade.parsed = self._parse(response);
 545                          self.setAttrs(parsed, options);
 546                      }
 547  
 548                      self.changed = {};
 549                      self.fire(EVT_SAVE, facade);
 550                  }
 551  
 552                  callback && callback.apply(null, arguments);
 553              });
 554          });
 555  
 556          return self;
 557      },
 558  
 559      /**
 560      Sets the value of a single attribute. If model validation fails, the
 561      attribute will not be set and an `error` event will be fired.
 562  
 563      Use `setAttrs()` to set multiple attributes at once.
 564  
 565      @example
 566          model.set('foo', 'bar');
 567  
 568      @method set
 569      @param {String} name Attribute name or object property path.
 570      @param {any} value Value to set.
 571      @param {Object} [options] Data to be mixed into the event facade of the
 572          `change` event(s) for these attributes.
 573        @param {Boolean} [options.silent=false] If `true`, no `change` event will
 574            be fired.
 575      @chainable
 576      **/
 577      set: function (name, value, options) {
 578          var attributes = {};
 579          attributes[name] = value;
 580  
 581          return this.setAttrs(attributes, options);
 582      },
 583  
 584      /**
 585      Sets the values of multiple attributes at once. If model validation fails,
 586      the attributes will not be set and an `error` event will be fired.
 587  
 588      @example
 589          model.setAttrs({
 590              foo: 'bar',
 591              baz: 'quux'
 592          });
 593  
 594      @method setAttrs
 595      @param {Object} attributes Hash of attribute names and values to set.
 596      @param {Object} [options] Data to be mixed into the event facade of the
 597          `change` event(s) for these attributes.
 598        @param {Boolean} [options.silent=false] If `true`, no `change` event will
 599            be fired.
 600      @chainable
 601      **/
 602      setAttrs: function (attributes, options) {
 603          var idAttribute = this.idAttribute,
 604              changed, e, key, lastChange, transaction;
 605  
 606          // Makes a shallow copy of the `options` object before adding the
 607          // `_transaction` object to it so we don't modify someone else's object.
 608          options     = Y.merge(options);
 609          transaction = options._transaction = {};
 610  
 611          // When a custom id attribute is in use, always keep the default `id`
 612          // attribute in sync.
 613          if (idAttribute !== 'id') {
 614              // So we don't modify someone else's object.
 615              attributes = Y.merge(attributes);
 616  
 617              if (YObject.owns(attributes, idAttribute)) {
 618                  attributes.id = attributes[idAttribute];
 619              } else if (YObject.owns(attributes, 'id')) {
 620                  attributes[idAttribute] = attributes.id;
 621              }
 622          }
 623  
 624          for (key in attributes) {
 625              if (YObject.owns(attributes, key)) {
 626                  this._setAttr(key, attributes[key], options);
 627              }
 628          }
 629  
 630          if (!YObject.isEmpty(transaction)) {
 631              changed    = this.changed;
 632              lastChange = this.lastChange = {};
 633  
 634              for (key in transaction) {
 635                  if (YObject.owns(transaction, key)) {
 636                      e = transaction[key];
 637  
 638                      changed[key] = e.newVal;
 639  
 640                      lastChange[key] = {
 641                          newVal : e.newVal,
 642                          prevVal: e.prevVal,
 643                          src    : e.src || null
 644                      };
 645                  }
 646              }
 647  
 648              if (!options.silent) {
 649                  // Lazy publish for the change event.
 650                  if (!this._changeEvent) {
 651                      this._changeEvent = this.publish(EVT_CHANGE, {
 652                          preventable: false
 653                      });
 654                  }
 655  
 656                  options.changed = lastChange;
 657  
 658                  this.fire(EVT_CHANGE, options);
 659              }
 660          }
 661  
 662          return this;
 663      },
 664  
 665      /**
 666      Override this method to provide a custom persistence implementation for this
 667      model. The default just calls the callback without actually doing anything.
 668  
 669      This method is called internally by `load()`, `save()`, and `destroy()`, and
 670      their implementations rely on the callback being called. This effectively
 671      means that when a callback is provided, it must be called at some point for
 672      the class to operate correctly.
 673  
 674      @method sync
 675      @param {String} action Sync action to perform. May be one of the following:
 676  
 677        * `create`: Store a newly-created model for the first time.
 678        * `delete`: Delete an existing model.
 679        * `read`  : Load an existing model.
 680        * `update`: Update an existing model.
 681  
 682      @param {Object} [options] Sync options. It's up to the custom sync
 683        implementation to determine what options it supports or requires, if any.
 684      @param {Function} [callback] Called when the sync operation finishes.
 685        @param {Error|null} callback.err If an error occurred, this parameter will
 686          contain the error. If the sync operation succeeded, _err_ will be
 687          falsy.
 688        @param {Any} [callback.response] The server's response.
 689      **/
 690      sync: function (/* action, options, callback */) {
 691          var callback = YArray(arguments, 0, true).pop();
 692  
 693          if (typeof callback === 'function') {
 694              callback();
 695          }
 696      },
 697  
 698      /**
 699      Returns a copy of this model's attributes that can be passed to
 700      `Y.JSON.stringify()` or used for other nefarious purposes.
 701  
 702      The `clientId` attribute is not included in the returned object.
 703  
 704      If you've specified a custom attribute name in the `idAttribute` property,
 705      the default `id` attribute will not be included in the returned object.
 706  
 707      Note: The ECMAScript 5 specification states that objects may implement a
 708      `toJSON` method to provide an alternate object representation to serialize
 709      when passed to `JSON.stringify(obj)`.  This allows class instances to be
 710      serialized as if they were plain objects.  This is why Model's `toJSON`
 711      returns an object, not a JSON string.
 712  
 713      See <http://es5.github.com/#x15.12.3> for details.
 714  
 715      @method toJSON
 716      @return {Object} Copy of this model's attributes.
 717      **/
 718      toJSON: function () {
 719          var attrs = this.getAttrs();
 720  
 721          delete attrs.clientId;
 722          delete attrs.destroyed;
 723          delete attrs.initialized;
 724  
 725          if (this.idAttribute !== 'id') {
 726              delete attrs.id;
 727          }
 728  
 729          return attrs;
 730      },
 731  
 732      /**
 733      Reverts the last change to the model.
 734  
 735      If an _attrNames_ array is provided, then only the named attributes will be
 736      reverted (and only if they were modified in the previous change). If no
 737      _attrNames_ array is provided, then all changed attributes will be reverted
 738      to their previous values.
 739  
 740      Note that only one level of undo is available: from the current state to the
 741      previous state. If `undo()` is called when no previous state is available,
 742      it will simply do nothing.
 743  
 744      @method undo
 745      @param {String[]} [attrNames] Array of specific attribute names to revert. If
 746        not specified, all attributes modified in the last change will be
 747        reverted.
 748      @param {Object} [options] Data to be mixed into the event facade of the
 749          change event(s) for these attributes.
 750        @param {Boolean} [options.silent=false] If `true`, no `change` event will
 751            be fired.
 752      @chainable
 753      **/
 754      undo: function (attrNames, options) {
 755          var lastChange  = this.lastChange,
 756              idAttribute = this.idAttribute,
 757              toUndo      = {},
 758              needUndo;
 759  
 760          attrNames || (attrNames = YObject.keys(lastChange));
 761  
 762          YArray.each(attrNames, function (name) {
 763              if (YObject.owns(lastChange, name)) {
 764                  // Don't generate a double change for custom id attributes.
 765                  name = name === idAttribute ? 'id' : name;
 766  
 767                  needUndo     = true;
 768                  toUndo[name] = lastChange[name].prevVal;
 769              }
 770          });
 771  
 772          return needUndo ? this.setAttrs(toUndo, options) : this;
 773      },
 774  
 775      /**
 776      Override this method to provide custom validation logic for this model.
 777  
 778      While attribute-specific validators can be used to validate individual
 779      attributes, this method gives you a hook to validate a hash of all
 780      attributes before the model is saved. This method is called automatically
 781      before `save()` takes any action. If validation fails, the `save()` call
 782      will be aborted.
 783  
 784      In your validation method, call the provided `callback` function with no
 785      arguments to indicate success. To indicate failure, pass a single argument,
 786      which may contain an error message, an array of error messages, or any other
 787      value. This value will be passed along to the `error` event.
 788  
 789      @example
 790  
 791          model.validate = function (attrs, callback) {
 792              if (attrs.pie !== true) {
 793                  // No pie?! Invalid!
 794                  callback('Must provide pie.');
 795                  return;
 796              }
 797  
 798              // Success!
 799              callback();
 800          };
 801  
 802      @method validate
 803      @param {Object} attrs Attribute hash containing all model attributes to
 804          be validated.
 805      @param {Function} callback Validation callback. Call this function when your
 806          validation logic finishes. To trigger a validation failure, pass any
 807          value as the first argument to the callback (ideally a meaningful
 808          validation error of some kind).
 809  
 810          @param {Any} [callback.err] Validation error. Don't provide this
 811              argument if validation succeeds. If validation fails, set this to an
 812              error message or some other meaningful value. It will be passed
 813              along to the resulting `error` event.
 814      **/
 815      validate: function (attrs, callback) {
 816          callback && callback();
 817      },
 818  
 819      // -- Protected Methods ----------------------------------------------------
 820  
 821      /**
 822      Duckpunches the `addAttr` method provided by `Y.Attribute` to keep the
 823      `id` attribute’s value and a custom id attribute’s (if provided) value
 824      in sync when adding the attributes to the model instance object.
 825  
 826      Marked as protected to hide it from Model's public API docs, even though
 827      this is a public method in Attribute.
 828  
 829      @method addAttr
 830      @param {String} name The name of the attribute.
 831      @param {Object} config An object with attribute configuration property/value
 832        pairs, specifying the configuration for the attribute.
 833      @param {Boolean} lazy (optional) Whether or not to add this attribute lazily
 834        (on the first call to get/set).
 835      @return {Object} A reference to the host object.
 836      @chainable
 837      @protected
 838      **/
 839      addAttr: function (name, config, lazy) {
 840          var idAttribute = this.idAttribute,
 841              idAttrCfg, id;
 842  
 843          if (idAttribute && name === idAttribute) {
 844              idAttrCfg = this._isLazyAttr('id') || this._getAttrCfg('id');
 845              id        = config.value === config.defaultValue ? null : config.value;
 846  
 847              if (!Lang.isValue(id)) {
 848                  // Hunt for the id value.
 849                  id = idAttrCfg.value === idAttrCfg.defaultValue ? null : idAttrCfg.value;
 850  
 851                  if (!Lang.isValue(id)) {
 852                      // No id value provided on construction, check defaults.
 853                      id = Lang.isValue(config.defaultValue) ?
 854                          config.defaultValue :
 855                          idAttrCfg.defaultValue;
 856                  }
 857              }
 858  
 859              config.value = id;
 860  
 861              // Make sure `id` is in sync.
 862              if (idAttrCfg.value !== id) {
 863                  idAttrCfg.value = id;
 864  
 865                  if (this._isLazyAttr('id')) {
 866                      this._state.add('id', 'lazy', idAttrCfg);
 867                  } else {
 868                      this._state.add('id', 'value', id);
 869                  }
 870              }
 871          }
 872  
 873          return Model.superclass.addAttr.apply(this, arguments);
 874      },
 875  
 876      /**
 877      Calls the public, overrideable `parse()` method and returns the result.
 878  
 879      Override this method to provide a custom pre-parsing implementation. This
 880      provides a hook for custom persistence implementations to "prep" a response
 881      before calling the `parse()` method.
 882  
 883      @method _parse
 884      @param {Any} response Server response.
 885      @return {Object} Attribute hash.
 886      @protected
 887      @see Model.parse()
 888      @since 3.7.0
 889      **/
 890      _parse: function (response) {
 891          return this.parse(response);
 892      },
 893  
 894      /**
 895      Calls the public, overridable `validate()` method and fires an `error` event
 896      if validation fails.
 897  
 898      @method _validate
 899      @param {Object} attributes Attribute hash.
 900      @param {Function} callback Validation callback.
 901          @param {Any} [callback.err] Value on failure, non-value on success.
 902      @protected
 903      **/
 904      _validate: function (attributes, callback) {
 905          var self = this;
 906  
 907          function handler(err) {
 908              if (Lang.isValue(err)) {
 909                  // Validation failed. Fire an error.
 910                  self.fire(EVT_ERROR, {
 911                      attributes: attributes,
 912                      error     : err,
 913                      src       : 'validate'
 914                  });
 915  
 916                  callback(err);
 917                  return;
 918              }
 919  
 920              callback();
 921          }
 922  
 923          if (self.validate.length === 1) {
 924              // Backcompat for 3.4.x-style synchronous validate() functions that
 925              // don't take a callback argument.
 926              Y.log('Synchronous validate() methods are deprecated since YUI 3.5.0.', 'warn', 'Model');
 927              handler(self.validate(attributes, handler));
 928          } else {
 929              self.validate(attributes, handler);
 930          }
 931      },
 932  
 933      // -- Private Methods ----------------------------------------------------
 934  
 935      /**
 936       Overrides AttributeCore's `_setAttrVal`, to register the changed value if it's part
 937       of a Model `setAttrs` transaction.
 938  
 939       NOTE: AttributeCore's `_setAttrVal` is currently private, but until we support coalesced
 940       change events in attribute, we need this override.
 941  
 942       @method _setAttrVal
 943       @private
 944       @param {String} attrName The attribute name.
 945       @param {String} subAttrName The sub-attribute name, if setting a sub-attribute property ("x.y.z").
 946       @param {Any} prevVal The currently stored value of the attribute.
 947       @param {Any} newVal The value which is going to be stored.
 948       @param {Object} [opts] Optional data providing the circumstances for the change.
 949       @param {Object} [attrCfg] Optional config hash for the attribute. This is added for performance along the critical path,
 950       where the calling method has already obtained the config from state.
 951  
 952       @return {boolean} true if the new attribute value was stored, false if not.
 953       **/
 954      _setAttrVal : function(attrName, subAttrName, prevVal, newVal, opts, attrCfg) {
 955  
 956          var didChange = Model.superclass._setAttrVal.apply(this, arguments),
 957              transaction = opts && opts._transaction,
 958              initializing = attrCfg && attrCfg.initializing;
 959  
 960          // value actually changed inside a model setAttrs transaction
 961          if (didChange && transaction && !initializing) {
 962              transaction[attrName] = {
 963                  newVal: this.get(attrName), // newVal may be impacted by getter
 964                  prevVal: prevVal,
 965                  src: opts.src || null
 966              };
 967          }
 968  
 969          return didChange;
 970      }
 971  
 972  }, {
 973      NAME: 'model',
 974  
 975      ATTRS: {
 976          /**
 977          A client-only identifier for this model.
 978  
 979          Like the `id` attribute, `clientId` may be used to retrieve model
 980          instances from lists. Unlike the `id` attribute, `clientId` is
 981          automatically generated, and is only intended to be used on the client
 982          during the current pageview.
 983  
 984          @attribute clientId
 985          @type String
 986          @readOnly
 987          **/
 988          clientId: {
 989              valueFn : 'generateClientId',
 990              readOnly: true
 991          },
 992  
 993          /**
 994          A unique identifier for this model. Among other things, this id may be
 995          used to retrieve model instances from lists, so it should be unique.
 996  
 997          If the id is empty, this model instance is assumed to represent a new
 998          item that hasn't yet been saved.
 999  
1000          If you would prefer to use a custom attribute as this model's id instead
1001          of using the `id` attribute (for example, maybe you'd rather use `_id`
1002          or `uid` as the primary id), you may set the `idAttribute` property to
1003          the name of your custom id attribute. The `id` attribute will then
1004          act as an alias for your custom attribute.
1005  
1006          @attribute id
1007          @type String|Number|null
1008          @default `null`
1009          **/
1010          id: {value: null}
1011      }
1012  });
1013  
1014  
1015  }, '3.17.2', {"requires": ["base-build", "escape", "json-parse"]});


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