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>
 |