not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

615 lines
18 KiB

  1. /*!
  2. * Reqwest! A general purpose XHR connection manager
  3. * license MIT (c) Dustin Diaz 2014
  4. * https://github.com/ded/reqwest
  5. */
  6. !function (name, context, definition) {
  7. if (typeof module != 'undefined' && module.exports) module.exports = definition()
  8. else if (typeof define == 'function' && define.amd) define(definition)
  9. else context[name] = definition()
  10. }('reqwest', this, function () {
  11. var win = window
  12. , doc = document
  13. , httpsRe = /^http/
  14. , protocolRe = /(^\w+):\/\//
  15. , twoHundo = /^(20\d|1223)$/ //http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request
  16. , byTag = 'getElementsByTagName'
  17. , readyState = 'readyState'
  18. , contentType = 'Content-Type'
  19. , requestedWith = 'X-Requested-With'
  20. , head = doc[byTag]('head')[0]
  21. , uniqid = 0
  22. , callbackPrefix = 'reqwest_' + (+new Date())
  23. , lastValue // data stored by the most recent JSONP callback
  24. , xmlHttpRequest = 'XMLHttpRequest'
  25. , xDomainRequest = 'XDomainRequest'
  26. , noop = function () {}
  27. , isArray = typeof Array.isArray == 'function'
  28. ? Array.isArray
  29. : function (a) {
  30. return a instanceof Array
  31. }
  32. , defaultHeaders = {
  33. 'contentType': 'application/x-www-form-urlencoded'
  34. , 'requestedWith': xmlHttpRequest
  35. , 'accept': {
  36. '*': 'text/javascript, text/html, application/xml, text/xml, */*'
  37. , 'xml': 'application/xml, text/xml'
  38. , 'html': 'text/html'
  39. , 'text': 'text/plain'
  40. , 'json': 'application/json, text/javascript'
  41. , 'js': 'application/javascript, text/javascript'
  42. }
  43. }
  44. , xhr = function(o) {
  45. // is it x-domain
  46. if (o['crossOrigin'] === true) {
  47. var xhr = win[xmlHttpRequest] ? new XMLHttpRequest() : null
  48. if (xhr && 'withCredentials' in xhr) {
  49. return xhr
  50. } else if (win[xDomainRequest]) {
  51. return new XDomainRequest()
  52. } else {
  53. throw new Error('Browser does not support cross-origin requests')
  54. }
  55. } else if (win[xmlHttpRequest]) {
  56. return new XMLHttpRequest()
  57. } else {
  58. return new ActiveXObject('Microsoft.XMLHTTP')
  59. }
  60. }
  61. , globalSetupOptions = {
  62. dataFilter: function (data) {
  63. return data
  64. }
  65. }
  66. function succeed(r) {
  67. var protocol = protocolRe.exec(r.url);
  68. protocol = (protocol && protocol[1]) || window.location.protocol;
  69. return httpsRe.test(protocol) ? twoHundo.test(r.request.status) : !!r.request.response;
  70. }
  71. function handleReadyState(r, success, error) {
  72. return function () {
  73. // use _aborted to mitigate against IE err c00c023f
  74. // (can't read props on aborted request objects)
  75. if (r._aborted) return error(r.request)
  76. if (r._timedOut) return error(r.request, 'Request is aborted: timeout')
  77. if (r.request && r.request[readyState] == 4) {
  78. r.request.onreadystatechange = noop
  79. if (succeed(r)) success(r.request)
  80. else
  81. error(r.request)
  82. }
  83. }
  84. }
  85. function setHeaders(http, o) {
  86. var headers = o['headers'] || {}
  87. , h
  88. headers['Accept'] = headers['Accept']
  89. || defaultHeaders['accept'][o['type']]
  90. || defaultHeaders['accept']['*']
  91. var isAFormData = typeof FormData === 'function' && (o['data'] instanceof FormData);
  92. // breaks cross-origin requests with legacy browsers
  93. if (!o['crossOrigin'] && !headers[requestedWith]) headers[requestedWith] = defaultHeaders['requestedWith']
  94. if (!headers[contentType] && !isAFormData) headers[contentType] = o['contentType'] || defaultHeaders['contentType']
  95. for (h in headers)
  96. headers.hasOwnProperty(h) && 'setRequestHeader' in http && http.setRequestHeader(h, headers[h])
  97. }
  98. function setCredentials(http, o) {
  99. if (typeof o['withCredentials'] !== 'undefined' && typeof http.withCredentials !== 'undefined') {
  100. http.withCredentials = !!o['withCredentials']
  101. }
  102. }
  103. function generalCallback(data) {
  104. lastValue = data
  105. }
  106. function urlappend (url, s) {
  107. return url + (/\?/.test(url) ? '&' : '?') + s
  108. }
  109. function handleJsonp(o, fn, err, url) {
  110. var reqId = uniqid++
  111. , cbkey = o['jsonpCallback'] || 'callback' // the 'callback' key
  112. , cbval = o['jsonpCallbackName'] || reqwest.getcallbackPrefix(reqId)
  113. , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)')
  114. , match = url.match(cbreg)
  115. , script = doc.createElement('script')
  116. , loaded = 0
  117. , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1
  118. if (match) {
  119. if (match[3] === '?') {
  120. url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name
  121. } else {
  122. cbval = match[3] // provided callback func name
  123. }
  124. } else {
  125. url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em
  126. }
  127. win[cbval] = generalCallback
  128. script.type = 'text/javascript'
  129. script.src = url
  130. script.async = true
  131. if (typeof script.onreadystatechange !== 'undefined' && !isIE10) {
  132. // need this for IE due to out-of-order onreadystatechange(), binding script
  133. // execution to an event listener gives us control over when the script
  134. // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
  135. script.htmlFor = script.id = '_reqwest_' + reqId
  136. }
  137. script.onload = script.onreadystatechange = function () {
  138. if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) {
  139. return false
  140. }
  141. script.onload = script.onreadystatechange = null
  142. script.onclick && script.onclick()
  143. // Call the user callback with the last value stored and clean up values and scripts.
  144. fn(lastValue)
  145. lastValue = undefined
  146. head.removeChild(script)
  147. loaded = 1
  148. }
  149. // Add the script to the DOM head
  150. head.appendChild(script)
  151. // Enable JSONP timeout
  152. return {
  153. abort: function () {
  154. script.onload = script.onreadystatechange = null
  155. err({}, 'Request is aborted: timeout', {})
  156. lastValue = undefined
  157. head.removeChild(script)
  158. loaded = 1
  159. }
  160. }
  161. }
  162. function getRequest(fn, err) {
  163. var o = this.o
  164. , method = (o['method'] || 'GET').toUpperCase()
  165. , url = typeof o === 'string' ? o : o['url']
  166. // convert non-string objects to query-string form unless o['processData'] is false
  167. , data = (o['processData'] !== false && o['data'] && typeof o['data'] !== 'string')
  168. ? reqwest.toQueryString(o['data'])
  169. : (o['data'] || null)
  170. , http
  171. , sendWait = false
  172. // if we're working on a GET request and we have data then we should append
  173. // query string to end of URL and not post data
  174. if ((o['type'] == 'jsonp' || method == 'GET') && data) {
  175. url = urlappend(url, data)
  176. data = null
  177. }
  178. if (o['type'] == 'jsonp') return handleJsonp(o, fn, err, url)
  179. // get the xhr from the factory if passed
  180. // if the factory returns null, fall-back to ours
  181. http = (o.xhr && o.xhr(o)) || xhr(o)
  182. http.open(method, url, o['async'] === false ? false : true)
  183. setHeaders(http, o)
  184. setCredentials(http, o)
  185. if (win[xDomainRequest] && http instanceof win[xDomainRequest]) {
  186. http.onload = fn
  187. http.onerror = err
  188. // NOTE: see
  189. // http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/30ef3add-767c-4436-b8a9-f1ca19b4812e
  190. http.onprogress = function() {}
  191. sendWait = true
  192. } else {
  193. http.onreadystatechange = handleReadyState(this, fn, err)
  194. }
  195. o['before'] && o['before'](http)
  196. if (sendWait) {
  197. setTimeout(function () {
  198. http.send(data)
  199. }, 200)
  200. } else {
  201. http.send(data)
  202. }
  203. return http
  204. }
  205. function Reqwest(o, fn) {
  206. this.o = o
  207. this.fn = fn
  208. init.apply(this, arguments)
  209. }
  210. function setType(header) {
  211. // json, javascript, text/plain, text/html, xml
  212. if (header.match('json')) return 'json'
  213. if (header.match('javascript')) return 'js'
  214. if (header.match('text')) return 'html'
  215. if (header.match('xml')) return 'xml'
  216. }
  217. function init(o, fn) {
  218. this.url = typeof o == 'string' ? o : o['url']
  219. this.timeout = null
  220. // whether request has been fulfilled for purpose
  221. // of tracking the Promises
  222. this._fulfilled = false
  223. // success handlers
  224. this._successHandler = function(){}
  225. this._fulfillmentHandlers = []
  226. // error handlers
  227. this._errorHandlers = []
  228. // complete (both success and fail) handlers
  229. this._completeHandlers = []
  230. this._erred = false
  231. this._responseArgs = {}
  232. var self = this
  233. fn = fn || function () {}
  234. if (o['timeout']) {
  235. this.timeout = setTimeout(function () {
  236. timedOut()
  237. }, o['timeout'])
  238. }
  239. if (o['success']) {
  240. this._successHandler = function () {
  241. o['success'].apply(o, arguments)
  242. }
  243. }
  244. if (o['error']) {
  245. this._errorHandlers.push(function () {
  246. o['error'].apply(o, arguments)
  247. })
  248. }
  249. if (o['complete']) {
  250. this._completeHandlers.push(function () {
  251. o['complete'].apply(o, arguments)
  252. })
  253. }
  254. function complete (resp) {
  255. o['timeout'] && clearTimeout(self.timeout)
  256. self.timeout = null
  257. while (self._completeHandlers.length > 0) {
  258. self._completeHandlers.shift()(resp)
  259. }
  260. }
  261. function success (resp) {
  262. var type = o['type'] || resp && setType(resp.getResponseHeader('Content-Type')) // resp can be undefined in IE
  263. resp = (type !== 'jsonp') ? self.request : resp
  264. // use global data filter on response text
  265. var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type)
  266. , r = filteredResponse
  267. try {
  268. resp.responseText = r
  269. } catch (e) {
  270. // can't assign this in IE<=8, just ignore
  271. }
  272. if (r) {
  273. switch (type) {
  274. case 'json':
  275. try {
  276. resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')')
  277. } catch (err) {
  278. return error(resp, 'Could not parse JSON in response', err)
  279. }
  280. break
  281. case 'js':
  282. resp = eval(r)
  283. break
  284. case 'html':
  285. resp = r
  286. break
  287. case 'xml':
  288. resp = resp.responseXML
  289. && resp.responseXML.parseError // IE trololo
  290. && resp.responseXML.parseError.errorCode
  291. && resp.responseXML.parseError.reason
  292. ? null
  293. : resp.responseXML
  294. break
  295. }
  296. }
  297. self._responseArgs.resp = resp
  298. self._fulfilled = true
  299. fn(resp)
  300. self._successHandler(resp)
  301. while (self._fulfillmentHandlers.length > 0) {
  302. resp = self._fulfillmentHandlers.shift()(resp)
  303. }
  304. complete(resp)
  305. }
  306. function timedOut() {
  307. self._timedOut = true
  308. self.request.abort()
  309. }
  310. function error(resp, msg, t) {
  311. resp = self.request
  312. self._responseArgs.resp = resp
  313. self._responseArgs.msg = msg
  314. self._responseArgs.t = t
  315. self._erred = true
  316. while (self._errorHandlers.length > 0) {
  317. self._errorHandlers.shift()(resp, msg, t)
  318. }
  319. complete(resp)
  320. }
  321. this.request = getRequest.call(this, success, error)
  322. }
  323. Reqwest.prototype = {
  324. abort: function () {
  325. this._aborted = true
  326. this.request.abort()
  327. }
  328. , retry: function () {
  329. init.call(this, this.o, this.fn)
  330. }
  331. /**
  332. * Small deviation from the Promises A CommonJs specification
  333. * http://wiki.commonjs.org/wiki/Promises/A
  334. */
  335. /**
  336. * `then` will execute upon successful requests
  337. */
  338. , then: function (success, fail) {
  339. success = success || function () {}
  340. fail = fail || function () {}
  341. if (this._fulfilled) {
  342. this._responseArgs.resp = success(this._responseArgs.resp)
  343. } else if (this._erred) {
  344. fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
  345. } else {
  346. this._fulfillmentHandlers.push(success)
  347. this._errorHandlers.push(fail)
  348. }
  349. return this
  350. }
  351. /**
  352. * `always` will execute whether the request succeeds or fails
  353. */
  354. , always: function (fn) {
  355. if (this._fulfilled || this._erred) {
  356. fn(this._responseArgs.resp)
  357. } else {
  358. this._completeHandlers.push(fn)
  359. }
  360. return this
  361. }
  362. /**
  363. * `fail` will execute when the request fails
  364. */
  365. , fail: function (fn) {
  366. if (this._erred) {
  367. fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
  368. } else {
  369. this._errorHandlers.push(fn)
  370. }
  371. return this
  372. }
  373. , 'catch': function (fn) {
  374. return this.fail(fn)
  375. }
  376. }
  377. function reqwest(o, fn) {
  378. return new Reqwest(o, fn)
  379. }
  380. // normalize newline variants according to spec -> CRLF
  381. function normalize(s) {
  382. return s ? s.replace(/\r?\n/g, '\r\n') : ''
  383. }
  384. function serial(el, cb) {
  385. var n = el.name
  386. , t = el.tagName.toLowerCase()
  387. , optCb = function (o) {
  388. // IE gives value="" even where there is no value attribute
  389. // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273
  390. if (o && !o['disabled'])
  391. cb(n, normalize(o['attributes']['value'] && o['attributes']['value']['specified'] ? o['value'] : o['text']))
  392. }
  393. , ch, ra, val, i
  394. // don't serialize elements that are disabled or without a name
  395. if (el.disabled || !n) return
  396. switch (t) {
  397. case 'input':
  398. if (!/reset|button|image|file/i.test(el.type)) {
  399. ch = /checkbox/i.test(el.type)
  400. ra = /radio/i.test(el.type)
  401. val = el.value
  402. // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here
  403. ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val))
  404. }
  405. break
  406. case 'textarea':
  407. cb(n, normalize(el.value))
  408. break
  409. case 'select':
  410. if (el.type.toLowerCase() === 'select-one') {
  411. optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null)
  412. } else {
  413. for (i = 0; el.length && i < el.length; i++) {
  414. el.options[i].selected && optCb(el.options[i])
  415. }
  416. }
  417. break
  418. }
  419. }
  420. // collect up all form elements found from the passed argument elements all
  421. // the way down to child elements; pass a '<form>' or form fields.
  422. // called with 'this'=callback to use for serial() on each element
  423. function eachFormElement() {
  424. var cb = this
  425. , e, i
  426. , serializeSubtags = function (e, tags) {
  427. var i, j, fa
  428. for (i = 0; i < tags.length; i++) {
  429. fa = e[byTag](tags[i])
  430. for (j = 0; j < fa.length; j++) serial(fa[j], cb)
  431. }
  432. }
  433. for (i = 0; i < arguments.length; i++) {
  434. e = arguments[i]
  435. if (/input|select|textarea/i.test(e.tagName)) serial(e, cb)
  436. serializeSubtags(e, [ 'input', 'select', 'textarea' ])
  437. }
  438. }
  439. // standard query string style serialization
  440. function serializeQueryString() {
  441. return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments))
  442. }
  443. // { 'name': 'value', ... } style serialization
  444. function serializeHash() {
  445. var hash = {}
  446. eachFormElement.apply(function (name, value) {
  447. if (name in hash) {
  448. hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]])
  449. hash[name].push(value)
  450. } else hash[name] = value
  451. }, arguments)
  452. return hash
  453. }
  454. // [ { name: 'name', value: 'value' }, ... ] style serialization
  455. reqwest.serializeArray = function () {
  456. var arr = []
  457. eachFormElement.apply(function (name, value) {
  458. arr.push({name: name, value: value})
  459. }, arguments)
  460. return arr
  461. }
  462. reqwest.serialize = function () {
  463. if (arguments.length === 0) return ''
  464. var opt, fn
  465. , args = Array.prototype.slice.call(arguments, 0)
  466. opt = args.pop()
  467. opt && opt.nodeType && args.push(opt) && (opt = null)
  468. opt && (opt = opt.type)
  469. if (opt == 'map') fn = serializeHash
  470. else if (opt == 'array') fn = reqwest.serializeArray
  471. else fn = serializeQueryString
  472. return fn.apply(null, args)
  473. }
  474. reqwest.toQueryString = function (o, trad) {
  475. var prefix, i
  476. , traditional = trad || false
  477. , s = []
  478. , enc = encodeURIComponent
  479. , add = function (key, value) {
  480. // If value is a function, invoke it and return its value
  481. value = ('function' === typeof value) ? value() : (value == null ? '' : value)
  482. s[s.length] = enc(key) + '=' + enc(value)
  483. }
  484. // If an array was passed in, assume that it is an array of form elements.
  485. if (isArray(o)) {
  486. for (i = 0; o && i < o.length; i++) add(o[i]['name'], o[i]['value'])
  487. } else {
  488. // If traditional, encode the "old" way (the way 1.3.2 or older
  489. // did it), otherwise encode params recursively.
  490. for (prefix in o) {
  491. if (o.hasOwnProperty(prefix)) buildParams(prefix, o[prefix], traditional, add)
  492. }
  493. }
  494. // spaces should be + according to spec
  495. return s.join('&').replace(/%20/g, '+')
  496. }
  497. function buildParams(prefix, obj, traditional, add) {
  498. var name, i, v
  499. , rbracket = /\[\]$/
  500. if (isArray(obj)) {
  501. // Serialize array item.
  502. for (i = 0; obj && i < obj.length; i++) {
  503. v = obj[i]
  504. if (traditional || rbracket.test(prefix)) {
  505. // Treat each array item as a scalar.
  506. add(prefix, v)
  507. } else {
  508. buildParams(prefix + '[' + (typeof v === 'object' ? i : '') + ']', v, traditional, add)
  509. }
  510. }
  511. } else if (obj && obj.toString() === '[object Object]') {
  512. // Serialize object item.
  513. for (name in obj) {
  514. buildParams(prefix + '[' + name + ']', obj[name], traditional, add)
  515. }
  516. } else {
  517. // Serialize scalar item.
  518. add(prefix, obj)
  519. }
  520. }
  521. reqwest.getcallbackPrefix = function () {
  522. return callbackPrefix
  523. }
  524. // jQuery and Zepto compatibility, differences can be remapped here so you can call
  525. // .ajax.compat(options, callback)
  526. reqwest.compat = function (o, fn) {
  527. if (o) {
  528. o['type'] && (o['method'] = o['type']) && delete o['type']
  529. o['dataType'] && (o['type'] = o['dataType'])
  530. o['jsonpCallback'] && (o['jsonpCallbackName'] = o['jsonpCallback']) && delete o['jsonpCallback']
  531. o['jsonp'] && (o['jsonpCallback'] = o['jsonp'])
  532. }
  533. return new Reqwest(o, fn)
  534. }
  535. reqwest.ajaxSetup = function (options) {
  536. options = options || {}
  537. for (var k in options) {
  538. globalSetupOptions[k] = options[k]
  539. }
  540. }
  541. return reqwest
  542. });