hoarder-js 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
+ });