ketan-mithril_rails 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +32 -0
  4. data/app/assets/javascripts/MSXTransformer.js +15950 -0
  5. data/app/assets/javascripts/mithril.js +1159 -0
  6. data/app/assets/javascripts/mithril_ujs.js +55 -0
  7. data/app/helpers/mithril_helper.rb +14 -0
  8. data/lib/mithril_rails.rb +2 -0
  9. data/lib/mithril_rails/msx.rb +31 -0
  10. data/lib/mithril_rails/msx/template.rb +17 -0
  11. data/lib/mithril_rails/rails.rb +2 -0
  12. data/lib/mithril_rails/rails/engine.rb +9 -0
  13. data/lib/mithril_rails/rails/railtie.rb +10 -0
  14. data/lib/mithril_rails/version.rb +3 -0
  15. data/test/dummy/README.rdoc +28 -0
  16. data/test/dummy/Rakefile +6 -0
  17. data/test/dummy/app/assets/javascripts/application.js +13 -0
  18. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  19. data/test/dummy/app/controllers/application_controller.rb +5 -0
  20. data/test/dummy/app/helpers/application_helper.rb +2 -0
  21. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  22. data/test/dummy/bin/bundle +3 -0
  23. data/test/dummy/bin/rails +4 -0
  24. data/test/dummy/bin/rake +4 -0
  25. data/test/dummy/config.ru +4 -0
  26. data/test/dummy/config/application.rb +23 -0
  27. data/test/dummy/config/boot.rb +5 -0
  28. data/test/dummy/config/database.yml +25 -0
  29. data/test/dummy/config/environment.rb +5 -0
  30. data/test/dummy/config/environments/development.rb +37 -0
  31. data/test/dummy/config/environments/production.rb +83 -0
  32. data/test/dummy/config/environments/test.rb +39 -0
  33. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  34. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  35. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  36. data/test/dummy/config/initializers/inflections.rb +16 -0
  37. data/test/dummy/config/initializers/mime_types.rb +4 -0
  38. data/test/dummy/config/initializers/session_store.rb +3 -0
  39. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  40. data/test/dummy/config/locales/en.yml +23 -0
  41. data/test/dummy/config/routes.rb +56 -0
  42. data/test/dummy/config/secrets.yml +22 -0
  43. data/test/dummy/public/404.html +67 -0
  44. data/test/dummy/public/422.html +67 -0
  45. data/test/dummy/public/500.html +66 -0
  46. data/test/dummy/public/favicon.ico +0 -0
  47. data/test/mithril_rails_test.rb +7 -0
  48. data/test/test_helper.rb +15 -0
  49. metadata +181 -0
