[ 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-sync-rest', function (Y, NAME) { 9 10 /** 11 An extension which provides a RESTful XHR sync implementation that can be mixed 12 into a Model or ModelList subclass. 13 14 @module app 15 @submodule model-sync-rest 16 @since 3.6.0 17 **/ 18 19 var Lang = Y.Lang; 20 21 /** 22 An extension which provides a RESTful XHR sync implementation that can be mixed 23 into a Model or ModelList subclass. 24 25 This makes it trivial for your Model or ModelList subclasses communicate and 26 transmit their data via RESTful XHRs. In most cases you'll only need to provide 27 a value for `root` when sub-classing `Y.Model`. 28 29 Y.User = Y.Base.create('user', Y.Model, [Y.ModelSync.REST], { 30 root: '/users' 31 }); 32 33 Y.Users = Y.Base.create('users', Y.ModelList, [Y.ModelSync.REST], { 34 // By convention `Y.User`'s `root` will be used for the lists' URL. 35 model: Y.User 36 }); 37 38 var users = new Y.Users(); 39 40 // GET users list from: "/users" 41 users.load(function () { 42 var firstUser = users.item(0); 43 44 firstUser.get('id'); // => "1" 45 46 // PUT updated user data at: "/users/1" 47 firstUser.set('name', 'Eric').save(); 48 }); 49 50 @class ModelSync.REST 51 @extensionfor Model 52 @extensionfor ModelList 53 @since 3.6.0 54 **/ 55 function RESTSync() {} 56 57 /** 58 A request authenticity token to validate HTTP requests made by this extension 59 with the server when the request results in changing persistent state. This 60 allows you to protect your server from Cross-Site Request Forgery attacks. 61 62 A CSRF token provided by the server can be embedded in the HTML document and 63 assigned to `YUI.Env.CSRF_TOKEN` like this: 64 65 <script> 66 YUI.Env.CSRF_TOKEN = {{session.authenticityToken}}; 67 </script> 68 69 The above should come after YUI seed file so that `YUI.Env` will be defined. 70 71 **Note:** This can be overridden on a per-request basis. See `sync()` method. 72 73 When a value for the CSRF token is provided, either statically or via `options` 74 passed to the `save()` and `destroy()` methods, the applicable HTTP requests 75 will have a `X-CSRF-Token` header added with the token value. 76 77 @property CSRF_TOKEN 78 @type String 79 @default YUI.Env.CSRF_TOKEN 80 @static 81 @since 3.6.0 82 **/ 83 RESTSync.CSRF_TOKEN = YUI.Env.CSRF_TOKEN; 84 85 /** 86 Static flag to use the HTTP POST method instead of PUT or DELETE. 87 88 If the server-side HTTP framework isn't RESTful, setting this flag to `true` 89 will cause all PUT and DELETE requests to instead use the POST HTTP method, and 90 add a `X-HTTP-Method-Override` HTTP header with the value of the method type 91 which was overridden. 92 93 @property EMULATE_HTTP 94 @type Boolean 95 @default false 96 @static 97 @since 3.6.0 98 **/ 99 RESTSync.EMULATE_HTTP = false; 100 101 /** 102 Default headers used with all XHRs. 103 104 By default the `Accept` and `Content-Type` headers are set to 105 "application/json", this signals to the HTTP server to process the request 106 bodies as JSON and send JSON responses. If you're sending and receiving content 107 other than JSON, you can override these headers and the `parse()` and 108 `serialize()` methods. 109 110 **Note:** These headers will be merged with any request-specific headers, and 111 the request-specific headers will take precedence. 112 113 @property HTTP_HEADERS 114 @type Object 115 @default 116 { 117 "Accept" : "application/json", 118 "Content-Type": "application/json" 119 } 120 @static 121 @since 3.6.0 122 **/ 123 RESTSync.HTTP_HEADERS = { 124 'Accept' : 'application/json', 125 'Content-Type': 'application/json' 126 }; 127 128 /** 129 Static mapping of RESTful HTTP methods corresponding to CRUD actions. 130 131 @property HTTP_METHODS 132 @type Object 133 @default 134 { 135 "create": "POST", 136 "read" : "GET", 137 "update": "PUT", 138 "delete": "DELETE" 139 } 140 @static 141 @since 3.6.0 142 **/ 143 RESTSync.HTTP_METHODS = { 144 'create': 'POST', 145 'read' : 'GET', 146 'update': 'PUT', 147 'delete': 'DELETE' 148 }; 149 150 /** 151 The number of milliseconds before the XHRs will timeout/abort. This defaults to 152 30 seconds. 153 154 **Note:** This can be overridden on a per-request basis. See `sync()` method. 155 156 @property HTTP_TIMEOUT 157 @type Number 158 @default 30000 159 @static 160 @since 3.6.0 161 **/ 162 RESTSync.HTTP_TIMEOUT = 30000; 163 164 /** 165 Properties that shouldn't be turned into ad-hoc attributes when passed to a 166 Model or ModelList constructor. 167 168 @property _NON_ATTRS_CFG 169 @type Array 170 @default ["root", "url"] 171 @static 172 @protected 173 @since 3.6.0 174 **/ 175 RESTSync._NON_ATTRS_CFG = ['root', 'url']; 176 177 RESTSync.prototype = { 178 179 // -- Public Properties ---------------------------------------------------- 180 181 /** 182 A string which represents the root or collection part of the URL which 183 relates to a Model or ModelList. Usually this value should be same for all 184 instances of a specific Model/ModelList subclass. 185 186 When sub-classing `Y.Model`, usually you'll only need to override this 187 property, which lets the URLs for the XHRs be generated by convention. If 188 the `root` string ends with a trailing-slash, XHR URLs will also end with a 189 "/", and if the `root` does not end with a slash, neither will the XHR URLs. 190 191 @example 192 Y.User = Y.Base.create('user', Y.Model, [Y.ModelSync.REST], { 193 root: '/users' 194 }); 195 196 var currentUser, newUser; 197 198 // GET the user data from: "/users/123" 199 currentUser = new Y.User({id: '123'}).load(); 200 201 // POST the new user data to: "/users" 202 newUser = new Y.User({name: 'Eric Ferraiuolo'}).save(); 203 204 When sub-classing `Y.ModelList`, usually you'll want to ignore configuring 205 the `root` and simply rely on the build-in convention of the list's 206 generated URLs defaulting to the `root` specified by the list's `model`. 207 208 @property root 209 @type String 210 @default "" 211 @since 3.6.0 212 **/ 213 root: '', 214 215 /** 216 A string which specifies the URL to use when making XHRs, if not value is 217 provided, the URLs used to make XHRs will be generated by convention. 218 219 While a `url` can be provided for each Model/ModelList instance, usually 220 you'll want to either rely on the default convention or provide a tokenized 221 string on the prototype which can be used for all instances. 222 223 When sub-classing `Y.Model`, you will probably be able to rely on the 224 default convention of generating URLs in conjunction with the `root` 225 property and whether the model is new or not (i.e. has an `id`). If the 226 `root` property ends with a trailing-slash, the generated URL for the 227 specific model will also end with a trailing-slash. 228 229 @example 230 Y.User = Y.Base.create('user', Y.Model, [Y.ModelSync.REST], { 231 root: '/users/' 232 }); 233 234 var currentUser, newUser; 235 236 // GET the user data from: "/users/123/" 237 currentUser = new Y.User({id: '123'}).load(); 238 239 // POST the new user data to: "/users/" 240 newUser = new Y.User({name: 'Eric Ferraiuolo'}).save(); 241 242 If a `url` is specified, it will be processed by `Y.Lang.sub()`, which is 243 useful when the URLs for a Model/ModelList subclass match a specific pattern 244 and can use simple replacement tokens; e.g.: 245 246 @example 247 Y.User = Y.Base.create('user', Y.Model, [Y.ModelSync.REST], { 248 root: '/users', 249 url : '/users/{username}' 250 }); 251 252 **Note:** String subsitituion of the `url` only use string an number values 253 provided by this object's attribute and/or the `options` passed to the 254 `getURL()` method. Do not expect something fancy to happen with Object, 255 Array, or Boolean values, they will simply be ignored. 256 257 If your URLs have plural roots or collection URLs, while the specific item 258 resources are under a singular name, e.g. "/users" (plural) and "/user/123" 259 (singular), you'll probably want to configure the `root` and `url` 260 properties like this: 261 262 @example 263 Y.User = Y.Base.create('user', Y.Model, [Y.ModelSync.REST], { 264 root: '/users', 265 url : '/user/{id}' 266 }); 267 268 var currentUser, newUser; 269 270 // GET the user data from: "/user/123" 271 currentUser = new Y.User({id: '123'}).load(); 272 273 // POST the new user data to: "/users" 274 newUser = new Y.User({name: 'Eric Ferraiuolo'}).save(); 275 276 When sub-classing `Y.ModelList`, usually you'll be able to rely on the 277 associated `model` to supply its `root` to be used as the model list's URL. 278 If this needs to be customized, you can provide a simple string for the 279 `url` property. 280 281 @example 282 Y.Users = Y.Base.create('users', Y.ModelList, [Y.ModelSync.REST], { 283 // Leverages `Y.User`'s `root`, which is "/users". 284 model: Y.User 285 }); 286 287 // Or specified explicitly... 288 289 Y.Users = Y.Base.create('users', Y.ModelList, [Y.ModelSync.REST], { 290 model: Y.User, 291 url : '/users' 292 }); 293 294 @property url 295 @type String 296 @default "" 297 @since 3.6.0 298 **/ 299 url: '', 300 301 // -- Lifecycle Methods ---------------------------------------------------- 302 303 initializer: function (config) { 304 config || (config = {}); 305 306 // Overrides `root` at the instance level. 307 if ('root' in config) { 308 this.root = config.root || ''; 309 } 310 311 // Overrides `url` at the instance level. 312 if ('url' in config) { 313 this.url = config.url || ''; 314 } 315 }, 316 317 // -- Public Methods ------------------------------------------------------- 318 319 /** 320 Returns the URL for this model or model list for the given `action` and 321 `options`, if specified. 322 323 This method correctly handles the variations of `root` and `url` values and 324 is called by the `sync()` method to get the URLs used to make the XHRs. 325 326 You can override this method if you need to provide a specific 327 implementation for how the URLs of your Model and ModelList subclasses need 328 to be generated. 329 330 @method getURL 331 @param {String} [action] Optional `sync()` action for which to generate the 332 URL. 333 @param {Object} [options] Optional options which may be used to help 334 generate the URL. 335 @return {String} this model's or model list's URL for the the given 336 `action` and `options`. 337 @since 3.6.0 338 **/ 339 getURL: function (action, options) { 340 var root = this.root, 341 url = this.url; 342 343 // If this is a model list, use its `url` and substitute placeholders, 344 // but default to the `root` of its `model`. By convention a model's 345 // `root` is the location to a collection resource. 346 if (this._isYUIModelList) { 347 if (!url) { 348 return this.model.prototype.root; 349 } 350 351 return this._substituteURL(url, Y.merge(this.getAttrs(), options)); 352 } 353 354 // Assume `this` is a model. 355 356 // When a model is new, i.e. has no `id`, the `root` should be used. By 357 // convention a model's `root` is the location to a collection resource. 358 // The model's `url` will be used as a fallback if `root` isn't defined. 359 if (root && (action === 'create' || this.isNew())) { 360 return root; 361 } 362 363 // When a model's `url` is not provided, we'll generate a URL to use by 364 // convention. This will combine the model's `id` with its configured 365 // `root` and add a trailing-slash if the root ends with "/". 366 if (!url) { 367 return this._joinURL(this.getAsURL('id') || ''); 368 } 369 370 // Substitute placeholders in the `url` with URL-encoded values from the 371 // model's attribute values or the specified `options`. 372 return this._substituteURL(url, Y.merge(this.getAttrs(), options)); 373 }, 374 375 /** 376 Called to parse the response object returned from `Y.io()`. This method 377 receives the full response object and is expected to "prep" a response which 378 is suitable to pass to the `parse()` method. 379 380 By default the response body is returned (`responseText`), because it 381 usually represents the entire entity of this model on the server. 382 383 If you need to parse data out of the response's headers you should do so by 384 overriding this method. If you'd like the entire response object from the 385 XHR to be passed to your `parse()` method, you can simply assign this 386 property to `false`. 387 388 @method parseIOResponse 389 @param {Object} response Response object from `Y.io()`. 390 @return {Any} The modified response to pass along to the `parse()` method. 391 @since 3.7.0 392 **/ 393 parseIOResponse: function (response) { 394 return response.responseText; 395 }, 396 397 /** 398 Serializes `this` model to be used as the HTTP request entity body. 399 400 By default this model will be serialized to a JSON string via its `toJSON()` 401 method. 402 403 You can override this method when the HTTP server expects a different 404 representation of this model's data that is different from the default JSON 405 serialization. If you're sending and receive content other than JSON, be 406 sure change the `Accept` and `Content-Type` `HTTP_HEADERS` as well. 407 408 **Note:** A model's `toJSON()` method can also be overridden. If you only 409 need to modify which attributes are serialized to JSON, that's a better 410 place to start. 411 412 @method serialize 413 @param {String} [action] Optional `sync()` action for which to generate the 414 the serialized representation of this model. 415 @return {String} serialized HTTP request entity body. 416 @since 3.6.0 417 **/ 418 serialize: function (action) { 419 return Y.JSON.stringify(this); 420 }, 421 422 /** 423 Communicates with a RESTful HTTP server by sending and receiving data via 424 XHRs. This method is called internally by load(), save(), and destroy(). 425 426 The URL used for each XHR will be retrieved by calling the `getURL()` method 427 and passing it the specified `action` and `options`. 428 429 This method relies heavily on standard RESTful HTTP conventions 430 431 @method sync 432 @param {String} action Sync action to perform. May be one of the following: 433 434 * `create`: Store a newly-created model for the first time. 435 * `delete`: Delete an existing model. 436 * `read` : Load an existing model. 437 * `update`: Update an existing model. 438 439 @param {Object} [options] Sync options: 440 @param {String} [options.csrfToken] The authenticity token used by the 441 server to verify the validity of this request and protected against CSRF 442 attacks. This overrides the default value provided by the static 443 `CSRF_TOKEN` property. 444 @param {Object} [options.headers] The HTTP headers to mix with the default 445 headers specified by the static `HTTP_HEADERS` property. 446 @param {Number} [options.timeout] The number of milliseconds before the 447 request will timeout and be aborted. This overrides the default provided 448 by the static `HTTP_TIMEOUT` property. 449 @param {Function} [callback] Called when the sync operation finishes. 450 @param {Error|null} callback.err If an error occurred, this parameter will 451 contain the error. If the sync operation succeeded, _err_ will be 452 falsy. 453 @param {Any} [callback.response] The server's response. 454 **/ 455 sync: function (action, options, callback) { 456 options || (options = {}); 457 458 var url = this.getURL(action, options), 459 method = RESTSync.HTTP_METHODS[action], 460 headers = Y.merge(RESTSync.HTTP_HEADERS, options.headers), 461 timeout = options.timeout || RESTSync.HTTP_TIMEOUT, 462 csrfToken = options.csrfToken || RESTSync.CSRF_TOKEN, 463 entity; 464 465 // Prepare the content if we are sending data to the server. 466 if (method === 'POST' || method === 'PUT') { 467 entity = this.serialize(action); 468 } else { 469 // Remove header, no content is being sent. 470 delete headers['Content-Type']; 471 } 472 473 // Setup HTTP emulation for older servers if we need it. 474 if (RESTSync.EMULATE_HTTP && 475 (method === 'PUT' || method === 'DELETE')) { 476 477 // Pass along original method type in the headers. 478 headers['X-HTTP-Method-Override'] = method; 479 480 // Fall-back to using POST method type. 481 method = 'POST'; 482 } 483 484 // Add CSRF token to HTTP request headers if one is specified and the 485 // request will cause side effects on the server. 486 if (csrfToken && 487 (method === 'POST' || method === 'PUT' || method === 'DELETE')) { 488 489 headers['X-CSRF-Token'] = csrfToken; 490 } 491 492 this._sendSyncIORequest({ 493 action : action, 494 callback: callback, 495 entity : entity, 496 headers : headers, 497 method : method, 498 timeout : timeout, 499 url : url 500 }); 501 }, 502 503 // -- Protected Methods ---------------------------------------------------- 504 505 /** 506 Joins the `root` URL to the specified `url`, normalizing leading/trailing 507 "/" characters. 508 509 @example 510 model.root = '/foo' 511 model._joinURL('bar'); // => '/foo/bar' 512 model._joinURL('/bar'); // => '/foo/bar' 513 514 model.root = '/foo/' 515 model._joinURL('bar'); // => '/foo/bar/' 516 model._joinURL('/bar'); // => '/foo/bar/' 517 518 @method _joinURL 519 @param {String} url URL to append to the `root` URL. 520 @return {String} Joined URL. 521 @protected 522 @since 3.6.0 523 **/ 524 _joinURL: function (url) { 525 var root = this.root; 526 527 if (!(root || url)) { 528 return ''; 529 } 530 531 if (url.charAt(0) === '/') { 532 url = url.substring(1); 533 } 534 535 // Combines the `root` with the `url` and adds a trailing-slash if the 536 // `root` has a trailing-slash. 537 return root && root.charAt(root.length - 1) === '/' ? 538 root + url + '/' : 539 root + '/' + url; 540 }, 541 542 543 /** 544 Calls both public, overrideable methods: `parseIOResponse()`, then `parse()` 545 and returns the result. 546 547 This will call into `parseIOResponse()`, if it's defined as a method, 548 passing it the full response object from the XHR and using its return value 549 to pass along to the `parse()`. This enables developers to easily parse data 550 out of the response headers which should be used by the `parse()` method. 551 552 @method _parse 553 @param {Object} response Response object from `Y.io()`. 554 @return {Object|Object[]} Attribute hash or Array of model attribute hashes. 555 @protected 556 @since 3.7.0 557 **/ 558 _parse: function (response) { 559 // When `parseIOResponse` is defined as a method, it will be invoked and 560 // the result will become the new response object that the `parse()` 561 // will be invoked with. 562 if (typeof this.parseIOResponse === 'function') { 563 response = this.parseIOResponse(response); 564 } 565 566 return this.parse(response); 567 }, 568 569 /** 570 Performs the XHR and returns the resulting `Y.io()` request object. 571 572 This method is called by `sync()`. 573 574 @method _sendSyncIORequest 575 @param {Object} config An object with the following properties: 576 @param {String} config.action The `sync()` action being performed. 577 @param {Function} [config.callback] Called when the sync operation 578 finishes. 579 @param {String} [config.entity] The HTTP request entity body. 580 @param {Object} config.headers The HTTP request headers. 581 @param {String} config.method The HTTP request method. 582 @param {Number} [config.timeout] Time until the HTTP request is aborted. 583 @param {String} config.url The URL of the HTTP resource. 584 @return {Object} The resulting `Y.io()` request object. 585 @protected 586 @since 3.6.0 587 **/ 588 _sendSyncIORequest: function (config) { 589 return Y.io(config.url, { 590 'arguments': { 591 action : config.action, 592 callback: config.callback, 593 url : config.url 594 }, 595 596 context: this, 597 data : config.entity, 598 headers: config.headers, 599 method : config.method, 600 timeout: config.timeout, 601 602 on: { 603 start : this._onSyncIOStart, 604 failure: this._onSyncIOFailure, 605 success: this._onSyncIOSuccess, 606 end : this._onSyncIOEnd 607 } 608 }); 609 }, 610 611 /** 612 Utility which takes a tokenized `url` string and substitutes its 613 placeholders using a specified `data` object. 614 615 This method will property URL-encode any values before substituting them. 616 Also, only expect it to work with String and Number values. 617 618 @example 619 var url = this._substituteURL('/users/{name}', {id: 'Eric F'}); 620 // => "/users/Eric%20F" 621 622 @method _substituteURL 623 @param {String} url Tokenized URL string to substitute placeholder values. 624 @param {Object} data Set of data to fill in the `url`'s placeholders. 625 @return {String} Substituted URL. 626 @protected 627 @since 3.6.0 628 **/ 629 _substituteURL: function (url, data) { 630 if (!url) { 631 return ''; 632 } 633 634 var values = {}; 635 636 // Creates a hash of the string and number values only to be used to 637 // replace any placeholders in a tokenized `url`. 638 Y.Object.each(data, function (v, k) { 639 if (Lang.isString(v) || Lang.isNumber(v)) { 640 // URL-encode any string or number values. 641 values[k] = encodeURIComponent(v); 642 } 643 }); 644 645 return Lang.sub(url, values); 646 }, 647 648 // -- Event Handlers ------------------------------------------------------- 649 650 /** 651 Called when the `Y.io` request has finished, after "success" or "failure" 652 has been determined. 653 654 This is a no-op by default, but provides a hook for overriding. 655 656 @method _onSyncIOEnd 657 @param {String} txId The `Y.io` transaction id. 658 @param {Object} details Extra details carried through from `sync()`: 659 @param {String} details.action The sync action performed. 660 @param {Function} [details.callback] The function to call after syncing. 661 @param {String} details.url The URL of the requested resource. 662 @protected 663 @since 3.6.0 664 **/ 665 _onSyncIOEnd: function (txId, details) {}, 666 667 /** 668 Called when the `Y.io` request has finished unsuccessfully. 669 670 By default this calls the `details.callback` function passing it the HTTP 671 status code and message as an error object along with the response body. 672 673 @method _onSyncIOFailure 674 @param {String} txId The `Y.io` transaction id. 675 @param {Object} res The `Y.io` response object. 676 @param {Object} details Extra details carried through from `sync()`: 677 @param {String} details.action The sync action performed. 678 @param {Function} [details.callback] The function to call after syncing. 679 @param {String} details.url The URL of the requested resource. 680 @protected 681 @since 3.6.0 682 **/ 683 _onSyncIOFailure: function (txId, res, details) { 684 var callback = details.callback; 685 686 if (callback) { 687 callback({ 688 code: res.status, 689 msg : res.statusText 690 }, res); 691 } 692 }, 693 694 /** 695 Called when the `Y.io` request has finished successfully. 696 697 By default this calls the `details.callback` function passing it the 698 response body. 699 700 @method _onSyncIOSuccess 701 @param {String} txId The `Y.io` transaction id. 702 @param {Object} res The `Y.io` response object. 703 @param {Object} details Extra details carried through from `sync()`: 704 @param {String} details.action The sync action performed. 705 @param {Function} [details.callback] The function to call after syncing. 706 @param {String} details.url The URL of the requested resource. 707 @protected 708 @since 3.6.0 709 **/ 710 _onSyncIOSuccess: function (txId, res, details) { 711 var callback = details.callback; 712 713 if (callback) { 714 callback(null, res); 715 } 716 }, 717 718 /** 719 Called when the `Y.io` request is made. 720 721 This is a no-op by default, but provides a hook for overriding. 722 723 @method _onSyncIOStart 724 @param {String} txId The `Y.io` transaction id. 725 @param {Object} details Extra details carried through from `sync()`: 726 @param {String} details.action The sync action performed. 727 @param {Function} [details.callback] The function to call after syncing. 728 @param {String} details.url The URL of the requested resource. 729 @protected 730 @since 3.6.0 731 **/ 732 _onSyncIOStart: function (txId, details) {} 733 }; 734 735 // -- Namespace ---------------------------------------------------------------- 736 737 Y.namespace('ModelSync').REST = RESTSync; 738 739 740 }, '3.17.2', {"requires": ["model", "io-base", "json-stringify"]});
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 |