~michael.nelson/ubuntu-webcatalog/1267731-import-sca-apps-error

« back to all changes in this revision

Viewing changes to src/webcatalog/static/yui/3.10.3/build/model/model.js

  • Committer: Tarmac
  • Author(s): Stephen Stewart
  • Date: 2013-06-26 09:19:32 UTC
  • mfrom: (184.1.4 ubuntu-global-nav)
  • Revision ID: tarmac-20130626091932-8urtuli368k8p7ds
[r=beuno,jonas-drange] add ubuntu global nav to apps.ubuntu.com

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
            handler(self.validate(attributes, handler));
 
927
        } else {
 
928
            self.validate(attributes, handler);
 
929
        }
 
930
    },
 
931
 
 
932
    // -- Protected Event Handlers ---------------------------------------------
 
933
 
 
934
    /**
 
935
    Duckpunches the `_defAttrChangeFn()` provided by `Y.Attribute` so we can
 
936
    have a single global notification when a change event occurs.
 
937
 
 
938
    @method _defAttrChangeFn
 
939
    @param {EventFacade} e
 
940
    @protected
 
941
    **/
 
942
    _defAttrChangeFn: function (e) {
 
943
        var attrName = e.attrName;
 
944
 
 
945
        if (!this._setAttrVal(attrName, e.subAttrName, e.prevVal, e.newVal)) {
 
946
            // Prevent "after" listeners from being invoked since nothing changed.
 
947
            e.stopImmediatePropagation();
 
948
        } else {
 
949
            e.newVal = this.get(attrName);
 
950
 
 
951
            if (e._transaction) {
 
952
                e._transaction[attrName] = e;
 
953
            }
 
954
        }
 
955
    }
 
956
}, {
 
957
    NAME: 'model',
 
958
 
 
959
    ATTRS: {
 
960
        /**
 
961
        A client-only identifier for this model.
 
962
 
 
963
        Like the `id` attribute, `clientId` may be used to retrieve model
 
964
        instances from lists. Unlike the `id` attribute, `clientId` is
 
965
        automatically generated, and is only intended to be used on the client
 
966
        during the current pageview.
 
967
 
 
968
        @attribute clientId
 
969
        @type String
 
970
        @readOnly
 
971
        **/
 
972
        clientId: {
 
973
            valueFn : 'generateClientId',
 
974
            readOnly: true
 
975
        },
 
976
 
 
977
        /**
 
978
        A unique identifier for this model. Among other things, this id may be
 
979
        used to retrieve model instances from lists, so it should be unique.
 
980
 
 
981
        If the id is empty, this model instance is assumed to represent a new
 
982
        item that hasn't yet been saved.
 
983
 
 
984
        If you would prefer to use a custom attribute as this model's id instead
 
985
        of using the `id` attribute (for example, maybe you'd rather use `_id`
 
986
        or `uid` as the primary id), you may set the `idAttribute` property to
 
987
        the name of your custom id attribute. The `id` attribute will then
 
988
        act as an alias for your custom attribute.
 
989
 
 
990
        @attribute id
 
991
        @type String|Number|null
 
992
        @default `null`
 
993
        **/
 
994
        id: {value: null}
 
995
    }
 
996
});
 
997
 
 
998
 
 
999
}, '3.10.3', {"requires": ["base-build", "escape", "json-parse"]});