@@ -0,0 +1,1159 @@
1
+ var m = (function app(window, undefined) {
2
+ var OBJECT = "[object Object]", ARRAY = "[object Array]", STRING = "[object String]", FUNCTION = "function";
3
+ var type = {}.toString;
4
+ var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/;
5
+ var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/;
6
+ var noop = function() {}
7
+
8
+ // caching commonly used variables
9
+ var $document, $location, $requestAnimationFrame, $cancelAnimationFrame;
10
+
11
+ // self invoking function needed because of the way mocks work
12
+ function initialize(window){
13
+ $document = window.document;
14
+ $location = window.location;
15
+ $cancelAnimationFrame = window.cancelAnimationFrame || window.clearTimeout;
16
+ $requestAnimationFrame = window.requestAnimationFrame || window.setTimeout;
17
+ }
18
+
19
+ initialize(window);
20
+
21
+
22
+ /**
23
+ * @typedef {String} Tag
24
+ * A string that looks like -> div.classname#id[param=one][param2=two]
25
+ * Which describes a DOM node
26
+ */
27
+
28
+ /**
29
+ *
30
+ * @param {Tag} The DOM node tag
31
+ * @param {Object=[]} optional key-value pairs to be mapped to DOM attrs
32
+ * @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array, or splat (optional)
33
+ *
34
+ */
35
+ function m() {
36
+ var args = [].slice.call(arguments);
37
+ var hasAttrs = args[1] != null && type.call(args[1]) === OBJECT && !("tag" in args[1] || "view" in args[1]) && !("subtree" in args[1]);
38
+ var attrs = hasAttrs ? args[1] : {};
39
+ var classAttrName = "class" in attrs ? "class" : "className";
40
+ var cell = {tag: "div", attrs: {}};
41
+ var match, classes = [];
42
+ if (type.call(args[0]) != STRING) throw new Error("selector in m(selector, attrs, children) should be a string")
43
+ while (match = parser.exec(args[0])) {
44
+ if (match[1] === "" && match[2]) cell.tag = match[2];
45
+ else if (match[1] === "#") cell.attrs.id = match[2];
46
+ else if (match[1] === ".") classes.push(match[2]);
47
+ else if (match[3][0] === "[") {
48
+ var pair = attrParser.exec(match[3]);
49
+ cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" :true)
50
+ }
51
+ }
52
+
53
+ var children = hasAttrs ? args.slice(2) : args.slice(1);
54
+ if (children.length === 1 && type.call(children[0]) === ARRAY) {
55
+ cell.children = children[0]
56
+ }
57
+ else {
58
+ cell.children = children
59
+ }
60
+
61
+ for (var attrName in attrs) {
62
+ if (attrs.hasOwnProperty(attrName)) {
63
+ if (attrName === classAttrName && attrs[attrName] != null && attrs[attrName] !== "") {
64
+ classes.push(attrs[attrName])
65
+ cell.attrs[attrName] = "" //create key in correct iteration order
66
+ }
67
+ else cell.attrs[attrName] = attrs[attrName]
68
+ }
69
+ }
70
+ if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ");
71
+
72
+ return cell
73
+ }
74
+ function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace, configs) {
75
+ //`build` is a recursive function that manages creation/diffing/removal of DOM elements based on comparison between `data` and `cached`
76
+ //the diff algorithm can be summarized as this:
77
+ //1 - compare `data` and `cached`
78
+ //2 - if they are different, copy `data` to `cached` and update the DOM based on what the difference is
79
+ //3 - recursively apply this algorithm for every array and for the children of every virtual element
80
+
81
+ //the `cached` data structure is essentially the same as the previous redraw's `data` data structure, with a few additions:
82
+ //- `cached` always has a property called `nodes`, which is a list of DOM elements that correspond to the data represented by the respective virtual element
83
+ //- in order to support attaching `nodes` as a property of `cached`, `cached` is *always* a non-primitive object, i.e. if the data was a string, then cached is a String instance. If data was `null` or `undefined`, cached is `new String("")`
84
+ //- `cached also has a `configContext` property, which is the state storage object exposed by config(element, isInitialized, context)
85
+ //- when `cached` is an Object, it represents a virtual element; when it's an Array, it represents a list of elements; when it's a String, Number or Boolean, it represents a text node
86
+
87
+ //`parentElement` is a DOM element used for W3C DOM API calls
88
+ //`parentTag` is only used for handling a corner case for textarea values
89
+ //`parentCache` is used to remove nodes in some multi-node cases
90
+ //`parentIndex` and `index` are used to figure out the offset of nodes. They're artifacts from before arrays started being flattened and are likely refactorable
91
+ //`data` and `cached` are, respectively, the new and old nodes being diffed
92
+ //`shouldReattach` is a flag indicating whether a parent node was recreated (if so, and if this node is reused, then this node must reattach itself to the new parent)
93
+ //`editable` is a flag that indicates whether an ancestor is contenteditable
94
+ //`namespace` indicates the closest HTML namespace as it cascades down from an ancestor
95
+ //`configs` is a list of config functions to run after the topmost `build` call finishes running
96
+
97
+ //there's logic that relies on the assumption that null and undefined data are equivalent to empty strings
98
+ //- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements (e.g. function foo() {if (cond) return m("div")}
99
+ //- it simplifies diffing code
100
+ //data.toString() might throw or return null if data is the return value of Console.log in Firefox (behavior depends on version)
101
+ try {if (data == null || data.toString() == null) data = "";} catch (e) {data = ""}
102
+ if (data.subtree === "retain") return cached;
103
+ var cachedType = type.call(cached), dataType = type.call(data);
104
+ if (cached == null || cachedType !== dataType) {
105
+ if (cached != null) {
106
+ if (parentCache && parentCache.nodes) {
107
+ var offset = index - parentIndex;
108
+ var end = offset + (dataType === ARRAY ? data : cached.nodes).length;
109
+ clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end))
110
+ }
111
+ else if (cached.nodes) clear(cached.nodes, cached)
112
+ }
113
+ cached = new data.constructor;
114
+ if (cached.tag) cached = {}; //if constructor creates a virtual dom element, use a blank object as the base cached node instead of copying the virtual el (#277)
115
+ cached.nodes = []
116
+ }
117
+
118
+ if (dataType === ARRAY) {
119
+ //recursively flatten array
120
+ for (var i = 0, len = data.length; i < len; i++) {
121
+ if (type.call(data[i]) === ARRAY) {
122
+ data = data.concat.apply([], data);
123
+ i-- //check current index again and flatten until there are no more nested arrays at that index
124
+ len = data.length
125
+ }
126
+ }
127
+
128
+ var nodes = [], intact = cached.length === data.length, subArrayCount = 0;
129
+
130
+ //keys algorithm: sort elements without recreating them if keys are present
131
+ //1) create a map of all existing keys, and mark all for deletion
132
+ //2) add new keys to map and mark them for addition
133
+ //3) if key exists in new list, change action from deletion to a move
134
+ //4) for each key, handle its corresponding action as marked in previous steps
135
+ var DELETION = 1, INSERTION = 2 , MOVE = 3;
136
+ var existing = {}, shouldMaintainIdentities = false;
137
+ for (var i = 0; i < cached.length; i++) {
138
+ if (cached[i] && cached[i].attrs && cached[i].attrs.key != null) {
139
+ shouldMaintainIdentities = true;
140
+ existing[cached[i].attrs.key] = {action: DELETION, index: i}
141
+ }
142
+ }
143
+
144
+ var guid = 0
145
+ for (var i = 0, len = data.length; i < len; i++) {
146
+ if (data[i] && data[i].attrs && data[i].attrs.key != null) {
147
+ for (var j = 0, len = data.length; j < len; j++) {
148
+ if (data[j] && data[j].attrs && data[j].attrs.key == null) data[j].attrs.key = "__mithril__" + guid++
149
+ }
150
+ break
151
+ }
152
+ }
153
+
154
+ if (shouldMaintainIdentities) {
155
+ var keysDiffer = false
156
+ if (data.length != cached.length) keysDiffer = true
157
+ else for (var i = 0, cachedCell, dataCell; cachedCell = cached[i], dataCell = data[i]; i++) {
158
+ if (cachedCell.attrs && dataCell.attrs && cachedCell.attrs.key != dataCell.attrs.key) {
159
+ keysDiffer = true
160
+ break
161
+ }
162
+ }
163
+
164
+ if (keysDiffer) {
165
+ for (var i = 0, len = data.length; i < len; i++) {
166
+ if (data[i] && data[i].attrs) {
167
+ if (data[i].attrs.key != null) {
168
+ var key = data[i].attrs.key;
169
+ if (!existing[key]) existing[key] = {action: INSERTION, index: i};
170
+ else existing[key] = {
171
+ action: MOVE,
172
+ index: i,
173
+ from: existing[key].index,
174
+ element: cached.nodes[existing[key].index] || $document.createElement("div")
175
+ }
176
+ }
177
+ }
178
+ }
179
+ var actions = []
180
+ for (var prop in existing) actions.push(existing[prop])
181
+ var changes = actions.sort(sortChanges);
182
+ var newCached = new Array(cached.length)
183
+ newCached.nodes = cached.nodes.slice()
184
+
185
+ for (var i = 0, change; change = changes[i]; i++) {
186
+ if (change.action === DELETION) {
187
+ clear(cached[change.index].nodes, cached[change.index]);
188
+ newCached.splice(change.index, 1)
189
+ }
190
+ if (change.action === INSERTION) {
191
+ var dummy = $document.createElement("div");
192
+ dummy.key = data[change.index].attrs.key;
193
+ parentElement.insertBefore(dummy, parentElement.childNodes[change.index] || null);
194
+ newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]})
195
+ newCached.nodes[change.index] = dummy
196
+ }
197
+
198
+ if (change.action === MOVE) {
199
+ if (parentElement.childNodes[change.index] !== change.element && change.element !== null) {
200
+ parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null)
201
+ }
202
+ newCached[change.index] = cached[change.from]
203
+ newCached.nodes[change.index] = change.element
204
+ }
205
+ }
206
+ cached = newCached;
207
+ }
208
+ }
209
+ //end key algorithm
210
+
211
+ for (var i = 0, cacheCount = 0, len = data.length; i < len; i++) {
212
+ //diff each item in the array
213
+ var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs);
214
+ if (item === undefined) continue;
215
+ if (!item.nodes.intact) intact = false;
216
+ if (item.$trusted) {
217
+ //fix offset of next element if item was a trusted string w/ more than one html element
218
+ //the first clause in the regexp matches elements
219
+ //the second clause (after the pipe) matches text nodes
220
+ subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || [0]).length
221
+ }
222
+ else subArrayCount += type.call(item) === ARRAY ? item.length : 1;
223
+ cached[cacheCount++] = item
224
+ }
225
+ if (!intact) {
226
+ //diff the array itself
227
+
228
+ //update the list of DOM nodes by collecting the nodes from each item
229
+ for (var i = 0, len = data.length; i < len; i++) {
230
+ if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes)
231
+ }
232
+ //remove items from the end of the array if the new array is shorter than the old one
233
+ //if errors ever happen here, the issue is most likely a bug in the construction of the `cached` data structure somewhere earlier in the program
234
+ for (var i = 0, node; node = cached.nodes[i]; i++) {
235
+ if (node.parentNode != null && nodes.indexOf(node) < 0) clear([node], [cached[i]])
236
+ }
237
+ if (data.length < cached.length) cached.length = data.length;
238
+ cached.nodes = nodes
239
+ }
240
+ }
241
+ else if (data != null && dataType === OBJECT) {
242
+ var views = [], controllers = []
243
+ while (data.view) {
244
+ var view = data.view.$original || data.view
245
+ var controllerIndex = m.redraw.strategy() == "diff" && cached.views ? cached.views.indexOf(view) : -1
246
+ var controller = controllerIndex > -1 ? cached.controllers[controllerIndex] : new (data.controller || noop)
247
+ var key = data && data.attrs && data.attrs.key
248
+ data = pendingRequests == 0 || (cached && cached.controllers && cached.controllers.indexOf(controller) > -1) ? data.view(controller) : {tag: "placeholder"}
249
+ if (data.subtree === "retain") return cached;
250
+ if (key) {
251
+ if (!data.attrs) data.attrs = {}
252
+ data.attrs.key = key
253
+ }
254
+ if (controller.onunload) unloaders.push({controller: controller, handler: controller.onunload})
255
+ views.push(view)
256
+ controllers.push(controller)
257
+ }
258
+ if (!data.tag && controllers.length) throw new Error("Component template must return a virtual element, not an array, string, etc.")
259
+ if (!data.attrs) data.attrs = {};
260
+ if (!cached.attrs) cached.attrs = {};
261
+
262
+ var dataAttrKeys = Object.keys(data.attrs)
263
+ var hasKeys = dataAttrKeys.length > ("key" in data.attrs ? 1 : 0)
264
+ //if an element is different enough from the one in cache, recreate it
265
+ if (data.tag != cached.tag || dataAttrKeys.sort().join() != Object.keys(cached.attrs).sort().join() || data.attrs.id != cached.attrs.id || data.attrs.key != cached.attrs.key || (m.redraw.strategy() == "all" && (!cached.configContext || cached.configContext.retain !== true)) || (m.redraw.strategy() == "diff" && cached.configContext && cached.configContext.retain === false)) {
266
+ if (cached.nodes.length) clear(cached.nodes);
267
+ if (cached.configContext && typeof cached.configContext.onunload === FUNCTION) cached.configContext.onunload()
268
+ if (cached.controllers) {
269
+ for (var i = 0, controller; controller = cached.controllers[i]; i++) {
270
+ if (typeof controller.onunload === FUNCTION) controller.onunload({preventDefault: noop})
271
+ }
272
+ }
273
+ }
274
+ if (type.call(data.tag) != STRING) return;
275
+
276
+ var node, isNew = cached.nodes.length === 0;
277
+ if (data.attrs.xmlns) namespace = data.attrs.xmlns;
278
+ else if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg";
279
+ else if (data.tag === "math") namespace = "http://www.w3.org/1998/Math/MathML";
280
+
281
+ if (isNew) {
282
+ if (data.attrs.is) node = namespace === undefined ? $document.createElement(data.tag, data.attrs.is) : $document.createElementNS(namespace, data.tag, data.attrs.is);
283
+ else node = namespace === undefined ? $document.createElement(data.tag) : $document.createElementNS(namespace, data.tag);
284
+ cached = {
285
+ tag: data.tag,
286
+ //set attributes first, then create children
287
+ attrs: hasKeys ? setAttributes(node, data.tag, data.attrs, {}, namespace) : data.attrs,
288
+ children: data.children != null && data.children.length > 0 ?
289
+ build(node, data.tag, undefined, undefined, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace, configs) :
290
+ data.children,
291
+ nodes: [node]
292
+ };
293
+ if (controllers.length) {
294
+ cached.views = views
295
+ cached.controllers = controllers
296
+ for (var i = 0, controller; controller = controllers[i]; i++) {
297
+ if (controller.onunload && controller.onunload.$old) controller.onunload = controller.onunload.$old
298
+ if (pendingRequests && controller.onunload) {
299
+ var onunload = controller.onunload
300
+ controller.onunload = noop
301
+ controller.onunload.$old = onunload
302
+ }
303
+ }
304
+ }
305
+
306
+ if (cached.children && !cached.children.nodes) cached.children.nodes = [];
307
+ //edge case: setting value on <select> doesn't work before children exist, so set it again after children have been created
308
+ if (data.tag === "select" && "value" in data.attrs) setAttributes(node, data.tag, {value: data.attrs.value}, {}, namespace);
309
+ parentElement.insertBefore(node, parentElement.childNodes[index] || null)
310
+ }
311
+ else {
312
+ node = cached.nodes[0];
313
+ if (hasKeys) setAttributes(node, data.tag, data.attrs, cached.attrs, namespace);
314
+ cached.children = build(node, data.tag, undefined, undefined, data.children, cached.children, false, 0, data.attrs.contenteditable ? node : editable, namespace, configs);
315
+ cached.nodes.intact = true;
316
+ if (controllers.length) {
317
+ cached.views = views
318
+ cached.controllers = controllers
319
+ }
320
+ if (shouldReattach === true && node != null) parentElement.insertBefore(node, parentElement.childNodes[index] || null)
321
+ }
322
+ //schedule configs to be called. They are called after `build` finishes running
323
+ if (typeof data.attrs["config"] === FUNCTION) {
324
+ var context = cached.configContext = cached.configContext || {};
325
+
326
+ // bind
327
+ var callback = function(data, args) {
328
+ return function() {
329
+ return data.attrs["config"].apply(data, args)
330
+ }
331
+ };
332
+ configs.push(callback(data, [node, !isNew, context, cached]))
333
+ }
334
+ }
335
+ else if (typeof data != FUNCTION) {
336
+ //handle text nodes
337
+ var nodes;
338
+ if (cached.nodes.length === 0) {
339
+ if (data.$trusted) {
340
+ nodes = injectHTML(parentElement, index, data)
341
+ }
342
+ else {
343
+ nodes = [$document.createTextNode(data)];
344
+ if (!parentElement.nodeName.match(voidElements)) parentElement.insertBefore(nodes[0], parentElement.childNodes[index] || null)
345
+ }
346
+ cached = "string number boolean".indexOf(typeof data) > -1 ? new data.constructor(data) : data;
347
+ cached.nodes = nodes
348
+ }
349
+ else if (cached.valueOf() !== data.valueOf() || shouldReattach === true) {
350
+ nodes = cached.nodes;
351
+ if (!editable || editable !== $document.activeElement) {
352
+ if (data.$trusted) {
353
+ clear(nodes, cached);
354
+ nodes = injectHTML(parentElement, index, data)
355
+ }
356
+ else {
357
+ //corner case: replacing the nodeValue of a text node that is a child of a textarea/contenteditable doesn't work
358
+ //we need to update the value property of the parent textarea or the innerHTML of the contenteditable element instead
359
+ if (parentTag === "textarea") parentElement.value = data;
360
+ else if (editable) editable.innerHTML = data;
361
+ else {
362
+ if (nodes[0].nodeType === 1 || nodes.length > 1) { //was a trusted string
363
+ clear(cached.nodes, cached);
364
+ nodes = [$document.createTextNode(data)]
365
+ }
366
+ parentElement.insertBefore(nodes[0], parentElement.childNodes[index] || null);
367
+ nodes[0].nodeValue = data
368
+ }
369
+ }
370
+ }
371
+ cached = new data.constructor(data);
372
+ cached.nodes = nodes
373
+ }
374
+ else cached.nodes.intact = true
375
+ }
376
+
377
+ return cached
378
+ }
379
+ function sortChanges(a, b) {return a.action - b.action || a.index - b.index}
380
+ function setAttributes(node, tag, dataAttrs, cachedAttrs, namespace) {
381
+ for (var attrName in dataAttrs) {
382
+ var dataAttr = dataAttrs[attrName];
383
+ var cachedAttr = cachedAttrs[attrName];
384
+ if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr)) {
385
+ cachedAttrs[attrName] = dataAttr;
386
+ try {
387
+ //`config` isn't a real attributes, so ignore it
388
+ if (attrName === "config" || attrName == "key") continue;
389
+ //hook event handlers to the auto-redrawing system
390
+ else if (typeof dataAttr === FUNCTION && attrName.indexOf("on") === 0) {
391
+ node[attrName] = autoredraw(dataAttr, node)
392
+ }
393
+ //handle `style: {...}`
394
+ else if (attrName === "style" && dataAttr != null && type.call(dataAttr) === OBJECT) {
395
+ for (var rule in dataAttr) {
396
+ if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule]
397
+ }
398
+ for (var rule in cachedAttr) {
399
+ if (!(rule in dataAttr)) node.style[rule] = ""
400
+ }
401
+ }
402
+ //handle SVG
403
+ else if (namespace != null) {
404
+ if (attrName === "href") node.setAttributeNS("http://www.w3.org/1999/xlink", "href", dataAttr);
405
+ else if (attrName === "className") node.setAttribute("class", dataAttr);
406
+ else node.setAttribute(attrName, dataAttr)
407
+ }
408
+ //handle cases that are properties (but ignore cases where we should use setAttribute instead)
409
+ //- list and form are typically used as strings, but are DOM element references in js
410
+ //- when using CSS selectors (e.g. `m("[style='']")`), style is used as a string, but it's an object in js
411
+ else if (attrName in node && !(attrName === "list" || attrName === "style" || attrName === "form" || attrName === "type" || attrName === "width" || attrName === "height")) {
412
+ //#348 don't set the value if not needed otherwise cursor placement breaks in Chrome
413
+ if (tag !== "input" || node[attrName] !== dataAttr) node[attrName] = dataAttr
414
+ }
415
+ else node.setAttribute(attrName, dataAttr)
416
+ }
417
+ catch (e) {
418
+ //swallow IE's invalid argument errors to mimic HTML's fallback-to-doing-nothing-on-invalid-attributes behavior
419
+ if (e.message.indexOf("Invalid argument") < 0) throw e
420
+ }
421
+ }
422
+ //#348 dataAttr may not be a string, so use loose comparison (double equal) instead of strict (triple equal)
423
+ else if (attrName === "value" && tag === "input" && node.value != dataAttr) {
424
+ node.value = dataAttr
425
+ }
426
+ }
427
+ return cachedAttrs
428
+ }
429
+ function clear(nodes, cached) {
430
+ for (var i = nodes.length - 1; i > -1; i--) {
431
+ if (nodes[i] && nodes[i].parentNode) {
432
+ try {nodes[i].parentNode.removeChild(nodes[i])}
433
+ catch (e) {} //ignore if this fails due to order of events (see http://stackoverflow.com/questions/21926083/failed-to-execute-removechild-on-node)
434
+ cached = [].concat(cached);
435
+ if (cached[i]) unload(cached[i])
436
+ }
437
+ }
438
+ if (nodes.length != 0) nodes.length = 0
439
+ }
440
+ function unload(cached) {
441
+ if (cached.configContext && typeof cached.configContext.onunload === FUNCTION) {
442
+ cached.configContext.onunload();
443
+ cached.configContext.onunload = null
444
+ }
445
+ if (cached.controllers) {
446
+ for (var i = 0, controller; controller = cached.controllers[i]; i++) {
447
+ if (typeof controller.onunload === FUNCTION) controller.onunload({preventDefault: noop});
448
+ }
449
+ }
450
+ if (cached.children) {
451
+ if (type.call(cached.children) === ARRAY) {
452
+ for (var i = 0, child; child = cached.children[i]; i++) unload(child)
453
+ }
454
+ else if (cached.children.tag) unload(cached.children)
455
+ }
456
+ }
457
+ function injectHTML(parentElement, index, data) {
458
+ var nextSibling = parentElement.childNodes[index];
459
+ if (nextSibling) {
460
+ var isElement = nextSibling.nodeType != 1;
461
+ var placeholder = $document.createElement("span");
462
+ if (isElement) {
463
+ parentElement.insertBefore(placeholder, nextSibling || null);
464
+ placeholder.insertAdjacentHTML("beforebegin", data);
465
+ parentElement.removeChild(placeholder)
466
+ }
467
+ else nextSibling.insertAdjacentHTML("beforebegin", data)
468
+ }
469
+ else parentElement.insertAdjacentHTML("beforeend", data);
470
+ var nodes = [];
471
+ while (parentElement.childNodes[index] !== nextSibling) {
472
+ nodes.push(parentElement.childNodes[index]);
473
+ index++
474
+ }
475
+ return nodes
476
+ }
477
+ function autoredraw(callback, object) {
478
+ return function(e) {
479
+ e = e || event;
480
+ m.redraw.strategy("diff");
481
+ m.startComputation();
482
+ try {return callback.call(object, e)}
483
+ finally {
484
+ endFirstComputation()
485
+ }
486
+ }
487
+ }
488
+
489
+ var html;
490
+ var documentNode = {
491
+ appendChild: function(node) {
492
+ if (html === undefined) html = $document.createElement("html");
493
+ if ($document.documentElement && $document.documentElement !== node) {
494
+ $document.replaceChild(node, $document.documentElement)
495
+ }
496
+ else $document.appendChild(node);
497
+ this.childNodes = $document.childNodes
498
+ },
499
+ insertBefore: function(node) {
500
+ this.appendChild(node)
501
+ },
502
+ childNodes: []
503
+ };
504
+ var nodeCache = [], cellCache = {};
505
+ m.render = function(root, cell, forceRecreation) {
506
+ var configs = [];
507
+ if (!root) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");
508
+ var id = getCellCacheKey(root);
509
+ var isDocumentRoot = root === $document;
510
+ var node = isDocumentRoot || root === $document.documentElement ? documentNode : root;
511
+ if (isDocumentRoot && cell.tag != "html") cell = {tag: "html", attrs: {}, children: cell};
512
+ if (cellCache[id] === undefined) clear(node.childNodes);
513
+ if (forceRecreation === true) reset(root);
514
+ cellCache[id] = build(node, null, undefined, undefined, cell, cellCache[id], false, 0, null, undefined, configs);
515
+ for (var i = 0, len = configs.length; i < len; i++) configs[i]()
516
+ };
517
+ function getCellCacheKey(element) {
518
+ var index = nodeCache.indexOf(element);
519
+ return index < 0 ? nodeCache.push(element) - 1 : index
520
+ }
521
+
522
+ m.trust = function(value) {
523
+ value = new String(value);
524
+ value.$trusted = true;
525
+ return value
526
+ };
527
+
528
+ function gettersetter(store) {
529
+ var prop = function() {
530
+ if (arguments.length) store = arguments[0];
531
+ return store
532
+ };
533
+
534
+ prop.toJSON = function() {
535
+ return store
536
+ };
537
+
538
+ return prop
539
+ }
540
+
541
+ m.prop = function (store) {
542
+ //note: using non-strict equality check here because we're checking if store is null OR undefined
543
+ if (((store != null && type.call(store) === OBJECT) || typeof store === FUNCTION) && typeof store.then === FUNCTION) {
544
+ return propify(store)
545
+ }
546
+
547
+ return gettersetter(store)
548
+ };
549
+
550
+ var roots = [], components = [], controllers = [], lastRedrawId = null, lastRedrawCallTime = 0, computePreRedrawHook = null, computePostRedrawHook = null, prevented = false, topComponent, unloaders = [];
551
+ var FRAME_BUDGET = 16; //60 frames per second = 1 call per 16 ms
552
+ function parameterize(component, args) {
553
+ var controller = function() {
554
+ return (component.controller || noop).apply(this, args) || this
555
+ }
556
+ var view = function(ctrl) {
557
+ if (arguments.length > 1) args = args.concat([].slice.call(arguments, 1))
558
+ return component.view.apply(component, args ? [ctrl].concat(args) : [ctrl])
559
+ }
560
+ view.$original = component.view
561
+ var output = {controller: controller, view: view}
562
+ if (args[0] && args[0].key != null) output.attrs = {key: args[0].key}
563
+ return output
564
+ }
565
+ m.component = function(component) {
566
+ return parameterize(component, [].slice.call(arguments, 1))
567
+ }
568
+ m.mount = m.module = function(root, component) {
569
+ if (!root) throw new Error("Please ensure the DOM element exists before rendering a template into it.");
570
+ var index = roots.indexOf(root);
571
+ if (index < 0) index = roots.length;
572
+
573
+ var isPrevented = false;
574
+ var event = {preventDefault: function() {
575
+ isPrevented = true;
576
+ computePreRedrawHook = computePostRedrawHook = null;
577
+ }};
578
+ for (var i = 0, unloader; unloader = unloaders[i]; i++) {
579
+ unloader.handler.call(unloader.controller, event)
580
+ unloader.controller.onunload = null
581
+ }
582
+ if (isPrevented) {
583
+ for (var i = 0, unloader; unloader = unloaders[i]; i++) unloader.controller.onunload = unloader.handler
584
+ }
585
+ else unloaders = []
586
+
587
+ if (controllers[index] && typeof controllers[index].onunload === FUNCTION) {
588
+ controllers[index].onunload(event)
589
+ }
590
+
591
+ if (!isPrevented) {
592
+ m.redraw.strategy("all");
593
+ m.startComputation();
594
+ roots[index] = root;
595
+ if (arguments.length > 2) component = subcomponent(component, [].slice.call(arguments, 2))
596
+ var currentComponent = topComponent = component = component || {controller: function() {}};
597
+ var constructor = component.controller || noop
598
+ var controller = new constructor;
599
+ //controllers may call m.mount recursively (via m.route redirects, for example)
600
+ //this conditional ensures only the last recursive m.mount call is applied
601
+ if (currentComponent === topComponent) {
602
+ controllers[index] = controller;
603
+ components[index] = component
604
+ }
605
+ endFirstComputation();
606
+ return controllers[index]
607
+ }
608
+ };
609
+ var redrawing = false
610
+ m.redraw = function(force) {
611
+ if (redrawing) return
612
+ redrawing = true
613
+ //lastRedrawId is a positive number if a second redraw is requested before the next animation frame
614
+ //lastRedrawID is null if it's the first redraw and not an event handler
615
+ if (lastRedrawId && force !== true) {
616
+ //when setTimeout: only reschedule redraw if time between now and previous redraw is bigger than a frame, otherwise keep currently scheduled timeout
617
+ //when rAF: always reschedule redraw
618
+ if ($requestAnimationFrame === window.requestAnimationFrame || new Date - lastRedrawCallTime > FRAME_BUDGET) {
619
+ if (lastRedrawId > 0) $cancelAnimationFrame(lastRedrawId);
620
+ lastRedrawId = $requestAnimationFrame(redraw, FRAME_BUDGET)
621
+ }
622
+ }
623
+ else {
624
+ redraw();
625
+ lastRedrawId = $requestAnimationFrame(function() {lastRedrawId = null}, FRAME_BUDGET)
626
+ }
627
+ redrawing = false
628
+ };
629
+ m.redraw.strategy = m.prop();
630
+ function redraw() {
631
+ if (computePreRedrawHook) {
632
+ computePreRedrawHook()
633
+ computePreRedrawHook = null
634
+ }
635
+ for (var i = 0, root; root = roots[i]; i++) {
636
+ if (controllers[i]) {
637
+ var args = components[i].controller && components[i].controller.$$args ? [controllers[i]].concat(components[i].controller.$$args) : [controllers[i]]
638
+ m.render(root, components[i].view ? components[i].view(controllers[i], args) : "")
639
+ }
640
+ }
641
+ //after rendering within a routed context, we need to scroll back to the top, and fetch the document title for history.pushState
642
+ if (computePostRedrawHook) {
643
+ computePostRedrawHook();
644
+ computePostRedrawHook = null
645
+ }
646
+ lastRedrawId = null;
647
+ lastRedrawCallTime = new Date;
648
+ m.redraw.strategy("diff")
649
+ }
650
+
651
+ var pendingRequests = 0;
652
+ m.startComputation = function() {pendingRequests++};
653
+ m.endComputation = function() {
654
+ pendingRequests = Math.max(pendingRequests - 1, 0);
655
+ if (pendingRequests === 0) m.redraw()
656
+ };
657
+ var endFirstComputation = function() {
658
+ if (m.redraw.strategy() == "none") {
659
+ pendingRequests--
660
+ m.redraw.strategy("diff")
661
+ }
662
+ else m.endComputation();
663
+ }
664
+
665
+ m.withAttr = function(prop, withAttrCallback) {
666
+ return function(e) {
667
+ e = e || event;
668
+ var currentTarget = e.currentTarget || this;
669
+ withAttrCallback(prop in currentTarget ? currentTarget[prop] : currentTarget.getAttribute(prop))
670
+ }
671
+ };
672
+
673
+ //routing
674
+ var modes = {pathname: "", hash: "#", search: "?"};
675
+ var redirect = noop, routeParams, currentRoute, isDefaultRoute = false;
676
+ m.route = function() {
677
+ //m.route()
678
+ if (arguments.length === 0) return currentRoute;
679
+ //m.route(el, defaultRoute, routes)
680
+ else if (arguments.length === 3 && type.call(arguments[1]) === STRING) {
681
+ var root = arguments[0], defaultRoute = arguments[1], router = arguments[2];
682
+ redirect = function(source) {
683
+ var path = currentRoute = normalizeRoute(source);
684
+ if (!routeByValue(root, router, path)) {
685
+ if (isDefaultRoute) throw new Error("Ensure the default route matches one of the routes defined in m.route")
686
+ isDefaultRoute = true
687
+ m.route(defaultRoute, true)
688
+ isDefaultRoute = false
689
+ }
690
+ };
691
+ var listener = m.route.mode === "hash" ? "onhashchange" : "onpopstate";
692
+ window[listener] = function() {
693
+ var path = $location[m.route.mode]
694
+ if (m.route.mode === "pathname") path += $location.search
695
+ if (currentRoute != normalizeRoute(path)) {
696
+ redirect(path)
697
+ }
698
+ };
699
+ computePreRedrawHook = setScroll;
700
+ window[listener]()
701
+ }
702
+ //config: m.route
703
+ else if (arguments[0].addEventListener || arguments[0].attachEvent) {
704
+ var element = arguments[0];
705
+ var isInitialized = arguments[1];
706
+ var context = arguments[2];
707
+ var vdom = arguments[3];
708
+ element.href = (m.route.mode !== 'pathname' ? $location.pathname : '') + modes[m.route.mode] + vdom.attrs.href;
709
+ if (element.addEventListener) {
710
+ element.removeEventListener("click", routeUnobtrusive);
711
+ element.addEventListener("click", routeUnobtrusive)
712
+ }
713
+ else {
714
+ element.detachEvent("onclick", routeUnobtrusive);
715
+ element.attachEvent("onclick", routeUnobtrusive)
716
+ }
717
+ }
718
+ //m.route(route, params, shouldReplaceHistoryEntry)
719
+ else if (type.call(arguments[0]) === STRING) {
720
+ var oldRoute = currentRoute;
721
+ currentRoute = arguments[0];
722
+ var args = arguments[1] || {}
723
+ var queryIndex = currentRoute.indexOf("?")
724
+ var params = queryIndex > -1 ? parseQueryString(currentRoute.slice(queryIndex + 1)) : {}
725
+ for (var i in args) params[i] = args[i]
726
+ var querystring = buildQueryString(params)
727
+ var currentPath = queryIndex > -1 ? currentRoute.slice(0, queryIndex) : currentRoute
728
+ if (querystring) currentRoute = currentPath + (currentPath.indexOf("?") === -1 ? "?" : "&") + querystring;
729
+
730
+ var shouldReplaceHistoryEntry = (arguments.length === 3 ? arguments[2] : arguments[1]) === true || oldRoute === arguments[0];
731
+
732
+ if (window.history.pushState) {
733
+ computePreRedrawHook = setScroll
734
+ computePostRedrawHook = function() {
735
+ window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, $document.title, modes[m.route.mode] + currentRoute);
736
+ };
737
+ redirect(modes[m.route.mode] + currentRoute)
738
+ }
739
+ else {
740
+ $location[m.route.mode] = currentRoute
741
+ redirect(modes[m.route.mode] + currentRoute)
742
+ }
743
+ }
744
+ };
745
+ m.route.param = function(key) {
746
+ if (!routeParams) throw new Error("You must call m.route(element, defaultRoute, routes) before calling m.route.param()")
747
+ return routeParams[key]
748
+ };
749
+ m.route.mode = "search";
750
+ function normalizeRoute(route) {
751
+ return route.slice(modes[m.route.mode].length)
752
+ }
753
+ function routeByValue(root, router, path) {
754
+ routeParams = {};
755
+
756
+ var queryStart = path.indexOf("?");
757
+ if (queryStart !== -1) {
758
+ routeParams = parseQueryString(path.substr(queryStart + 1, path.length));
759
+ path = path.substr(0, queryStart)
760
+ }
761
+
762
+ // Get all routes and check if there's
763
+ // an exact match for the current path
764
+ var keys = Object.keys(router);
765
+ var index = keys.indexOf(path);
766
+ if(index !== -1){
767
+ m.mount(root, router[keys [index]]);
768
+ return true;
769
+ }
770
+
771
+ for (var route in router) {
772
+ if (route === path) {
773
+ m.mount(root, router[route]);
774
+ return true
775
+ }
776
+
777
+ var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$");
778
+
779
+ if (matcher.test(path)) {
780
+ path.replace(matcher, function() {
781
+ var keys = route.match(/:[^\/]+/g) || [];
782
+ var values = [].slice.call(arguments, 1, -2);
783
+ for (var i = 0, len = keys.length; i < len; i++) routeParams[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
784
+ m.mount(root, router[route])
785
+ });
786
+ return true
787
+ }
788
+ }
789
+ }
790
+ function routeUnobtrusive(e) {
791
+ e = e || event;
792
+ if (e.ctrlKey || e.metaKey || e.which === 2) return;
793
+ if (e.preventDefault) e.preventDefault();
794
+ else e.returnValue = false;
795
+ var currentTarget = e.currentTarget || e.srcElement;
796
+ var args = m.route.mode === "pathname" && currentTarget.search ? parseQueryString(currentTarget.search.slice(1)) : {};
797
+ while (currentTarget && currentTarget.nodeName.toUpperCase() != "A") currentTarget = currentTarget.parentNode
798
+ m.route(currentTarget[m.route.mode].slice(modes[m.route.mode].length), args)
799
+ }
800
+ function setScroll() {
801
+ if (m.route.mode != "hash" && $location.hash) $location.hash = $location.hash;
802
+ else window.scrollTo(0, 0)
803
+ }
804
+ function buildQueryString(object, prefix) {
805
+ var duplicates = {}
806
+ var str = []
807
+ for (var prop in object) {
808
+ var key = prefix ? prefix + "[" + prop + "]" : prop
809
+ var value = object[prop]
810
+ var valueType = type.call(value)
811
+ var pair = (value === null) ? encodeURIComponent(key) :
812
+ valueType === OBJECT ? buildQueryString(value, key) :
813
+ valueType === ARRAY ? value.reduce(function(memo, item) {
814
+ if (!duplicates[key]) duplicates[key] = {}
815
+ if (!duplicates[key][item]) {
816
+ duplicates[key][item] = true
817
+ return memo.concat(encodeURIComponent(key) + "=" + encodeURIComponent(item))
818
+ }
819
+ return memo
820
+ }, []).join("&") :
821
+ encodeURIComponent(key) + "=" + encodeURIComponent(value)
822
+ if (value !== undefined) str.push(pair)
823
+ }
824
+ return str.join("&")
825
+ }
826
+ function parseQueryString(str) {
827
+ if (str.charAt(0) === "?") str = str.substring(1);
828
+
829
+ var pairs = str.split("&"), params = {};
830
+ for (var i = 0, len = pairs.length; i < len; i++) {
831
+ var pair = pairs[i].split("=");
832
+ var key = decodeURIComponent(pair[0])
833
+ var value = pair.length == 2 ? decodeURIComponent(pair[1]) : null
834
+ if (params[key] != null) {
835
+ if (type.call(params[key]) !== ARRAY) params[key] = [params[key]]
836
+ params[key].push(value)
837
+ }
838
+ else params[key] = value
839
+ }
840
+ return params
841
+ }
842
+ m.route.buildQueryString = buildQueryString
843
+ m.route.parseQueryString = parseQueryString
844
+
845
+ function reset(root) {
846
+ var cacheKey = getCellCacheKey(root);
847
+ clear(root.childNodes, cellCache[cacheKey]);
848
+ cellCache[cacheKey] = undefined
849
+ }
850
+
851
+ m.deferred = function () {
852
+ var deferred = new Deferred();
853
+ deferred.promise = propify(deferred.promise);
854
+ return deferred
855
+ };
856
+ function propify(promise, initialValue) {
857
+ var prop = m.prop(initialValue);
858
+ promise.then(prop);
859
+ prop.then = function(resolve, reject) {
860
+ return propify(promise.then(resolve, reject), initialValue)
861
+ };
862
+ return prop
863
+ }
864
+ //Promiz.mithril.js | Zolmeister | MIT
865
+ //a modified version of Promiz.js, which does not conform to Promises/A+ for two reasons:
866
+ //1) `then` callbacks are called synchronously (because setTimeout is too slow, and the setImmediate polyfill is too big
867
+ //2) throwing subclasses of Error cause the error to be bubbled up instead of triggering rejection (because the spec does not account for the important use case of default browser error handling, i.e. message w/ line number)
868
+ function Deferred(successCallback, failureCallback) {
869
+ var RESOLVING = 1, REJECTING = 2, RESOLVED = 3, REJECTED = 4;
870
+ var self = this, state = 0, promiseValue = 0, next = [];
871
+
872
+ self["promise"] = {};
873
+
874
+ self["resolve"] = function(value) {
875
+ if (!state) {
876
+ promiseValue = value;
877
+ state = RESOLVING;
878
+
879
+ fire()
880
+ }
881
+ return this
882
+ };
883
+
884
+ self["reject"] = function(value) {
885
+ if (!state) {
886
+ promiseValue = value;
887
+ state = REJECTING;
888
+
889
+ fire()
890
+ }
891
+ return this
892
+ };
893
+
894
+ self.promise["then"] = function(successCallback, failureCallback) {
895
+ var deferred = new Deferred(successCallback, failureCallback);
896
+ if (state === RESOLVED) {
897
+ deferred.resolve(promiseValue)
898
+ }
899
+ else if (state === REJECTED) {
900
+ deferred.reject(promiseValue)
901
+ }
902
+ else {
903
+ next.push(deferred)
904
+ }
905
+ return deferred.promise
906
+ };
907
+
908
+ function finish(type) {
909
+ state = type || REJECTED;
910
+ next.map(function(deferred) {
911
+ state === RESOLVED && deferred.resolve(promiseValue) || deferred.reject(promiseValue)
912
+ })
913
+ }
914
+
915
+ function thennable(then, successCallback, failureCallback, notThennableCallback) {
916
+ if (((promiseValue != null && type.call(promiseValue) === OBJECT) || typeof promiseValue === FUNCTION) && typeof then === FUNCTION) {
917
+ try {
918
+ // count protects against abuse calls from spec checker
919
+ var count = 0;
920
+ then.call(promiseValue, function(value) {
921
+ if (count++) return;
922
+ promiseValue = value;
923
+ successCallback()
924
+ }, function (value) {
925
+ if (count++) return;
926
+ promiseValue = value;
927
+ failureCallback()
928
+ })
929
+ }
930
+ catch (e) {
931
+ m.deferred.onerror(e);
932
+ promiseValue = e;
933
+ failureCallback()
934
+ }
935
+ } else {
936
+ notThennableCallback()
937
+ }
938
+ }
939
+
940
+ function fire() {
941
+ // check if it's a thenable
942
+ var then;
943
+ try {
944
+ then = promiseValue && promiseValue.then
945
+ }
946
+ catch (e) {
947
+ m.deferred.onerror(e);
948
+ promiseValue = e;
949
+ state = REJECTING;
950
+ return fire()
951
+ }
952
+ thennable(then, function() {
953
+ state = RESOLVING;
954
+ fire()
955
+ }, function() {
956
+ state = REJECTING;
957
+ fire()
958
+ }, function() {
959
+ try {
960
+ if (state === RESOLVING && typeof successCallback === FUNCTION) {
961
+ promiseValue = successCallback(promiseValue)
962
+ }
963
+ else if (state === REJECTING && typeof failureCallback === "function") {
964
+ promiseValue = failureCallback(promiseValue);
965
+ state = RESOLVING
966
+ }
967
+ }
968
+ catch (e) {
969
+ m.deferred.onerror(e);
970
+ promiseValue = e;
971
+ return finish()
972
+ }
973
+
974
+ if (promiseValue === self) {
975
+ promiseValue = TypeError();
976
+ finish()
977
+ }
978
+ else {
979
+ thennable(then, function () {
980
+ finish(RESOLVED)
981
+ }, finish, function () {
982
+ finish(state === RESOLVING && RESOLVED)
983
+ })
984
+ }
985
+ })
986
+ }
987
+ }
988
+ m.deferred.onerror = function(e) {
989
+ if (type.call(e) === "[object Error]" && !e.constructor.toString().match(/ Error/)) throw e
990
+ };
991
+
992
+ m.sync = function(args) {
993
+ var method = "resolve";
994
+ function synchronizer(pos, resolved) {
995
+ return function(value) {
996
+ results[pos] = value;
997
+ if (!resolved) method = "reject";
998
+ if (--outstanding === 0) {
999
+ deferred.promise(results);
1000
+ deferred[method](results)
1001
+ }
1002
+ return value
1003
+ }
1004
+ }
1005
+
1006
+ var deferred = m.deferred();
1007
+ var outstanding = args.length;
1008
+ var results = new Array(outstanding);
1009
+ if (args.length > 0) {
1010
+ for (var i = 0; i < args.length; i++) {
1011
+ args[i].then(synchronizer(i, true), synchronizer(i, false))
1012
+ }
1013
+ }
1014
+ else deferred.resolve([]);
1015
+
1016
+ return deferred.promise
1017
+ };
1018
+ function identity(value) {return value}
1019
+
1020
+ function ajax(options) {
1021
+ if (options.dataType && options.dataType.toLowerCase() === "jsonp") {
1022
+ var callbackKey = "mithril_callback_" + new Date().getTime() + "_" + (Math.round(Math.random() * 1e16)).toString(36);
1023
+ var script = $document.createElement("script");
1024
+
1025
+ window[callbackKey] = function(resp) {
1026
+ script.parentNode.removeChild(script);
1027
+ options.onload({
1028
+ type: "load",
1029
+ target: {
1030
+ responseText: resp
1031
+ }
1032
+ });
1033
+ window[callbackKey] = undefined
1034
+ };
1035
+
1036
+ script.onerror = function(e) {
1037
+ script.parentNode.removeChild(script);
1038
+
1039
+ options.onerror({
1040
+ type: "error",
1041
+ target: {
1042
+ status: 500,
1043
+ responseText: JSON.stringify({error: "Error making jsonp request"})
1044
+ }
1045
+ });
1046
+ window[callbackKey] = undefined;
1047
+
1048
+ return false
1049
+ };
1050
+
1051
+ script.onload = function(e) {
1052
+ return false
1053
+ };
1054
+
1055
+ script.src = options.url
1056
+ + (options.url.indexOf("?") > 0 ? "&" : "?")
1057
+ + (options.callbackKey ? options.callbackKey : "callback")
1058
+ + "=" + callbackKey
1059
+ + "&" + buildQueryString(options.data || {});
1060
+ $document.body.appendChild(script)
1061
+ }
1062
+ else {
1063
+ var xhr = new window.XMLHttpRequest;
1064
+ xhr.open(options.method, options.url, true, options.user, options.password);
1065
+ xhr.onreadystatechange = function() {
1066
+ if (xhr.readyState === 4) {
1067
+ if (xhr.status >= 200 && xhr.status < 300) options.onload({type: "load", target: xhr});
1068
+ else options.onerror({type: "error", target: xhr})
1069
+ }
1070
+ };
1071
+ if (options.serialize === JSON.stringify && options.data && options.method !== "GET") {
1072
+ xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8")
1073
+ }
1074
+ if (options.deserialize === JSON.parse) {
1075
+ xhr.setRequestHeader("Accept", "application/json, text/*");
1076
+ }
1077
+ if (typeof options.config === FUNCTION) {
1078
+ var maybeXhr = options.config(xhr, options);
1079
+ if (maybeXhr != null) xhr = maybeXhr
1080
+ }
1081
+
1082
+ var data = options.method === "GET" || !options.data ? "" : options.data
1083
+ if (data && (type.call(data) != STRING && data.constructor != window.FormData)) {
1084
+ throw "Request data should be either be a string or FormData. Check the `serialize` option in `m.request`";
1085
+ }
1086
+ xhr.send(data);
1087
+ return xhr
1088
+ }
1089
+ }
1090
+ function bindData(xhrOptions, data, serialize) {
1091
+ if (xhrOptions.method === "GET" && xhrOptions.dataType != "jsonp") {
1092
+ var prefix = xhrOptions.url.indexOf("?") < 0 ? "?" : "&";
1093
+ var querystring = buildQueryString(data);
1094
+ xhrOptions.url = xhrOptions.url + (querystring ? prefix + querystring : "")
1095
+ }
1096
+ else xhrOptions.data = serialize(data);
1097
+ return xhrOptions
1098
+ }
1099
+ function parameterizeUrl(url, data) {
1100
+ var tokens = url.match(/:[a-z]\w+/gi);
1101
+ if (tokens && data) {
1102
+ for (var i = 0; i < tokens.length; i++) {
1103
+ var key = tokens[i].slice(1);
1104
+ url = url.replace(tokens[i], data[key]);
1105
+ delete data[key]
1106
+ }
1107
+ }
1108
+ return url
1109
+ }
1110
+
1111
+ m.request = function(xhrOptions) {
1112
+ if (xhrOptions.background !== true) m.startComputation();
1113
+ var deferred = new Deferred();
1114
+ var isJSONP = xhrOptions.dataType && xhrOptions.dataType.toLowerCase() === "jsonp";
1115
+ var serialize = xhrOptions.serialize = isJSONP ? identity : xhrOptions.serialize || JSON.stringify;
1116
+ var deserialize = xhrOptions.deserialize = isJSONP ? identity : xhrOptions.deserialize || JSON.parse;
1117
+ var extract = isJSONP ? function(jsonp) {return jsonp.responseText} : xhrOptions.extract || function(xhr) {
1118
+ return xhr.responseText.length === 0 && deserialize === JSON.parse ? null : xhr.responseText
1119
+ };
1120
+ xhrOptions.method = (xhrOptions.method || 'GET').toUpperCase();
1121
+ xhrOptions.url = parameterizeUrl(xhrOptions.url, xhrOptions.data);
1122
+ xhrOptions = bindData(xhrOptions, xhrOptions.data, serialize);
1123
+ xhrOptions.onload = xhrOptions.onerror = function(e) {
1124
+ try {
1125
+ e = e || event;
1126
+ var unwrap = (e.type === "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity;
1127
+ var response = unwrap(deserialize(extract(e.target, xhrOptions)), e.target);
1128
+ if (e.type === "load") {
1129
+ if (type.call(response) === ARRAY && xhrOptions.type) {
1130
+ for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i])
1131
+ }
1132
+ else if (xhrOptions.type) response = new xhrOptions.type(response)
1133
+ }
1134
+ deferred[e.type === "load" ? "resolve" : "reject"](response)
1135
+ }
1136
+ catch (e) {
1137
+ m.deferred.onerror(e);
1138
+ deferred.reject(e)
1139
+ }
1140
+ if (xhrOptions.background !== true) m.endComputation()
1141
+ };
1142
+ ajax(xhrOptions);
1143
+ deferred.promise = propify(deferred.promise, xhrOptions.initialValue);
1144
+ return deferred.promise
1145
+ };
1146
+
1147
+ //testing API
1148
+ m.deps = function(mock) {
1149
+ initialize(window = mock || window);
1150
+ return window;
1151
+ };
1152
+ //for internal testing only, do not use `m.deps.factory`
1153
+ m.deps.factory = app;
1154
+
1155
+ return m
1156
+ })(typeof window != "undefined" ? window : {});
1157
+
1158
+ if (typeof module != "undefined" && module !== null && module.exports) module.exports = m;
1159
+ else if (typeof define === "function" && define.amd) define(function() {return m});