text: /**
   * Create JSONP request.
   *
   * @param {String} url
   * @param {Object} options
   */
  
  function ajaxJSONP (url, options) {
    var name = (name = /callback\=([A-Za-z0-9\-\.]+)/.exec(url)) ? name[1] : 'jsonp' + (+new Date())
      , el = document.createElement('script')
      , abortTimeout = null
      , cleanUp = function () {
          if (abortTimeout !== null) clearTimeout(abortTimeout);
          tire(el).remove();
          try { delete window[name]; }
          catch (e) { window[name] = undefined; }
        }
      , abort = function (error) {
          cleanUp();
          if (error === 'timeout') window[name] = noop;
          if (tire.isFunction(options.error)) options.error(error, options);
        };
  
    el.onerror = function () {
      abort('error');
    };
  
    if (options.timeout > 0) {
      abortTimeout = setTimeout(function () {
        abort('timeout');
      }, options.timeout);
    }
  
    window[name] = function (data) {
      tire(el).remove();
      try { delete window[name]; }
      catch (e) { window[name] = undefined; }
      tire.ajaxSuccess(data, null, options);
    };
  
    options.data = tire.param(options.data);
    el.src = url.replace(/\=\?/, '=' + name);
    tire('head')[0].appendChild(el);
  }
  
  tire.extend({
  
    /**
     * Ajax method to create ajax request with XMLHTTPRequest (or ActiveXObject).
     * Support for JSONP. Cross domain via plugin.
     *
     * @param {String|Object} url
     * @param {Object|Function} options
     * @return {Object}
     */
  
    ajax: function (url, options) {
      options = options || tire.ajaxSettings;
  
      if (tire.isObject(url)) {
        if (tire.isFunction(options)) {
          url.success = url.success || options;
        }
        options = url;
        url = options.url;
      }
  
      if (tire.isFunction(options)) options = { success: options };
  
      for (var opt in tire.ajaxSettings) {
        if (!options.hasOwnProperty(opt)) {
          options[opt] = tire.ajaxSettings[opt];
        }
      }
  
      if (!url) return options.xhr();
  
      var xhr = options.xhr()
        , error = 'error'
        , abortTimeout = null
        , jsonp = options.dataType === 'jsonp'
        , mime = {
            html: 'text/html',
            text: 'text/plain',
            xml: 'application/xml, text/xml',
            json: 'application/json'
          }
        , params = tire.param(options.data) !== '' ? tire.param(options.data) : options.data;
  
      for (var k in mime) {
        if (url.indexOf('.' + k) !== -1 && !options.dataType) options.dataType = k;
      }
  
      // test for jsonp
      if (jsonp || /\=\?|callback\=/.test(url)) {
        if (!/\=\?/.test(url)) url = (url + '&callback=?').replace(/[&?]{1,2}/, '?');
        return ajaxJSONP(url, options);
      }
  
      if (tire.isFunction(options.beforeOpen)) {
        var bc = options.beforeOpen(xhr, options);
        if (!bc) {
          xhr.abort();
          return xhr;
        }
        xhr = bc;
      }
  
      if (xhr) {
        xhr.open(options.type, url, true);
  
        if ((mime = mime[options.dataType.toLowerCase()]) !== undefined) {
          xhr.setRequestHeader('Accept', mime);
          if (mime.indexOf(',') !== -1) mime = mime.split(',')[0];
          if (xhr.overrideMimeType) xhr.overrideMimeType(mime);
        }
  
        if (options.contentType || options.data && options.type !== 'GET') {
          xhr.setRequestHeader('Content-Type', (options.contentType || 'application/x-www-form-urlencoded'));
        }
  
        for (var key in options.headers) {
          if (options.headers.hasOwnProperty(key)) {
            xhr.setRequestHeader(key, options.headers[key]);
          }
        }
  
        if (options.timeout > 0) {
          abortTimeout = setTimeout(function () {
            error = 'timeout';
            xhr.abort();
          }, options.timeout);
        }
  
        xhr.onreadystatechange = function () {
          if (xhr.readyState === 4) {
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
              if (options.success !== undefined) {
                tire.ajaxSuccess(null, xhr, options);
              }
            } else if (options.error !== undefined) {
              if (abortTimeout !== null) clearTimeout(abortTimeout);
              options.error(error, options, xhr);
            }
          }
        };
  
        if (tire.isFunction(options.beforeSend)) {
          var bs = options.beforeSend(xhr, options);
          if (bs !== false) {
            xhr.send(params);
          }
          xhr = bs;
        } else {
          xhr.send(params);
        }
  
        return xhr;
      }
    },
  
    /**
     * Default ajax settings.
     */
  
    ajaxSettings: {
  
      // Modify the xhr object before open it. Default is null.
      beforeOpen: null,
  
      // Modify the xhr object before send. Default is null.
      beforeSend: null,
  
      // Tell server witch content type it is.
      contentType: 'application/x-www-form-urlencoded',
  
      // Data that is send to the server.
      data: {},
  
      // The type of the data.
      // Can be: json, jsonp, html, text, xml.
      dataType: '',
  
      // Error function that is called on failed request.
      // Take to arguments, xhr and the options object.
      error: noop,
  
      // An object of additional header key/value pairs to send along with the request
      headers: {
        'X-Requested-With': 'XMLHttpRequest'
      },
  
      // Function that runs on a successful request.
      // Takes on argument, the response.
      success: noop,
  
      // Set a timeout (in milliseconds) for the request.
      timeout: 0,
  
      // The type of the request. Default is GET.
      type: 'GET',
  
      // The url to make request to. If empty no request will be made.
      url: '',
  
      // ActiveXObject when available (IE), otherwise XMLHttpRequest.
      xhr: function () {
        var xhr = null;
        if (window.XMLHttpRequest) {
          xhr = new XMLHttpRequest();
        } else if (window.ActiveXObject) { // < IE 9
          xhr = new ActiveXObject('Microsoft.XMLHTTP');
        }
        return xhr;
      }
    },
  
    /**
     * Ajax success. Check if the dataType is JSON and try to parse it or just return the response text/xml.
     *
     * @param {Object} data
     * @param {Object} xhr
     * @param {Object} options
     *
     * @return {Object}
     */
  
    ajaxSuccess: function (data, xhr, options) {
      var res;
      if (xhr) {
        if ((options.dataType === 'json' || false) && (res = tire.parseJSON(xhr.responseText)) === null) res = xhr.responseText;
        if (options.dataType === 'xml') res = xhr.responseXML;
        res = res || xhr.responseText;
      }
      if (!res && data) res = data;
      if (tire.isFunction(options.success)) options.success(res);
    },
  
    /**
     * Create a serialized representation of an array or object.
     *
     * @param {Array|Object} obj
     * @param {Object} prefix
     * @return {String}
     */
  
    param: function (obj, prefix) {
      var str = [];
      this.each(obj, function (p, v) {
        var k = prefix ? prefix + '[' + p + ']' : p;
        str.push(tire.isObject(v) ? tire.param(v, k) : encodeURIComponent(k) + '=' + encodeURIComponent(v));
      });
      return str.join('&').replace('%20', '+');
    }
  });
  