├── .gitignore ├── .jshintrc ├── .travis.yml ├── Makefile ├── README.md ├── build.json ├── make ├── bump.js └── tests.js ├── package.json ├── phantom.js ├── reqwest.js ├── reqwest.min.js ├── src ├── copyright.js ├── ender.js └── reqwest.js ├── test.js ├── tests ├── ender.js ├── fixtures │ ├── badfixtures.xml │ ├── fixtures.html │ ├── fixtures.js │ ├── fixtures.json │ ├── fixtures.xml │ ├── fixtures_jsonp.jsonp │ ├── fixtures_jsonp2.jsonp │ ├── fixtures_jsonp3.jsonp │ ├── fixtures_jsonp_multi.jsonp │ ├── fixtures_jsonp_multi_b.jsonp │ ├── fixtures_jsonp_multi_c.jsonp │ ├── fixtures_with_prefix.json │ └── invalidJSON.json ├── tests.html └── tests.js ├── use-me.sublime-project └── vendor └── phantomjs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.sublime-workspace 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": ["define", "ActiveXObject", "XDomainRequest"] 3 | , "bitwise": true 4 | , "camelcase": false 5 | , "curly": false 6 | , "eqeqeq": false 7 | , "forin": false 8 | , "immed": false 9 | , "latedef": false 10 | , "newcap": true 11 | , "noarg": true 12 | , "noempty": true 13 | , "nonew": true 14 | , "plusplus": false 15 | , "quotmark": true 16 | , "regexp": false 17 | , "undef": true 18 | , "unused": true 19 | , "strict": false 20 | , "trailing": true 21 | , "maxlen": 120 22 | , "asi": true 23 | , "boss": true 24 | , "debug": true 25 | , "eqnull": true 26 | , "esnext": true 27 | , "evil": true 28 | , "expr": true 29 | , "funcscope": false 30 | , "globalstrict": false 31 | , "iterator": false 32 | , "lastsemic": true 33 | , "laxbreak": true 34 | , "laxcomma": true 35 | , "loopfunc": true 36 | , "multistr": false 37 | , "onecase": false 38 | , "proto": false 39 | , "regexdash": false 40 | , "scripturl": true 41 | , "smarttabs": false 42 | , "shadow": false 43 | , "sub": true 44 | , "supernew": false 45 | , "validthis": true 46 | , "browser": true 47 | , "couch": false 48 | , "devel": false 49 | , "dojo": false 50 | , "mootools": false 51 | , "node": true 52 | , "nonstandard": true 53 | , "prototypejs": false 54 | , "rhino": false 55 | , "worker": true 56 | , "wsh": false 57 | , "nomen": false 58 | , "onevar": false 59 | , "passfail": false 60 | } 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | notifications: 5 | email: 6 | - dustin@dustindiaz.com 7 | before_script: 8 | - node make/tests.js & 9 | 10 | script: phantomjs ./phantom.js 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: boosh test 2 | 3 | boosh: 4 | @node -e "var json = require('./build');json.JSHINT_OPTS=JSON.parse(require('fs').readFileSync('./.jshintrc'));require('fs').writeFileSync('./build.json', JSON.stringify(json, null, 2))" 5 | @node_modules/smoosh/bin/smoosh make build.json 6 | 7 | test: 8 | npm test 9 | 10 | bump: boosh 11 | npm version patch 12 | node make/bump 13 | 14 | publish: 15 | npm publish 16 | git push origin master --tags 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # It's AJAX 2 | 3 | All over again. Includes support for xmlHttpRequest, JSONP, CORS, and CommonJS Promises A. 4 | 5 | It is also isomorphic allowing you to `require('reqwest')` in `Node.js` through the peer dependency [xhr2](https://github.com/pwnall/node-xhr2), albeit the original intent of this library is for the browser. For a more thorough solution for Node.js, see [mikeal/request](https://github.com/request/request). 6 | 7 | ## API 8 | 9 | ``` js 10 | reqwest('path/to/html', function (resp) { 11 | qwery('#content').html(resp) 12 | }) 13 | 14 | reqwest({ 15 | url: 'path/to/html' 16 | , method: 'post' 17 | , data: { foo: 'bar', baz: 100 } 18 | , success: function (resp) { 19 | qwery('#content').html(resp) 20 | } 21 | }) 22 | 23 | reqwest({ 24 | url: 'path/to/html' 25 | , method: 'get' 26 | , data: [ { name: 'foo', value: 'bar' }, { name: 'baz', value: 100 } ] 27 | , success: function (resp) { 28 | qwery('#content').html(resp) 29 | } 30 | }) 31 | 32 | reqwest({ 33 | url: 'path/to/json' 34 | , type: 'json' 35 | , method: 'post' 36 | , error: function (err) { } 37 | , success: function (resp) { 38 | qwery('#content').html(resp.content) 39 | } 40 | }) 41 | 42 | reqwest({ 43 | url: 'path/to/json' 44 | , type: 'json' 45 | , method: 'post' 46 | , contentType: 'application/json' 47 | , headers: { 48 | 'X-My-Custom-Header': 'SomethingImportant' 49 | } 50 | , error: function (err) { } 51 | , success: function (resp) { 52 | qwery('#content').html(resp.content) 53 | } 54 | }) 55 | 56 | // Uses XMLHttpRequest2 credentialled requests (cookies, HTTP basic auth) if supported 57 | reqwest({ 58 | url: 'path/to/json' 59 | , type: 'json' 60 | , method: 'post' 61 | , contentType: 'application/json' 62 | , crossOrigin: true 63 | , withCredentials: true 64 | , error: function (err) { } 65 | , success: function (resp) { 66 | qwery('#content').html(resp.content) 67 | } 68 | }) 69 | 70 | reqwest({ 71 | url: 'path/to/data.jsonp?callback=?' 72 | , type: 'jsonp' 73 | , success: function (resp) { 74 | qwery('#content').html(resp.content) 75 | } 76 | }) 77 | 78 | reqwest({ 79 | url: 'path/to/data.jsonp?foo=bar' 80 | , type: 'jsonp' 81 | , jsonpCallback: 'foo' 82 | , jsonpCallbackName: 'bar' 83 | , success: function (resp) { 84 | qwery('#content').html(resp.content) 85 | } 86 | }) 87 | 88 | reqwest({ 89 | url: 'path/to/data.jsonp?foo=bar' 90 | , type: 'jsonp' 91 | , jsonpCallback: 'foo' 92 | , success: function (resp) { 93 | qwery('#content').html(resp.content) 94 | } 95 | , complete: function (resp) { 96 | qwery('#hide-this').hide() 97 | } 98 | }) 99 | ``` 100 | 101 | ## Promises 102 | 103 | ``` js 104 | reqwest({ 105 | url: 'path/to/data.jsonp?foo=bar' 106 | , type: 'jsonp' 107 | , jsonpCallback: 'foo' 108 | }) 109 | .then(function (resp) { 110 | qwery('#content').html(resp.content) 111 | }, function (err, msg) { 112 | qwery('#errors').html(msg) 113 | }) 114 | .always(function (resp) { 115 | qwery('#hide-this').hide() 116 | }) 117 | ``` 118 | 119 | ``` js 120 | reqwest({ 121 | url: 'path/to/data.jsonp?foo=bar' 122 | , type: 'jsonp' 123 | , jsonpCallback: 'foo' 124 | }) 125 | .then(function (resp) { 126 | qwery('#content').html(resp.content) 127 | }) 128 | .fail(function (err, msg) { 129 | qwery('#errors').html(msg) 130 | }) 131 | .always(function (resp) { 132 | qwery('#hide-this').hide() 133 | }) 134 | ``` 135 | 136 | ``` js 137 | var r = reqwest({ 138 | url: 'path/to/data.jsonp?foo=bar' 139 | , type: 'jsonp' 140 | , jsonpCallback: 'foo' 141 | , success: function () { 142 | setTimeout(function () { 143 | r 144 | .then(function (resp) { 145 | qwery('#content').html(resp.content) 146 | }, function (err) { }) 147 | .always(function (resp) { 148 | qwery('#hide-this').hide() 149 | }) 150 | }, 15) 151 | } 152 | }) 153 | ``` 154 | 155 | ## Options 156 | 157 | * `url` a fully qualified uri 158 | * `method` http method (default: `GET`) 159 | * `headers` http headers (default: `{}`) 160 | * `data` entity body for `PATCH`, `POST` and `PUT` requests. Must be a query `String` or `JSON` object 161 | * `type` a string enum. `html`, `xml`, `json`, or `jsonp`. Default is inferred by resource extension. Eg: `.json` will set `type` to `json`. `.xml` to `xml` etc. 162 | * `contentType` sets the `Content-Type` of the request. Eg: `application/json` 163 | * `crossOrigin` for cross-origin requests for browsers that support this feature. 164 | * `success` A function called when the request successfully completes 165 | * `error` A function called when the request fails. 166 | * `complete` A function called whether the request is a success or failure. Always called when complete. 167 | * `jsonpCallback` Specify the callback function name for a `JSONP` request. This value will be used instead of the random (but recommended) name automatically generated by reqwest. 168 | 169 | ## Security 170 | 171 | If you are *still* requiring support for IE6/IE7, consider including [JSON3](https://bestiejs.github.io/json3/) in your project. Or simply do the following 172 | 173 | ``` html 174 | 181 | ``` 182 | 183 | 184 | ## Contributing 185 | 186 | ``` sh 187 | $ git clone git://github.com/ded/reqwest.git reqwest 188 | $ cd !$ 189 | $ npm install 190 | ``` 191 | 192 | Please keep your local edits to `src/reqwest.js`. 193 | The base `./reqwest.js` and `./reqwest.min.js` will be built upon releases. 194 | 195 | ## Running Tests 196 | 197 | ``` sh 198 | make test 199 | ``` 200 | 201 | ## Browser support 202 | 203 | * IE6+ 204 | * Chrome 1+ 205 | * Safari 3+ 206 | * Firefox 1+ 207 | * Opera 208 | 209 | ## Ender Support 210 | Reqwest can be used as an [Ender](http://enderjs.com) module. Add it to your existing build as such: 211 | 212 | $ ender add reqwest 213 | 214 | Use it as such: 215 | 216 | ``` js 217 | $.ajax({ ... }) 218 | ``` 219 | 220 | Serialize things: 221 | 222 | ``` js 223 | $(form).serialize() // returns query string -> x=y&... 224 | $(form).serialize({type:'array'}) // returns array name/value pairs -> [ { name: x, value: y}, ... ] 225 | $(form).serialize({type:'map'}) // returns an object representation -> { x: y, ... } 226 | $(form).serializeArray() 227 | $.toQueryString({ 228 | foo: 'bar' 229 | , baz: 'thunk' 230 | }) // returns query string -> foo=bar&baz=thunk 231 | ``` 232 | 233 | Or, get a bit fancy: 234 | 235 | ``` js 236 | $('#myform input[name=myradios]').serialize({type:'map'})['myradios'] // get the selected value 237 | $('input[type=text],#specialthing').serialize() // turn any arbitrary set of form elements into a query string 238 | ``` 239 | 240 | ## ajaxSetup 241 | Use the `request.ajaxSetup` to predefine a data filter on all requests. See the example below that demonstrates JSON hijacking prevention: 242 | 243 | ``` js 244 | $.ajaxSetup({ 245 | dataFilter: function (response, type) { 246 | if (type == 'json') return response.substring('])}while(1);'.length) 247 | else return response 248 | } 249 | }) 250 | ``` 251 | 252 | ## RequireJs and Jam 253 | Reqwest can also be used with RequireJs and can be installed via jam 254 | 255 | ``` 256 | jam install reqwest 257 | ``` 258 | 259 | ```js 260 | define(function(require){ 261 | var reqwest = require('reqwest') 262 | }); 263 | ``` 264 | 265 | ## spm 266 | Reqwest can also be installed via spm [![](http://spmjs.io/badge/reqwest)](http://spmjs.io/package/reqwest) 267 | 268 | ``` 269 | spm install reqwest 270 | ``` 271 | 272 | ## jQuery and Zepto Compatibility 273 | There are some differences between the *Reqwest way* and the 274 | *jQuery/Zepto way*. 275 | 276 | ### method ### 277 | jQuery/Zepto use `type` to specify the request method while Reqwest uses 278 | `method` and reserves `type` for the response data type. 279 | 280 | ### dataType ### 281 | When using jQuery/Zepto you use the `dataType` option to specify the type 282 | of data to expect from the server, Reqwest uses `type`. jQuery also can 283 | also take a space-separated list of data types to specify the request, 284 | response and response-conversion types but Reqwest uses the `type` 285 | parameter to infer the response type and leaves conversion up to you. 286 | 287 | ### JSONP ### 288 | Reqwest also takes optional `jsonpCallback` and `jsonpCallbackName` 289 | options to specify the callback query-string key and the callback function 290 | name respectively while jQuery uses `jsonp` and `jsonpCallback` for 291 | these same options. 292 | 293 | 294 | But fear not! If you must work the jQuery/Zepto way then Reqwest has 295 | a wrapper that will remap these options for you: 296 | 297 | ```js 298 | reqwest.compat({ 299 | url: 'path/to/data.jsonp?foo=bar' 300 | , dataType: 'jsonp' 301 | , jsonp: 'foo' 302 | , jsonpCallback: 'bar' 303 | , success: function (resp) { 304 | qwery('#content').html(resp.content) 305 | } 306 | }) 307 | 308 | // or from Ender: 309 | 310 | $.ajax.compat({ 311 | ... 312 | }) 313 | ``` 314 | 315 | If you want to install jQuery/Zepto compatibility mode as the default 316 | then simply place this snippet at the top of your code: 317 | 318 | ```js 319 | $.ajax.compat && $.ender({ ajax: $.ajax.compat }); 320 | ``` 321 | 322 | 323 | **Happy Ajaxing!** 324 | -------------------------------------------------------------------------------- /build.json: -------------------------------------------------------------------------------- 1 | { 2 | "YO": "This file is built by the Makefile, your edits may not be saved on build", 3 | "JAVASCRIPT": { 4 | "DIST_DIR": "./", 5 | "reqwest": [ 6 | "src/copyright.js", 7 | "src/reqwest.js" 8 | ] 9 | }, 10 | "JSHINT_OPTS": { 11 | "predef": [ 12 | "define", 13 | "ActiveXObject", 14 | "XDomainRequest" 15 | ], 16 | "bitwise": true, 17 | "camelcase": false, 18 | "curly": false, 19 | "eqeqeq": false, 20 | "forin": false, 21 | "immed": false, 22 | "latedef": false, 23 | "newcap": true, 24 | "noarg": true, 25 | "noempty": true, 26 | "nonew": true, 27 | "plusplus": false, 28 | "quotmark": true, 29 | "regexp": false, 30 | "undef": true, 31 | "unused": true, 32 | "strict": false, 33 | "trailing": true, 34 | "maxlen": 120, 35 | "asi": true, 36 | "boss": true, 37 | "debug": true, 38 | "eqnull": true, 39 | "esnext": true, 40 | "evil": true, 41 | "expr": true, 42 | "funcscope": false, 43 | "globalstrict": false, 44 | "iterator": false, 45 | "lastsemic": true, 46 | "laxbreak": true, 47 | "laxcomma": true, 48 | "loopfunc": true, 49 | "multistr": false, 50 | "onecase": false, 51 | "proto": false, 52 | "regexdash": false, 53 | "scripturl": true, 54 | "smarttabs": false, 55 | "shadow": false, 56 | "sub": true, 57 | "supernew": false, 58 | "validthis": true, 59 | "browser": true, 60 | "couch": false, 61 | "devel": false, 62 | "dojo": false, 63 | "mootools": false, 64 | "node": true, 65 | "nonstandard": true, 66 | "prototypejs": false, 67 | "rhino": false, 68 | "worker": true, 69 | "wsh": false, 70 | "nomen": false, 71 | "onevar": false, 72 | "passfail": false 73 | } 74 | } -------------------------------------------------------------------------------- /make/bump.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | , version = require('../package.json').version; 3 | 4 | ['./reqwest.js', './reqwest.min.js'].forEach(function (file) { 5 | var data = fs.readFileSync(file, 'utf8') 6 | data = data.replace(/^\/\*\!/, '/*! version: ' + version) 7 | fs.writeFileSync(file, data) 8 | }) 9 | -------------------------------------------------------------------------------- /make/tests.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec 2 | , fs = require('fs') 3 | , Connect = require('connect') 4 | , dispatch = require('dispatch') 5 | , mime = require('mime') 6 | , DelayedStream = require('delayed-stream') 7 | 8 | , getMime = function(ext) { 9 | return mime.lookup(ext == 'jsonp' ? 'js' : ext) 10 | } 11 | 12 | var routes = { 13 | '/': function (req, res) { 14 | res.write(fs.readFileSync('./tests/tests.html', 'utf8')) 15 | res.end() 16 | }, 17 | '/tests/timeout$': function (req, res) { 18 | var delayed = DelayedStream.create(req) 19 | setTimeout(function() { 20 | res.writeHead(200, { 21 | 'Expires': 0 22 | , 'Cache-Control': 'max-age=0, no-cache, no-store' 23 | }) 24 | req.query.callback && res.write(req.query.callback + '(') 25 | res.write(JSON.stringify({ method: req.method, query: req.query, headers: req.headers })) 26 | req.query.callback && res.write(');') 27 | delayed.pipe(res) 28 | }, 2000) 29 | }, 30 | '/tests/204': function(req, res) { 31 | res.writeHead(204); 32 | res.end(); 33 | }, 34 | '(([\\w\\-\\/\\.]+)\\.(css|js|json|jsonp|html|xml)$)': function (req, res, next, uri, file, ext) { 35 | res.writeHead(200, { 36 | 'Expires': 0 37 | , 'Cache-Control': 'max-age=0, no-cache, no-store' 38 | , 'Content-Type': getMime(ext) 39 | }) 40 | if (req.query.echo !== undefined) { 41 | ext == 'jsonp' && res.write((req.query.callback || req.query.testCallback || 'echoCallback') + '(') 42 | res.write(JSON.stringify({ method: req.method, query: req.query, headers: req.headers })) 43 | ext == 'jsonp' && res.write(');') 44 | } else { 45 | res.write(fs.readFileSync('./' + file + '.' + ext)) 46 | } 47 | res.end() 48 | } 49 | } 50 | 51 | Connect.createServer(Connect.query(), dispatch(routes)).listen(1234) 52 | 53 | var otherOriginRoutes = { 54 | '/get-value': function (req, res) { 55 | res.writeHead(200, { 56 | 'Access-Control-Allow-Origin': req.headers.origin, 57 | 'Content-Type': 'text/plain' 58 | }) 59 | res.end('hello') 60 | }, 61 | '/set-cookie': function (req, res) { 62 | res.writeHead(200, { 63 | 'Access-Control-Allow-Origin': req.headers.origin, 64 | 'Access-Control-Allow-Credentials': 'true', 65 | 'Content-Type': 'text/plain', 66 | 'Set-Cookie': 'cookie=hello' 67 | }) 68 | res.end('Set a cookie!') 69 | }, 70 | '/get-cookie-value': function (req, res) { 71 | var cookies = {} 72 | , value 73 | 74 | req.headers.cookie && req.headers.cookie.split(';').forEach(function( cookie ) { 75 | var parts = cookie.split('=') 76 | cookies[ parts[ 0 ].trim() ] = ( parts[ 1 ] || '' ).trim() 77 | }) 78 | value = cookies.cookie 79 | 80 | res.writeHead(200, { 81 | 'Access-Control-Allow-Origin': req.headers.origin, 82 | 'Access-Control-Allow-Credentials': 'true', 83 | 'Content-Type': 'text/plain' 84 | }) 85 | res.end(value) 86 | } 87 | } 88 | 89 | Connect.createServer(Connect.query(), dispatch(otherOriginRoutes)).listen(5678) 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reqwest", 3 | "description": "A wrapper for asynchronous http requests", 4 | "keywords": [ 5 | "ender", 6 | "ajax", 7 | "xhr", 8 | "connection", 9 | "web 2.0", 10 | "async", 11 | "sync" 12 | ], 13 | "version": "2.0.5", 14 | "homepage": "https://github.com/ded/reqwest", 15 | "author": "Dustin Diaz (http://dustindiaz.com)", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/ded/reqwest.git" 19 | }, 20 | "main": "./reqwest.js", 21 | "ender": "./src/ender.js", 22 | "browser": { 23 | "xhr2": false 24 | }, 25 | "devDependencies": { 26 | "connect": "1.8.x", 27 | "mime": "1.x.x", 28 | "sink-test": ">=0.1.2", 29 | "dispatch": "0.x.x", 30 | "valentine": ">=1.4.7", 31 | "smoosh": "0.4.0", 32 | "delayed-stream": "0.0.5", 33 | "bump": "0.2.3" 34 | }, 35 | "scripts": { 36 | "boosh": "smoosh make ./build.json", 37 | "test": "node ./test.js" 38 | }, 39 | "license": "MIT", 40 | "spm": { 41 | "main": "reqwest.js", 42 | "ignore": [ 43 | "vendor", 44 | "test", 45 | "make" 46 | ] 47 | }, 48 | "files": [ 49 | "reqwest.js", 50 | "reqwest.min.js" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /phantom.js: -------------------------------------------------------------------------------- 1 | var page = require('webpage').create() 2 | page.open('http://localhost:1234', function() { 3 | 4 | function f() { 5 | setTimeout(function () { 6 | var clsName = page.evaluate(function() { 7 | var el = document.getElementById('tests') 8 | return el.className 9 | }) 10 | if (!clsName.match(/sink-done/)) f() 11 | else { 12 | var count = 0 13 | var fail = page.evaluate(function () { 14 | var t = '' 15 | var els = document.querySelectorAll('ol#tests .fail .fail') 16 | for (var i = 0; i < els.length; i++) { 17 | t += els[i].textContent + '\n' 18 | } 19 | return {text: t, count: els.length} 20 | }) 21 | var pass = !!clsName.match(/sink-pass/) 22 | if (pass) console.log('All tests have passed!') 23 | else { 24 | console.log(fail.count + ' test(s) failed') 25 | console.log(fail.text.trim()) 26 | } 27 | 28 | phantom.exit(pass ? 0 : 1) 29 | } 30 | }, 10) 31 | } 32 | f() 33 | }) 34 | -------------------------------------------------------------------------------- /reqwest.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Reqwest! A general purpose XHR connection manager 3 | * license MIT (c) Dustin Diaz 2015 4 | * https://github.com/ded/reqwest 5 | */ 6 | 7 | !function (name, context, definition) { 8 | if (typeof module != 'undefined' && module.exports) module.exports = definition() 9 | else if (typeof define == 'function' && define.amd) define(definition) 10 | else context[name] = definition() 11 | }('reqwest', this, function () { 12 | 13 | var context = this 14 | 15 | if ('window' in context) { 16 | var doc = document 17 | , byTag = 'getElementsByTagName' 18 | , head = doc[byTag]('head')[0] 19 | } else { 20 | var XHR2 21 | try { 22 | XHR2 = require('xhr2') 23 | } catch (ex) { 24 | throw new Error('Peer dependency `xhr2` required! Please npm install xhr2') 25 | } 26 | } 27 | 28 | 29 | var httpsRe = /^http/ 30 | , protocolRe = /(^\w+):\/\// 31 | , twoHundo = /^(20\d|1223)$/ //http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request 32 | , readyState = 'readyState' 33 | , contentType = 'Content-Type' 34 | , requestedWith = 'X-Requested-With' 35 | , uniqid = 0 36 | , callbackPrefix = 'reqwest_' + (+new Date()) 37 | , lastValue // data stored by the most recent JSONP callback 38 | , xmlHttpRequest = 'XMLHttpRequest' 39 | , xDomainRequest = 'XDomainRequest' 40 | , noop = function () {} 41 | 42 | , isArray = typeof Array.isArray == 'function' 43 | ? Array.isArray 44 | : function (a) { 45 | return a instanceof Array 46 | } 47 | 48 | , defaultHeaders = { 49 | 'contentType': 'application/x-www-form-urlencoded' 50 | , 'requestedWith': xmlHttpRequest 51 | , 'accept': { 52 | '*': 'text/javascript, text/html, application/xml, text/xml, */*' 53 | , 'xml': 'application/xml, text/xml' 54 | , 'html': 'text/html' 55 | , 'text': 'text/plain' 56 | , 'json': 'application/json, text/javascript' 57 | , 'js': 'application/javascript, text/javascript' 58 | } 59 | } 60 | 61 | , xhr = function(o) { 62 | // is it x-domain 63 | if (o['crossOrigin'] === true) { 64 | var xhr = context[xmlHttpRequest] ? new XMLHttpRequest() : null 65 | if (xhr && 'withCredentials' in xhr) { 66 | return xhr 67 | } else if (context[xDomainRequest]) { 68 | return new XDomainRequest() 69 | } else { 70 | throw new Error('Browser does not support cross-origin requests') 71 | } 72 | } else if (context[xmlHttpRequest]) { 73 | return new XMLHttpRequest() 74 | } else if (XHR2) { 75 | return new XHR2() 76 | } else { 77 | return new ActiveXObject('Microsoft.XMLHTTP') 78 | } 79 | } 80 | , globalSetupOptions = { 81 | dataFilter: function (data) { 82 | return data 83 | } 84 | } 85 | 86 | function succeed(r) { 87 | var protocol = protocolRe.exec(r.url) 88 | protocol = (protocol && protocol[1]) || context.location.protocol 89 | return httpsRe.test(protocol) ? twoHundo.test(r.request.status) : !!r.request.response 90 | } 91 | 92 | function handleReadyState(r, success, error) { 93 | return function () { 94 | // use _aborted to mitigate against IE err c00c023f 95 | // (can't read props on aborted request objects) 96 | if (r._aborted) return error(r.request) 97 | if (r._timedOut) return error(r.request, 'Request is aborted: timeout') 98 | if (r.request && r.request[readyState] == 4) { 99 | r.request.onreadystatechange = noop 100 | if (succeed(r)) success(r.request) 101 | else 102 | error(r.request) 103 | } 104 | } 105 | } 106 | 107 | function setHeaders(http, o) { 108 | var headers = o['headers'] || {} 109 | , h 110 | 111 | headers['Accept'] = headers['Accept'] 112 | || defaultHeaders['accept'][o['type']] 113 | || defaultHeaders['accept']['*'] 114 | 115 | var isAFormData = typeof FormData !== 'undefined' && (o['data'] instanceof FormData); 116 | // breaks cross-origin requests with legacy browsers 117 | if (!o['crossOrigin'] && !headers[requestedWith]) headers[requestedWith] = defaultHeaders['requestedWith'] 118 | if (!headers[contentType] && !isAFormData) headers[contentType] = o['contentType'] || defaultHeaders['contentType'] 119 | for (h in headers) 120 | headers.hasOwnProperty(h) && 'setRequestHeader' in http && http.setRequestHeader(h, headers[h]) 121 | } 122 | 123 | function setCredentials(http, o) { 124 | if (typeof o['withCredentials'] !== 'undefined' && typeof http.withCredentials !== 'undefined') { 125 | http.withCredentials = !!o['withCredentials'] 126 | } 127 | } 128 | 129 | function generalCallback(data) { 130 | lastValue = data 131 | } 132 | 133 | function urlappend (url, s) { 134 | return url + (/\?/.test(url) ? '&' : '?') + s 135 | } 136 | 137 | function handleJsonp(o, fn, err, url) { 138 | var reqId = uniqid++ 139 | , cbkey = o['jsonpCallback'] || 'callback' // the 'callback' key 140 | , cbval = o['jsonpCallbackName'] || reqwest.getcallbackPrefix(reqId) 141 | , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)') 142 | , match = url.match(cbreg) 143 | , script = doc.createElement('script') 144 | , loaded = 0 145 | , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1 146 | 147 | if (match) { 148 | if (match[3] === '?') { 149 | url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name 150 | } else { 151 | cbval = match[3] // provided callback func name 152 | } 153 | } else { 154 | url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em 155 | } 156 | 157 | context[cbval] = generalCallback 158 | 159 | script.type = 'text/javascript' 160 | script.src = url 161 | script.async = true 162 | if (typeof script.onreadystatechange !== 'undefined' && !isIE10) { 163 | // need this for IE due to out-of-order onreadystatechange(), binding script 164 | // execution to an event listener gives us control over when the script 165 | // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html 166 | script.htmlFor = script.id = '_reqwest_' + reqId 167 | } 168 | 169 | script.onload = script.onreadystatechange = function () { 170 | if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) { 171 | return false 172 | } 173 | script.onload = script.onreadystatechange = null 174 | script.onclick && script.onclick() 175 | // Call the user callback with the last value stored and clean up values and scripts. 176 | fn(lastValue) 177 | lastValue = undefined 178 | head.removeChild(script) 179 | loaded = 1 180 | } 181 | 182 | // Add the script to the DOM head 183 | head.appendChild(script) 184 | 185 | // Enable JSONP timeout 186 | return { 187 | abort: function () { 188 | script.onload = script.onreadystatechange = null 189 | err({}, 'Request is aborted: timeout', {}) 190 | lastValue = undefined 191 | head.removeChild(script) 192 | loaded = 1 193 | } 194 | } 195 | } 196 | 197 | function getRequest(fn, err) { 198 | var o = this.o 199 | , method = (o['method'] || 'GET').toUpperCase() 200 | , url = typeof o === 'string' ? o : o['url'] 201 | // convert non-string objects to query-string form unless o['processData'] is false 202 | , data = (o['processData'] !== false && o['data'] && typeof o['data'] !== 'string') 203 | ? reqwest.toQueryString(o['data']) 204 | : (o['data'] || null) 205 | , http 206 | , sendWait = false 207 | 208 | // if we're working on a GET request and we have data then we should append 209 | // query string to end of URL and not post data 210 | if ((o['type'] == 'jsonp' || method == 'GET') && data) { 211 | url = urlappend(url, data) 212 | data = null 213 | } 214 | 215 | if (o['type'] == 'jsonp') return handleJsonp(o, fn, err, url) 216 | 217 | // get the xhr from the factory if passed 218 | // if the factory returns null, fall-back to ours 219 | http = (o.xhr && o.xhr(o)) || xhr(o) 220 | 221 | http.open(method, url, o['async'] === false ? false : true) 222 | setHeaders(http, o) 223 | setCredentials(http, o) 224 | if (context[xDomainRequest] && http instanceof context[xDomainRequest]) { 225 | http.onload = fn 226 | http.onerror = err 227 | // NOTE: see 228 | // http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/30ef3add-767c-4436-b8a9-f1ca19b4812e 229 | http.onprogress = function() {} 230 | sendWait = true 231 | } else { 232 | http.onreadystatechange = handleReadyState(this, fn, err) 233 | } 234 | o['before'] && o['before'](http) 235 | if (sendWait) { 236 | setTimeout(function () { 237 | http.send(data) 238 | }, 200) 239 | } else { 240 | http.send(data) 241 | } 242 | return http 243 | } 244 | 245 | function Reqwest(o, fn) { 246 | this.o = o 247 | this.fn = fn 248 | 249 | init.apply(this, arguments) 250 | } 251 | 252 | function setType(header) { 253 | // json, javascript, text/plain, text/html, xml 254 | if (header === null) return undefined; //In case of no content-type. 255 | if (header.match('json')) return 'json' 256 | if (header.match('javascript')) return 'js' 257 | if (header.match('text')) return 'html' 258 | if (header.match('xml')) return 'xml' 259 | } 260 | 261 | function init(o, fn) { 262 | 263 | this.url = typeof o == 'string' ? o : o['url'] 264 | this.timeout = null 265 | 266 | // whether request has been fulfilled for purpose 267 | // of tracking the Promises 268 | this._fulfilled = false 269 | // success handlers 270 | this._successHandler = function(){} 271 | this._fulfillmentHandlers = [] 272 | // error handlers 273 | this._errorHandlers = [] 274 | // complete (both success and fail) handlers 275 | this._completeHandlers = [] 276 | this._erred = false 277 | this._responseArgs = {} 278 | 279 | var self = this 280 | 281 | fn = fn || function () {} 282 | 283 | if (o['timeout']) { 284 | this.timeout = setTimeout(function () { 285 | timedOut() 286 | }, o['timeout']) 287 | } 288 | 289 | if (o['success']) { 290 | this._successHandler = function () { 291 | o['success'].apply(o, arguments) 292 | } 293 | } 294 | 295 | if (o['error']) { 296 | this._errorHandlers.push(function () { 297 | o['error'].apply(o, arguments) 298 | }) 299 | } 300 | 301 | if (o['complete']) { 302 | this._completeHandlers.push(function () { 303 | o['complete'].apply(o, arguments) 304 | }) 305 | } 306 | 307 | function complete (resp) { 308 | o['timeout'] && clearTimeout(self.timeout) 309 | self.timeout = null 310 | while (self._completeHandlers.length > 0) { 311 | self._completeHandlers.shift()(resp) 312 | } 313 | } 314 | 315 | function success (resp) { 316 | var type = o['type'] || resp && setType(resp.getResponseHeader('Content-Type')) // resp can be undefined in IE 317 | resp = (type !== 'jsonp') ? self.request : resp 318 | // use global data filter on response text 319 | var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type) 320 | , r = filteredResponse 321 | try { 322 | resp.responseText = r 323 | } catch (e) { 324 | // can't assign this in IE<=8, just ignore 325 | } 326 | if (r) { 327 | switch (type) { 328 | case 'json': 329 | try { 330 | resp = context.JSON ? context.JSON.parse(r) : eval('(' + r + ')') 331 | } catch (err) { 332 | return error(resp, 'Could not parse JSON in response', err) 333 | } 334 | break 335 | case 'js': 336 | resp = eval(r) 337 | break 338 | case 'html': 339 | resp = r 340 | break 341 | case 'xml': 342 | resp = resp.responseXML 343 | && resp.responseXML.parseError // IE trololo 344 | && resp.responseXML.parseError.errorCode 345 | && resp.responseXML.parseError.reason 346 | ? null 347 | : resp.responseXML 348 | break 349 | } 350 | } 351 | 352 | self._responseArgs.resp = resp 353 | self._fulfilled = true 354 | fn(resp) 355 | self._successHandler(resp) 356 | while (self._fulfillmentHandlers.length > 0) { 357 | resp = self._fulfillmentHandlers.shift()(resp) 358 | } 359 | 360 | complete(resp) 361 | } 362 | 363 | function timedOut() { 364 | self._timedOut = true 365 | self.request.abort() 366 | } 367 | 368 | function error(resp, msg, t) { 369 | resp = self.request 370 | self._responseArgs.resp = resp 371 | self._responseArgs.msg = msg 372 | self._responseArgs.t = t 373 | self._erred = true 374 | while (self._errorHandlers.length > 0) { 375 | self._errorHandlers.shift()(resp, msg, t) 376 | } 377 | complete(resp) 378 | } 379 | 380 | this.request = getRequest.call(this, success, error) 381 | } 382 | 383 | Reqwest.prototype = { 384 | abort: function () { 385 | this._aborted = true 386 | this.request.abort() 387 | } 388 | 389 | , retry: function () { 390 | init.call(this, this.o, this.fn) 391 | } 392 | 393 | /** 394 | * Small deviation from the Promises A CommonJs specification 395 | * http://wiki.commonjs.org/wiki/Promises/A 396 | */ 397 | 398 | /** 399 | * `then` will execute upon successful requests 400 | */ 401 | , then: function (success, fail) { 402 | success = success || function () {} 403 | fail = fail || function () {} 404 | if (this._fulfilled) { 405 | this._responseArgs.resp = success(this._responseArgs.resp) 406 | } else if (this._erred) { 407 | fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) 408 | } else { 409 | this._fulfillmentHandlers.push(success) 410 | this._errorHandlers.push(fail) 411 | } 412 | return this 413 | } 414 | 415 | /** 416 | * `always` will execute whether the request succeeds or fails 417 | */ 418 | , always: function (fn) { 419 | if (this._fulfilled || this._erred) { 420 | fn(this._responseArgs.resp) 421 | } else { 422 | this._completeHandlers.push(fn) 423 | } 424 | return this 425 | } 426 | 427 | /** 428 | * `fail` will execute when the request fails 429 | */ 430 | , fail: function (fn) { 431 | if (this._erred) { 432 | fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) 433 | } else { 434 | this._errorHandlers.push(fn) 435 | } 436 | return this 437 | } 438 | , 'catch': function (fn) { 439 | return this.fail(fn) 440 | } 441 | } 442 | 443 | function reqwest(o, fn) { 444 | return new Reqwest(o, fn) 445 | } 446 | 447 | // normalize newline variants according to spec -> CRLF 448 | function normalize(s) { 449 | return s ? s.replace(/\r?\n/g, '\r\n') : '' 450 | } 451 | 452 | function serial(el, cb) { 453 | var n = el.name 454 | , t = el.tagName.toLowerCase() 455 | , optCb = function (o) { 456 | // IE gives value="" even where there is no value attribute 457 | // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273 458 | if (o && !o['disabled']) 459 | cb(n, normalize(o['attributes']['value'] && o['attributes']['value']['specified'] ? o['value'] : o['text'])) 460 | } 461 | , ch, ra, val, i 462 | 463 | // don't serialize elements that are disabled or without a name 464 | if (el.disabled || !n) return 465 | 466 | switch (t) { 467 | case 'input': 468 | if (!/reset|button|image|file/i.test(el.type)) { 469 | ch = /checkbox/i.test(el.type) 470 | ra = /radio/i.test(el.type) 471 | val = el.value 472 | // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here 473 | ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val)) 474 | } 475 | break 476 | case 'textarea': 477 | cb(n, normalize(el.value)) 478 | break 479 | case 'select': 480 | if (el.type.toLowerCase() === 'select-one') { 481 | optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null) 482 | } else { 483 | for (i = 0; el.length && i < el.length; i++) { 484 | el.options[i].selected && optCb(el.options[i]) 485 | } 486 | } 487 | break 488 | } 489 | } 490 | 491 | // collect up all form elements found from the passed argument elements all 492 | // the way down to child elements; pass a '
' or form fields. 493 | // called with 'this'=callback to use for serial() on each element 494 | function eachFormElement() { 495 | var cb = this 496 | , e, i 497 | , serializeSubtags = function (e, tags) { 498 | var i, j, fa 499 | for (i = 0; i < tags.length; i++) { 500 | fa = e[byTag](tags[i]) 501 | for (j = 0; j < fa.length; j++) serial(fa[j], cb) 502 | } 503 | } 504 | 505 | for (i = 0; i < arguments.length; i++) { 506 | e = arguments[i] 507 | if (/input|select|textarea/i.test(e.tagName)) serial(e, cb) 508 | serializeSubtags(e, [ 'input', 'select', 'textarea' ]) 509 | } 510 | } 511 | 512 | // standard query string style serialization 513 | function serializeQueryString() { 514 | return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments)) 515 | } 516 | 517 | // { 'name': 'value', ... } style serialization 518 | function serializeHash() { 519 | var hash = {} 520 | eachFormElement.apply(function (name, value) { 521 | if (name in hash) { 522 | hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]]) 523 | hash[name].push(value) 524 | } else hash[name] = value 525 | }, arguments) 526 | return hash 527 | } 528 | 529 | // [ { name: 'name', value: 'value' }, ... ] style serialization 530 | reqwest.serializeArray = function () { 531 | var arr = [] 532 | eachFormElement.apply(function (name, value) { 533 | arr.push({name: name, value: value}) 534 | }, arguments) 535 | return arr 536 | } 537 | 538 | reqwest.serialize = function () { 539 | if (arguments.length === 0) return '' 540 | var opt, fn 541 | , args = Array.prototype.slice.call(arguments, 0) 542 | 543 | opt = args.pop() 544 | opt && opt.nodeType && args.push(opt) && (opt = null) 545 | opt && (opt = opt.type) 546 | 547 | if (opt == 'map') fn = serializeHash 548 | else if (opt == 'array') fn = reqwest.serializeArray 549 | else fn = serializeQueryString 550 | 551 | return fn.apply(null, args) 552 | } 553 | 554 | reqwest.toQueryString = function (o, trad) { 555 | var prefix, i 556 | , traditional = trad || false 557 | , s = [] 558 | , enc = encodeURIComponent 559 | , add = function (key, value) { 560 | // If value is a function, invoke it and return its value 561 | value = ('function' === typeof value) ? value() : (value == null ? '' : value) 562 | s[s.length] = enc(key) + '=' + enc(value) 563 | } 564 | // If an array was passed in, assume that it is an array of form elements. 565 | if (isArray(o)) { 566 | for (i = 0; o && i < o.length; i++) add(o[i]['name'], o[i]['value']) 567 | } else { 568 | // If traditional, encode the "old" way (the way 1.3.2 or older 569 | // did it), otherwise encode params recursively. 570 | for (prefix in o) { 571 | if (o.hasOwnProperty(prefix)) buildParams(prefix, o[prefix], traditional, add) 572 | } 573 | } 574 | 575 | // spaces should be + according to spec 576 | return s.join('&').replace(/%20/g, '+') 577 | } 578 | 579 | function buildParams(prefix, obj, traditional, add) { 580 | var name, i, v 581 | , rbracket = /\[\]$/ 582 | 583 | if (isArray(obj)) { 584 | // Serialize array item. 585 | for (i = 0; obj && i < obj.length; i++) { 586 | v = obj[i] 587 | if (traditional || rbracket.test(prefix)) { 588 | // Treat each array item as a scalar. 589 | add(prefix, v) 590 | } else { 591 | buildParams(prefix + '[' + (typeof v === 'object' ? i : '') + ']', v, traditional, add) 592 | } 593 | } 594 | } else if (obj && obj.toString() === '[object Object]') { 595 | // Serialize object item. 596 | for (name in obj) { 597 | buildParams(prefix + '[' + name + ']', obj[name], traditional, add) 598 | } 599 | 600 | } else { 601 | // Serialize scalar item. 602 | add(prefix, obj) 603 | } 604 | } 605 | 606 | reqwest.getcallbackPrefix = function () { 607 | return callbackPrefix 608 | } 609 | 610 | // jQuery and Zepto compatibility, differences can be remapped here so you can call 611 | // .ajax.compat(options, callback) 612 | reqwest.compat = function (o, fn) { 613 | if (o) { 614 | o['type'] && (o['method'] = o['type']) && delete o['type'] 615 | o['dataType'] && (o['type'] = o['dataType']) 616 | o['jsonpCallback'] && (o['jsonpCallbackName'] = o['jsonpCallback']) && delete o['jsonpCallback'] 617 | o['jsonp'] && (o['jsonpCallback'] = o['jsonp']) 618 | } 619 | return new Reqwest(o, fn) 620 | } 621 | 622 | reqwest.ajaxSetup = function (options) { 623 | options = options || {} 624 | for (var k in options) { 625 | globalSetupOptions[k] = options[k] 626 | } 627 | } 628 | 629 | return reqwest 630 | }); 631 | -------------------------------------------------------------------------------- /reqwest.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Reqwest! A general purpose XHR connection manager 3 | * license MIT (c) Dustin Diaz 2015 4 | * https://github.com/ded/reqwest 5 | */ 6 | !function(e,t,n){typeof module!="undefined"&&module.exports?module.exports=n():typeof define=="function"&&define.amd?define(n):t[e]=n()}("reqwest",this,function(){function succeed(e){var t=protocolRe.exec(e.url);return t=t&&t[1]||context.location.protocol,httpsRe.test(t)?twoHundo.test(e.request.status):!!e.request.response}function handleReadyState(e,t,n){return function(){if(e._aborted)return n(e.request);if(e._timedOut)return n(e.request,"Request is aborted: timeout");e.request&&e.request[readyState]==4&&(e.request.onreadystatechange=noop,succeed(e)?t(e.request):n(e.request))}}function setHeaders(e,t){var n=t.headers||{},r;n.Accept=n.Accept||defaultHeaders.accept[t.type]||defaultHeaders.accept["*"];var i=typeof FormData!="undefined"&&t.data instanceof FormData;!t.crossOrigin&&!n[requestedWith]&&(n[requestedWith]=defaultHeaders.requestedWith),!n[contentType]&&!i&&(n[contentType]=t.contentType||defaultHeaders.contentType);for(r in n)n.hasOwnProperty(r)&&"setRequestHeader"in e&&e.setRequestHeader(r,n[r])}function setCredentials(e,t){typeof t.withCredentials!="undefined"&&typeof e.withCredentials!="undefined"&&(e.withCredentials=!!t.withCredentials)}function generalCallback(e){lastValue=e}function urlappend(e,t){return e+(/\?/.test(e)?"&":"?")+t}function handleJsonp(e,t,n,r){var i=uniqid++,s=e.jsonpCallback||"callback",o=e.jsonpCallbackName||reqwest.getcallbackPrefix(i),u=new RegExp("((^|\\?|&)"+s+")=([^&]+)"),a=r.match(u),f=doc.createElement("script"),l=0,c=navigator.userAgent.indexOf("MSIE 10.0")!==-1;return a?a[3]==="?"?r=r.replace(u,"$1="+o):o=a[3]:r=urlappend(r,s+"="+o),context[o]=generalCallback,f.type="text/javascript",f.src=r,f.async=!0,typeof f.onreadystatechange!="undefined"&&!c&&(f.htmlFor=f.id="_reqwest_"+i),f.onload=f.onreadystatechange=function(){if(f[readyState]&&f[readyState]!=="complete"&&f[readyState]!=="loaded"||l)return!1;f.onload=f.onreadystatechange=null,f.onclick&&f.onclick(),t(lastValue),lastValue=undefined,head.removeChild(f),l=1},head.appendChild(f),{abort:function(){f.onload=f.onreadystatechange=null,n({},"Request is aborted: timeout",{}),lastValue=undefined,head.removeChild(f),l=1}}}function getRequest(e,t){var n=this.o,r=(n.method||"GET").toUpperCase(),i=typeof n=="string"?n:n.url,s=n.processData!==!1&&n.data&&typeof n.data!="string"?reqwest.toQueryString(n.data):n.data||null,o,u=!1;return(n["type"]=="jsonp"||r=="GET")&&s&&(i=urlappend(i,s),s=null),n["type"]=="jsonp"?handleJsonp(n,e,t,i):(o=n.xhr&&n.xhr(n)||xhr(n),o.open(r,i,n.async===!1?!1:!0),setHeaders(o,n),setCredentials(o,n),context[xDomainRequest]&&o instanceof context[xDomainRequest]?(o.onload=e,o.onerror=t,o.onprogress=function(){},u=!0):o.onreadystatechange=handleReadyState(this,e,t),n.before&&n.before(o),u?setTimeout(function(){o.send(s)},200):o.send(s),o)}function Reqwest(e,t){this.o=e,this.fn=t,init.apply(this,arguments)}function setType(e){if(e===null)return undefined;if(e.match("json"))return"json";if(e.match("javascript"))return"js";if(e.match("text"))return"html";if(e.match("xml"))return"xml"}function init(o,fn){function complete(e){o.timeout&&clearTimeout(self.timeout),self.timeout=null;while(self._completeHandlers.length>0)self._completeHandlers.shift()(e)}function success(resp){var type=o.type||resp&&setType(resp.getResponseHeader("Content-Type"));resp=type!=="jsonp"?self.request:resp;var filteredResponse=globalSetupOptions.dataFilter(resp.responseText,type),r=filteredResponse;try{resp.responseText=r}catch(e){}if(r)switch(type){case"json":try{resp=context.JSON?context.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&&resp.responseXML.parseError.errorCode&&resp.responseXML.parseError.reason?null:resp.responseXML}self._responseArgs.resp=resp,self._fulfilled=!0,fn(resp),self._successHandler(resp);while(self._fulfillmentHandlers.length>0)resp=self._fulfillmentHandlers.shift()(resp);complete(resp)}function timedOut(){self._timedOut=!0,self.request.abort()}function error(e,t,n){e=self.request,self._responseArgs.resp=e,self._responseArgs.msg=t,self._responseArgs.t=n,self._erred=!0;while(self._errorHandlers.length>0)self._errorHandlers.shift()(e,t,n);complete(e)}this.url=typeof o=="string"?o:o.url,this.timeout=null,this._fulfilled=!1,this._successHandler=function(){},this._fulfillmentHandlers=[],this._errorHandlers=[],this._completeHandlers=[],this._erred=!1,this._responseArgs={};var self=this;fn=fn||function(){},o.timeout&&(this.timeout=setTimeout(function(){timedOut()},o.timeout)),o.success&&(this._successHandler=function(){o.success.apply(o,arguments)}),o.error&&this._errorHandlers.push(function(){o.error.apply(o,arguments)}),o.complete&&this._completeHandlers.push(function(){o.complete.apply(o,arguments)}),this.request=getRequest.call(this,success,error)}function reqwest(e,t){return new Reqwest(e,t)}function normalize(e){return e?e.replace(/\r?\n/g,"\r\n"):""}function serial(e,t){var n=e.name,r=e.tagName.toLowerCase(),i=function(e){e&&!e.disabled&&t(n,normalize(e.attributes.value&&e.attributes.value.specified?e.value:e.text))},s,o,u,a;if(e.disabled||!n)return;switch(r){case"input":/reset|button|image|file/i.test(e.type)||(s=/checkbox/i.test(e.type),o=/radio/i.test(e.type),u=e.value,(!s&&!o||e.checked)&&t(n,normalize(s&&u===""?"on":u)));break;case"textarea":t(n,normalize(e.value));break;case"select":if(e.type.toLowerCase()==="select-one")i(e.selectedIndex>=0?e.options[e.selectedIndex]:null);else for(a=0;e.length&&a 0) { 309 | self._completeHandlers.shift()(resp) 310 | } 311 | } 312 | 313 | function success (resp) { 314 | var type = o['type'] || resp && setType(resp.getResponseHeader('Content-Type')) // resp can be undefined in IE 315 | resp = (type !== 'jsonp') ? self.request : resp 316 | // use global data filter on response text 317 | var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type) 318 | , r = filteredResponse 319 | try { 320 | resp.responseText = r 321 | } catch (e) { 322 | // can't assign this in IE<=8, just ignore 323 | } 324 | if (r) { 325 | switch (type) { 326 | case 'json': 327 | try { 328 | resp = context.JSON ? context.JSON.parse(r) : eval('(' + r + ')') 329 | } catch (err) { 330 | return error(resp, 'Could not parse JSON in response', err) 331 | } 332 | break 333 | case 'js': 334 | resp = eval(r) 335 | break 336 | case 'html': 337 | resp = r 338 | break 339 | case 'xml': 340 | resp = resp.responseXML 341 | && resp.responseXML.parseError // IE trololo 342 | && resp.responseXML.parseError.errorCode 343 | && resp.responseXML.parseError.reason 344 | ? null 345 | : resp.responseXML 346 | break 347 | } 348 | } 349 | 350 | self._responseArgs.resp = resp 351 | self._fulfilled = true 352 | fn(resp) 353 | self._successHandler(resp) 354 | while (self._fulfillmentHandlers.length > 0) { 355 | resp = self._fulfillmentHandlers.shift()(resp) 356 | } 357 | 358 | complete(resp) 359 | } 360 | 361 | function timedOut() { 362 | self._timedOut = true 363 | self.request.abort() 364 | } 365 | 366 | function error(resp, msg, t) { 367 | resp = self.request 368 | self._responseArgs.resp = resp 369 | self._responseArgs.msg = msg 370 | self._responseArgs.t = t 371 | self._erred = true 372 | while (self._errorHandlers.length > 0) { 373 | self._errorHandlers.shift()(resp, msg, t) 374 | } 375 | complete(resp) 376 | } 377 | 378 | this.request = getRequest.call(this, success, error) 379 | } 380 | 381 | Reqwest.prototype = { 382 | abort: function () { 383 | this._aborted = true 384 | this.request.abort() 385 | } 386 | 387 | , retry: function () { 388 | init.call(this, this.o, this.fn) 389 | } 390 | 391 | /** 392 | * Small deviation from the Promises A CommonJs specification 393 | * http://wiki.commonjs.org/wiki/Promises/A 394 | */ 395 | 396 | /** 397 | * `then` will execute upon successful requests 398 | */ 399 | , then: function (success, fail) { 400 | success = success || function () {} 401 | fail = fail || function () {} 402 | if (this._fulfilled) { 403 | this._responseArgs.resp = success(this._responseArgs.resp) 404 | } else if (this._erred) { 405 | fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) 406 | } else { 407 | this._fulfillmentHandlers.push(success) 408 | this._errorHandlers.push(fail) 409 | } 410 | return this 411 | } 412 | 413 | /** 414 | * `always` will execute whether the request succeeds or fails 415 | */ 416 | , always: function (fn) { 417 | if (this._fulfilled || this._erred) { 418 | fn(this._responseArgs.resp) 419 | } else { 420 | this._completeHandlers.push(fn) 421 | } 422 | return this 423 | } 424 | 425 | /** 426 | * `fail` will execute when the request fails 427 | */ 428 | , fail: function (fn) { 429 | if (this._erred) { 430 | fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) 431 | } else { 432 | this._errorHandlers.push(fn) 433 | } 434 | return this 435 | } 436 | , 'catch': function (fn) { 437 | return this.fail(fn) 438 | } 439 | } 440 | 441 | function reqwest(o, fn) { 442 | return new Reqwest(o, fn) 443 | } 444 | 445 | // normalize newline variants according to spec -> CRLF 446 | function normalize(s) { 447 | return s ? s.replace(/\r?\n/g, '\r\n') : '' 448 | } 449 | 450 | function serial(el, cb) { 451 | var n = el.name 452 | , t = el.tagName.toLowerCase() 453 | , optCb = function (o) { 454 | // IE gives value="" even where there is no value attribute 455 | // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273 456 | if (o && !o['disabled']) 457 | cb(n, normalize(o['attributes']['value'] && o['attributes']['value']['specified'] ? o['value'] : o['text'])) 458 | } 459 | , ch, ra, val, i 460 | 461 | // don't serialize elements that are disabled or without a name 462 | if (el.disabled || !n) return 463 | 464 | switch (t) { 465 | case 'input': 466 | if (!/reset|button|image|file/i.test(el.type)) { 467 | ch = /checkbox/i.test(el.type) 468 | ra = /radio/i.test(el.type) 469 | val = el.value 470 | // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here 471 | ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val)) 472 | } 473 | break 474 | case 'textarea': 475 | cb(n, normalize(el.value)) 476 | break 477 | case 'select': 478 | if (el.type.toLowerCase() === 'select-one') { 479 | optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null) 480 | } else { 481 | for (i = 0; el.length && i < el.length; i++) { 482 | el.options[i].selected && optCb(el.options[i]) 483 | } 484 | } 485 | break 486 | } 487 | } 488 | 489 | // collect up all form elements found from the passed argument elements all 490 | // the way down to child elements; pass a '' or form fields. 491 | // called with 'this'=callback to use for serial() on each element 492 | function eachFormElement() { 493 | var cb = this 494 | , e, i 495 | , serializeSubtags = function (e, tags) { 496 | var i, j, fa 497 | for (i = 0; i < tags.length; i++) { 498 | fa = e[byTag](tags[i]) 499 | for (j = 0; j < fa.length; j++) serial(fa[j], cb) 500 | } 501 | } 502 | 503 | for (i = 0; i < arguments.length; i++) { 504 | e = arguments[i] 505 | if (/input|select|textarea/i.test(e.tagName)) serial(e, cb) 506 | serializeSubtags(e, [ 'input', 'select', 'textarea' ]) 507 | } 508 | } 509 | 510 | // standard query string style serialization 511 | function serializeQueryString() { 512 | return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments)) 513 | } 514 | 515 | // { 'name': 'value', ... } style serialization 516 | function serializeHash() { 517 | var hash = {} 518 | eachFormElement.apply(function (name, value) { 519 | if (name in hash) { 520 | hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]]) 521 | hash[name].push(value) 522 | } else hash[name] = value 523 | }, arguments) 524 | return hash 525 | } 526 | 527 | // [ { name: 'name', value: 'value' }, ... ] style serialization 528 | reqwest.serializeArray = function () { 529 | var arr = [] 530 | eachFormElement.apply(function (name, value) { 531 | arr.push({name: name, value: value}) 532 | }, arguments) 533 | return arr 534 | } 535 | 536 | reqwest.serialize = function () { 537 | if (arguments.length === 0) return '' 538 | var opt, fn 539 | , args = Array.prototype.slice.call(arguments, 0) 540 | 541 | opt = args.pop() 542 | opt && opt.nodeType && args.push(opt) && (opt = null) 543 | opt && (opt = opt.type) 544 | 545 | if (opt == 'map') fn = serializeHash 546 | else if (opt == 'array') fn = reqwest.serializeArray 547 | else fn = serializeQueryString 548 | 549 | return fn.apply(null, args) 550 | } 551 | 552 | reqwest.toQueryString = function (o, trad) { 553 | var prefix, i 554 | , traditional = trad || false 555 | , s = [] 556 | , enc = encodeURIComponent 557 | , add = function (key, value) { 558 | // If value is a function, invoke it and return its value 559 | value = ('function' === typeof value) ? value() : (value == null ? '' : value) 560 | s[s.length] = enc(key) + '=' + enc(value) 561 | } 562 | // If an array was passed in, assume that it is an array of form elements. 563 | if (isArray(o)) { 564 | for (i = 0; o && i < o.length; i++) add(o[i]['name'], o[i]['value']) 565 | } else { 566 | // If traditional, encode the "old" way (the way 1.3.2 or older 567 | // did it), otherwise encode params recursively. 568 | for (prefix in o) { 569 | if (o.hasOwnProperty(prefix)) buildParams(prefix, o[prefix], traditional, add) 570 | } 571 | } 572 | 573 | // spaces should be + according to spec 574 | return s.join('&').replace(/%20/g, '+') 575 | } 576 | 577 | function buildParams(prefix, obj, traditional, add) { 578 | var name, i, v 579 | , rbracket = /\[\]$/ 580 | 581 | if (isArray(obj)) { 582 | // Serialize array item. 583 | for (i = 0; obj && i < obj.length; i++) { 584 | v = obj[i] 585 | if (traditional || rbracket.test(prefix)) { 586 | // Treat each array item as a scalar. 587 | add(prefix, v) 588 | } else { 589 | buildParams(prefix + '[' + (typeof v === 'object' ? i : '') + ']', v, traditional, add) 590 | } 591 | } 592 | } else if (obj && obj.toString() === '[object Object]') { 593 | // Serialize object item. 594 | for (name in obj) { 595 | buildParams(prefix + '[' + name + ']', obj[name], traditional, add) 596 | } 597 | 598 | } else { 599 | // Serialize scalar item. 600 | add(prefix, obj) 601 | } 602 | } 603 | 604 | reqwest.getcallbackPrefix = function () { 605 | return callbackPrefix 606 | } 607 | 608 | // jQuery and Zepto compatibility, differences can be remapped here so you can call 609 | // .ajax.compat(options, callback) 610 | reqwest.compat = function (o, fn) { 611 | if (o) { 612 | o['type'] && (o['method'] = o['type']) && delete o['type'] 613 | o['dataType'] && (o['type'] = o['dataType']) 614 | o['jsonpCallback'] && (o['jsonpCallbackName'] = o['jsonpCallback']) && delete o['jsonpCallback'] 615 | o['jsonp'] && (o['jsonpCallback'] = o['jsonp']) 616 | } 617 | return new Reqwest(o, fn) 618 | } 619 | 620 | reqwest.ajaxSetup = function (options) { 621 | options = options || {} 622 | for (var k in options) { 623 | globalSetupOptions[k] = options[k] 624 | } 625 | } 626 | 627 | return reqwest 628 | }); 629 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var spawn = require('child_process').spawn 2 | , server = spawn('node', ['make/tests.js']) 3 | , phantom = spawn('./vendor/phantomjs', ['./phantom.js']) 4 | 5 | 6 | phantom.stdout.on('data', function (data) { 7 | console.log('stdout: ' + data); 8 | }) 9 | 10 | phantom.on('exit', function (code, signal) { 11 | var outcome = code == 0 ? 'passed' : 'failed' 12 | console.log('Reqwest tests have %s', outcome, code) 13 | server.kill('SIGHUP') 14 | process.exit(code) 15 | }) 16 | -------------------------------------------------------------------------------- /tests/ender.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Ender: open module JavaScript framework (client-lib) 3 | * copyright Dustin Diaz & Jacob Thornton 2011-2012 (@ded @fat) 4 | * http://ender.no.de 5 | * License MIT 6 | */ 7 | (function (context) { 8 | 9 | // a global object for node.js module compatiblity 10 | // ============================================ 11 | 12 | context['global'] = context 13 | 14 | // Implements simple module system 15 | // losely based on CommonJS Modules spec v1.1.1 16 | // ============================================ 17 | 18 | var modules = {} 19 | , old = context['$'] 20 | , oldRequire = context['require'] 21 | , oldProvide = context['provide'] 22 | 23 | function require (identifier) { 24 | // modules can be required from ender's build system, or found on the window 25 | var module = modules['$' + identifier] || window[identifier] 26 | if (!module) throw new Error("Ender Error: Requested module '" + identifier + "' has not been defined.") 27 | return module 28 | } 29 | 30 | function provide (name, what) { 31 | return (modules['$' + name] = what) 32 | } 33 | 34 | context['provide'] = provide 35 | context['require'] = require 36 | 37 | function aug(o, o2) { 38 | for (var k in o2) k != 'noConflict' && k != '_VERSION' && (o[k] = o2[k]) 39 | return o 40 | } 41 | 42 | /** 43 | * main Ender return object 44 | * @constructor 45 | * @param {Array|Node|string} s a CSS selector or DOM node(s) 46 | * @param {Array.|Node} r a root node(s) 47 | */ 48 | function Ender(s, r) { 49 | var elements 50 | , i 51 | 52 | this.selector = s 53 | // string || node || nodelist || window 54 | if (typeof s == 'undefined') { 55 | elements = [] 56 | this.selector = '' 57 | } else if (typeof s == 'string' || s.nodeName || (s.length && 'item' in s) || s == window) { 58 | elements = ender._select(s, r) 59 | } else { 60 | elements = isFinite(s.length) ? s : [s] 61 | } 62 | this.length = elements.length 63 | for (i = this.length; i--;) this[i] = elements[i] 64 | } 65 | 66 | /** 67 | * @param {function(el, i, inst)} fn 68 | * @param {Object} opt_scope 69 | * @returns {Ender} 70 | */ 71 | Ender.prototype['forEach'] = function (fn, opt_scope) { 72 | var i, l 73 | // opt out of native forEach so we can intentionally call our own scope 74 | // defaulting to the current item and be able to return self 75 | for (i = 0, l = this.length; i < l; ++i) i in this && fn.call(opt_scope || this[i], this[i], i, this) 76 | // return self for chaining 77 | return this 78 | } 79 | 80 | Ender.prototype.$ = ender // handy reference to self 81 | 82 | 83 | function ender(s, r) { 84 | return new Ender(s, r) 85 | } 86 | 87 | ender['_VERSION'] = '0.4.3-dev' 88 | 89 | ender.fn = Ender.prototype // for easy compat to jQuery plugins 90 | 91 | ender.ender = function (o, chain) { 92 | aug(chain ? Ender.prototype : ender, o) 93 | } 94 | 95 | ender._select = function (s, r) { 96 | if (typeof s == 'string') return (r || document).querySelectorAll(s) 97 | if (s.nodeName) return [s] 98 | return s 99 | } 100 | 101 | 102 | // use callback to receive Ender's require & provide 103 | ender.noConflict = function (callback) { 104 | context['$'] = old 105 | if (callback) { 106 | context['provide'] = oldProvide 107 | context['require'] = oldRequire 108 | callback(require, provide, this) 109 | } 110 | return this 111 | } 112 | 113 | if (typeof module !== 'undefined' && module.exports) module.exports = ender 114 | // use subscript notation as extern for Closure compilation 115 | context['ender'] = context['$'] = context['ender'] || ender 116 | 117 | }(this)); 118 | -------------------------------------------------------------------------------- /tests/fixtures/badfixtures.xml: -------------------------------------------------------------------------------- 1 | ><><>Not a valid xml document<><>< -------------------------------------------------------------------------------- /tests/fixtures/fixtures.html: -------------------------------------------------------------------------------- 1 |

boosh

-------------------------------------------------------------------------------- /tests/fixtures/fixtures.js: -------------------------------------------------------------------------------- 1 | window.boosh = 'boosh'; -------------------------------------------------------------------------------- /tests/fixtures/fixtures.json: -------------------------------------------------------------------------------- 1 | { "boosh": "boosh" } -------------------------------------------------------------------------------- /tests/fixtures/fixtures.xml: -------------------------------------------------------------------------------- 1 | boosh -------------------------------------------------------------------------------- /tests/fixtures/fixtures_jsonp.jsonp: -------------------------------------------------------------------------------- 1 | reqwest_0({ "boosh": "boosh" }); -------------------------------------------------------------------------------- /tests/fixtures/fixtures_jsonp2.jsonp: -------------------------------------------------------------------------------- 1 | bar({ "boosh": "boosh" }); -------------------------------------------------------------------------------- /tests/fixtures/fixtures_jsonp3.jsonp: -------------------------------------------------------------------------------- 1 | reqwest_2({ "boosh": "boosh" }); 2 | -------------------------------------------------------------------------------- /tests/fixtures/fixtures_jsonp_multi.jsonp: -------------------------------------------------------------------------------- 1 | reqwest_0({ "a": "a" }); 2 | -------------------------------------------------------------------------------- /tests/fixtures/fixtures_jsonp_multi_b.jsonp: -------------------------------------------------------------------------------- 1 | reqwest_0({ "b": "b" }); 2 | -------------------------------------------------------------------------------- /tests/fixtures/fixtures_jsonp_multi_c.jsonp: -------------------------------------------------------------------------------- 1 | reqwest_0({ "c": "c" }); 2 | -------------------------------------------------------------------------------- /tests/fixtures/fixtures_with_prefix.json: -------------------------------------------------------------------------------- 1 | ])}while(1);{ "boosh": "boosh" } -------------------------------------------------------------------------------- /tests/fixtures/invalidJSON.json: -------------------------------------------------------------------------------- 1 | this is not valid JSON!, there: are ~!_+ punctuation 2 | 3 | marks 4 | 5 | all, over, the:place ^ 2 6 | -------------------------------------------------------------------------------- /tests/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Reqwest tests 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | 20 |

