hoarder-js 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. data/CHANGELOG +2 -0
  2. data/Gemfile.lock +2 -2
  3. data/Manifest +14 -3
  4. data/assets/scripts/coffee/hoarder/form/form.coffee +47 -44
  5. data/assets/scripts/coffee/hoarder/form/form_serializer.coffee +23 -0
  6. data/assets/scripts/coffee/hoarder/form_manager.coffee +35 -19
  7. data/assets/scripts/coffee/hoarder/submitter/form_submitter.coffee +10 -15
  8. data/assets/scripts/coffee/hoarder/submitter/submitters/base_submitter.coffee +11 -0
  9. data/assets/scripts/coffee/hoarder/submitter/submitters/polling_submitter.coffee +30 -27
  10. data/assets/scripts/coffee/hoarder/submitter/submitters/simple_submitter.coffee +11 -21
  11. data/assets/scripts/coffee/hoarder/validator/constraints/alpha_constraint.coffee +8 -14
  12. data/assets/scripts/coffee/hoarder/validator/constraints/alphanumeric_constraint.coffee +8 -14
  13. data/assets/scripts/coffee/hoarder/validator/constraints/base_constraint.coffee +10 -0
  14. data/assets/scripts/coffee/hoarder/validator/constraints/credit_card_constraint.coffee +8 -14
  15. data/assets/scripts/coffee/hoarder/validator/constraints/email_constraint.coffee +8 -14
  16. data/assets/scripts/coffee/hoarder/validator/constraints/max_length_constraint.coffee +8 -14
  17. data/assets/scripts/coffee/hoarder/validator/constraints/min_length_constraint.coffee +8 -14
  18. data/assets/scripts/coffee/hoarder/validator/constraints/numeric_constraint.coffee +8 -14
  19. data/assets/scripts/coffee/hoarder/validator/constraints/phone_constraint.coffee +8 -14
  20. data/assets/scripts/coffee/hoarder/validator/constraints/required_constraint.coffee +8 -14
  21. data/assets/scripts/coffee/hoarder/validator/form_validator.coffee +30 -25
  22. data/assets/scripts/coffee/hoarder/validator/rules/validation_rule.coffee +10 -0
  23. data/assets/scripts/js/lib/bonzo.js +1151 -0
  24. data/assets/scripts/js/lib/qwery.js +369 -0
  25. data/assets/scripts/js/lib/reqwest.js +565 -0
  26. data/assets/scripts/js/patches/event_listeners.js +73 -0
  27. data/bin/hoarder.js +2807 -369
  28. data/hoarder-js.gemspec +3 -3
  29. data/spec/jasmine.yml +3 -0
  30. data/spec/runner.html +44 -34
  31. data/spec/support/fixtures.coffee +19 -0
  32. data/spec/support/lib/jasmine-fixture.js +506 -0
  33. data/spec/support/lib/jquery-1.7.1.min.js +4 -0
  34. data/spec/support/mocks.coffee +12 -37
  35. data/spec/support/requirements.coffee +0 -1
  36. data/spec/tests/form/form_serializer_spec.coffee +78 -0
  37. data/spec/tests/form/form_spec.coffee +127 -0
  38. data/spec/tests/form_manager_spec.coffee +128 -40
  39. data/spec/tests/submitter/form_submitter_spec.coffee +59 -42
  40. data/spec/tests/submitter/submitters/polling_submitter_spec.coffee +102 -57
  41. data/spec/tests/submitter/submitters/simple_submitter_spec.coffee +42 -16
  42. data/spec/tests/validator/constraints_spec.coffee +65 -57
  43. data/spec/tests/validator/form_validator_spec.coffee +39 -23
  44. data/spec/tests/validator/validation_rule_spec.coffee +14 -0
  45. metadata +16 -5
  46. data/assets/scripts/coffee/hoarder/form/form_element.coffee +0 -22
  47. data/assets/scripts/coffee/hoarder/validator/error/validation_error.coffee +0 -9
  48. data/assets/scripts/js/lib/jquery.js +0 -5
