/*! * 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 '