Permalink
| <!-- | |
| Copyright (c) 2014 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 | |
| --> | |
| <!-- | |
| @group Polymer Core Elements | |
| The `core-ajax` element exposes `XMLHttpRequest` functionality. | |
| <core-ajax | |
| auto | |
| url="http://gdata.youtube.com/feeds/api/videos/" | |
| params='{"alt":"json", "q":"chrome"}' | |
| handleAs="json" | |
| on-core-response="{{handleResponse}}"></core-ajax> | |
| With `auto` set to `true`, the element performs a request whenever | |
| its `url`, `params` or `body` properties are changed. | |
| Note: The `params` attribute must be double quoted JSON. | |
| You can trigger a request explicitly by calling `go` on the | |
| element. | |
| @element core-ajax | |
| @status beta | |
| @homepage github.io | |
| --> | |
| <link rel="import" href="core-xhr.html"> | |
| <polymer-element name="core-ajax" hidden attributes="url handleAs auto params response error method headers body contentType withCredentials progress loading"> | |
| <script> | |
| Polymer('core-ajax', { | |
| /** | |
| * Fired when a response is received. | |
| * | |
| * @event core-response | |
| */ | |
| /** | |
| * Fired when an error is received. | |
| * | |
| * @event core-error | |
| */ | |
| /** | |
| * Fired whenever a response or an error is received. | |
| * | |
| * @event core-complete | |
| */ | |
| /** | |
| * The URL target of the request. | |
| * | |
| * @attribute url | |
| * @type string | |
| * @default '' | |
| */ | |
| url: '', | |
| /** | |
| * Specifies what data to store in the `response` property, and | |
| * to deliver as `event.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`. | |
| * | |
| * @attribute handleAs | |
| * @type string | |
| * @default 'text' | |
| */ | |
| handleAs: '', | |
| /** | |
| * If true, automatically performs an Ajax request when either `url` or `params` changes. | |
| * | |
| * @attribute auto | |
| * @type boolean | |
| * @default false | |
| */ | |
| auto: false, | |
| /** | |
| * Parameters to send to the specified URL, as JSON. | |
| * | |
| * @attribute params | |
| * @type string | |
| * @default '' | |
| */ | |
| params: '', | |
| /** | |
| * The response for the current request, or null if it hasn't | |
| * completed yet or the request resulted in error. | |
| * | |
| * @attribute response | |
| * @type Object | |
| * @default null | |
| */ | |
| response: null, | |
| /** | |
| * The error for the current request, or null if it hasn't | |
| * completed yet or the request resulted in success. | |
| * | |
| * @attribute error | |
| * @type Object | |
| * @default null | |
| */ | |
| error: null, | |
| /** | |
| * Whether the current request is currently loading. | |
| * | |
| * @attribute loading | |
| * @type boolean | |
| * @default false | |
| */ | |
| loading: false, | |
| /** | |
| * The progress of the current request. | |
| * | |
| * @attribute progress | |
| * @type {loaded: number, total: number, lengthComputable: boolean} | |
| * @default {} | |
| */ | |
| progress: null, | |
| /** | |
| * The HTTP method to use such as 'GET', 'POST', 'PUT', or 'DELETE'. | |
| * Default is 'GET'. | |
| * | |
| * @attribute method | |
| * @type string | |
| * @default '' | |
| */ | |
| method: '', | |
| /** | |
| * HTTP request headers to send. | |
| * | |
| * Example: | |
| * | |
| * <core-ajax | |
| * auto | |
| * url="http://somesite.com" | |
| * headers='{"X-Requested-With": "XMLHttpRequest"}' | |
| * handleAs="json" | |
| * on-core-response="{{handleResponse}}"></core-ajax> | |
| * | |
| * @attribute headers | |
| * @type Object | |
| * @default null | |
| */ | |
| headers: null, | |
| /** | |
| * Optional raw body content to send when method === "POST". | |
| * | |
| * Example: | |
| * | |
| * <core-ajax method="POST" auto url="http://somesite.com" | |
| * body='{"foo":1, "bar":2}'> | |
| * </core-ajax> | |
| * | |
| * @attribute body | |
| * @type Object | |
| * @default null | |
| */ | |
| body: null, | |
| /** | |
| * Content type to use when sending data. | |
| * | |
| * @attribute contentType | |
| * @type string | |
| * @default 'application/x-www-form-urlencoded' | |
| */ | |
| contentType: 'application/x-www-form-urlencoded', | |
| /** | |
| * Set the withCredentials flag on the request. | |
| * | |
| * @attribute withCredentials | |
| * @type boolean | |
| * @default false | |
| */ | |
| withCredentials: false, | |
| /** | |
| * Additional properties to send to core-xhr. | |
| * | |
| * Can be set to an object containing default properties | |
| * to send as arguments to the `core-xhr.request()` method | |
| * which implements the low-level communication. | |
| * | |
| * @property xhrArgs | |
| * @type Object | |
| * @default null | |
| */ | |
| xhrArgs: null, | |
| created: function() { | |
| this.progress = {}; | |
| }, | |
| ready: function() { | |
| this.xhr = document.createElement('core-xhr'); | |
| }, | |
| receive: function(response, xhr) { | |
| if (this.isSuccess(xhr)) { | |
| this.processResponse(xhr); | |
| } else { | |
| this.processError(xhr); | |
| } | |
| this.complete(xhr); | |
| }, | |
| isSuccess: function(xhr) { | |
| var status = xhr.status || 0; | |
| return (status >= 200 && status < 300); | |
| }, | |
| processResponse: function(xhr) { | |
| var response = this.evalResponse(xhr); | |
| if (xhr === this.activeRequest) { | |
| this.response = response; | |
| } | |
| this.fire('core-response', {response: response, xhr: xhr}); | |
| }, | |
| processError: function(xhr) { | |
| var response = this.evalResponse(xhr); | |
| var error = { | |
| statusCode: xhr.status, | |
| response: response | |
| }; | |
| if (xhr === this.activeRequest) { | |
| this.error = error; | |
| } | |
| this.fire('core-error', {response: error, xhr: xhr}); | |
| }, | |
| processProgress: function(progress, xhr) { | |
| if (xhr !== this.activeRequest) { | |
| return; | |
| } | |
| // We create a proxy object here because these fields | |
| // on the progress event are readonly properties, which | |
| // causes problems in common use cases (e.g. binding to | |
| // <paper-progress> attributes). | |
| var progressProxy = { | |
| lengthComputable: progress.lengthComputable, | |
| loaded: progress.loaded, | |
| total: progress.total | |
| } | |
| this.progress = progressProxy; | |
| }, | |
| complete: function(xhr) { | |
| if (xhr === this.activeRequest) { | |
| this.loading = false; | |
| } | |
| this.fire('core-complete', {response: xhr.status, xhr: xhr}); | |
| }, | |
| evalResponse: function(xhr) { | |
| return this[(this.handleAs || 'text') + 'Handler'](xhr); | |
| }, | |
| xmlHandler: function(xhr) { | |
| return xhr.responseXML; | |
| }, | |
| textHandler: function(xhr) { | |
| return xhr.responseText; | |
| }, | |
| jsonHandler: function(xhr) { | |
| var r = xhr.responseText; | |
| try { | |
| return JSON.parse(r); | |
| } catch (x) { | |
| console.warn('core-ajax caught an exception trying to parse response as JSON:'); | |
| console.warn('url:', this.url); | |
| console.warn(x); | |
| return r; | |
| } | |
| }, | |
| documentHandler: function(xhr) { | |
| return xhr.response; | |
| }, | |
| blobHandler: function(xhr) { | |
| return xhr.response; | |
| }, | |
| arraybufferHandler: function(xhr) { | |
| return xhr.response; | |
| }, | |
| urlChanged: function() { | |
| if (!this.handleAs) { | |
| var ext = String(this.url).split('.').pop(); | |
| switch (ext) { | |
| case 'json': | |
| this.handleAs = 'json'; | |
| break; | |
| } | |
| } | |
| this.autoGo(); | |
| }, | |
| paramsChanged: function() { | |
| this.autoGo(); | |
| }, | |
| bodyChanged: function() { | |
| this.autoGo(); | |
| }, | |
| autoChanged: function() { | |
| this.autoGo(); | |
| }, | |
| // TODO(sorvell): multiple side-effects could call autoGo | |
| // during one micro-task, use a job to have only one action | |
| // occur | |
| autoGo: function() { | |
| if (this.auto) { | |
| this.goJob = this.job(this.goJob, this.go, 0); | |
| } | |
| }, | |
| getParams: function(params) { | |
| params = this.params || params; | |
| if (params && typeof(params) == 'string') { | |
| params = JSON.parse(params); | |
| } | |
| return params; | |
| }, | |
| /** | |
| * Performs an Ajax request to the specified URL. | |
| * | |
| * @method go | |
| */ | |
| go: function() { | |
| var args = this.xhrArgs || {}; | |
| // TODO(sjmiles): we may want XHR to default to POST if body is set | |
| args.body = this.body || args.body; | |
| args.params = this.getParams(args.params); | |
| args.headers = this.headers || args.headers || {}; | |
| if (args.headers && typeof(args.headers) == 'string') { | |
| args.headers = JSON.parse(args.headers); | |
| } | |
| var hasContentType = Object.keys(args.headers).some(function (header) { | |
| return header.toLowerCase() === 'content-type'; | |
| }); | |
| // No Content-Type should be specified if sending `FormData`. | |
| // The UA must set the Content-Type w/ a calculated multipart boundary ID. | |
| if (args.body instanceof FormData) { | |
| delete args.headers['Content-Type']; | |
| } | |
| else if (!hasContentType && this.contentType) { | |
| args.headers['Content-Type'] = this.contentType; | |
| } | |
| if (this.handleAs === 'arraybuffer' || this.handleAs === 'blob' || | |
| this.handleAs === 'document') { | |
| args.responseType = this.handleAs; | |
| } | |
| args.withCredentials = this.withCredentials; | |
| args.callback = this.receive.bind(this); | |
| args.url = this.url; | |
| args.method = this.method; | |
| this.response = this.error = this.progress = null; | |
| this.activeRequest = args.url && this.xhr.request(args); | |
| if (this.activeRequest) { | |
| this.loading = true; | |
| var activeRequest = this.activeRequest; | |
| // IE < 10 doesn't support progress events. | |
| if ('onprogress' in activeRequest) { | |
| this.activeRequest.addEventListener( | |
| 'progress', | |
| function(progress) { | |
| this.processProgress(progress, activeRequest); | |
| }.bind(this), false); | |
| } else { | |
| this.progress = { | |
| lengthComputable: false, | |
| } | |
| } | |
| } | |
| return this.activeRequest; | |
| }, | |
| /** | |
| * Aborts the current active request if there is one and resets internal | |
| * state appropriately. | |
| * | |
| * @method abort | |
| */ | |
| abort: function() { | |
| if (!this.activeRequest) return; | |
| this.activeRequest.abort(); | |
| this.activeRequest = null; | |
| this.progress = {}; | |
| this.loading = false; | |
| } | |
| }); | |
| </script> | |
| </polymer-element> |