OLD | NEW |
(Empty) | |
| 1 <!-- |
| 2 @license |
| 3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. |
| 4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt |
| 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt |
| 7 Code distributed by Google as part of the polymer project is also |
| 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt |
| 9 --> |
| 10 |
| 11 <link rel="import" href="../polymer/polymer.html"> |
| 12 <link rel="import" href="iron-request.html"> |
| 13 |
| 14 <!-- |
| 15 The `iron-ajax` element exposes network request functionality. |
| 16 |
| 17 <iron-ajax |
| 18 auto |
| 19 url="https://www.googleapis.com/youtube/v3/search" |
| 20 params='{"part":"snippet", "q":"polymer", "key": "YOUTUBE_API_KEY", "typ
e": "video"}' |
| 21 handle-as="json" |
| 22 on-response="handleResponse" |
| 23 debounce-duration="300"></iron-ajax> |
| 24 |
| 25 With `auto` set to `true`, the element performs a request whenever |
| 26 its `url`, `params` or `body` properties are changed. Automatically generated |
| 27 requests will be debounced in the case that multiple attributes are changed |
| 28 sequentially. |
| 29 |
| 30 Note: The `params` attribute must be double quoted JSON. |
| 31 |
| 32 You can trigger a request explicitly by calling `generateRequest` on the |
| 33 element. |
| 34 |
| 35 @demo demo/index.html |
| 36 @hero hero.svg |
| 37 --> |
| 38 |
| 39 <script> |
| 40 'use strict'; |
| 41 |
| 42 Polymer({ |
| 43 |
| 44 is: 'iron-ajax', |
| 45 |
| 46 /** |
| 47 * Fired when a request is sent. |
| 48 * |
| 49 * @event request |
| 50 * @event iron-ajax-request |
| 51 */ |
| 52 |
| 53 /** |
| 54 * Fired when a response is received. |
| 55 * |
| 56 * @event response |
| 57 * @event iron-ajax-response |
| 58 */ |
| 59 |
| 60 /** |
| 61 * Fired when an error is received. |
| 62 * |
| 63 * @event error |
| 64 * @event iron-ajax-error |
| 65 */ |
| 66 |
| 67 hostAttributes: { |
| 68 hidden: true |
| 69 }, |
| 70 |
| 71 properties: { |
| 72 /** |
| 73 * The URL target of the request. |
| 74 */ |
| 75 url: { |
| 76 type: String |
| 77 }, |
| 78 |
| 79 /** |
| 80 * An object that contains query parameters to be appended to the |
| 81 * specified `url` when generating a request. If you wish to set the body |
| 82 * content when making a POST request, you should use the `body` property |
| 83 * instead. |
| 84 */ |
| 85 params: { |
| 86 type: Object, |
| 87 value: function() { |
| 88 return {}; |
| 89 } |
| 90 }, |
| 91 |
| 92 /** |
| 93 * The HTTP method to use such as 'GET', 'POST', 'PUT', or 'DELETE'. |
| 94 * Default is 'GET'. |
| 95 */ |
| 96 method: { |
| 97 type: String, |
| 98 value: 'GET' |
| 99 }, |
| 100 |
| 101 /** |
| 102 * HTTP request headers to send. |
| 103 * |
| 104 * Example: |
| 105 * |
| 106 * <iron-ajax |
| 107 * auto |
| 108 * url="http://somesite.com" |
| 109 * headers='{"X-Requested-With": "XMLHttpRequest"}' |
| 110 * handle-as="json"></iron-ajax> |
| 111 * |
| 112 * Note: setting a `Content-Type` header here will override the value |
| 113 * specified by the `contentType` property of this element. |
| 114 */ |
| 115 headers: { |
| 116 type: Object, |
| 117 value: function() { |
| 118 return {}; |
| 119 } |
| 120 }, |
| 121 |
| 122 /** |
| 123 * Content type to use when sending data. If the `contentType` property |
| 124 * is set and a `Content-Type` header is specified in the `headers` |
| 125 * property, the `headers` property value will take precedence. |
| 126 * |
| 127 * Varies the handling of the `body` param. |
| 128 */ |
| 129 contentType: { |
| 130 type: String, |
| 131 value: null |
| 132 }, |
| 133 |
| 134 /** |
| 135 * Body content to send with the request, typically used with "POST" |
| 136 * requests. |
| 137 * |
| 138 * If body is a string it will be sent unmodified. |
| 139 * |
| 140 * If Content-Type is set to a value listed below, then |
| 141 * the body will be encoded accordingly. |
| 142 * |
| 143 * * `content-type="application/json"` |
| 144 * * body is encoded like `{"foo":"bar baz","x":1}` |
| 145 * * `content-type="application/x-www-form-urlencoded"` |
| 146 * * body is encoded like `foo=bar+baz&x=1` |
| 147 * |
| 148 * Otherwise the body will be passed to the browser unmodified, and it |
| 149 * will handle any encoding (e.g. for FormData, Blob, ArrayBuffer). |
| 150 * |
| 151 * @type (ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|u
ndefined|Object) |
| 152 */ |
| 153 body: { |
| 154 type: Object, |
| 155 value: null |
| 156 }, |
| 157 |
| 158 /** |
| 159 * Toggle whether XHR is synchronous or asynchronous. Don't change this |
| 160 * to true unless You Know What You Are Doing™. |
| 161 */ |
| 162 sync: { |
| 163 type: Boolean, |
| 164 value: false |
| 165 }, |
| 166 |
| 167 /** |
| 168 * Specifies what data to store in the `response` property, and |
| 169 * to deliver as `event.detail.response` in `response` events. |
| 170 * |
| 171 * One of: |
| 172 * |
| 173 * `text`: uses `XHR.responseText`. |
| 174 * |
| 175 * `xml`: uses `XHR.responseXML`. |
| 176 * |
| 177 * `json`: uses `XHR.responseText` parsed as JSON. |
| 178 * |
| 179 * `arraybuffer`: uses `XHR.response`. |
| 180 * |
| 181 * `blob`: uses `XHR.response`. |
| 182 * |
| 183 * `document`: uses `XHR.response`. |
| 184 */ |
| 185 handleAs: { |
| 186 type: String, |
| 187 value: 'json' |
| 188 }, |
| 189 |
| 190 /** |
| 191 * Set the withCredentials flag on the request. |
| 192 */ |
| 193 withCredentials: { |
| 194 type: Boolean, |
| 195 value: false |
| 196 }, |
| 197 |
| 198 /** |
| 199 * Set the timeout flag on the request. |
| 200 */ |
| 201 timeout: { |
| 202 type: Number, |
| 203 value: 0 |
| 204 }, |
| 205 |
| 206 /** |
| 207 * If true, automatically performs an Ajax request when either `url` or |
| 208 * `params` changes. |
| 209 */ |
| 210 auto: { |
| 211 type: Boolean, |
| 212 value: false |
| 213 }, |
| 214 |
| 215 /** |
| 216 * If true, error messages will automatically be logged to the console. |
| 217 */ |
| 218 verbose: { |
| 219 type: Boolean, |
| 220 value: false |
| 221 }, |
| 222 |
| 223 /** |
| 224 * The most recent request made by this iron-ajax element. |
| 225 */ |
| 226 lastRequest: { |
| 227 type: Object, |
| 228 notify: true, |
| 229 readOnly: true |
| 230 }, |
| 231 |
| 232 /** |
| 233 * True while lastRequest is in flight. |
| 234 */ |
| 235 loading: { |
| 236 type: Boolean, |
| 237 notify: true, |
| 238 readOnly: true |
| 239 }, |
| 240 |
| 241 /** |
| 242 * lastRequest's response. |
| 243 * |
| 244 * Note that lastResponse and lastError are set when lastRequest finishes, |
| 245 * so if loading is true, then lastResponse and lastError will correspond |
| 246 * to the result of the previous request. |
| 247 * |
| 248 * The type of the response is determined by the value of `handleAs` at |
| 249 * the time that the request was generated. |
| 250 * |
| 251 * @type {Object} |
| 252 */ |
| 253 lastResponse: { |
| 254 type: Object, |
| 255 notify: true, |
| 256 readOnly: true |
| 257 }, |
| 258 |
| 259 /** |
| 260 * lastRequest's error, if any. |
| 261 * |
| 262 * @type {Object} |
| 263 */ |
| 264 lastError: { |
| 265 type: Object, |
| 266 notify: true, |
| 267 readOnly: true |
| 268 }, |
| 269 |
| 270 /** |
| 271 * An Array of all in-flight requests originating from this iron-ajax |
| 272 * element. |
| 273 */ |
| 274 activeRequests: { |
| 275 type: Array, |
| 276 notify: true, |
| 277 readOnly: true, |
| 278 value: function() { |
| 279 return []; |
| 280 } |
| 281 }, |
| 282 |
| 283 /** |
| 284 * Length of time in milliseconds to debounce multiple automatically gener
ated requests. |
| 285 */ |
| 286 debounceDuration: { |
| 287 type: Number, |
| 288 value: 0, |
| 289 notify: true |
| 290 }, |
| 291 |
| 292 /** |
| 293 * Prefix to be stripped from a JSON response before parsing it. |
| 294 * |
| 295 * In order to prevent an attack using CSRF with Array responses |
| 296 * (http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnera
bility.aspx/) |
| 297 * many backends will mitigate this by prefixing all JSON response bodies |
| 298 * with a string that would be nonsensical to a JavaScript parser. |
| 299 * |
| 300 */ |
| 301 jsonPrefix: { |
| 302 type: String, |
| 303 value: '' |
| 304 }, |
| 305 |
| 306 /** |
| 307 * By default, iron-ajax's events do not bubble. Setting this attribute wi
ll cause its |
| 308 * request and response events as well as its iron-ajax-request, -response
, and -error |
| 309 * events to bubble to the window object. The vanilla error event never bu
bbles when |
| 310 * using shadow dom even if this.bubbles is true because a scoped flag is
not passed with |
| 311 * it (first link) and because the shadow dom spec did not used to allow c
ertain events, |
| 312 * including events named error, to leak outside of shadow trees (second l
ink). |
| 313 * https://www.w3.org/TR/shadow-dom/#scoped-flag |
| 314 * https://www.w3.org/TR/2015/WD-shadow-dom-20151215/#events-that-are-not-
leaked-into-ancestor-trees |
| 315 */ |
| 316 bubbles: { |
| 317 type: Boolean, |
| 318 value: false |
| 319 }, |
| 320 |
| 321 _boundHandleResponse: { |
| 322 type: Function, |
| 323 value: function() { |
| 324 return this._handleResponse.bind(this); |
| 325 } |
| 326 } |
| 327 }, |
| 328 |
| 329 observers: [ |
| 330 '_requestOptionsChanged(url, method, params.*, headers, contentType, ' + |
| 331 'body, sync, handleAs, jsonPrefix, withCredentials, timeout, auto)' |
| 332 ], |
| 333 |
| 334 /** |
| 335 * The query string that should be appended to the `url`, serialized from |
| 336 * the current value of `params`. |
| 337 * |
| 338 * @return {string} |
| 339 */ |
| 340 get queryString () { |
| 341 var queryParts = []; |
| 342 var param; |
| 343 var value; |
| 344 |
| 345 for (param in this.params) { |
| 346 value = this.params[param]; |
| 347 param = window.encodeURIComponent(param); |
| 348 |
| 349 if (Array.isArray(value)) { |
| 350 for (var i = 0; i < value.length; i++) { |
| 351 queryParts.push(param + '=' + window.encodeURIComponent(value[i])); |
| 352 } |
| 353 } else if (value !== null) { |
| 354 queryParts.push(param + '=' + window.encodeURIComponent(value)); |
| 355 } else { |
| 356 queryParts.push(param); |
| 357 } |
| 358 } |
| 359 |
| 360 return queryParts.join('&'); |
| 361 }, |
| 362 |
| 363 /** |
| 364 * The `url` with query string (if `params` are specified), suitable for |
| 365 * providing to an `iron-request` instance. |
| 366 * |
| 367 * @return {string} |
| 368 */ |
| 369 get requestUrl() { |
| 370 var queryString = this.queryString; |
| 371 var url = this.url || ''; |
| 372 |
| 373 if (queryString) { |
| 374 var bindingChar = url.indexOf('?') >= 0 ? '&' : '?'; |
| 375 return url + bindingChar + queryString; |
| 376 } |
| 377 |
| 378 return url; |
| 379 }, |
| 380 |
| 381 /** |
| 382 * An object that maps header names to header values, first applying the |
| 383 * the value of `Content-Type` and then overlaying the headers specified |
| 384 * in the `headers` property. |
| 385 * |
| 386 * @return {Object} |
| 387 */ |
| 388 get requestHeaders() { |
| 389 var headers = {}; |
| 390 var contentType = this.contentType; |
| 391 if (contentType == null && (typeof this.body === 'string')) { |
| 392 contentType = 'application/x-www-form-urlencoded'; |
| 393 } |
| 394 if (contentType) { |
| 395 headers['content-type'] = contentType; |
| 396 } |
| 397 var header; |
| 398 |
| 399 if (this.headers instanceof Object) { |
| 400 for (header in this.headers) { |
| 401 headers[header] = this.headers[header].toString(); |
| 402 } |
| 403 } |
| 404 |
| 405 return headers; |
| 406 }, |
| 407 |
| 408 /** |
| 409 * Request options suitable for generating an `iron-request` instance based |
| 410 * on the current state of the `iron-ajax` instance's properties. |
| 411 * |
| 412 * @return {{ |
| 413 * url: string, |
| 414 * method: (string|undefined), |
| 415 * async: (boolean|undefined), |
| 416 * body: (ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|u
ndefined|Object), |
| 417 * headers: (Object|undefined), |
| 418 * handleAs: (string|undefined), |
| 419 * jsonPrefix: (string|undefined), |
| 420 * withCredentials: (boolean|undefined)}} |
| 421 */ |
| 422 toRequestOptions: function() { |
| 423 return { |
| 424 url: this.requestUrl || '', |
| 425 method: this.method, |
| 426 headers: this.requestHeaders, |
| 427 body: this.body, |
| 428 async: !this.sync, |
| 429 handleAs: this.handleAs, |
| 430 jsonPrefix: this.jsonPrefix, |
| 431 withCredentials: this.withCredentials, |
| 432 timeout: this.timeout |
| 433 }; |
| 434 }, |
| 435 |
| 436 /** |
| 437 * Performs an AJAX request to the specified URL. |
| 438 * |
| 439 * @return {!IronRequestElement} |
| 440 */ |
| 441 generateRequest: function() { |
| 442 var request = /** @type {!IronRequestElement} */ (document.createElement('
iron-request')); |
| 443 var requestOptions = this.toRequestOptions(); |
| 444 |
| 445 this.push('activeRequests', request); |
| 446 |
| 447 request.completes.then( |
| 448 this._boundHandleResponse |
| 449 ).catch( |
| 450 this._handleError.bind(this, request) |
| 451 ).then( |
| 452 this._discardRequest.bind(this, request) |
| 453 ); |
| 454 |
| 455 request.send(requestOptions); |
| 456 |
| 457 this._setLastRequest(request); |
| 458 this._setLoading(true); |
| 459 |
| 460 this.fire('request', { |
| 461 request: request, |
| 462 options: requestOptions |
| 463 }, {bubbles: this.bubbles}); |
| 464 |
| 465 this.fire('iron-ajax-request', { |
| 466 request: request, |
| 467 options: requestOptions |
| 468 }, {bubbles: this.bubbles}); |
| 469 |
| 470 return request; |
| 471 }, |
| 472 |
| 473 _handleResponse: function(request) { |
| 474 if (request === this.lastRequest) { |
| 475 this._setLastResponse(request.response); |
| 476 this._setLastError(null); |
| 477 this._setLoading(false); |
| 478 } |
| 479 this.fire('response', request, {bubbles: this.bubbles}); |
| 480 this.fire('iron-ajax-response', request, {bubbles: this.bubbles}); |
| 481 }, |
| 482 |
| 483 _handleError: function(request, error) { |
| 484 if (this.verbose) { |
| 485 Polymer.Base._error(error); |
| 486 } |
| 487 |
| 488 if (request === this.lastRequest) { |
| 489 this._setLastError({ |
| 490 request: request, |
| 491 error: error, |
| 492 status: request.xhr.status, |
| 493 statusText: request.xhr.statusText, |
| 494 response: request.xhr.response |
| 495 }); |
| 496 this._setLastResponse(null); |
| 497 this._setLoading(false); |
| 498 } |
| 499 |
| 500 // Tests fail if this goes after the normal this.fire('error', ...) |
| 501 this.fire('iron-ajax-error', { |
| 502 request: request, |
| 503 error: error |
| 504 }, {bubbles: this.bubbles}); |
| 505 |
| 506 this.fire('error', { |
| 507 request: request, |
| 508 error: error |
| 509 }, {bubbles: this.bubbles}); |
| 510 }, |
| 511 |
| 512 _discardRequest: function(request) { |
| 513 var requestIndex = this.activeRequests.indexOf(request); |
| 514 |
| 515 if (requestIndex > -1) { |
| 516 this.splice('activeRequests', requestIndex, 1); |
| 517 } |
| 518 }, |
| 519 |
| 520 _requestOptionsChanged: function() { |
| 521 this.debounce('generate-request', function() { |
| 522 if (this.url == null) { |
| 523 return; |
| 524 } |
| 525 |
| 526 if (this.auto) { |
| 527 this.generateRequest(); |
| 528 } |
| 529 }, this.debounceDuration); |
| 530 }, |
| 531 |
| 532 }); |
| 533 </script> |
OLD | NEW |