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