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