@@ -0,0 +1,369 @@
1
+ /*!
2
+ * @preserve Qwery - A Blazing Fast query selector engine
3
+ * https://github.com/ded/qwery
4
+ * copyright Dustin Diaz 2012
5
+ * MIT License
6
+ */
7
+
8
+ (function (name, context, definition) {
9
+ if (typeof module != 'undefined' && module.exports) module.exports = definition()
10
+ else if (typeof define == 'function' && define.amd) define(definition)
11
+ else context[name] = definition()
12
+ })('qwery', this, function () {
13
+ var doc = document
14
+ , html = doc.documentElement
15
+ , byClass = 'getElementsByClassName'
16
+ , byTag = 'getElementsByTagName'
17
+ , qSA = 'querySelectorAll'
18
+ , useNativeQSA = 'useNativeQSA'
19
+ , tagName = 'tagName'
20
+ , nodeType = 'nodeType'
21
+ , select // main select() method, assign later
22
+
23
+ , id = /#([\w\-]+)/
24
+ , clas = /\.[\w\-]+/g
25
+ , idOnly = /^#([\w\-]+)$/
26
+ , classOnly = /^\.([\w\-]+)$/
27
+ , tagOnly = /^([\w\-]+)$/
28
+ , tagAndOrClass = /^([\w]+)?\.([\w\-]+)$/
29
+ , splittable = /(^|,)\s*[>~+]/
30
+ , normalizr = /^\s+|\s*([,\s\+\~>]|$)\s*/g
31
+ , splitters = /[\s\>\+\~]/
32
+ , splittersMore = /(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\]|[\s\w\+\-]*\))/
33
+ , specialChars = /([.*+?\^=!:${}()|\[\]\/\\])/g
34
+ , simple = /^(\*|[a-z0-9]+)?(?:([\.\#]+[\w\-\.#]+)?)/
35
+ , attr = /\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\]/
36
+ , pseudo = /:([\w\-]+)(\(['"]?([^()]+)['"]?\))?/
37
+ , easy = new RegExp(idOnly.source + '|' + tagOnly.source + '|' + classOnly.source)
38
+ , dividers = new RegExp('(' + splitters.source + ')' + splittersMore.source, 'g')
39
+ , tokenizr = new RegExp(splitters.source + splittersMore.source)
40
+ , chunker = new RegExp(simple.source + '(' + attr.source + ')?' + '(' + pseudo.source + ')?')
41
+
42
+ var walker = {
43
+ ' ': function (node) {
44
+ return node && node !== html && node.parentNode
45
+ }
46
+ , '>': function (node, contestant) {
47
+ return node && node.parentNode == contestant.parentNode && node.parentNode
48
+ }
49
+ , '~': function (node) {
50
+ return node && node.previousSibling
51
+ }
52
+ , '+': function (node, contestant, p1, p2) {
53
+ if (!node) return false
54
+ return (p1 = previous(node)) && (p2 = previous(contestant)) && p1 == p2 && p1
55
+ }
56
+ }
57
+
58
+ function cache() {
59
+ this.c = {}
60
+ }
61
+ cache.prototype = {
62
+ g: function (k) {
63
+ return this.c[k] || undefined
64
+ }
65
+ , s: function (k, v, r) {
66
+ v = r ? new RegExp(v) : v
67
+ return (this.c[k] = v)
68
+ }
69
+ }
70
+
71
+ var classCache = new cache()
72
+ , cleanCache = new cache()
73
+ , attrCache = new cache()
74
+ , tokenCache = new cache()
75
+
76
+ function classRegex(c) {
77
+ return classCache.g(c) || classCache.s(c, '(^|\\s+)' + c + '(\\s+|$)', 1)
78
+ }
79
+
80
+ // not quite as fast as inline loops in older browsers so don't use liberally
81
+ function each(a, fn) {
82
+ var i = 0, l = a.length
83
+ for (; i < l; i++) fn(a[i])
84
+ }
85
+
86
+ function flatten(ar) {
87
+ for (var r = [], i = 0, l = ar.length; i < l; ++i) arrayLike(ar[i]) ? (r = r.concat(ar[i])) : (r[r.length] = ar[i])
88
+ return r
89
+ }
90
+
91
+ function arrayify(ar) {
92
+ var i = 0, l = ar.length, r = []
93
+ for (; i < l; i++) r[i] = ar[i]
94
+ return r
95
+ }
96
+
97
+ function previous(n) {
98
+ while (n = n.previousSibling) if (n[nodeType] == 1) break;
99
+ return n
100
+ }
101
+
102
+ function q(query) {
103
+ return query.match(chunker)
104
+ }
105
+
106
+ // called using `this` as element and arguments from regex group results.
107
+ // given => div.hello[title="world"]:foo('bar')
108
+ // div.hello[title="world"]:foo('bar'), div, .hello, [title="world"], title, =, world, :foo('bar'), foo, ('bar'), bar]
109
+ function interpret(whole, tag, idsAndClasses, wholeAttribute, attribute, qualifier, value, wholePseudo, pseudo, wholePseudoVal, pseudoVal) {
110
+ var i, m, k, o, classes
111
+ if (this[nodeType] !== 1) return false
112
+ if (tag && tag !== '*' && this[tagName] && this[tagName].toLowerCase() !== tag) return false
113
+ if (idsAndClasses && (m = idsAndClasses.match(id)) && m[1] !== this.id) return false
114
+ if (idsAndClasses && (classes = idsAndClasses.match(clas))) {
115
+ for (i = classes.length; i--;) if (!classRegex(classes[i].slice(1)).test(this.className)) return false
116
+ }
117
+ if (pseudo && qwery.pseudos[pseudo] && !qwery.pseudos[pseudo](this, pseudoVal)) return false
118
+ if (wholeAttribute && !value) { // select is just for existance of attrib
119
+ o = this.attributes
120
+ for (k in o) {
121
+ if (Object.prototype.hasOwnProperty.call(o, k) && (o[k].name || k) == attribute) {
122
+ return this
123
+ }
124
+ }
125
+ }
126
+ if (wholeAttribute && !checkAttr(qualifier, getAttr(this, attribute) || '', value)) {
127
+ // select is for attrib equality
128
+ return false
129
+ }
130
+ return this
131
+ }
132
+
133
+ function clean(s) {
134
+ return cleanCache.g(s) || cleanCache.s(s, s.replace(specialChars, '\\$1'))
135
+ }
136
+
137
+ function checkAttr(qualify, actual, val) {
138
+ switch (qualify) {
139
+ case '=':
140
+ return actual == val
141
+ case '^=':
142
+ return actual.match(attrCache.g('^=' + val) || attrCache.s('^=' + val, '^' + clean(val), 1))
143
+ case '$=':
144
+ return actual.match(attrCache.g('$=' + val) || attrCache.s('$=' + val, clean(val) + '$', 1))
145
+ case '*=':
146
+ return actual.match(attrCache.g(val) || attrCache.s(val, clean(val), 1))
147
+ case '~=':
148
+ return actual.match(attrCache.g('~=' + val) || attrCache.s('~=' + val, '(?:^|\\s+)' + clean(val) + '(?:\\s+|$)', 1))
149
+ case '|=':
150
+ return actual.match(attrCache.g('|=' + val) || attrCache.s('|=' + val, '^' + clean(val) + '(-|$)', 1))
151
+ }
152
+ return 0
153
+ }
154
+
155
+ // given a selector, first check for simple cases then collect all base candidate matches and filter
156
+ function _qwery(selector, _root) {
157
+ var r = [], ret = [], i, l, m, token, tag, els, intr, item, root = _root
158
+ , tokens = tokenCache.g(selector) || tokenCache.s(selector, selector.split(tokenizr))
159
+ , dividedTokens = selector.match(dividers)
160
+
161
+ if (!tokens.length) return r
162
+
163
+ token = (tokens = tokens.slice(0)).pop() // copy cached tokens, take the last one
164
+ if (tokens.length && (m = tokens[tokens.length - 1].match(idOnly))) root = byId(_root, m[1])
165
+ if (!root) return r
166
+
167
+ intr = q(token)
168
+ // collect base candidates to filter
169
+ els = root !== _root && root[nodeType] !== 9 && dividedTokens && /^[+~]$/.test(dividedTokens[dividedTokens.length - 1]) ?
170
+ function (r) {
171
+ while (root = root.nextSibling) {
172
+ root[nodeType] == 1 && (intr[1] ? intr[1] == root[tagName].toLowerCase() : 1) && (r[r.length] = root)
173
+ }
174
+ return r
175
+ }([]) :
176
+ root[byTag](intr[1] || '*')
177
+ // filter elements according to the right-most part of the selector
178
+ for (i = 0, l = els.length; i < l; i++) {
179
+ if (item = interpret.apply(els[i], intr)) r[r.length] = item
180
+ }
181
+ if (!tokens.length) return r
182
+
183
+ // filter further according to the rest of the selector (the left side)
184
+ each(r, function (e) { if (ancestorMatch(e, tokens, dividedTokens)) ret[ret.length] = e })
185
+ return ret
186
+ }
187
+
188
+ // compare element to a selector
189
+ function is(el, selector, root) {
190
+ if (isNode(selector)) return el == selector
191
+ if (arrayLike(selector)) return !!~flatten(selector).indexOf(el) // if selector is an array, is el a member?
192
+
193
+ var selectors = selector.split(','), tokens, dividedTokens
194
+ while (selector = selectors.pop()) {
195
+ tokens = tokenCache.g(selector) || tokenCache.s(selector, selector.split(tokenizr))
196
+ dividedTokens = selector.match(dividers)
197
+ tokens = tokens.slice(0) // copy array
198
+ if (interpret.apply(el, q(tokens.pop())) && (!tokens.length || ancestorMatch(el, tokens, dividedTokens, root))) {
199
+ return true
200
+ }
201
+ }
202
+ return false
203
+ }
204
+
205
+ // given elements matching the right-most part of a selector, filter out any that don't match the rest
206
+ function ancestorMatch(el, tokens, dividedTokens, root) {
207
+ var cand
208
+ // recursively work backwards through the tokens and up the dom, covering all options
209
+ function crawl(e, i, p) {
210
+ while (p = walker[dividedTokens[i]](p, e)) {
211
+ if (isNode(p) && (interpret.apply(p, q(tokens[i])))) {
212
+ if (i) {
213
+ if (cand = crawl(p, i - 1, p)) return cand
214
+ } else return p
215
+ }
216
+ }
217
+ }
218
+ return (cand = crawl(el, tokens.length - 1, el)) && (!root || isAncestor(cand, root))
219
+ }
220
+
221
+ function isNode(el, t) {
222
+ return el && typeof el === 'object' && (t = el[nodeType]) && (t == 1 || t == 9)
223
+ }
224
+
225
+ function uniq(ar) {
226
+ var a = [], i, j;
227
+ o:
228
+ for (i = 0; i < ar.length; ++i) {
229
+ for (j = 0; j < a.length; ++j) if (a[j] == ar[i]) continue o
230
+ a[a.length] = ar[i]
231
+ }
232
+ return a
233
+ }
234
+
235
+ function arrayLike(o) {
236
+ return (typeof o === 'object' && isFinite(o.length))
237
+ }
238
+
239
+ function normalizeRoot(root) {
240
+ if (!root) return doc
241
+ if (typeof root == 'string') return qwery(root)[0]
242
+ if (!root[nodeType] && arrayLike(root)) return root[0]
243
+ return root
244
+ }
245
+
246
+ function byId(root, id, el) {
247
+ // if doc, query on it, else query the parent doc or if a detached fragment rewrite the query and run on the fragment
248
+ return root[nodeType] === 9 ? root.getElementById(id) :
249
+ root.ownerDocument &&
250
+ (((el = root.ownerDocument.getElementById(id)) && isAncestor(el, root) && el) ||
251
+ (!isAncestor(root, root.ownerDocument) && select('[id="' + id + '"]', root)[0]))
252
+ }
253
+
254
+ function qwery(selector, _root) {
255
+ var m, el, root = normalizeRoot(_root)
256
+
257
+ // easy, fast cases that we can dispatch with simple DOM calls
258
+ if (!root || !selector) return []
259
+ if (selector === window || isNode(selector)) {
260
+ return !_root || (selector !== window && isNode(root) && isAncestor(selector, root)) ? [selector] : []
261
+ }
262
+ if (selector && arrayLike(selector)) return flatten(selector)
263
+ if (m = selector.match(easy)) {
264
+ if (m[1]) return (el = byId(root, m[1])) ? [el] : []
265
+ if (m[2]) return arrayify(root[byTag](m[2]))
266
+ if (hasByClass && m[3]) return arrayify(root[byClass](m[3]))
267
+ }
268
+
269
+ return select(selector, root)
270
+ }
271
+
272
+ // where the root is not document and a relationship selector is first we have to
273
+ // do some awkward adjustments to get it to work, even with qSA
274
+ function collectSelector(root, collector) {
275
+ return function (s) {
276
+ var oid, nid
277
+ if (splittable.test(s)) {
278
+ if (root[nodeType] !== 9) {
279
+ // make sure the el has an id, rewrite the query, set root to doc and run it
280
+ if (!(nid = oid = root.getAttribute('id'))) root.setAttribute('id', nid = '__qwerymeupscotty')
281
+ s = '[id="' + nid + '"]' + s // avoid byId and allow us to match context element
282
+ collector(root.parentNode || root, s, true)
283
+ oid || root.removeAttribute('id')
284
+ }
285
+ return;
286
+ }
287
+ s.length && collector(root, s, false)
288
+ }
289
+ }
290
+
291
+ var isAncestor = 'compareDocumentPosition' in html ?
292
+ function (element, container) {
293
+ return (container.compareDocumentPosition(element) & 16) == 16
294
+ } : 'contains' in html ?
295
+ function (element, container) {
296
+ container = container[nodeType] === 9 || container == window ? html : container
297
+ return container !== element && container.contains(element)
298
+ } :
299
+ function (element, container) {
300
+ while (element = element.parentNode) if (element === container) return 1
301
+ return 0
302
+ }
303
+ , getAttr = function () {
304
+ // detect buggy IE src/href getAttribute() call
305
+ var e = doc.createElement('p')
306
+ return ((e.innerHTML = '<a href="#x">x</a>') && e.firstChild.getAttribute('href') != '#x') ?
307
+ function (e, a) {
308
+ return a === 'class' ? e.className : (a === 'href' || a === 'src') ?
309
+ e.getAttribute(a, 2) : e.getAttribute(a)
310
+ } :
311
+ function (e, a) { return e.getAttribute(a) }
312
+ }()
313
+ , hasByClass = !!doc[byClass]
314
+ // has native qSA support
315
+ , hasQSA = doc.querySelector && doc[qSA]
316
+ // use native qSA
317
+ , selectQSA = function (selector, root) {
318
+ var result = [], ss, e
319
+ try {
320
+ if (root[nodeType] === 9 || !splittable.test(selector)) {
321
+ // most work is done right here, defer to qSA
322
+ return arrayify(root[qSA](selector))
323
+ }
324
+ // special case where we need the services of `collectSelector()`
325
+ each(ss = selector.split(','), collectSelector(root, function (ctx, s) {
326
+ e = ctx[qSA](s)
327
+ if (e.length == 1) result[result.length] = e.item(0)
328
+ else if (e.length) result = result.concat(arrayify(e))
329
+ }))
330
+ return ss.length > 1 && result.length > 1 ? uniq(result) : result
331
+ } catch (ex) { }
332
+ return selectNonNative(selector, root)
333
+ }
334
+ // no native selector support
335
+ , selectNonNative = function (selector, root) {
336
+ var result = [], items, m, i, l, r, ss
337
+ selector = selector.replace(normalizr, '$1')
338
+ if (m = selector.match(tagAndOrClass)) {
339
+ r = classRegex(m[2])
340
+ items = root[byTag](m[1] || '*')
341
+ for (i = 0, l = items.length; i < l; i++) {
342
+ if (r.test(items[i].className)) result[result.length] = items[i]
343
+ }
344
+ return result
345
+ }
346
+ // more complex selector, get `_qwery()` to do the work for us
347
+ each(ss = selector.split(','), collectSelector(root, function (ctx, s, rewrite) {
348
+ r = _qwery(s, ctx)
349
+ for (i = 0, l = r.length; i < l; i++) {
350
+ if (ctx[nodeType] === 9 || rewrite || isAncestor(r[i], root)) result[result.length] = r[i]
351
+ }
352
+ }))
353
+ return ss.length > 1 && result.length > 1 ? uniq(result) : result
354
+ }
355
+ , configure = function (options) {
356
+ // configNativeQSA: use fully-internal selector or native qSA where present
357
+ if (typeof options[useNativeQSA] !== 'undefined')
358
+ select = !options[useNativeQSA] ? selectNonNative : hasQSA ? selectQSA : selectNonNative
359
+ }
360
+
361
+ configure({ useNativeQSA: true })
362
+
363
+ qwery.configure = configure
364
+ qwery.uniq = uniq
365
+ qwery.is = is
366
+ qwery.pseudos = {}
367
+
368
+ return qwery
369
+ });
@@ -0,0 +1,565 @@
1
+ /*!
2
+ * Reqwest! A general purpose XHR connection manager
3
+ * (c) Dustin Diaz 2013
4
+ * https://github.com/ded/reqwest
5
+ * license MIT
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 win = window
14
+ , doc = document
15
+ , twoHundo = /^20\d$/
16
+ , byTag = 'getElementsByTagName'
17
+ , readyState = 'readyState'
18
+ , contentType = 'Content-Type'
19
+ , requestedWith = 'X-Requested-With'
20
+ , head = doc[byTag]('head')[0]
21
+ , uniqid = 0
22
+ , callbackPrefix = 'reqwest_' + (+new Date())
23
+ , lastValue // data stored by the most recent JSONP callback
24
+ , xmlHttpRequest = 'XMLHttpRequest'
25
+ , noop = function () {}
26
+
27
+ , isArray = typeof Array.isArray == 'function'
28
+ ? Array.isArray
29
+ : function (a) {
30
+ return a instanceof Array
31
+ }
32
+
33
+ , defaultHeaders = {
34
+ contentType: 'application/x-www-form-urlencoded'
35
+ , requestedWith: xmlHttpRequest
36
+ , accept: {
37
+ '*': 'text/javascript, text/html, application/xml, text/xml, */*'
38
+ , xml: 'application/xml, text/xml'
39
+ , html: 'text/html'
40
+ , text: 'text/plain'
41
+ , json: 'application/json, text/javascript'
42
+ , js: 'application/javascript, text/javascript'
43
+ }
44
+ }
45
+
46
+ , xhr = win[xmlHttpRequest]
47
+ ? function () {
48
+ return new XMLHttpRequest()
49
+ }
50
+ : function () {
51
+ return new ActiveXObject('Microsoft.XMLHTTP')
52
+ }
53
+ , globalSetupOptions = {
54
+ dataFilter: function (data) {
55
+ return data
56
+ }
57
+ }
58
+
59
+ function handleReadyState(r, success, error) {
60
+ return function () {
61
+ // use _aborted to mitigate against IE err c00c023f
62
+ // (can't read props on aborted request objects)
63
+ if (r._aborted) return error(r.request)
64
+ if (r.request && r.request[readyState] == 4) {
65
+ r.request.onreadystatechange = noop
66
+ if (twoHundo.test(r.request.status))
67
+ success(r.request)
68
+ else
69
+ error(r.request)
70
+ }
71
+ }
72
+ }
73
+
74
+ function setHeaders(http, o) {
75
+ var headers = o.headers || {}
76
+ , h
77
+
78
+ headers.Accept = headers.Accept
79
+ || defaultHeaders.accept[o.type]
80
+ || defaultHeaders.accept['*']
81
+
82
+ // breaks cross-origin requests with legacy browsers
83
+ if (!o.crossOrigin && !headers[requestedWith]) headers[requestedWith] = defaultHeaders.requestedWith
84
+ if (!headers[contentType]) headers[contentType] = o.contentType || defaultHeaders.contentType
85
+ for (h in headers)
86
+ headers.hasOwnProperty(h) && http.setRequestHeader(h, headers[h])
87
+ }
88
+
89
+ function setCredentials(http, o) {
90
+ if (typeof o.withCredentials !== 'undefined' && typeof http.withCredentials !== 'undefined') {
91
+ http.withCredentials = !!o.withCredentials
92
+ }
93
+ }
94
+
95
+ function generalCallback(data) {
96
+ lastValue = data
97
+ }
98
+
99
+ function urlappend (url, s) {
100
+ return url + (/\?/.test(url) ? '&' : '?') + s
101
+ }
102
+
103
+ function handleJsonp(o, fn, err, url) {
104
+ var reqId = uniqid++
105
+ , cbkey = o.jsonpCallback || 'callback' // the 'callback' key
106
+ , cbval = o.jsonpCallbackName || reqwest.getcallbackPrefix(reqId)
107
+ // , cbval = o.jsonpCallbackName || ('reqwest_' + reqId) // the 'callback' value
108
+ , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)')
109
+ , match = url.match(cbreg)
110
+ , script = doc.createElement('script')
111
+ , loaded = 0
112
+ , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1
113
+
114
+ if (match) {
115
+ if (match[3] === '?') {
116
+ url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name
117
+ } else {
118
+ cbval = match[3] // provided callback func name
119
+ }
120
+ } else {
121
+ url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em
122
+ }
123
+
124
+ win[cbval] = generalCallback
125
+
126
+ script.type = 'text/javascript'
127
+ script.src = url
128
+ script.async = true
129
+ if (typeof script.onreadystatechange !== 'undefined' && !isIE10) {
130
+ // need this for IE due to out-of-order onreadystatechange(), binding script
131
+ // execution to an event listener gives us control over when the script
132
+ // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
133
+ //
134
+ // if this hack is used in IE10 jsonp callback are never called
135
+ script.event = 'onclick'
136
+ script.htmlFor = script.id = '_reqwest_' + reqId
137
+ }
138
+
139
+ script.onload = script.onreadystatechange = function () {
140
+ if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) {
141
+ return false
142
+ }
143
+ script.onload = script.onreadystatechange = null
144
+ script.onclick && script.onclick()
145
+ // Call the user callback with the last value stored and clean up values and scripts.
146
+ fn(lastValue)
147
+ lastValue = undefined
148
+ head.removeChild(script)
149
+ loaded = 1
150
+ }
151
+
152
+ // Add the script to the DOM head
153
+ head.appendChild(script)
154
+
155
+ // Enable JSONP timeout
156
+ return {
157
+ abort: function () {
158
+ script.onload = script.onreadystatechange = null
159
+ err({}, 'Request is aborted: timeout', {})
160
+ lastValue = undefined
161
+ head.removeChild(script)
162
+ loaded = 1
163
+ }
164
+ }
165
+ }
166
+
167
+ function getRequest(fn, err) {
168
+ var o = this.o
169
+ , method = (o.method || 'GET').toUpperCase()
170
+ , url = typeof o === 'string' ? o : o.url
171
+ // convert non-string objects to query-string form unless o.processData is false
172
+ , data = (o.processData !== false && o.data && typeof o.data !== 'string')
173
+ ? reqwest.toQueryString(o.data)
174
+ : (o.data || null)
175
+ , http
176
+
177
+ // if we're working on a GET request and we have data then we should append
178
+ // query string to end of URL and not post data
179
+ if ((o.type == 'jsonp' || method == 'GET') && data) {
180
+ url = urlappend(url, data)
181
+ data = null
182
+ }
183
+
184
+ if (o.type == 'jsonp') return handleJsonp(o, fn, err, url)
185
+
186
+ http = xhr()
187
+ http.open(method, url, o.async === false ? false : true)
188
+ setHeaders(http, o)
189
+ setCredentials(http, o)
190
+ http.onreadystatechange = handleReadyState(this, fn, err)
191
+ o.before && o.before(http)
192
+ http.send(data)
193
+ return http
194
+ }
195
+
196
+ function Reqwest(o, fn) {
197
+ this.o = o
198
+ this.fn = fn
199
+
200
+ init.apply(this, arguments)
201
+ }
202
+
203
+ function setType(url) {
204
+ var m = url.match(/\.(json|jsonp|html|xml)(\?|$)/)
205
+ return m ? m[1] : 'js'
206
+ }
207
+
208
+ function init(o, fn) {
209
+
210
+ this.url = typeof o == 'string' ? o : o.url
211
+ this.timeout = null
212
+
213
+ // whether request has been fulfilled for purpose
214
+ // of tracking the Promises
215
+ this._fulfilled = false
216
+ // success handlers
217
+ this._fulfillmentHandlers = []
218
+ // error handlers
219
+ this._errorHandlers = []
220
+ // complete (both success and fail) handlers
221
+ this._completeHandlers = []
222
+ this._erred = false
223
+ this._responseArgs = {}
224
+
225
+ var self = this
226
+ , type = o.type || setType(this.url)
227
+
228
+ fn = fn || function () {}
229
+
230
+ if (o.timeout) {
231
+ this.timeout = setTimeout(function () {
232
+ self.abort()
233
+ }, o.timeout)
234
+ }
235
+
236
+ if (o.success) {
237
+ this._fulfillmentHandlers.push(function () {
238
+ o.success.apply(o, arguments)
239
+ })
240
+ }
241
+
242
+ if (o.error) {
243
+ this._errorHandlers.push(function () {
244
+ o.error.apply(o, arguments)
245
+ })
246
+ }
247
+
248
+ if (o.complete) {
249
+ this._completeHandlers.push(function () {
250
+ o.complete.apply(o, arguments)
251
+ })
252
+ }
253
+
254
+ function complete (resp) {
255
+ o.timeout && clearTimeout(self.timeout)
256
+ self.timeout = null
257
+ while (self._completeHandlers.length > 0) {
258
+ self._completeHandlers.shift()(resp)
259
+ }
260
+ }
261
+
262
+ function success (resp) {
263
+ // use global data filter on response text
264
+ var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type)
265
+ , r = filteredResponse
266
+ try {
267
+ resp.responseText = r
268
+ } catch (e) {
269
+ // can't assign this in IE<=8, just ignore
270
+ }
271
+ if (r) {
272
+ switch (type) {
273
+ case 'json':
274
+ try {
275
+ resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')')
276
+ } catch (err) {
277
+ return error(resp, 'Could not parse JSON in response', err)
278
+ }
279
+ break
280
+ case 'js':
281
+ resp = eval(r)
282
+ break
283
+ case 'html':
284
+ resp = r
285
+ break
286
+ case 'xml':
287
+ resp = resp.responseXML
288
+ && resp.responseXML.parseError // IE trololo
289
+ && resp.responseXML.parseError.errorCode
290
+ && resp.responseXML.parseError.reason
291
+ ? null
292
+ : resp.responseXML
293
+ break
294
+ }
295
+ }
296
+
297
+ self._responseArgs.resp = resp
298
+ self._fulfilled = true
299
+ fn(resp)
300
+ while (self._fulfillmentHandlers.length > 0) {
301
+ self._fulfillmentHandlers.shift()(resp)
302
+ }
303
+
304
+ complete(resp)
305
+ }
306
+
307
+ function error(resp, msg, t) {
308
+ self._responseArgs.resp = resp
309
+ self._responseArgs.msg = msg
310
+ self._responseArgs.t = t
311
+ self._erred = true
312
+ while (self._errorHandlers.length > 0) {
313
+ self._errorHandlers.shift()(resp, msg, t)
314
+ }
315
+ complete(resp)
316
+ }
317
+
318
+ this.request = getRequest.call(this, success, error)
319
+ }
320
+
321
+ Reqwest.prototype = {
322
+ abort: function () {
323
+ this._aborted = true
324
+ this.request.abort()
325
+ }
326
+
327
+ , retry: function () {
328
+ init.call(this, this.o, this.fn)
329
+ }
330
+
331
+ /**
332
+ * Small deviation from the Promises A CommonJs specification
333
+ * http://wiki.commonjs.org/wiki/Promises/A
334
+ */
335
+
336
+ /**
337
+ * `then` will execute upon successful requests
338
+ */
339
+ , then: function (success, fail) {
340
+ success = success || function () {}
341
+ fail = fail || function () {}
342
+ if (this._fulfilled) {
343
+ success(this._responseArgs.resp)
344
+ } else if (this._erred) {
345
+ fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
346
+ } else {
347
+ this._fulfillmentHandlers.push(success)
348
+ this._errorHandlers.push(fail)
349
+ }
350
+ return this
351
+ }
352
+
353
+ /**
354
+ * `always` will execute whether the request succeeds or fails
355
+ */
356
+ , always: function (fn) {
357
+ if (this._fulfilled || this._erred) {
358
+ fn(this._responseArgs.resp)
359
+ } else {
360
+ this._completeHandlers.push(fn)
361
+ }
362
+ return this
363
+ }
364
+
365
+ /**
366
+ * `fail` will execute when the request fails
367
+ */
368
+ , fail: function (fn) {
369
+ if (this._erred) {
370
+ fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
371
+ } else {
372
+ this._errorHandlers.push(fn)
373
+ }
374
+ return this
375
+ }
376
+ }
377
+
378
+ function reqwest(o, fn) {
379
+ return new Reqwest(o, fn)
380
+ }
381
+
382
+ // normalize newline variants according to spec -> CRLF
383
+ function normalize(s) {
384
+ return s ? s.replace(/\r?\n/g, '\r\n') : ''
385
+ }
386
+
387
+ function serial(el, cb) {
388
+ var n = el.name
389
+ , t = el.tagName.toLowerCase()
390
+ , optCb = function (o) {
391
+ // IE gives value="" even where there is no value attribute
392
+ // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273
393
+ if (o && !o.disabled)
394
+ cb(n, normalize(o.attributes.value && o.attributes.value.specified ? o.value : o.text))
395
+ }
396
+ , ch, ra, val, i
397
+
398
+ // don't serialize elements that are disabled or without a name
399
+ if (el.disabled || !n) return
400
+
401
+ switch (t) {
402
+ case 'input':
403
+ if (!/reset|button|image|file/i.test(el.type)) {
404
+ ch = /checkbox/i.test(el.type)
405
+ ra = /radio/i.test(el.type)
406
+ val = el.value
407
+ // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here
408
+ ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val))
409
+ }
410
+ break
411
+ case 'textarea':
412
+ cb(n, normalize(el.value))
413
+ break
414
+ case 'select':
415
+ if (el.type.toLowerCase() === 'select-one') {
416
+ optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null)
417
+ } else {
418
+ for (i = 0; el.length && i < el.length; i++) {
419
+ el.options[i].selected && optCb(el.options[i])
420
+ }
421
+ }
422
+ break
423
+ }
424
+ }
425
+
426
+ // collect up all form elements found from the passed argument elements all
427
+ // the way down to child elements; pass a '<form>' or form fields.
428
+ // called with 'this'=callback to use for serial() on each element
429
+ function eachFormElement() {
430
+ var cb = this
431
+ , e, i
432
+ , serializeSubtags = function (e, tags) {
433
+ var i, j, fa
434
+ for (i = 0; i < tags.length; i++) {
435
+ fa = e[byTag](tags[i])
436
+ for (j = 0; j < fa.length; j++) serial(fa[j], cb)
437
+ }
438
+ }
439
+
440
+ for (i = 0; i < arguments.length; i++) {
441
+ e = arguments[i]
442
+ if (/input|select|textarea/i.test(e.tagName)) serial(e, cb)
443
+ serializeSubtags(e, [ 'input', 'select', 'textarea' ])
444
+ }
445
+ }
446
+
447
+ // standard query string style serialization
448
+ function serializeQueryString() {
449
+ return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments))
450
+ }
451
+
452
+ // { 'name': 'value', ... } style serialization
453
+ function serializeHash() {
454
+ var hash = {}
455
+ eachFormElement.apply(function (name, value) {
456
+ if (name in hash) {
457
+ hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]])
458
+ hash[name].push(value)
459
+ } else hash[name] = value
460
+ }, arguments)
461
+ return hash
462
+ }
463
+
464
+ // [ { name: 'name', value: 'value' }, ... ] style serialization
465
+ reqwest.serializeArray = function () {
466
+ var arr = []
467
+ eachFormElement.apply(function (name, value) {
468
+ arr.push({name: name, value: value})
469
+ }, arguments)
470
+ return arr
471
+ }
472
+
473
+ reqwest.serialize = function () {
474
+ if (arguments.length === 0) return ''
475
+ var opt, fn
476
+ , args = Array.prototype.slice.call(arguments, 0)
477
+
478
+ opt = args.pop()
479
+ opt && opt.nodeType && args.push(opt) && (opt = null)
480
+ opt && (opt = opt.type)
481
+
482
+ if (opt == 'map') fn = serializeHash
483
+ else if (opt == 'array') fn = reqwest.serializeArray
484
+ else fn = serializeQueryString
485
+
486
+ return fn.apply(null, args)
487
+ }
488
+
489
+ reqwest.toQueryString = function (o, trad) {
490
+ var prefix, i
491
+ , traditional = trad || false
492
+ , s = []
493
+ , enc = encodeURIComponent
494
+ , add = function (key, value) {
495
+ // If value is a function, invoke it and return its value
496
+ value = ('function' === typeof value) ? value() : (value == null ? '' : value)
497
+ s[s.length] = enc(key) + '=' + enc(value)
498
+ }
499
+ // If an array was passed in, assume that it is an array of form elements.
500
+ if (isArray(o)) {
501
+ for (i = 0; o && i < o.length; i++) add(o[i].name, o[i].value)
502
+ } else {
503
+ // If traditional, encode the "old" way (the way 1.3.2 or older
504
+ // did it), otherwise encode params recursively.
505
+ for (prefix in o) {
506
+ buildParams(prefix, o[prefix], traditional, add)
507
+ }
508
+ }
509
+
510
+ // spaces should be + according to spec
511
+ return s.join('&').replace(/%20/g, '+')
512
+ }
513
+
514
+ function buildParams(prefix, obj, traditional, add) {
515
+ var name, i, v
516
+ , rbracket = /\[\]$/
517
+
518
+ if (isArray(obj)) {
519
+ // Serialize array item.
520
+ for (i = 0; obj && i < obj.length; i++) {
521
+ v = obj[i]
522
+ if (traditional || rbracket.test(prefix)) {
523
+ // Treat each array item as a scalar.
524
+ add(prefix, v)
525
+ } else {
526
+ buildParams(prefix + '[' + (typeof v === 'object' ? i : '') + ']', v, traditional, add)
527
+ }
528
+ }
529
+ } else if (obj && obj.toString() === '[object Object]') {
530
+ // Serialize object item.
531
+ for (name in obj) {
532
+ buildParams(prefix + '[' + name + ']', obj[name], traditional, add)
533
+ }
534
+
535
+ } else {
536
+ // Serialize scalar item.
537
+ add(prefix, obj)
538
+ }
539
+ }
540
+
541
+ reqwest.getcallbackPrefix = function () {
542
+ return callbackPrefix
543
+ }
544
+
545
+ // jQuery and Zepto compatibility, differences can be remapped here so you can call
546
+ // .ajax.compat(options, callback)
547
+ reqwest.compat = function (o, fn) {
548
+ if (o) {
549
+ o.type && (o.method = o.type) && delete o.type
550
+ o.dataType && (o.type = o.dataType)
551
+ o.jsonpCallback && (o.jsonpCallbackName = o.jsonpCallback) && delete o.jsonpCallback
552
+ o.jsonp && (o.jsonpCallback = o.jsonp)
553
+ }
554
+ return new Reqwest(o, fn)
555
+ }
556
+
557
+ reqwest.ajaxSetup = function (options) {
558
+ options = options || {}
559
+ for (var k in options) {
560
+ globalSetupOptions[k] = options[k]
561
+ }
562
+ }
563
+
564
+ return reqwest
565
+ });