|
|
- /*!
- * Reqwest! A general purpose XHR connection manager
- * license MIT (c) Dustin Diaz 2014
- * https://github.com/ded/reqwest
- */
-
- !function (name, context, definition) {
- if (typeof module != 'undefined' && module.exports) module.exports = definition()
- else if (typeof define == 'function' && define.amd) define(definition)
- else context[name] = definition()
- }('reqwest', this, function () {
-
- var win = window
- , doc = document
- , httpsRe = /^http/
- , protocolRe = /(^\w+):\/\//
- , twoHundo = /^(20\d|1223)$/ //http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request
- , byTag = 'getElementsByTagName'
- , readyState = 'readyState'
- , contentType = 'Content-Type'
- , requestedWith = 'X-Requested-With'
- , head = doc[byTag]('head')[0]
- , uniqid = 0
- , callbackPrefix = 'reqwest_' + (+new Date())
- , lastValue // data stored by the most recent JSONP callback
- , xmlHttpRequest = 'XMLHttpRequest'
- , xDomainRequest = 'XDomainRequest'
- , noop = function () {}
-
- , isArray = typeof Array.isArray == 'function'
- ? Array.isArray
- : function (a) {
- return a instanceof Array
- }
-
- , defaultHeaders = {
- 'contentType': 'application/x-www-form-urlencoded'
- , 'requestedWith': xmlHttpRequest
- , 'accept': {
- '*': 'text/javascript, text/html, application/xml, text/xml, */*'
- , 'xml': 'application/xml, text/xml'
- , 'html': 'text/html'
- , 'text': 'text/plain'
- , 'json': 'application/json, text/javascript'
- , 'js': 'application/javascript, text/javascript'
- }
- }
-
- , xhr = function(o) {
- // is it x-domain
- if (o['crossOrigin'] === true) {
- var xhr = win[xmlHttpRequest] ? new XMLHttpRequest() : null
- if (xhr && 'withCredentials' in xhr) {
- return xhr
- } else if (win[xDomainRequest]) {
- return new XDomainRequest()
- } else {
- throw new Error('Browser does not support cross-origin requests')
- }
- } else if (win[xmlHttpRequest]) {
- return new XMLHttpRequest()
- } else {
- return new ActiveXObject('Microsoft.XMLHTTP')
- }
- }
- , globalSetupOptions = {
- dataFilter: function (data) {
- return data
- }
- }
-
- function succeed(r) {
- var protocol = protocolRe.exec(r.url);
- protocol = (protocol && protocol[1]) || window.location.protocol;
- return httpsRe.test(protocol) ? twoHundo.test(r.request.status) : !!r.request.response;
- }
-
- function handleReadyState(r, success, error) {
- return function () {
- // use _aborted to mitigate against IE err c00c023f
- // (can't read props on aborted request objects)
- if (r._aborted) return error(r.request)
- if (r._timedOut) return error(r.request, 'Request is aborted: timeout')
- if (r.request && r.request[readyState] == 4) {
- r.request.onreadystatechange = noop
- if (succeed(r)) success(r.request)
- else
- error(r.request)
- }
- }
- }
-
- function setHeaders(http, o) {
- var headers = o['headers'] || {}
- , h
-
- headers['Accept'] = headers['Accept']
- || defaultHeaders['accept'][o['type']]
- || defaultHeaders['accept']['*']
-
- var isAFormData = typeof FormData === 'function' && (o['data'] instanceof FormData);
- // breaks cross-origin requests with legacy browsers
- if (!o['crossOrigin'] && !headers[requestedWith]) headers[requestedWith] = defaultHeaders['requestedWith']
- if (!headers[contentType] && !isAFormData) headers[contentType] = o['contentType'] || defaultHeaders['contentType']
- for (h in headers)
- headers.hasOwnProperty(h) && 'setRequestHeader' in http && http.setRequestHeader(h, headers[h])
- }
-
- function setCredentials(http, o) {
- if (typeof o['withCredentials'] !== 'undefined' && typeof http.withCredentials !== 'undefined') {
- http.withCredentials = !!o['withCredentials']
- }
- }
-
- function generalCallback(data) {
- lastValue = data
- }
-
- function urlappend (url, s) {
- return url + (/\?/.test(url) ? '&' : '?') + s
- }
-
- function handleJsonp(o, fn, err, url) {
- var reqId = uniqid++
- , cbkey = o['jsonpCallback'] || 'callback' // the 'callback' key
- , cbval = o['jsonpCallbackName'] || reqwest.getcallbackPrefix(reqId)
- , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)')
- , match = url.match(cbreg)
- , script = doc.createElement('script')
- , loaded = 0
- , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1
-
- if (match) {
- if (match[3] === '?') {
- url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name
- } else {
- cbval = match[3] // provided callback func name
- }
- } else {
- url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em
- }
-
- win[cbval] = generalCallback
-
- script.type = 'text/javascript'
- script.src = url
- script.async = true
- if (typeof script.onreadystatechange !== 'undefined' && !isIE10) {
- // need this for IE due to out-of-order onreadystatechange(), binding script
- // execution to an event listener gives us control over when the script
- // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
- script.htmlFor = script.id = '_reqwest_' + reqId
- }
-
- script.onload = script.onreadystatechange = function () {
- if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) {
- return false
- }
- script.onload = script.onreadystatechange = null
- script.onclick && script.onclick()
- // Call the user callback with the last value stored and clean up values and scripts.
- fn(lastValue)
- lastValue = undefined
- head.removeChild(script)
- loaded = 1
- }
-
- // Add the script to the DOM head
- head.appendChild(script)
-
- // Enable JSONP timeout
- return {
- abort: function () {
- script.onload = script.onreadystatechange = null
- err({}, 'Request is aborted: timeout', {})
- lastValue = undefined
- head.removeChild(script)
- loaded = 1
- }
- }
- }
-
- function getRequest(fn, err) {
- var o = this.o
- , method = (o['method'] || 'GET').toUpperCase()
- , url = typeof o === 'string' ? o : o['url']
- // convert non-string objects to query-string form unless o['processData'] is false
- , data = (o['processData'] !== false && o['data'] && typeof o['data'] !== 'string')
- ? reqwest.toQueryString(o['data'])
- : (o['data'] || null)
- , http
- , sendWait = false
-
- // if we're working on a GET request and we have data then we should append
- // query string to end of URL and not post data
- if ((o['type'] == 'jsonp' || method == 'GET') && data) {
- url = urlappend(url, data)
- data = null
- }
-
- if (o['type'] == 'jsonp') return handleJsonp(o, fn, err, url)
-
- // get the xhr from the factory if passed
- // if the factory returns null, fall-back to ours
- http = (o.xhr && o.xhr(o)) || xhr(o)
-
- http.open(method, url, o['async'] === false ? false : true)
- setHeaders(http, o)
- setCredentials(http, o)
- if (win[xDomainRequest] && http instanceof win[xDomainRequest]) {
- http.onload = fn
- http.onerror = err
- // NOTE: see
- // http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/30ef3add-767c-4436-b8a9-f1ca19b4812e
- http.onprogress = function() {}
- sendWait = true
- } else {
- http.onreadystatechange = handleReadyState(this, fn, err)
- }
- o['before'] && o['before'](http)
- if (sendWait) {
- setTimeout(function () {
- http.send(data)
- }, 200)
- } else {
- http.send(data)
- }
- return http
- }
-
- function Reqwest(o, fn) {
- this.o = o
- this.fn = fn
-
- init.apply(this, arguments)
- }
-
- function setType(header) {
- // json, javascript, text/plain, text/html, xml
- if (header.match('json')) return 'json'
- if (header.match('javascript')) return 'js'
- if (header.match('text')) return 'html'
- if (header.match('xml')) return 'xml'
- }
-
- function init(o, fn) {
-
- this.url = typeof o == 'string' ? o : o['url']
- this.timeout = null
-
- // whether request has been fulfilled for purpose
- // of tracking the Promises
- this._fulfilled = false
- // success handlers
- this._successHandler = function(){}
- this._fulfillmentHandlers = []
- // error handlers
- this._errorHandlers = []
- // complete (both success and fail) handlers
- this._completeHandlers = []
- this._erred = false
- this._responseArgs = {}
-
- var self = this
-
- fn = fn || function () {}
-
- if (o['timeout']) {
- this.timeout = setTimeout(function () {
- timedOut()
- }, o['timeout'])
- }
-
- if (o['success']) {
- this._successHandler = function () {
- o['success'].apply(o, arguments)
- }
- }
-
- if (o['error']) {
- this._errorHandlers.push(function () {
- o['error'].apply(o, arguments)
- })
- }
-
- if (o['complete']) {
- this._completeHandlers.push(function () {
- o['complete'].apply(o, arguments)
- })
- }
-
- function complete (resp) {
- o['timeout'] && clearTimeout(self.timeout)
- self.timeout = null
- while (self._completeHandlers.length > 0) {
- self._completeHandlers.shift()(resp)
- }
- }
-
- function success (resp) {
- var type = o['type'] || resp && setType(resp.getResponseHeader('Content-Type')) // resp can be undefined in IE
- resp = (type !== 'jsonp') ? self.request : resp
- // use global data filter on response text
- var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type)
- , r = filteredResponse
- try {
- resp.responseText = r
- } catch (e) {
- // can't assign this in IE<=8, just ignore
- }
- if (r) {
- switch (type) {
- case 'json':
- try {
- resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')')
- } catch (err) {
- return error(resp, 'Could not parse JSON in response', err)
- }
- break
- case 'js':
- resp = eval(r)
- break
- case 'html':
- resp = r
- break
- case 'xml':
- resp = resp.responseXML
- && resp.responseXML.parseError // IE trololo
- && resp.responseXML.parseError.errorCode
- && resp.responseXML.parseError.reason
- ? null
- : resp.responseXML
- break
- }
- }
-
- self._responseArgs.resp = resp
- self._fulfilled = true
- fn(resp)
- self._successHandler(resp)
- while (self._fulfillmentHandlers.length > 0) {
- resp = self._fulfillmentHandlers.shift()(resp)
- }
-
- complete(resp)
- }
-
- function timedOut() {
- self._timedOut = true
- self.request.abort()
- }
-
- function error(resp, msg, t) {
- resp = self.request
- self._responseArgs.resp = resp
- self._responseArgs.msg = msg
- self._responseArgs.t = t
- self._erred = true
- while (self._errorHandlers.length > 0) {
- self._errorHandlers.shift()(resp, msg, t)
- }
- complete(resp)
- }
-
- this.request = getRequest.call(this, success, error)
- }
-
- Reqwest.prototype = {
- abort: function () {
- this._aborted = true
- this.request.abort()
- }
-
- , retry: function () {
- init.call(this, this.o, this.fn)
- }
-
- /**
- * Small deviation from the Promises A CommonJs specification
- * http://wiki.commonjs.org/wiki/Promises/A
- */
-
- /**
- * `then` will execute upon successful requests
- */
- , then: function (success, fail) {
- success = success || function () {}
- fail = fail || function () {}
- if (this._fulfilled) {
- this._responseArgs.resp = success(this._responseArgs.resp)
- } else if (this._erred) {
- fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
- } else {
- this._fulfillmentHandlers.push(success)
- this._errorHandlers.push(fail)
- }
- return this
- }
-
- /**
- * `always` will execute whether the request succeeds or fails
- */
- , always: function (fn) {
- if (this._fulfilled || this._erred) {
- fn(this._responseArgs.resp)
- } else {
- this._completeHandlers.push(fn)
- }
- return this
- }
-
- /**
- * `fail` will execute when the request fails
- */
- , fail: function (fn) {
- if (this._erred) {
- fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
- } else {
- this._errorHandlers.push(fn)
- }
- return this
- }
- , 'catch': function (fn) {
- return this.fail(fn)
- }
- }
-
- function reqwest(o, fn) {
- return new Reqwest(o, fn)
- }
-
- // normalize newline variants according to spec -> CRLF
- function normalize(s) {
- return s ? s.replace(/\r?\n/g, '\r\n') : ''
- }
-
- function serial(el, cb) {
- var n = el.name
- , t = el.tagName.toLowerCase()
- , optCb = function (o) {
- // IE gives value="" even where there is no value attribute
- // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273
- if (o && !o['disabled'])
- cb(n, normalize(o['attributes']['value'] && o['attributes']['value']['specified'] ? o['value'] : o['text']))
- }
- , ch, ra, val, i
-
- // don't serialize elements that are disabled or without a name
- if (el.disabled || !n) return
-
- switch (t) {
- case 'input':
- if (!/reset|button|image|file/i.test(el.type)) {
- ch = /checkbox/i.test(el.type)
- ra = /radio/i.test(el.type)
- val = el.value
- // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here
- ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val))
- }
- break
- case 'textarea':
- cb(n, normalize(el.value))
- break
- case 'select':
- if (el.type.toLowerCase() === 'select-one') {
- optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null)
- } else {
- for (i = 0; el.length && i < el.length; i++) {
- el.options[i].selected && optCb(el.options[i])
- }
- }
- break
- }
- }
-
- // collect up all form elements found from the passed argument elements all
- // the way down to child elements; pass a '<form>' or form fields.
- // called with 'this'=callback to use for serial() on each element
- function eachFormElement() {
- var cb = this
- , e, i
- , serializeSubtags = function (e, tags) {
- var i, j, fa
- for (i = 0; i < tags.length; i++) {
- fa = e[byTag](tags[i])
- for (j = 0; j < fa.length; j++) serial(fa[j], cb)
- }
- }
-
- for (i = 0; i < arguments.length; i++) {
- e = arguments[i]
- if (/input|select|textarea/i.test(e.tagName)) serial(e, cb)
- serializeSubtags(e, [ 'input', 'select', 'textarea' ])
- }
- }
-
- // standard query string style serialization
- function serializeQueryString() {
- return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments))
- }
-
- // { 'name': 'value', ... } style serialization
- function serializeHash() {
- var hash = {}
- eachFormElement.apply(function (name, value) {
- if (name in hash) {
- hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]])
- hash[name].push(value)
- } else hash[name] = value
- }, arguments)
- return hash
- }
-
- // [ { name: 'name', value: 'value' }, ... ] style serialization
- reqwest.serializeArray = function () {
- var arr = []
- eachFormElement.apply(function (name, value) {
- arr.push({name: name, value: value})
- }, arguments)
- return arr
- }
-
- reqwest.serialize = function () {
- if (arguments.length === 0) return ''
- var opt, fn
- , args = Array.prototype.slice.call(arguments, 0)
-
- opt = args.pop()
- opt && opt.nodeType && args.push(opt) && (opt = null)
- opt && (opt = opt.type)
-
- if (opt == 'map') fn = serializeHash
- else if (opt == 'array') fn = reqwest.serializeArray
- else fn = serializeQueryString
-
- return fn.apply(null, args)
- }
-
- reqwest.toQueryString = function (o, trad) {
- var prefix, i
- , traditional = trad || false
- , s = []
- , enc = encodeURIComponent
- , add = function (key, value) {
- // If value is a function, invoke it and return its value
- value = ('function' === typeof value) ? value() : (value == null ? '' : value)
- s[s.length] = enc(key) + '=' + enc(value)
- }
- // If an array was passed in, assume that it is an array of form elements.
- if (isArray(o)) {
- for (i = 0; o && i < o.length; i++) add(o[i]['name'], o[i]['value'])
- } else {
- // If traditional, encode the "old" way (the way 1.3.2 or older
- // did it), otherwise encode params recursively.
- for (prefix in o) {
- if (o.hasOwnProperty(prefix)) buildParams(prefix, o[prefix], traditional, add)
- }
- }
-
- // spaces should be + according to spec
- return s.join('&').replace(/%20/g, '+')
- }
-
- function buildParams(prefix, obj, traditional, add) {
- var name, i, v
- , rbracket = /\[\]$/
-
- if (isArray(obj)) {
- // Serialize array item.
- for (i = 0; obj && i < obj.length; i++) {
- v = obj[i]
- if (traditional || rbracket.test(prefix)) {
- // Treat each array item as a scalar.
- add(prefix, v)
- } else {
- buildParams(prefix + '[' + (typeof v === 'object' ? i : '') + ']', v, traditional, add)
- }
- }
- } else if (obj && obj.toString() === '[object Object]') {
- // Serialize object item.
- for (name in obj) {
- buildParams(prefix + '[' + name + ']', obj[name], traditional, add)
- }
-
- } else {
- // Serialize scalar item.
- add(prefix, obj)
- }
- }
-
- reqwest.getcallbackPrefix = function () {
- return callbackPrefix
- }
-
- // jQuery and Zepto compatibility, differences can be remapped here so you can call
- // .ajax.compat(options, callback)
- reqwest.compat = function (o, fn) {
- if (o) {
- o['type'] && (o['method'] = o['type']) && delete o['type']
- o['dataType'] && (o['type'] = o['dataType'])
- o['jsonpCallback'] && (o['jsonpCallbackName'] = o['jsonpCallback']) && delete o['jsonpCallback']
- o['jsonp'] && (o['jsonpCallback'] = o['jsonp'])
- }
- return new Reqwest(o, fn)
- }
-
- reqwest.ajaxSetup = function (options) {
- options = options || {}
- for (var k in options) {
- globalSetupOptions[k] = options[k]
- }
- }
-
- return reqwest
- });
|