<!-- Copyright (c) 2015 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt --> <link rel="import" href="../polymer/polymer.html"> <link rel="import" href="iron-request.html"> <!-- The `iron-ajax` element exposes network request functionality. <iron-ajax auto url="http://gdata.youtube.com/feeds/api/videos/" params='{"alt":"json", "q":"chrome"}' handle-as="json" on-response="handleResponse" debounce-duration="300"></iron-ajax> With `auto` set to `true`, the element performs a request whenever its `url`, `params` or `body` properties are changed. Automatically generated requests will be debounced in the case that multiple attributes are changed sequentially. Note: The `params` attribute must be double quoted JSON. You can trigger a request explicitly by calling `generateRequest` on the element. @demo demo/index.html @hero hero.svg --> <script> 'use strict'; Polymer({ is: 'iron-ajax', /** * Fired when a request is sent. * * @event request */ /** * Fired when a response is received. * * @event response */ /** * Fired when an error is received. * * @event error */ hostAttributes: { hidden: true }, properties: { /** * The URL target of the request. */ url: { type: String }, /** * An object that contains query parameters to be appended to the * specified `url` when generating a request. If you wish to set the body * content when making a POST request, you should use the `body` property * instead. */ params: { type: Object, value: function() { return {}; } }, /** * The HTTP method to use such as 'GET', 'POST', 'PUT', or 'DELETE'. * Default is 'GET'. */ method: { type: String, value: 'GET' }, /** * HTTP request headers to send. * * Example: * * <iron-ajax * auto * url="http://somesite.com" * headers='{"X-Requested-With": "XMLHttpRequest"}' * handle-as="json"></iron-ajax> * * Note: setting a `Content-Type` header here will override the value * specified by the `contentType` property of this element. */ headers: { type: Object, value: function() { return {}; } }, /** * Content type to use when sending data. If the `contentType` property * is set and a `Content-Type` header is specified in the `headers` * property, the `headers` property value will take precedence. */ contentType: { type: String, value: null }, /** * Body content to send with the request, typically used with "POST" * requests. * * If body is a string it will be sent unmodified. * * If Content-Type is set to a value listed below, then * the body will be encoded accordingly. * * * `content-type="application/json"` * * body is encoded like `{"foo":"bar baz","x":1}` * * `content-type="application/x-www-form-urlencoded"` * * body is encoded like `foo=bar+baz&x=1` * * Otherwise the body will be passed to the browser unmodified, and it * will handle any encoding (e.g. for FormData, Blob, ArrayBuffer). * * @type (ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|undefined|Object) */ body: { type: Object, value: null }, /** * Toggle whether XHR is synchronous or asynchronous. Don't change this * to true unless You Know What You Are Doing™. */ sync: { type: Boolean, value: false }, /** * Specifies what data to store in the `response` property, and * to deliver as `event.detail.response` in `response` events. * * One of: * * `text`: uses `XHR.responseText`. * * `xml`: uses `XHR.responseXML`. * * `json`: uses `XHR.responseText` parsed as JSON. * * `arraybuffer`: uses `XHR.response`. * * `blob`: uses `XHR.response`. * * `document`: uses `XHR.response`. */ handleAs: { type: String, value: 'json' }, /** * Set the withCredentials flag on the request. */ withCredentials: { type: Boolean, value: false }, /** * Set the timeout flag on the request. */ timeout: { type: Number, value: 0 }, /** * If true, automatically performs an Ajax request when either `url` or * `params` changes. */ auto: { type: Boolean, value: false }, /** * If true, error messages will automatically be logged to the console. */ verbose: { type: Boolean, value: false }, /** * The most recent request made by this iron-ajax element. */ lastRequest: { type: Object, notify: true, readOnly: true }, /** * True while lastRequest is in flight. */ loading: { type: Boolean, notify: true, readOnly: true }, /** * lastRequest's response. * * Note that lastResponse and lastError are set when lastRequest finishes, * so if loading is true, then lastResponse and lastError will correspond * to the result of the previous request. * * The type of the response is determined by the value of `handleAs` at * the time that the request was generated. * * @type {Object} */ lastResponse: { type: Object, notify: true, readOnly: true }, /** * lastRequest's error, if any. * * @type {Object} */ lastError: { type: Object, notify: true, readOnly: true }, /** * An Array of all in-flight requests originating from this iron-ajax * element. */ activeRequests: { type: Array, notify: true, readOnly: true, value: function() { return []; } }, /** * Length of time in milliseconds to debounce multiple requests. */ debounceDuration: { type: Number, value: 0, notify: true }, _boundHandleResponse: { type: Function, value: function() { return this._handleResponse.bind(this); } } }, observers: [ '_requestOptionsChanged(url, method, params.*, headers,' + 'contentType, body, sync, handleAs, withCredentials, timeout, auto)' ], /** * The query string that should be appended to the `url`, serialized from * the current value of `params`. * * @return {string} */ get queryString () { var queryParts = []; var param; var value; for (param in this.params) { value = this.params[param]; param = window.encodeURIComponent(param); if (Array.isArray(value)) { for (var i = 0; i < value.length; i++) { queryParts.push(param + '=' + window.encodeURIComponent(value[i])); } } else if (value !== null) { queryParts.push(param + '=' + window.encodeURIComponent(value)); } else { queryParts.push(param); } } return queryParts.join('&'); }, /** * The `url` with query string (if `params` are specified), suitable for * providing to an `iron-request` instance. * * @return {string} */ get requestUrl() { var queryString = this.queryString; if (queryString) { var bindingChar = this.url.indexOf('?') >= 0 ? '&' : '?'; return this.url + bindingChar + queryString; } return this.url; }, /** * An object that maps header names to header values, first applying the * the value of `Content-Type` and then overlaying the headers specified * in the `headers` property. * * @return {Object} */ get requestHeaders() { var headers = {}; var contentType = this.contentType; if (contentType == null && (typeof this.body === 'string')) { contentType = 'application/x-www-form-urlencoded'; } if (contentType) { headers['content-type'] = contentType; } var header; if (this.headers instanceof Object) { for (header in this.headers) { // Normalize headers in lower case to make it easier for iron-request // to reason about them. headers[header.toLowerCase()] = this.headers[header].toString(); } } return headers; }, /** * Request options suitable for generating an `iron-request` instance based * on the current state of the `iron-ajax` instance's properties. * * @return {{ * url: string, * method: (string|undefined), * async: (boolean|undefined), * body: (ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|undefined|Object), * headers: (Object|undefined), * handleAs: (string|undefined), * withCredentials: (boolean|undefined)}} */ toRequestOptions: function() { return { url: this.requestUrl || '', method: this.method, headers: this.requestHeaders, body: this.body, async: !this.sync, handleAs: this.handleAs, withCredentials: this.withCredentials, timeout: this.timeout }; }, /** * Performs an AJAX request to the specified URL. * * @return {!IronRequestElement} */ generateRequest: function() { var request = /** @type {!IronRequestElement} */ (document.createElement('iron-request')); var requestOptions = this.toRequestOptions(); this.activeRequests.push(request); request.completes.then( this._boundHandleResponse ).catch( this._handleError.bind(this, request) ).then( this._discardRequest.bind(this, request) ); request.send(requestOptions); this._setLastRequest(request); this._setLoading(true); this.fire('request', { request: request, options: requestOptions }, {bubbles: false}); return request; }, _handleResponse: function(request) { if (request === this.lastRequest) { this._setLastResponse(request.response); this._setLastError(null); this._setLoading(false); } this.fire('response', request, {bubbles: false}); }, _handleError: function(request, error) { if (this.verbose) { console.error(error); } if (request === this.lastRequest) { this._setLastError({ request: request, error: error }); this._setLastResponse(null); this._setLoading(false); } this.fire('error', { request: request, error: error }, {bubbles: false}); }, _discardRequest: function(request) { var requestIndex = this.activeRequests.indexOf(request); if (requestIndex > -1) { this.activeRequests.splice(requestIndex, 1); } }, _requestOptionsChanged: function() { this.debounce('generate-request', function() { if (this.url == null) { return; } if (this.auto) { this.generateRequest(); } }, this.debounceDuration); }, }); </script>