src/cm/media/js/lib/yui/yui_3.10.3/build/model/model-debug.js
changeset 525 89ef5ed3c48b
equal deleted inserted replaced
524:322d0feea350 525:89ef5ed3c48b
       
     1 /*
       
     2 YUI 3.10.3 (build 2fb5187)
       
     3 Copyright 2013 Yahoo! Inc. All rights reserved.
       
     4 Licensed under the BSD License.
       
     5 http://yuilibrary.com/license/
       
     6 */
       
     7 
       
     8 YUI.add('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 {callback} [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 {callback} [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 {Array} [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     // -- Protected Event Handlers ---------------------------------------------
       
   934 
       
   935     /**
       
   936     Duckpunches the `_defAttrChangeFn()` provided by `Y.Attribute` so we can
       
   937     have a single global notification when a change event occurs.
       
   938 
       
   939     @method _defAttrChangeFn
       
   940     @param {EventFacade} e
       
   941     @protected
       
   942     **/
       
   943     _defAttrChangeFn: function (e) {
       
   944         var attrName = e.attrName;
       
   945 
       
   946         if (!this._setAttrVal(attrName, e.subAttrName, e.prevVal, e.newVal)) {
       
   947             Y.log('State not updated and stopImmediatePropagation called for attribute: ' + attrName + ' , value:' + e.newVal, 'warn', 'attribute');
       
   948             // Prevent "after" listeners from being invoked since nothing changed.
       
   949             e.stopImmediatePropagation();
       
   950         } else {
       
   951             e.newVal = this.get(attrName);
       
   952 
       
   953             if (e._transaction) {
       
   954                 e._transaction[attrName] = e;
       
   955             }
       
   956         }
       
   957     }
       
   958 }, {
       
   959     NAME: 'model',
       
   960 
       
   961     ATTRS: {
       
   962         /**
       
   963         A client-only identifier for this model.
       
   964 
       
   965         Like the `id` attribute, `clientId` may be used to retrieve model
       
   966         instances from lists. Unlike the `id` attribute, `clientId` is
       
   967         automatically generated, and is only intended to be used on the client
       
   968         during the current pageview.
       
   969 
       
   970         @attribute clientId
       
   971         @type String
       
   972         @readOnly
       
   973         **/
       
   974         clientId: {
       
   975             valueFn : 'generateClientId',
       
   976             readOnly: true
       
   977         },
       
   978 
       
   979         /**
       
   980         A unique identifier for this model. Among other things, this id may be
       
   981         used to retrieve model instances from lists, so it should be unique.
       
   982 
       
   983         If the id is empty, this model instance is assumed to represent a new
       
   984         item that hasn't yet been saved.
       
   985 
       
   986         If you would prefer to use a custom attribute as this model's id instead
       
   987         of using the `id` attribute (for example, maybe you'd rather use `_id`
       
   988         or `uid` as the primary id), you may set the `idAttribute` property to
       
   989         the name of your custom id attribute. The `id` attribute will then
       
   990         act as an alias for your custom attribute.
       
   991 
       
   992         @attribute id
       
   993         @type String|Number|null
       
   994         @default `null`
       
   995         **/
       
   996         id: {value: null}
       
   997     }
       
   998 });
       
   999 
       
  1000 
       
  1001 }, '3.10.3', {"requires": ["base-build", "escape", "json-parse"]});