Reqwest Tests

21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 38 | 39 | 40 |
41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 71 | 82 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
101 |
102 |
    103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | /*jshint maxlen:80*/ 2 | /*global reqwest:true, sink:true, start:true, ender:true, v:true, boosh:true*/ 3 | 4 | (function (ajax) { 5 | var BIND_ARGS = 'bind' 6 | , PASS_ARGS = 'pass' 7 | , FakeXHR = (function () { 8 | function FakeXHR () { 9 | this.args = {} 10 | FakeXHR.last = this 11 | } 12 | FakeXHR.setup = function () { 13 | FakeXHR.oldxhr = window['XMLHttpRequest'] 14 | FakeXHR.oldaxo = window['ActiveXObject'] 15 | window['XMLHttpRequest'] = FakeXHR 16 | window['ActiveXObject'] = FakeXHR 17 | FakeXHR.last = null 18 | } 19 | FakeXHR.restore = function () { 20 | window['XMLHttpRequest'] = FakeXHR.oldxhr 21 | window['ActiveXObject'] = FakeXHR.oldaxo 22 | } 23 | FakeXHR.prototype.methodCallCount = function (name) { 24 | return this.args[name] ? this.args[name].length : 0 25 | } 26 | FakeXHR.prototype.methodCallArgs = function (name, i, j) { 27 | var a = this.args[name] 28 | && this.args[name].length > i ? this.args[name][i] : null 29 | if (arguments.length > 2) return a && a.length > j ? a[j] : null 30 | return a 31 | } 32 | v.each(['open', 'send', 'setRequestHeader' ], function (f) { 33 | FakeXHR.prototype[f] = function () { 34 | if (!this.args[f]) this.args[f] = [] 35 | this.args[f].push(arguments) 36 | } 37 | }) 38 | return FakeXHR 39 | }()) 40 | 41 | sink('Setup', function (test, ok, before, after) { 42 | before(function () { 43 | ajax.ajaxSetup({ 44 | dataFilter: function (resp, type) { 45 | // example filter to prevent json hijacking 46 | return resp.substring('])}while(1);'.length) 47 | } 48 | }) 49 | }) 50 | after(function () { 51 | ajax.ajaxSetup({ 52 | // reset to original data filter 53 | dataFilter: function (resp, type) { 54 | return resp 55 | } 56 | }) 57 | }) 58 | test('dataFilter', function (complete) { 59 | ajax({ 60 | url: '/tests/fixtures/fixtures_with_prefix.json' 61 | , type: 'json' 62 | , success: function (resp) { 63 | ok(resp, 'received response') 64 | ok( 65 | resp && resp.boosh == 'boosh' 66 | , 'correctly evaluated response as JSON' 67 | ) 68 | complete() 69 | } 70 | }) 71 | }) 72 | }) 73 | 74 | sink('Mime Types', function (test, ok) { 75 | test('JSON', function (complete) { 76 | ajax({ 77 | url: '/tests/fixtures/fixtures.json' 78 | , type: 'json' 79 | , success: function (resp) { 80 | ok(resp, 'received response') 81 | ok( 82 | resp && resp.boosh == 'boosh' 83 | , 'correctly evaluated response as JSON' 84 | ) 85 | complete() 86 | } 87 | }) 88 | }) 89 | 90 | test('JSONP', function (complete) { 91 | // stub callback prefix 92 | reqwest.getcallbackPrefix = function (id) { 93 | return 'reqwest_' + id 94 | } 95 | ajax({ 96 | url: '/tests/fixtures/fixtures_jsonp.jsonp?callback=?' 97 | , type: 'jsonp' 98 | , success: function (resp) { 99 | ok(resp, 'received response for unique generated callback') 100 | ok( 101 | resp && resp.boosh == 'boosh' 102 | , 'correctly evaled response for unique generated cb as JSONP' 103 | ) 104 | complete() 105 | } 106 | }) 107 | }) 108 | 109 | test('JS', function (complete) { 110 | ajax({ 111 | url: '/tests/fixtures/fixtures.js' 112 | , type: 'js' 113 | , success: function () { 114 | ok( 115 | typeof boosh !== 'undefined' && boosh == 'boosh' 116 | , 'evaluated response as JavaScript' 117 | ) 118 | complete() 119 | } 120 | }) 121 | }) 122 | 123 | test('HTML', function (complete) { 124 | ajax({ 125 | url: '/tests/fixtures/fixtures.html' 126 | , type: 'html' 127 | , success: function (resp) { 128 | ok(resp == '

    boosh

    ', 'evaluated response as HTML') 129 | complete() 130 | } 131 | }) 132 | }) 133 | 134 | test('XML', function (complete) { 135 | ajax({ 136 | url: '/tests/fixtures/fixtures.xml' 137 | , type: 'xml' 138 | , success: function (resp) { 139 | ok(resp 140 | && resp.documentElement 141 | && resp.documentElement.nodeName == 'root' 142 | , 'XML Response root is ' 143 | ) 144 | ok(resp 145 | && resp.documentElement 146 | && resp.documentElement.hasChildNodes 147 | && resp.documentElement.firstChild.nodeName == 'boosh' 148 | && resp.documentElement.firstChild.firstChild.nodeValue 149 | == 'boosh' 150 | , 'Correct XML response' 151 | ) 152 | complete() 153 | } 154 | , error: function (err) { 155 | ok(false, err.responseText) 156 | complete() 157 | } 158 | }) 159 | }) 160 | 161 | test('XML (404)', function (complete) { 162 | ajax({ 163 | url:'/tests/fixtures/badfixtures.xml' 164 | , type:'xml' 165 | , success: function (resp) { 166 | if (resp == null) { 167 | ok(true, 'XML response is null') 168 | complete() 169 | } else { 170 | ok(resp 171 | && resp.documentElement 172 | && resp.documentElement.firstChild 173 | && (/error/i).test(resp.documentElement.firstChild.nodeValue) 174 | , 'XML response reports parsing error' 175 | ) 176 | complete() 177 | } 178 | } 179 | , error: function () { 180 | ok(true, 'No XML response (error())') 181 | complete() 182 | } 183 | }) 184 | }) 185 | }) 186 | 187 | sink('JSONP', function (test, ok) { 188 | test('Named callback in query string', function (complete) { 189 | ajax({ 190 | url: '/tests/fixtures/fixtures_jsonp2.jsonp?foo=bar' 191 | , type: 'jsonp' 192 | , jsonpCallback: 'foo' 193 | , success: function (resp) { 194 | ok(resp, 'received response for custom callback') 195 | ok( 196 | resp && resp.boosh == 'boosh' 197 | , 'correctly evaluated response as JSONP with custom callback' 198 | ) 199 | complete() 200 | } 201 | }) 202 | }) 203 | 204 | test('Unnamed callback in query string', function (complete) { 205 | ajax({ 206 | url: '/tests/fixtures/fixtures_jsonp3.jsonp?foo=?' 207 | , type: 'jsonp' 208 | , jsonpCallback: 'foo' 209 | , success: function (resp) { 210 | ok(resp, 'received response for custom wildcard callback') 211 | ok( 212 | resp && resp.boosh == 'boosh' 213 | , 'correctly evaled response as JSONP with custom wildcard cb' 214 | ) 215 | complete() 216 | } 217 | }) 218 | }) 219 | 220 | test('No callback, no query string', function (complete) { 221 | ajax({ 222 | url: '/tests/fixtures/fixtures_jsonp3.jsonp' 223 | , type: 'jsonp' 224 | , jsonpCallback: 'foo' 225 | , success: function (resp) { 226 | ok(resp, 'received response for custom wildcard callback') 227 | ok( 228 | resp && resp.boosh == 'boosh' 229 | , 'correctly evaled response as JSONP with custom cb not in url' 230 | ) 231 | complete() 232 | } 233 | }) 234 | }) 235 | 236 | test('No callback in existing query string', function (complete) { 237 | ajax({ 238 | url: '/tests/none.jsonp?echo&somevar=some+long+str+here' 239 | , type: 'jsonp' 240 | , jsonpCallbackName: 'yohoho' 241 | , success: function (resp) { 242 | ok(resp && resp.query, 'received response from echo callback') 243 | ok( 244 | resp && resp.query && resp.query.somevar == 'some long str here' 245 | , 'correctly evaluated response as JSONP with echo callback' 246 | ) 247 | complete() 248 | } 249 | }) 250 | }) 251 | 252 | test('Append data to existing query string', function (complete) { 253 | ajax({ 254 | url: '/tests/none.jsonp?echo' // should append &somevar... 255 | , type: 'jsonp' 256 | , data: { somevar: 'some long str here', anothervar: 'yo ho ho!' } 257 | , success: function (resp) { 258 | ok(resp && resp.query, 'received response from echo callback') 259 | ok( 260 | resp && resp.query && resp.query.somevar == 'some long str here' 261 | , 'correctly sent and received data object from JSONP echo (1)' 262 | ) 263 | ok( 264 | resp && resp.query && resp.query.anothervar == 'yo ho ho!' 265 | , 'correctly sent and received data object from JSONP echo (2)' 266 | ) 267 | complete() 268 | } 269 | }) 270 | }) 271 | 272 | test('Generate complete query string from data', function (complete) { 273 | ajax({ 274 | url: '/tests/none.jsonp' // should append ?echo...etc. 275 | , type: 'jsonp' 276 | , data: [ 277 | { name: 'somevar', value: 'some long str here' } 278 | , { name: 'anothervar', value: 'yo ho ho!' } 279 | , { name: 'echo', value: true } 280 | ] 281 | , success: function (resp) { 282 | ok(resp && resp.query, 'received response from echo callback') 283 | ok( 284 | resp && resp.query && resp.query.somevar == 'some long str here' 285 | , 'correctly sent and received data array from JSONP echo (1)' 286 | ) 287 | ok( 288 | resp && resp.query && resp.query.anothervar == 'yo ho ho!' 289 | , 'correctly sent and received data array from JSONP echo (2)' 290 | ) 291 | complete() 292 | } 293 | }) 294 | }) 295 | 296 | test('Append data to query string and insert callback name' 297 | , function (complete) { 298 | 299 | ajax({ 300 | // should append data and match callback correctly 301 | url: '/tests/none.jsonp?callback=?' 302 | , type: 'jsonp' 303 | , jsonpCallbackName: 'reqwest_foo' 304 | , data: { foo: 'bar', boo: 'baz', echo: true } 305 | , success: function (resp) { 306 | ok(resp && resp.query, 'received response from echo callback') 307 | ok( 308 | resp && resp.query && resp.query.callback == 'reqwest_foo' 309 | , 'correctly matched callback in URL' 310 | ) 311 | complete() 312 | } 313 | }) 314 | }) 315 | }) 316 | 317 | sink('Callbacks', function (test, ok) { 318 | 319 | test('sync version', function (done) { 320 | var r = ajax({ 321 | method: 'get' 322 | , url: '/tests/fixtures/fixtures.json' 323 | , type: 'json' 324 | , async: false 325 | }) 326 | var request = r.request, 327 | responseText = request.response !== undefined ? request.response : request.responseText 328 | ok(eval('(' + responseText + ')').boosh == 'boosh', 'can make sync calls') 329 | done() 330 | }) 331 | 332 | test('no callbacks', function (complete) { 333 | var pass = true 334 | try { 335 | ajax('/tests/fixtures/fixtures.js') 336 | } catch (ex) { 337 | pass = false 338 | } finally { 339 | ok(pass, 'successfully doesnt fail without callback') 340 | complete() 341 | } 342 | }) 343 | 344 | test('complete is called', function (complete) { 345 | ajax({ 346 | url: '/tests/fixtures/fixtures.js' 347 | , complete: function () { 348 | ok(true, 'called complete') 349 | complete() 350 | } 351 | }) 352 | }) 353 | 354 | test('invalid JSON sets error on resp object', function (complete) { 355 | ajax({ 356 | url: '/tests/fixtures/invalidJSON.json' 357 | , type: 'json' 358 | , success: function () { 359 | ok(false, 'success callback fired') 360 | complete() 361 | } 362 | , error: function (resp, msg) { 363 | ok( 364 | msg == 'Could not parse JSON in response' 365 | , 'error callback fired' 366 | ) 367 | complete() 368 | } 369 | }) 370 | }) 371 | 372 | test('multiple parallel named JSONP callbacks', 8, function () { 373 | ajax({ 374 | url: '/tests/fixtures/fixtures_jsonp_multi.jsonp?callback=reqwest_0' 375 | , type: 'jsonp' 376 | , success: function (resp) { 377 | ok(resp, 'received response from call #1') 378 | ok( 379 | resp && resp.a == 'a' 380 | , 'evaluated response from call #1 as JSONP' 381 | ) 382 | } 383 | }) 384 | ajax({ 385 | url: '/tests/fixtures/fixtures_jsonp_multi_b.jsonp?callback=reqwest_0' 386 | , type: 'jsonp' 387 | , success: function (resp) { 388 | ok(resp, 'received response from call #2') 389 | ok( 390 | resp && resp.b == 'b' 391 | , 'evaluated response from call #2 as JSONP' 392 | ) 393 | } 394 | }) 395 | ajax({ 396 | url: '/tests/fixtures/fixtures_jsonp_multi_c.jsonp?callback=reqwest_0' 397 | , type: 'jsonp' 398 | , success: function (resp) { 399 | ok(resp, 'received response from call #2') 400 | ok( 401 | resp && resp.c == 'c' 402 | , 'evaluated response from call #3 as JSONP' 403 | ) 404 | } 405 | }) 406 | ajax({ 407 | url: '/tests/fixtures/fixtures_jsonp_multi.jsonp?callback=reqwest_0' 408 | , type: 'jsonp' 409 | , success: function (resp) { 410 | ok(resp, 'received response from call #2') 411 | ok( 412 | resp && resp.a == 'a' 413 | , 'evaluated response from call #4 as JSONP' 414 | ) 415 | } 416 | }) 417 | }) 418 | 419 | test('JSONP also supports success promises', function (complete) { 420 | ajax({ 421 | url: '/tests/none.jsonp?echo' 422 | , type: 'jsonp' 423 | , success: function (resp) { 424 | ok(resp, 'received response in constructor success callback') 425 | } 426 | }) 427 | .then(function (resp) { 428 | ok(resp, 'received response in promise success callback') 429 | return resp; 430 | }) 431 | .then(function (resp) { 432 | ok(resp, 'received response in second promise success callback') 433 | complete() 434 | }) 435 | }) 436 | 437 | test('JSONP also supports error promises', function (complete) { 438 | ajax({ 439 | url: '/tests/timeout/' 440 | , type: 'jsonp' 441 | , error: function (err) { 442 | ok(err, 'received error response in constructor error callback') 443 | } 444 | }) 445 | .fail(function (err) { 446 | ok(err, 'received error response in promise error callback') 447 | }) 448 | .fail(function (err) { 449 | ok(err, 'received error response in second promise error callback') 450 | complete() 451 | }) 452 | .abort() 453 | }) 454 | 455 | }) 456 | 457 | if (window.XMLHttpRequest 458 | && ('withCredentials' in new window.XMLHttpRequest())) { 459 | 460 | sink('Cross-origin Resource Sharing', function (test, ok) { 461 | test('make request to another origin', 1, function () { 462 | ajax({ 463 | url: 'http://' + window.location.hostname + ':5678/get-value' 464 | , type: 'text' 465 | , method: 'get' 466 | , crossOrigin: true 467 | , complete: function (resp) { 468 | ok(resp.responseText === 'hello', 'request made successfully') 469 | } 470 | }) 471 | }) 472 | 473 | test('set cookie on other origin', 2, function () { 474 | ajax({ 475 | url: 'http://' + window.location.hostname + ':5678/set-cookie' 476 | , type: 'text' 477 | , method: 'get' 478 | , crossOrigin: true 479 | , withCredentials: true 480 | , before: function (http) { 481 | ok( 482 | http.withCredentials === true 483 | , 'has set withCredentials on connection object' 484 | ) 485 | } 486 | , complete: function (resp) { 487 | ok(resp.status === 200, 'cookie set successfully') 488 | } 489 | }) 490 | }) 491 | 492 | test('get cookie from other origin', 1, function () { 493 | ajax({ 494 | url: 'http://' 495 | + window.location.hostname 496 | + ':5678/get-cookie-value' 497 | , type: 'text' 498 | , method: 'get' 499 | , crossOrigin: true 500 | , withCredentials: true 501 | , complete: function (resp) { 502 | ok( 503 | resp.responseText == 'hello' 504 | , 'cookie value retrieved successfully' 505 | ) 506 | } 507 | }) 508 | }) 509 | 510 | }) 511 | } 512 | 513 | sink('Connection Object', function (test, ok) { 514 | 515 | test('use xhr factory provided in the options', function (complete) { 516 | var reqwest 517 | , xhr 518 | 519 | if (typeof XMLHttpRequest !== 'undefined') { 520 | xhr = new XMLHttpRequest() 521 | } else if (typeof ActiveXObject !== 'undefined') { 522 | xhr = new ActiveXObject('Microsoft.XMLHTTP') 523 | } else { 524 | ok(false, 'browser not supported') 525 | } 526 | 527 | reqwest = ajax({ 528 | url: '/tests/fixtures/fixtures.html', 529 | xhr: function () { 530 | return xhr 531 | } 532 | }) 533 | 534 | ok(reqwest.request === xhr, 'uses factory') 535 | complete() 536 | }) 537 | 538 | test('fallbacks to own xhr factory if falsy is returned', function (complete) { 539 | var reqwest 540 | 541 | FakeXHR.setup() 542 | try { 543 | reqwest = ajax({ 544 | url: '/tests/fixtures/fixtures.html', 545 | xhr: function () { 546 | return null 547 | } 548 | }) 549 | 550 | ok(reqwest.request instanceof FakeXHR, 'fallbacks correctly') 551 | complete() 552 | } finally { 553 | FakeXHR.restore() 554 | } 555 | }) 556 | 557 | test('setRequestHeaders', function (complete) { 558 | ajax({ 559 | url: '/tests/fixtures/fixtures.html' 560 | , data: 'foo=bar&baz=thunk' 561 | , method: 'post' 562 | , headers: { 563 | 'Accept': 'application/x-foo' 564 | } 565 | , success: function () { 566 | ok(true, 'can post headers') 567 | complete() 568 | } 569 | }) 570 | }) 571 | 572 | test('can inspect http before send', function (complete) { 573 | var connection = ajax({ 574 | url: '/tests/fixtures/fixtures.js' 575 | , method: 'post' 576 | , type: 'js' 577 | , before: function (http) { 578 | ok(http.readyState == 1, 'received http connection object') 579 | } 580 | , success: function () { 581 | // Microsoft.XMLHTTP appears not to run this async in IE6&7, it 582 | // processes the request and triggers success() before ajax() even 583 | // returns. Perhaps a better solution would be to defer the calls 584 | // within handleReadyState() 585 | setTimeout(function () { 586 | ok( 587 | connection.request.readyState == 4 588 | , 'success callback has readyState of 4' 589 | ) 590 | complete() 591 | }, 0) 592 | } 593 | }) 594 | }) 595 | 596 | test('ajax() encodes array `data`', function (complete) { 597 | FakeXHR.setup() 598 | try { 599 | ajax({ 600 | url: '/tests/fixtures/fixtures.html' 601 | , method: 'post' 602 | , data: [ 603 | { name: 'foo', value: 'bar' } 604 | , { name: 'baz', value: 'thunk' } 605 | ] 606 | }) 607 | ok(FakeXHR.last.methodCallCount('send') == 1, 'send called') 608 | ok( 609 | FakeXHR.last.methodCallArgs('send', 0).length == 1 610 | , 'send called with 1 arg' 611 | ) 612 | ok( 613 | FakeXHR.last.methodCallArgs('send', 0, 0) == 'foo=bar&baz=thunk' 614 | , 'send called with encoded array' 615 | ) 616 | complete() 617 | } finally { 618 | FakeXHR.restore() 619 | } 620 | }) 621 | 622 | test('ajax() encodes hash `data`', function (complete) { 623 | FakeXHR.setup() 624 | try { 625 | ajax({ 626 | url: '/tests/fixtures/fixtures.html' 627 | , method: 'post' 628 | , data: { bar: 'foo', thunk: 'baz' } 629 | }) 630 | ok(FakeXHR.last.methodCallCount('send') == 1, 'send called') 631 | ok( 632 | FakeXHR.last.methodCallArgs('send', 0).length == 1 633 | , 'send called with 1 arg' 634 | ) 635 | ok( 636 | FakeXHR.last.methodCallArgs('send', 0, 0) == 'bar=foo&thunk=baz' 637 | , 'send called with encoded array' 638 | ) 639 | complete() 640 | } finally { 641 | FakeXHR.restore() 642 | } 643 | }) 644 | 645 | test('ajax() obeys `processData`', function (complete) { 646 | FakeXHR.setup() 647 | try { 648 | var d = { bar: 'foo', thunk: 'baz' } 649 | ajax({ 650 | url: '/tests/fixtures/fixtures.html' 651 | , processData: false 652 | , method: 'post' 653 | , data: d 654 | }) 655 | ok(FakeXHR.last.methodCallCount('send') == 1, 'send called') 656 | ok( 657 | FakeXHR.last.methodCallArgs('send', 0).length == 1 658 | , 'send called with 1 arg' 659 | ) 660 | ok( 661 | FakeXHR.last.methodCallArgs('send', 0, 0) === d 662 | , 'send called with exact `data` object' 663 | ) 664 | complete() 665 | } finally { 666 | FakeXHR.restore() 667 | } 668 | }) 669 | 670 | function testXhrGetUrlAdjustment(url, data, expectedUrl, complete) { 671 | FakeXHR.setup() 672 | try { 673 | ajax({ url: url, data: data }) 674 | ok(FakeXHR.last.methodCallCount('open') == 1, 'open called') 675 | ok( 676 | FakeXHR.last.methodCallArgs('open', 0).length == 3 677 | , 'open called with 3 args' 678 | ) 679 | ok( 680 | FakeXHR.last.methodCallArgs('open', 0, 0) == 'GET' 681 | , 'first arg of open() is "GET"' 682 | ) 683 | ok(FakeXHR.last.methodCallArgs('open', 0, 1) == expectedUrl 684 | , 'second arg of open() is URL with query string') 685 | ok( 686 | FakeXHR.last.methodCallArgs('open', 0, 2) === true 687 | , 'third arg of open() is `true`' 688 | ) 689 | ok(FakeXHR.last.methodCallCount('send') == 1, 'send called') 690 | ok( 691 | FakeXHR.last.methodCallArgs('send', 0).length == 1 692 | , 'send called with 1 arg' 693 | ) 694 | ok( 695 | FakeXHR.last.methodCallArgs('send', 0, 0) === null 696 | , 'send called with null' 697 | ) 698 | complete() 699 | } finally { 700 | FakeXHR.restore() 701 | } 702 | } 703 | 704 | test('ajax() appends GET URL with ?`data`', function (complete) { 705 | testXhrGetUrlAdjustment( 706 | '/tests/fixtures/fixtures.html' 707 | , 'bar=foo&thunk=baz' 708 | , '/tests/fixtures/fixtures.html?bar=foo&thunk=baz' 709 | , complete 710 | ) 711 | }) 712 | 713 | test('ajax() appends GET URL with ?`data` (serialized object)' 714 | , function (complete) { 715 | 716 | testXhrGetUrlAdjustment( 717 | '/tests/fixtures/fixtures.html' 718 | , { bar: 'foo', thunk: 'baz' } 719 | , '/tests/fixtures/fixtures.html?bar=foo&thunk=baz' 720 | , complete 721 | ) 722 | }) 723 | 724 | test('ajax() appends GET URL with &`data` (serialized array)' 725 | , function (complete) { 726 | 727 | testXhrGetUrlAdjustment( 728 | '/tests/fixtures/fixtures.html?x=y' 729 | , [ { name: 'bar', value: 'foo'}, {name: 'thunk', value: 'baz' } ] 730 | , '/tests/fixtures/fixtures.html?x=y&bar=foo&thunk=baz' 731 | , complete 732 | ) 733 | }) 734 | }) 735 | 736 | sink('Standard vs compat mode', function (test, ok) { 737 | function methodMatch(resp, method) { 738 | return resp && resp.method === method 739 | } 740 | function headerMatch(resp, key, expected) { 741 | return resp && resp.headers && resp.headers[key] === expected 742 | } 743 | function queryMatch(resp, key, expected) { 744 | return resp && resp.query && resp.query[key] === expected 745 | } 746 | 747 | test('standard mode default', function (complete) { 748 | ajax({ 749 | url: '/tests/none.json?echo' 750 | , success: function (resp) { 751 | ok(methodMatch(resp, 'GET'), 'correct request method (GET)') 752 | ok( 753 | headerMatch( 754 | resp 755 | , 'content-type' 756 | , 'application/x-www-form-urlencoded' 757 | ) 758 | , 'correct Content-Type request header' 759 | ) 760 | ok( 761 | headerMatch(resp, 'x-requested-with', 'XMLHttpRequest') 762 | , 'correct X-Requested-With header' 763 | ) 764 | ok( 765 | headerMatch( 766 | resp 767 | , 'accept' 768 | , 'text/javascript, text/html, application/xml, text/xml, */*' 769 | ) 770 | , 'correct Accept header' 771 | ) 772 | complete() 773 | } 774 | }) 775 | }) 776 | 777 | test('standard mode custom content-type', function (complete) { 778 | ajax({ 779 | url: '/tests/none.json?echo' 780 | , contentType: 'yapplication/foobar' 781 | , success: function (resp) { 782 | ok(methodMatch(resp, 'GET'), 'correct request method (GET)') 783 | ok( 784 | headerMatch(resp, 'content-type', 'yapplication/foobar') 785 | , 'correct Content-Type request header' 786 | ) 787 | ok( 788 | headerMatch(resp, 'x-requested-with', 'XMLHttpRequest') 789 | , 'correct X-Requested-With header' 790 | ) 791 | ok( 792 | headerMatch( 793 | resp 794 | , 'accept' 795 | , 'text/javascript, text/html, application/xml, text/xml, */*' 796 | ) 797 | , 'correct Accept header' 798 | ) 799 | complete() 800 | } 801 | }) 802 | }) 803 | 804 | test('standard mode on no content-type', function (complete) { 805 | ajax({ 806 | url: '/tests/204' 807 | , success: function (resp) { 808 | ok(true, 'Nothing blew up.') 809 | } 810 | }) 811 | }) 812 | 813 | test('compat mode "dataType=json" headers', function (complete) { 814 | ajax.compat({ 815 | url: '/tests/none.json?echo' 816 | , dataType: 'json' // should map to 'type' 817 | , success: function (resp) { 818 | ok(methodMatch(resp, 'GET'), 'correct request method (GET)') 819 | ok( 820 | headerMatch( 821 | resp 822 | , 'content-type' 823 | , 'application/x-www-form-urlencoded' 824 | ) 825 | , 'correct Content-Type request header' 826 | ) 827 | ok( 828 | headerMatch(resp, 'x-requested-with', 'XMLHttpRequest') 829 | , 'correct X-Requested-With header' 830 | ) 831 | ok( 832 | headerMatch(resp, 'accept', 'application/json, text/javascript') 833 | , 'correct Accept header' 834 | ) 835 | complete() 836 | } 837 | }) 838 | }) 839 | 840 | test('compat mode "dataType=json" with "type=post" headers' 841 | , function (complete) { 842 | ajax.compat({ 843 | url: '/tests/none.json?echo' 844 | , type: 'post' 845 | , dataType: 'json' // should map to 'type' 846 | , success: function (resp) { 847 | ok(methodMatch(resp, 'POST'), 'correct request method (POST)') 848 | ok( 849 | headerMatch( 850 | resp 851 | , 'content-type' 852 | , 'application/x-www-form-urlencoded' 853 | ) 854 | , 'correct Content-Type request header' 855 | ) 856 | ok( 857 | headerMatch(resp, 'x-requested-with', 'XMLHttpRequest') 858 | , 'correct X-Requested-With header' 859 | ) 860 | ok( 861 | headerMatch(resp, 'accept', 'application/json, text/javascript') 862 | , 'correct Accept header' 863 | ) 864 | complete() 865 | } 866 | }) 867 | }) 868 | 869 | test('compat mode "dataType=json" headers (with additional headers)' 870 | , function (complete) { 871 | 872 | ajax.compat({ 873 | url: '/tests/none.json?echo' 874 | , dataType: 'json' // should map to 'type' 875 | // verify that these are left intact and nothing screwy 876 | // happens with headers 877 | , headers: { one: 1, two: 2 } 878 | , success: function (resp) { 879 | ok( 880 | headerMatch( 881 | resp 882 | , 'content-type' 883 | , 'application/x-www-form-urlencoded' 884 | ) 885 | , 'correct Content-Type request header' 886 | ) 887 | ok( 888 | headerMatch(resp, 'x-requested-with', 'XMLHttpRequest') 889 | , 'correct X-Requested-With header' 890 | ) 891 | ok( 892 | headerMatch(resp, 'accept', 'application/json, text/javascript') 893 | , 'correct Accept header' 894 | ) 895 | ok( 896 | headerMatch(resp, 'one', '1') && headerMatch(resp, 'two', '2') 897 | , 'left additional headers intact' 898 | ) 899 | complete() 900 | } 901 | }) 902 | }) 903 | 904 | test('compat mode "dataType=jsonp" query string', function (complete) { 905 | ajax.compat({ 906 | url: '/tests/none.jsonp?echo' 907 | , dataType: 'jsonp' 908 | , jsonp: 'testCallback' // should map to jsonpCallback 909 | , jsonpCallback: 'foobar' // should map to jsonpCallbackName 910 | , success: function (resp) { 911 | ok( 912 | queryMatch(resp, 'echo', '') 913 | , 'correct Content-Type request header' 914 | ) 915 | ok( 916 | queryMatch(resp, 'testCallback', 'foobar') 917 | , 'correct X-Requested-With header' 918 | ) 919 | complete() 920 | } 921 | }) 922 | }) 923 | }) 924 | 925 | /***************** SERIALIZER TESTS ***********************/ 926 | 927 | // define some helpers for the serializer tests that are used often and 928 | // shared with the ender integration tests 929 | 930 | function createSerializeHelper(ok) { 931 | var forms = document.forms 932 | , foo = forms[0].getElementsByTagName('input')[1] 933 | , bar = forms[0].getElementsByTagName('input')[2] 934 | , choices = forms[0].getElementsByTagName('select')[0] 935 | , BIND_ARGS = 'bind' 936 | , PASS_ARGS = 'pass' 937 | 938 | function reset() { 939 | forms[1].reset() 940 | } 941 | 942 | function formElements(formIndex, tagName, elementIndex) { 943 | return forms[formIndex].getElementsByTagName(tagName)[elementIndex] 944 | } 945 | 946 | function isArray(a) { 947 | return Object.prototype.toString.call(a) == '[object Array]' 948 | } 949 | 950 | function sameValue(value, expected) { 951 | if (expected == null) { 952 | return value === null 953 | } else if (isArray(expected)) { 954 | if (value.length !== expected.length) return false 955 | for (var i = 0; i < expected.length; i++) { 956 | if (value[i] != expected[i]) return false 957 | } 958 | return true 959 | } else return value == expected 960 | } 961 | 962 | function testInput(input, name, value, str) { 963 | var sa = ajax.serialize(input, { type: 'array' }) 964 | , sh = ajax.serialize(input, { type: 'map' }) 965 | , av, i 966 | 967 | if (value != null) { 968 | av = isArray(value) ? value : [ value ] 969 | 970 | ok( 971 | sa.length == av.length 972 | , 'serialize(' + str + ', {type:\'array\'}) returns array ' 973 | + '[{name,value}]' 974 | ) 975 | 976 | for (i = 0; i < av.length; i++) { 977 | ok( 978 | name == sa[i].name 979 | , 'serialize(' + str + ', {type:\'array\'})[' + i + '].name' 980 | ) 981 | ok( 982 | av[i] == sa[i].value 983 | , 'serialize(' + str + ', {type:\'array\'})[' + i + '].value' 984 | ) 985 | } 986 | 987 | ok(sameValue(sh[name], value), 'serialize(' + str + ', {type:\'map\'})') 988 | } else { 989 | // the cases where an element shouldn't show up at all, checkbox not 990 | // checked for example 991 | ok(sa.length === 0, 'serialize(' + str + ', {type:\'array\'}) is []') 992 | ok( 993 | v.keys(sh).length === 0 994 | , 'serialize(' + str + ', {type:\'map\'}) is {}' 995 | ) 996 | } 997 | } 998 | 999 | function testFormSerialize(method, type) { 1000 | var expected = 1001 | 'foo=bar&bar=baz&wha=1&wha=3&who=tawoo&%24escapable+name' 1002 | + '%24=escapeme&choices=two&opinions=world+peace+is+not+real' 1003 | 1004 | ok(method, 'serialize() bound to context') 1005 | ok( 1006 | (method ? method(forms[0]) : null) == expected 1007 | , 'serialized form (' + type + ')' 1008 | ) 1009 | } 1010 | 1011 | function executeMultiArgumentMethod(method, argType, options) { 1012 | var els = [ foo, bar, choices ] 1013 | , ths = argType === BIND_ARGS ? ender(els) : null 1014 | , args = argType === PASS_ARGS ? els : [] 1015 | 1016 | if (!!options) args.push(options) 1017 | 1018 | return method.apply(ths, args) 1019 | } 1020 | 1021 | function testMultiArgumentSerialize(method, type, argType) { 1022 | ok(method, 'serialize() bound in context') 1023 | var result = method ? executeMultiArgumentMethod(method, argType) : null 1024 | ok( 1025 | result == 'foo=bar&bar=baz&choices=two' 1026 | , 'serialized all 3 arguments together' 1027 | ) 1028 | } 1029 | 1030 | function verifyFormSerializeArray(result, type) { 1031 | var expected = [ 1032 | { name: 'foo', value: 'bar' } 1033 | , { name: 'bar', value: 'baz' } 1034 | , { name: 'wha', value: 1 } 1035 | , { name: 'wha', value: 3 } 1036 | , { name: 'who', value: 'tawoo' } 1037 | , { name: '$escapable name$', value: 'escapeme' } 1038 | , { name: 'choices', value: 'two' } 1039 | , { name: 'opinions', value: 'world peace is not real' } 1040 | ] 1041 | , i 1042 | 1043 | for (i = 0; i < expected.length; i++) { 1044 | ok(v.some(result, function (v) { 1045 | return v.name == expected[i].name && v.value == expected[i].value 1046 | }), 'serialized ' + expected[i].name + ' (' + type + ')') 1047 | } 1048 | } 1049 | 1050 | function testFormSerializeArray(method, type) { 1051 | ok(method, 'serialize(..., {type:\'array\'}) bound to context') 1052 | 1053 | var result = method ? method(forms[0], { type: 'array' }) : [] 1054 | if (!result) result = [] 1055 | 1056 | verifyFormSerializeArray(result, type) 1057 | } 1058 | 1059 | function testMultiArgumentSerializeArray(method, type, argType) { 1060 | ok(method, 'serialize(..., {type:\'array\'}) bound to context') 1061 | var result = method 1062 | ? executeMultiArgumentMethod(method, argType, { type: 'array' }) 1063 | : [] 1064 | 1065 | if (!result) result = [] 1066 | 1067 | ok(result.length == 3, 'serialized as array of 3') 1068 | ok( 1069 | result.length == 3 1070 | && result[0].name == 'foo' 1071 | && result[0].value == 'bar' 1072 | , 'serialized first element (' + type + ')' 1073 | ) 1074 | ok( 1075 | result.length == 3 1076 | && result[1].name == 'bar' 1077 | && result[1].value == 'baz' 1078 | , 'serialized second element (' + type + ')' 1079 | ) 1080 | ok( 1081 | result.length == 3 1082 | && result[2].name == 'choices' 1083 | && result[2].value == 'two' 1084 | , 'serialized third element (' + type + ')' 1085 | ) 1086 | } 1087 | 1088 | function testFormSerializeHash(method, type) { 1089 | var expected = { 1090 | foo: 'bar' 1091 | , bar: 'baz' 1092 | , wha: [ '1', '3' ] 1093 | , who: 'tawoo' 1094 | , '$escapable name$': 'escapeme' 1095 | , choices: 'two' 1096 | , opinions: 'world peace is not real' 1097 | } 1098 | , result 1099 | 1100 | ok(method, 'serialize({type:\'map\'}) bound to context') 1101 | 1102 | result = method ? method(forms[0], { type: 'map' }) : {} 1103 | if (!result) result = {} 1104 | 1105 | ok( 1106 | v.keys(expected).length === v.keys(result).length 1107 | , 'same number of keys (' + type + ')' 1108 | ) 1109 | 1110 | v.each(v.keys(expected), function (k) { 1111 | ok( 1112 | sameValue(expected[k], result[k]) 1113 | , 'same value for ' + k + ' (' + type + ')' 1114 | ) 1115 | }) 1116 | } 1117 | 1118 | function testMultiArgumentSerializeHash(method, type, argType) { 1119 | ok(method, 'serialize({type:\'map\'}) bound to context') 1120 | var result = method 1121 | ? executeMultiArgumentMethod(method, argType, { type: 'map' }) 1122 | : {} 1123 | if (!result) result = {} 1124 | ok(result.foo == 'bar', 'serialized first element (' + type + ')') 1125 | ok(result.bar == 'baz', 'serialized second element (' + type + ')') 1126 | ok(result.choices == 'two', 'serialized third element (' + type + ')') 1127 | } 1128 | 1129 | return { 1130 | reset: reset 1131 | , formElements: formElements 1132 | , testInput: testInput 1133 | , testFormSerialize: testFormSerialize 1134 | , testMultiArgumentSerialize: testMultiArgumentSerialize 1135 | , testFormSerializeArray: testFormSerializeArray 1136 | , verifyFormSerializeArray: verifyFormSerializeArray 1137 | , testMultiArgumentSerializeArray: testMultiArgumentSerializeArray 1138 | , testFormSerializeHash: testFormSerializeHash 1139 | , testMultiArgumentSerializeHash: testMultiArgumentSerializeHash 1140 | } 1141 | } 1142 | 1143 | sink('Serializing', function (test, ok) { 1144 | 1145 | /* 1146 | * Serialize forms according to spec. 1147 | * * reqwest.serialize(ele[, ele...]) returns a query string style 1148 | * serialization 1149 | * * reqwest.serialize(ele[, ele...], {type:'array'}) returns a 1150 | * [ { name: 'name', value: 'value'}, ... ] style serialization, 1151 | * compatible with jQuery.serializeArray() 1152 | * * reqwest.serialize(ele[, ele...], {type:\'map\'}) returns a 1153 | * { 'name': 'value', ... } style serialization, compatible with 1154 | * Prototype Form.serializeElements({hash:true}) 1155 | * Some tests based on spec notes here: 1156 | * http://malsup.com/jquery/form/comp/test.html 1157 | */ 1158 | 1159 | var sHelper = createSerializeHelper(ok) 1160 | sHelper.reset() 1161 | 1162 | test('correctly serialize textarea', function (complete) { 1163 | var textarea = sHelper.formElements(1, 'textarea', 0) 1164 | , sa 1165 | 1166 | // the texarea has 2 different newline styles, should come out as 1167 | // normalized CRLF as per forms spec 1168 | ok( 1169 | 'T3=%3F%0D%0AA+B%0D%0AZ' == ajax.serialize(textarea) 1170 | , 'serialize(textarea)' 1171 | ) 1172 | sa = ajax.serialize(textarea, { type: 'array' }) 1173 | ok(sa.length == 1, 'serialize(textarea, {type:\'array\'}) returns array') 1174 | sa = sa[0] 1175 | ok('T3' == sa.name, 'serialize(textarea, {type:\'array\'}).name') 1176 | ok( 1177 | '?\r\nA B\r\nZ' == sa.value 1178 | , 'serialize(textarea, {type:\'array\'}).value' 1179 | ) 1180 | ok( 1181 | '?\r\nA B\r\nZ' == ajax.serialize(textarea, { type: 'map' }).T3 1182 | , 'serialize(textarea, {type:\'map\'})' 1183 | ) 1184 | complete() 1185 | }) 1186 | 1187 | test('correctly serialize input[type=hidden]', function (complete) { 1188 | sHelper.testInput( 1189 | sHelper.formElements(1, 'input', 0) 1190 | , 'H1' 1191 | , 'x' 1192 | , 'hidden' 1193 | ) 1194 | sHelper.testInput( 1195 | sHelper.formElements(1, 'input', 1) 1196 | , 'H2' 1197 | , '' 1198 | , 'hidden[no value]' 1199 | ) 1200 | complete() 1201 | }) 1202 | 1203 | test('correctly serialize input[type=password]', function (complete) { 1204 | sHelper.testInput( 1205 | sHelper.formElements(1, 'input', 2) 1206 | , 'PWD1' 1207 | , 'xyz' 1208 | , 'password' 1209 | ) 1210 | sHelper.testInput( 1211 | sHelper.formElements(1, 'input', 3) 1212 | , 'PWD2' 1213 | , '' 1214 | , 'password[no value]' 1215 | ) 1216 | complete() 1217 | }) 1218 | 1219 | test('correctly serialize input[type=text]', function (complete) { 1220 | sHelper.testInput( 1221 | sHelper.formElements(1, 'input', 4) 1222 | , 'T1' 1223 | , '' 1224 | , 'text[no value]' 1225 | ) 1226 | sHelper.testInput( 1227 | sHelper.formElements(1, 'input', 5) 1228 | , 'T2' 1229 | , 'YES' 1230 | , 'text[readonly]' 1231 | ) 1232 | sHelper.testInput( 1233 | sHelper.formElements(1, 'input', 10) 1234 | , 'My Name' 1235 | , 'me' 1236 | , 'text[space name]' 1237 | ) 1238 | complete() 1239 | }) 1240 | 1241 | test('correctly serialize input[type=checkbox]', function (complete) { 1242 | var cb1 = sHelper.formElements(1, 'input', 6) 1243 | , cb2 = sHelper.formElements(1, 'input', 7) 1244 | sHelper.testInput(cb1, 'C1', null, 'checkbox[not checked]') 1245 | cb1.checked = true 1246 | sHelper.testInput(cb1, 'C1', '1', 'checkbox[checked]') 1247 | // special case here, checkbox with no value='' should give you 'on' 1248 | // for cb.value 1249 | sHelper.testInput(cb2, 'C2', null, 'checkbox[no value, not checked]') 1250 | cb2.checked = true 1251 | sHelper.testInput(cb2, 'C2', 'on', 'checkbox[no value, checked]') 1252 | complete() 1253 | }) 1254 | 1255 | test('correctly serialize input[type=radio]', function (complete) { 1256 | var r1 = sHelper.formElements(1, 'input', 8) 1257 | , r2 = sHelper.formElements(1, 'input', 9) 1258 | sHelper.testInput(r1, 'R1', null, 'radio[not checked]') 1259 | r1.checked = true 1260 | sHelper.testInput(r1, 'R1', '1', 'radio[not checked]') 1261 | sHelper.testInput(r2, 'R1', null, 'radio[no value, not checked]') 1262 | r2.checked = true 1263 | sHelper.testInput(r2, 'R1', '', 'radio[no value, checked]') 1264 | complete() 1265 | }) 1266 | 1267 | test('correctly serialize input[type=reset]', function (complete) { 1268 | sHelper.testInput( 1269 | sHelper.formElements(1, 'input', 11) 1270 | , 'rst' 1271 | , null 1272 | , 'reset' 1273 | ) 1274 | complete() 1275 | }) 1276 | 1277 | test('correctly serialize input[type=file]', function (complete) { 1278 | sHelper.testInput( 1279 | sHelper.formElements(1, 'input', 12) 1280 | , 'file' 1281 | , null 1282 | , 'file' 1283 | ) 1284 | complete() 1285 | }) 1286 | 1287 | test('correctly serialize input[type=submit]', function (complete) { 1288 | // we're only supposed to serialize a submit button if it was clicked to 1289 | // perform this serialization: 1290 | // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2 1291 | // but we'll pretend to be oblivious to this part of the spec... 1292 | sHelper.testInput( 1293 | sHelper.formElements(1, 'input', 13) 1294 | , 'sub' 1295 | , 'NO' 1296 | , 'submit' 1297 | ) 1298 | complete() 1299 | }) 1300 | 1301 | test('correctly serialize select with no options', function (complete) { 1302 | var select = sHelper.formElements(1, 'select', 0) 1303 | sHelper.testInput(select, 'S1', null, 'select, no options') 1304 | complete() 1305 | }) 1306 | 1307 | test('correctly serialize select with values', function (complete) { 1308 | var select = sHelper.formElements(1, 'select', 1) 1309 | sHelper.testInput(select, 'S2', 'abc', 'select option 1 (default)') 1310 | select.selectedIndex = 1 1311 | sHelper.testInput(select, 'S2', 'def', 'select option 2') 1312 | select.selectedIndex = 6 1313 | sHelper.testInput(select, 'S2', 'disco stu', 'select option 7') 1314 | // a special case where we have , should 1315 | // return '' rather than X which will happen if you just do a simple 1316 | // `value=(option.value||option.text)` 1317 | select.selectedIndex = 9 1318 | sHelper.testInput( 1319 | select 1320 | , 'S2' 1321 | , '' 1322 | , 'select option 9, value="" should yield ""' 1323 | ) 1324 | select.selectedIndex = -1 1325 | sHelper.testInput(select, 'S2', null, 'select, unselected') 1326 | complete() 1327 | }) 1328 | 1329 | test('correctly serialize select without explicit values' 1330 | , function (complete) { 1331 | 1332 | var select = sHelper.formElements(1, 'select', 2) 1333 | sHelper.testInput(select, 'S3', 'ABC', 'select option 1 (default)') 1334 | select.selectedIndex = 1 1335 | sHelper.testInput(select, 'S3', 'DEF', 'select option 2') 1336 | select.selectedIndex = 6 1337 | sHelper.testInput(select, 'S3', 'DISCO STU!', 'select option 7') 1338 | select.selectedIndex = -1 1339 | sHelper.testInput(select, 'S3', null, 'select, unselected') 1340 | complete() 1341 | }) 1342 | 1343 | test('correctly serialize select multiple', function (complete) { 1344 | var select = sHelper.formElements(1, 'select', 3) 1345 | sHelper.testInput(select, 'S4', null, 'select, unselected (default)') 1346 | select.options[1].selected = true 1347 | sHelper.testInput(select, 'S4', '2', 'select option 2') 1348 | select.options[3].selected = true 1349 | sHelper.testInput(select, 'S4', [ '2', '4' ], 'select options 2 & 4') 1350 | select.options[8].selected = true 1351 | sHelper.testInput( 1352 | select 1353 | , 'S4' 1354 | , [ '2', '4', 'Disco Stu!' ] 1355 | , 'select option 2 & 4 & 9' 1356 | ) 1357 | select.options[3].selected = false 1358 | sHelper.testInput( 1359 | select 1360 | , 'S4' 1361 | , [ '2', 'Disco Stu!' ] 1362 | , 'select option 2 & 9' 1363 | ) 1364 | select.options[1].selected = false 1365 | select.options[8].selected = false 1366 | sHelper.testInput(select, 'S4', null, 'select, all unselected') 1367 | complete() 1368 | }) 1369 | 1370 | test('correctly serialize options', function (complete) { 1371 | var option = sHelper.formElements(1, 'select', 1).options[6] 1372 | sHelper.testInput( 1373 | option 1374 | , '-' 1375 | , null 1376 | , 'just option (with value), shouldn\'t serialize' 1377 | ) 1378 | 1379 | option = sHelper.formElements(1, 'select', 2).options[6] 1380 | sHelper.testInput( 1381 | option 1382 | , '-' 1383 | , null 1384 | , 'option (without value), shouldn\'t serialize' 1385 | ) 1386 | 1387 | complete() 1388 | }) 1389 | 1390 | test('correctly serialize disabled', function (complete) { 1391 | var input = sHelper.formElements(1, 'input', 14) 1392 | , select 1393 | 1394 | sHelper.testInput(input, 'D1', null, 'disabled text input') 1395 | input = sHelper.formElements(1, 'input', 15) 1396 | sHelper.testInput(input, 'D2', null, 'disabled checkbox') 1397 | input = sHelper.formElements(1, 'input', 16) 1398 | sHelper.testInput(input, 'D3', null, 'disabled radio') 1399 | 1400 | select = sHelper.formElements(1, 'select', 4) 1401 | sHelper.testInput(select, 'D4', null, 'disabled select') 1402 | select = sHelper.formElements(1, 'select', 3) 1403 | sHelper.testInput(select, 'D5', null, 'disabled select option') 1404 | select = sHelper.formElements(1, 'select', 6) 1405 | sHelper.testInput(select, 'D6', null, 'disabled multi select') 1406 | select = sHelper.formElements(1, 'select', 7) 1407 | sHelper.testInput(select, 'D7', null, 'disabled multi select option') 1408 | complete() 1409 | }) 1410 | 1411 | test('serialize(form)', function (complete) { 1412 | sHelper.testFormSerialize(ajax.serialize, 'direct') 1413 | complete() 1414 | }) 1415 | 1416 | test('serialize(form, {type:\'array\'})', function (complete) { 1417 | sHelper.testFormSerializeArray(ajax.serialize, 'direct') 1418 | complete() 1419 | }) 1420 | 1421 | test('serialize(form, {type:\'map\'})', function (complete) { 1422 | sHelper.testFormSerializeHash(ajax.serialize, 'direct') 1423 | complete() 1424 | }) 1425 | 1426 | // mainly for Ender integration, so you can do this: 1427 | // $('input[name=T2],input[name=who],input[name=wha]').serialize() 1428 | test('serialize(element, element, element...)', function (complete) { 1429 | sHelper.testMultiArgumentSerialize(ajax.serialize, 'direct', PASS_ARGS) 1430 | complete() 1431 | }) 1432 | 1433 | // mainly for Ender integration, so you can do this: 1434 | // $('input[name=T2],input[name=who],input[name=wha]') 1435 | // .serialize({type:'array'}) 1436 | test('serialize(element, element, element..., {type:\'array\'})' 1437 | , function (complete) { 1438 | sHelper.testMultiArgumentSerializeArray( 1439 | ajax.serialize 1440 | , 'direct' 1441 | , PASS_ARGS 1442 | ) 1443 | complete() 1444 | }) 1445 | 1446 | // mainly for Ender integration, so you can do this: 1447 | // $('input[name=T2],input[name=who],input[name=wha]') 1448 | // .serialize({type:'map'}) 1449 | test('serialize(element, element, element...)', function (complete) { 1450 | sHelper.testMultiArgumentSerializeHash( 1451 | ajax.serialize 1452 | , 'direct' 1453 | , PASS_ARGS 1454 | ) 1455 | complete() 1456 | }) 1457 | 1458 | test('toQueryString([{ name: x, value: y }, ... ]) name/value array' 1459 | , function (complete) { 1460 | 1461 | var arr = [ 1462 | { name: 'foo', value: 'bar' } 1463 | , { name: 'baz', value: '' } 1464 | , { name: 'x', value: -20 } 1465 | , { name: 'x', value: 20 } 1466 | ] 1467 | 1468 | ok(ajax.toQueryString(arr) == 'foo=bar&baz=&x=-20&x=20', 'simple') 1469 | 1470 | arr = [ 1471 | { name: 'dotted.name.intact', value: '$@%' } 1472 | , { name: '$ $', value: 20 } 1473 | , { name: 'leave britney alone', value: 'waa haa haa' } 1474 | ] 1475 | 1476 | ok( 1477 | ajax.toQueryString(arr) == 1478 | 'dotted.name.intact=%24%40%25&%24+%24=20' 1479 | + '&leave+britney+alone=waa+haa+haa' 1480 | , 'escaping required' 1481 | ) 1482 | 1483 | complete() 1484 | }) 1485 | 1486 | test('toQueryString({name: value,...} complex object', function (complete) { 1487 | var obj = { 'foo': 'bar', 'baz': '', 'x': -20 } 1488 | 1489 | ok(ajax.toQueryString(obj) == 'foo=bar&baz=&x=-20', 'simple') 1490 | 1491 | obj = { 1492 | 'dotted.name.intact': '$@%' 1493 | , '$ $': 20 1494 | , 'leave britney alone': 'waa haa haa' 1495 | } 1496 | ok( 1497 | ajax.toQueryString(obj) == 1498 | 'dotted.name.intact=%24%40%25&%24+%24=20' 1499 | + '&leave+britney+alone=waa+haa+haa' 1500 | , 'escaping required' 1501 | ) 1502 | 1503 | complete() 1504 | }) 1505 | 1506 | test('toQueryString({name: [ value1, value2 ...],...} object with arrays', function (complete) { 1507 | var obj = { 'foo': 'bar', 'baz': [ '', '', 'boo!' ], 'x': [ -20, 2.2, 20 ] } 1508 | ok(ajax.toQueryString(obj, true) == "foo=bar&baz=&baz=&baz=boo!&x=-20&x=2.2&x=20", "object with arrays") 1509 | ok(ajax.toQueryString(obj) == "foo=bar&baz%5B%5D=&baz%5B%5D=&baz%5B%5D=boo!&x%5B%5D=-20&x%5B%5D=2.2&x%5B%5D=20") 1510 | complete() 1511 | }) 1512 | 1513 | test('toQueryString({name: { nestedName: value },...} object with objects', function(complete) { 1514 | var obj = { 'foo': { 'bar': 'baz' }, 'x': [ { 'bar': 'baz' }, { 'boo': 'hiss' } ] } 1515 | ok(ajax.toQueryString(obj) == "foo%5Bbar%5D=baz&x%5B0%5D%5Bbar%5D=baz&x%5B1%5D%5Bboo%5D=hiss", "object with objects") 1516 | complete() 1517 | }) 1518 | 1519 | }) 1520 | 1521 | sink('Ender Integration', function (test, ok) { 1522 | var sHelper = createSerializeHelper(ok) 1523 | sHelper.reset() 1524 | 1525 | test('$.ajax alias for reqwest, not bound to boosh', 1, function () { 1526 | ok(ender.ajax === ajax, '$.ajax is reqwest') 1527 | }) 1528 | 1529 | // sHelper.test that you can do $.serialize(form) 1530 | test('$.serialize(form)', function (complete) { 1531 | sHelper.testFormSerialize(ender.serialize, 'ender') 1532 | complete() 1533 | }) 1534 | 1535 | // sHelper.test that you can do $.serialize(form) 1536 | test('$.serialize(form, {type:\'array\'})', function (complete) { 1537 | sHelper.testFormSerializeArray(ender.serialize, 'ender') 1538 | complete() 1539 | }) 1540 | 1541 | // sHelper.test that you can do $.serialize(form) 1542 | test('$.serialize(form, {type:\'map\'})', function (complete) { 1543 | sHelper.testFormSerializeHash(ender.serialize, 'ender') 1544 | complete() 1545 | }) 1546 | 1547 | // sHelper.test that you can do $.serializeObject(form) 1548 | test('$.serializeArray(...) alias for serialize(..., {type:\'map\'}' 1549 | , function (complete) { 1550 | sHelper.verifyFormSerializeArray( 1551 | ender.serializeArray(document.forms[0]) 1552 | , 'ender' 1553 | ) 1554 | complete() 1555 | }) 1556 | 1557 | test('$.serialize(element, element, element...)', function (complete) { 1558 | sHelper.testMultiArgumentSerialize(ender.serialize, 'ender', PASS_ARGS) 1559 | complete() 1560 | }) 1561 | 1562 | test('$.serialize(element, element, element..., {type:\'array\'})' 1563 | , function (complete) { 1564 | sHelper.testMultiArgumentSerializeArray( 1565 | ender.serialize 1566 | , 'ender' 1567 | , PASS_ARGS 1568 | ) 1569 | complete() 1570 | }) 1571 | 1572 | test('$.serialize(element, element, element..., {type:\'map\'})' 1573 | , function (complete) { 1574 | sHelper.testMultiArgumentSerializeHash( 1575 | ender.serialize 1576 | , 'ender' 1577 | , PASS_ARGS 1578 | ) 1579 | complete() 1580 | }) 1581 | 1582 | test('$(element, element, element...).serialize()', function (complete) { 1583 | sHelper.testMultiArgumentSerialize(ender.fn.serialize, 'ender', BIND_ARGS) 1584 | complete() 1585 | }) 1586 | 1587 | test('$(element, element, element...).serialize({type:\'array\'})' 1588 | , function (complete) { 1589 | sHelper.testMultiArgumentSerializeArray( 1590 | ender.fn.serialize 1591 | , 'ender' 1592 | , BIND_ARGS 1593 | ) 1594 | complete() 1595 | }) 1596 | 1597 | test('$(element, element, element...).serialize({type:\'map\'})' 1598 | , function (complete) { 1599 | sHelper.testMultiArgumentSerializeHash( 1600 | ender.fn.serialize 1601 | , 'ender' 1602 | , BIND_ARGS 1603 | ) 1604 | complete() 1605 | }) 1606 | 1607 | test('$.toQueryString alias for reqwest.toQueryString, not bound to boosh' 1608 | , function (complete) { 1609 | ok( 1610 | ender.toQueryString === ajax.toQueryString 1611 | , '$.toQueryString is reqwest.toQueryString' 1612 | ) 1613 | complete() 1614 | }) 1615 | }) 1616 | 1617 | 1618 | /** 1619 | * Promise tests for `then` `fail` and `always` 1620 | */ 1621 | sink('Promises', function (test, ok) { 1622 | 1623 | test('always callback is called', function (complete) { 1624 | ajax({ 1625 | url: '/tests/fixtures/fixtures.js' 1626 | }) 1627 | .always(function () { 1628 | ok(true, 'called complete') 1629 | complete() 1630 | }) 1631 | }) 1632 | 1633 | test('success and error handlers are called', 3, function () { 1634 | ajax({ 1635 | url: '/tests/fixtures/invalidJSON.json' 1636 | , type: 'json' 1637 | }) 1638 | .then( 1639 | function () { 1640 | ok(false, 'success callback fired') 1641 | } 1642 | , function (resp, msg) { 1643 | ok( 1644 | msg == 'Could not parse JSON in response' 1645 | , 'error callback fired' 1646 | ) 1647 | } 1648 | ) 1649 | 1650 | ajax({ 1651 | url: '/tests/fixtures/invalidJSON.json' 1652 | , type: 'json' 1653 | }) 1654 | .fail(function (resp, msg) { 1655 | ok(msg == 'Could not parse JSON in response', 'fail callback fired') 1656 | }) 1657 | 1658 | ajax({ 1659 | url: '/tests/fixtures/fixtures.json' 1660 | , type: 'json' 1661 | }) 1662 | .then( 1663 | function () { 1664 | ok(true, 'success callback fired') 1665 | } 1666 | , function () { 1667 | ok(false, 'error callback fired') 1668 | } 1669 | ) 1670 | }) 1671 | 1672 | test('then is chainable', 2, function () { 1673 | ajax({ 1674 | url: '/tests/fixtures/fixtures.json' 1675 | , type: 'json' 1676 | }) 1677 | .then( 1678 | function (resp) { 1679 | ok(true, 'first success callback fired') 1680 | return 'new value'; 1681 | } 1682 | ) 1683 | .then( 1684 | function (resp) { 1685 | ok(resp === 'new value', 'second success callback fired') 1686 | } 1687 | ) 1688 | }) 1689 | 1690 | test('success does not chain with then', 2, function () { 1691 | ajax({ 1692 | url: '/tests/fixtures/fixtures.json' 1693 | , type: 'json' 1694 | , success: function() { 1695 | ok(true, 'success callback fired') 1696 | return 'some independent value'; 1697 | } 1698 | }) 1699 | .then( 1700 | function (resp) { 1701 | ok( 1702 | resp && resp !== 'some independent value' 1703 | , 'then callback fired' 1704 | ) 1705 | } 1706 | ) 1707 | }) 1708 | 1709 | test('then & always handlers can be added after a response is received' 1710 | , 2 1711 | , function () { 1712 | 1713 | var a = ajax({ 1714 | url: '/tests/fixtures/fixtures.json' 1715 | , type: 'json' 1716 | }) 1717 | .always(function () { 1718 | setTimeout(function () { 1719 | a.then( 1720 | function () { 1721 | ok(true, 'success callback called') 1722 | } 1723 | , function () { 1724 | ok(false, 'error callback called') 1725 | } 1726 | ).always(function () { 1727 | ok(true, 'complete callback called') 1728 | }) 1729 | }, 1) 1730 | }) 1731 | }) 1732 | 1733 | test('then is chainable after a response is received' 1734 | , 2 1735 | , function () { 1736 | 1737 | var a = ajax({ 1738 | url: '/tests/fixtures/fixtures.json' 1739 | , type: 'json' 1740 | }) 1741 | .always(function () { 1742 | setTimeout(function () { 1743 | a.then(function () { 1744 | ok(true, 'first success callback called') 1745 | return 'new value'; 1746 | }).then(function (resp) { 1747 | ok(resp === 'new value', 'second success callback called') 1748 | }) 1749 | }, 1) 1750 | }) 1751 | }) 1752 | 1753 | test('failure handlers can be added after a response is received' 1754 | , function (complete) { 1755 | 1756 | var a = ajax({ 1757 | url: '/tests/fixtures/invalidJSON.json' 1758 | , type: 'json' 1759 | }) 1760 | .always(function () { 1761 | setTimeout(function () { 1762 | a 1763 | .fail(function () { 1764 | ok(true, 'fail callback called') 1765 | complete() 1766 | }) 1767 | }, 1) 1768 | }) 1769 | }) 1770 | 1771 | test('.then success and fail are optional parameters', 1, function () { 1772 | try { 1773 | ajax({ 1774 | url: '/tests/fixtures/invalidJSON.json' 1775 | , type: 'json' 1776 | }) 1777 | .then() 1778 | } catch (ex) { 1779 | ok(false, '.then() parameters should be optional') 1780 | } finally { 1781 | ok(true, 'passed .then() optional parameters') 1782 | } 1783 | }) 1784 | 1785 | }) 1786 | 1787 | 1788 | 1789 | sink('Timeout', function (test, ok) { 1790 | test('xmlHttpRequest', function (complete) { 1791 | var ts = +new Date() 1792 | ajax({ 1793 | url: '/tests/timeout' 1794 | , type: 'json' 1795 | , timeout: 250 1796 | , error: function (err, msg) { 1797 | ok(err, 'received error response') 1798 | try { 1799 | ok(err && err.status === 0, 'correctly caught timeout') 1800 | ok(msg && msg === 'Request is aborted: timeout', 'timeout message received') 1801 | } catch (e) { 1802 | ok(true, 'IE is a troll') 1803 | } 1804 | var tt = Math.abs(+new Date() - ts) 1805 | ok( 1806 | tt > 200 && tt < 300 1807 | , 'timeout close enough to 250 (' + tt + ')' 1808 | ) 1809 | complete() 1810 | } 1811 | }) 1812 | }) 1813 | 1814 | test('jsonpRequest', function (complete) { 1815 | var ts = +new Date() 1816 | ajax({ 1817 | url: '/tests/timeout' 1818 | , type: 'jsonp' 1819 | , timeout: 250 1820 | , error: function (err) { 1821 | ok(err, 'received error response') 1822 | var tt = Math.abs(+new Date() - ts) 1823 | ok( 1824 | tt > 200 && tt < 300 1825 | , 'timeout close enough to 250 (' + tt + ')' 1826 | ) 1827 | complete() 1828 | } 1829 | }) 1830 | }) 1831 | }) 1832 | 1833 | start() 1834 | 1835 | }(reqwest)) 1836 | -------------------------------------------------------------------------------- /use-me.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [{ 3 | "path": ".", 4 | "folder_exclude_patterns": [ 5 | "node_modules" 6 | ], 7 | "file_exclude_patterns": [ 8 | "*.sublime-workspace" 9 | ]} 10 | ], 11 | 12 | "settings": { 13 | "tab_size": 2, 14 | "translate_tabs_to_spaces": true, 15 | "trim_trailing_white_space_on_save": true, 16 | "ensure_newline_at_eof_on_save": true 17 | }, 18 | 19 | "build_systems": [{ 20 | "name": "test", 21 | "working_dir": "$project_path", 22 | "cmd": ["make", "test"] 23 | }] 24 | } 25 | -------------------------------------------------------------------------------- /vendor/phantomjs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ded/reqwest/22da21c5de609d5b75ee21eb4484bce42c474973/vendor/phantomjs --------------------------------------------------------------------------------