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