581 lines
15 KiB
HTML
581 lines
15 KiB
HTML
<!--
|
|
@license
|
|
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="https://www.googleapis.com/youtube/v3/search"
|
|
params='{"part":"snippet", "q":"polymer", "key": "YOUTUBE_API_KEY", "type": "video"}'
|
|
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 before a request is sent.
|
|
*
|
|
* @event iron-ajax-presend
|
|
*/
|
|
|
|
/**
|
|
* Fired when a request is sent.
|
|
*
|
|
* @event request
|
|
* @event iron-ajax-request
|
|
*/
|
|
|
|
/**
|
|
* Fired when a response is received.
|
|
*
|
|
* @event response
|
|
* @event iron-ajax-response
|
|
*/
|
|
|
|
/**
|
|
* Fired when an error is received.
|
|
*
|
|
* @event error
|
|
* @event iron-ajax-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.
|
|
*
|
|
* Varies the handling of the `body` param.
|
|
*/
|
|
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 automatically generated requests.
|
|
*/
|
|
debounceDuration: {
|
|
type: Number,
|
|
value: 0,
|
|
notify: true
|
|
},
|
|
|
|
/**
|
|
* Prefix to be stripped from a JSON response before parsing it.
|
|
*
|
|
* In order to prevent an attack using CSRF with Array responses
|
|
* (http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx/)
|
|
* many backends will mitigate this by prefixing all JSON response bodies
|
|
* with a string that would be nonsensical to a JavaScript parser.
|
|
*
|
|
*/
|
|
jsonPrefix: {
|
|
type: String,
|
|
value: ''
|
|
},
|
|
|
|
/**
|
|
* By default, iron-ajax's events do not bubble. Setting this attribute will cause its
|
|
* request and response events as well as its iron-ajax-request, -response, and -error
|
|
* events to bubble to the window object. The vanilla error event never bubbles when
|
|
* using shadow dom even if this.bubbles is true because a scoped flag is not passed with
|
|
* it (first link) and because the shadow dom spec did not used to allow certain events,
|
|
* including events named error, to leak outside of shadow trees (second link).
|
|
* https://www.w3.org/TR/shadow-dom/#scoped-flag
|
|
* https://www.w3.org/TR/2015/WD-shadow-dom-20151215/#events-that-are-not-leaked-into-ancestor-trees
|
|
*/
|
|
bubbles: {
|
|
type: Boolean,
|
|
value: false
|
|
},
|
|
|
|
/**
|
|
* Changes the [`completes`](iron-request#property-completes) promise chain
|
|
* from `generateRequest` to reject with an object
|
|
* containing the original request, as well an error message.
|
|
* If false (default), the promise rejects with an error message only.
|
|
*/
|
|
rejectWithRequest: {
|
|
type: Boolean,
|
|
value: false
|
|
},
|
|
|
|
_boundHandleResponse: {
|
|
type: Function,
|
|
value: function() {
|
|
return this._handleResponse.bind(this);
|
|
}
|
|
}
|
|
},
|
|
|
|
observers: [
|
|
'_requestOptionsChanged(url, method, params.*, headers, contentType, ' +
|
|
'body, sync, handleAs, jsonPrefix, 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;
|
|
var url = this.url || '';
|
|
|
|
if (queryString) {
|
|
var bindingChar = url.indexOf('?') >= 0 ? '&' : '?';
|
|
return url + bindingChar + queryString;
|
|
}
|
|
|
|
return 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 (typeof this.headers === 'object') {
|
|
for (header in this.headers) {
|
|
headers[header] = 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),
|
|
* jsonPrefix: (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,
|
|
jsonPrefix: this.jsonPrefix,
|
|
withCredentials: this.withCredentials,
|
|
timeout: this.timeout,
|
|
rejectWithRequest: this.rejectWithRequest,
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 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.push('activeRequests', request);
|
|
|
|
request.completes.then(
|
|
this._boundHandleResponse
|
|
).catch(
|
|
this._handleError.bind(this, request)
|
|
).then(
|
|
this._discardRequest.bind(this, request)
|
|
);
|
|
|
|
var evt = this.fire('iron-ajax-presend', {
|
|
request: request,
|
|
options: requestOptions
|
|
}, {bubbles: this.bubbles, cancelable: true});
|
|
|
|
if (evt.defaultPrevented) {
|
|
request.abort();
|
|
request.rejectCompletes(request);
|
|
return request;
|
|
}
|
|
|
|
request.send(requestOptions);
|
|
|
|
this._setLastRequest(request);
|
|
this._setLoading(true);
|
|
|
|
this.fire('request', {
|
|
request: request,
|
|
options: requestOptions
|
|
}, {
|
|
bubbles: this.bubbles,
|
|
composed: true
|
|
});
|
|
|
|
this.fire('iron-ajax-request', {
|
|
request: request,
|
|
options: requestOptions
|
|
}, {
|
|
bubbles: this.bubbles,
|
|
composed: true
|
|
});
|
|
|
|
return request;
|
|
},
|
|
|
|
_handleResponse: function(request) {
|
|
if (request === this.lastRequest) {
|
|
this._setLastResponse(request.response);
|
|
this._setLastError(null);
|
|
this._setLoading(false);
|
|
}
|
|
this.fire('response', request, {
|
|
bubbles: this.bubbles,
|
|
composed: true
|
|
});
|
|
this.fire('iron-ajax-response', request, {
|
|
bubbles: this.bubbles,
|
|
composed: true
|
|
});
|
|
},
|
|
|
|
_handleError: function(request, error) {
|
|
if (this.verbose) {
|
|
Polymer.Base._error(error);
|
|
}
|
|
|
|
if (request === this.lastRequest) {
|
|
this._setLastError({
|
|
request: request,
|
|
error: error,
|
|
status: request.xhr.status,
|
|
statusText: request.xhr.statusText,
|
|
response: request.xhr.response
|
|
});
|
|
this._setLastResponse(null);
|
|
this._setLoading(false);
|
|
}
|
|
|
|
// Tests fail if this goes after the normal this.fire('error', ...)
|
|
this.fire('iron-ajax-error', {
|
|
request: request,
|
|
error: error
|
|
}, {
|
|
bubbles: this.bubbles,
|
|
composed: true
|
|
});
|
|
|
|
this.fire('error', {
|
|
request: request,
|
|
error: error
|
|
}, {
|
|
bubbles: this.bubbles,
|
|
composed: true
|
|
});
|
|
},
|
|
|
|
_discardRequest: function(request) {
|
|
var requestIndex = this.activeRequests.indexOf(request);
|
|
|
|
if (requestIndex > -1) {
|
|
this.splice('activeRequests', requestIndex, 1);
|
|
}
|
|
},
|
|
|
|
_requestOptionsChanged: function() {
|
|
this.debounce('generate-request', function() {
|
|
if (this.url == null) {
|
|
return;
|
|
}
|
|
|
|
if (this.auto) {
|
|
this.generateRequest();
|
|
}
|
|
}, this.debounceDuration);
|
|
},
|
|
|
|
});
|
|
</script>
|