mithril_rails 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/{JSXTransformer.js → MSXTransformer.js} +7045 -3603
- data/app/assets/javascripts/mithril.js +228 -81
- data/lib/mithril_rails/msx.rb +2 -2
- data/lib/mithril_rails/version.rb +1 -1
- metadata +18 -3
@@ -3,6 +3,7 @@ var m = (function app(window, undefined) {
|
|
3
3
|
var type = {}.toString;
|
4
4
|
var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/;
|
5
5
|
var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/;
|
6
|
+
var noop = function() {}
|
6
7
|
|
7
8
|
// caching commonly used variables
|
8
9
|
var $document, $location, $requestAnimationFrame, $cancelAnimationFrame;
|
@@ -18,13 +19,13 @@ var m = (function app(window, undefined) {
|
|
18
19
|
initialize(window);
|
19
20
|
|
20
21
|
|
21
|
-
|
22
|
+
/**
|
22
23
|
* @typedef {String} Tag
|
23
24
|
* A string that looks like -> div.classname#id[param=one][param2=two]
|
24
25
|
* Which describes a DOM node
|
25
26
|
*/
|
26
27
|
|
27
|
-
|
28
|
+
/**
|
28
29
|
*
|
29
30
|
* @param {Tag} The DOM node tag
|
30
31
|
* @param {Object=[]} optional key-value pairs to be mapped to DOM attrs
|
@@ -33,7 +34,7 @@ var m = (function app(window, undefined) {
|
|
33
34
|
*/
|
34
35
|
function m() {
|
35
36
|
var args = [].slice.call(arguments);
|
36
|
-
var hasAttrs = args[1] != null && type.call(args[1]) === OBJECT && !("tag" in args[1]) && !("subtree" in args[1]);
|
37
|
+
var hasAttrs = args[1] != null && type.call(args[1]) === OBJECT && !("tag" in args[1] || "view" in args[1]) && !("subtree" in args[1]);
|
37
38
|
var attrs = hasAttrs ? args[1] : {};
|
38
39
|
var classAttrName = "class" in attrs ? "class" : "className";
|
39
40
|
var cell = {tag: "div", attrs: {}};
|
@@ -48,21 +49,26 @@ var m = (function app(window, undefined) {
|
|
48
49
|
cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" :true)
|
49
50
|
}
|
50
51
|
}
|
51
|
-
if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ");
|
52
|
-
|
53
52
|
|
54
|
-
var children = hasAttrs ? args
|
55
|
-
if (type.call(children) === ARRAY) {
|
56
|
-
cell.children = children
|
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]
|
57
56
|
}
|
58
57
|
else {
|
59
|
-
cell.children =
|
58
|
+
cell.children = children
|
60
59
|
}
|
61
|
-
|
60
|
+
|
62
61
|
for (var attrName in attrs) {
|
63
|
-
if (
|
64
|
-
|
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
|
+
}
|
65
69
|
}
|
70
|
+
if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ");
|
71
|
+
|
66
72
|
return cell
|
67
73
|
}
|
68
74
|
function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace, configs) {
|
@@ -91,8 +97,8 @@ var m = (function app(window, undefined) {
|
|
91
97
|
//there's logic that relies on the assumption that null and undefined data are equivalent to empty strings
|
92
98
|
//- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements (e.g. function foo() {if (cond) return m("div")}
|
93
99
|
//- it simplifies diffing code
|
94
|
-
//data.toString()
|
95
|
-
if (data == null || data.toString() == null) data = "";
|
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 = ""}
|
96
102
|
if (data.subtree === "retain") return cached;
|
97
103
|
var cachedType = type.call(cached), dataType = type.call(data);
|
98
104
|
if (cached == null || cachedType !== dataType) {
|
@@ -115,6 +121,7 @@ var m = (function app(window, undefined) {
|
|
115
121
|
if (type.call(data[i]) === ARRAY) {
|
116
122
|
data = data.concat.apply([], data);
|
117
123
|
i-- //check current index again and flatten until there are no more nested arrays at that index
|
124
|
+
len = data.length
|
118
125
|
}
|
119
126
|
}
|
120
127
|
|
@@ -125,18 +132,26 @@ var m = (function app(window, undefined) {
|
|
125
132
|
//2) add new keys to map and mark them for addition
|
126
133
|
//3) if key exists in new list, change action from deletion to a move
|
127
134
|
//4) for each key, handle its corresponding action as marked in previous steps
|
128
|
-
//5) copy unkeyed items into their respective gaps
|
129
135
|
var DELETION = 1, INSERTION = 2 , MOVE = 3;
|
130
|
-
var existing = {},
|
136
|
+
var existing = {}, shouldMaintainIdentities = false;
|
131
137
|
for (var i = 0; i < cached.length; i++) {
|
132
138
|
if (cached[i] && cached[i].attrs && cached[i].attrs.key != null) {
|
133
139
|
shouldMaintainIdentities = true;
|
134
140
|
existing[cached[i].attrs.key] = {action: DELETION, index: i}
|
135
141
|
}
|
136
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
|
+
|
137
154
|
if (shouldMaintainIdentities) {
|
138
|
-
if (data.indexOf(null) > -1) data = data.filter(function(x) {return x != null})
|
139
|
-
|
140
155
|
var keysDiffer = false
|
141
156
|
if (data.length != cached.length) keysDiffer = true
|
142
157
|
else for (var i = 0, cachedCell, dataCell; cachedCell = cached[i], dataCell = data[i]; i++) {
|
@@ -156,16 +171,16 @@ var m = (function app(window, undefined) {
|
|
156
171
|
action: MOVE,
|
157
172
|
index: i,
|
158
173
|
from: existing[key].index,
|
159
|
-
element:
|
174
|
+
element: cached.nodes[existing[key].index] || $document.createElement("div")
|
160
175
|
}
|
161
176
|
}
|
162
|
-
else unkeyed.push({index: i, element: parentElement.childNodes[i] || $document.createElement("div")})
|
163
177
|
}
|
164
178
|
}
|
165
179
|
var actions = []
|
166
180
|
for (var prop in existing) actions.push(existing[prop])
|
167
181
|
var changes = actions.sort(sortChanges);
|
168
182
|
var newCached = new Array(cached.length)
|
183
|
+
newCached.nodes = cached.nodes.slice()
|
169
184
|
|
170
185
|
for (var i = 0, change; change = changes[i]; i++) {
|
171
186
|
if (change.action === DELETION) {
|
@@ -177,6 +192,7 @@ var m = (function app(window, undefined) {
|
|
177
192
|
dummy.key = data[change.index].attrs.key;
|
178
193
|
parentElement.insertBefore(dummy, parentElement.childNodes[change.index] || null);
|
179
194
|
newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]})
|
195
|
+
newCached.nodes[change.index] = dummy
|
180
196
|
}
|
181
197
|
|
182
198
|
if (change.action === MOVE) {
|
@@ -184,16 +200,10 @@ var m = (function app(window, undefined) {
|
|
184
200
|
parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null)
|
185
201
|
}
|
186
202
|
newCached[change.index] = cached[change.from]
|
203
|
+
newCached.nodes[change.index] = change.element
|
187
204
|
}
|
188
205
|
}
|
189
|
-
for (var i = 0, len = unkeyed.length; i < len; i++) {
|
190
|
-
var change = unkeyed[i];
|
191
|
-
parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null);
|
192
|
-
newCached[change.index] = cached[change.index]
|
193
|
-
}
|
194
206
|
cached = newCached;
|
195
|
-
cached.nodes = new Array(parentElement.childNodes.length);
|
196
|
-
for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes[i] = child
|
197
207
|
}
|
198
208
|
}
|
199
209
|
//end key algorithm
|
@@ -207,7 +217,7 @@ var m = (function app(window, undefined) {
|
|
207
217
|
//fix offset of next element if item was a trusted string w/ more than one html element
|
208
218
|
//the first clause in the regexp matches elements
|
209
219
|
//the second clause (after the pipe) matches text nodes
|
210
|
-
subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || []).length
|
220
|
+
subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || [0]).length
|
211
221
|
}
|
212
222
|
else subArrayCount += type.call(item) === ARRAY ? item.length : 1;
|
213
223
|
cached[cacheCount++] = item
|
@@ -229,15 +239,37 @@ var m = (function app(window, undefined) {
|
|
229
239
|
}
|
230
240
|
}
|
231
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.")
|
232
259
|
if (!data.attrs) data.attrs = {};
|
233
260
|
if (!cached.attrs) cached.attrs = {};
|
234
261
|
|
235
262
|
var dataAttrKeys = Object.keys(data.attrs)
|
236
263
|
var hasKeys = dataAttrKeys.length > ("key" in data.attrs ? 1 : 0)
|
237
264
|
//if an element is different enough from the one in cache, recreate it
|
238
|
-
if (data.tag != cached.tag || dataAttrKeys.join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) {
|
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)) {
|
239
266
|
if (cached.nodes.length) clear(cached.nodes);
|
240
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
|
+
}
|
241
273
|
}
|
242
274
|
if (type.call(data.tag) != STRING) return;
|
243
275
|
|
@@ -245,6 +277,7 @@ var m = (function app(window, undefined) {
|
|
245
277
|
if (data.attrs.xmlns) namespace = data.attrs.xmlns;
|
246
278
|
else if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg";
|
247
279
|
else if (data.tag === "math") namespace = "http://www.w3.org/1998/Math/MathML";
|
280
|
+
|
248
281
|
if (isNew) {
|
249
282
|
if (data.attrs.is) node = namespace === undefined ? $document.createElement(data.tag, data.attrs.is) : $document.createElementNS(namespace, data.tag, data.attrs.is);
|
250
283
|
else node = namespace === undefined ? $document.createElement(data.tag) : $document.createElementNS(namespace, data.tag);
|
@@ -257,9 +290,22 @@ var m = (function app(window, undefined) {
|
|
257
290
|
data.children,
|
258
291
|
nodes: [node]
|
259
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
|
+
|
260
306
|
if (cached.children && !cached.children.nodes) cached.children.nodes = [];
|
261
307
|
//edge case: setting value on <select> doesn't work before children exist, so set it again after children have been created
|
262
|
-
if (data.tag === "select" && data.attrs
|
308
|
+
if (data.tag === "select" && "value" in data.attrs) setAttributes(node, data.tag, {value: data.attrs.value}, {}, namespace);
|
263
309
|
parentElement.insertBefore(node, parentElement.childNodes[index] || null)
|
264
310
|
}
|
265
311
|
else {
|
@@ -267,6 +313,10 @@ var m = (function app(window, undefined) {
|
|
267
313
|
if (hasKeys) setAttributes(node, data.tag, data.attrs, cached.attrs, namespace);
|
268
314
|
cached.children = build(node, data.tag, undefined, undefined, data.children, cached.children, false, 0, data.attrs.contenteditable ? node : editable, namespace, configs);
|
269
315
|
cached.nodes.intact = true;
|
316
|
+
if (controllers.length) {
|
317
|
+
cached.views = views
|
318
|
+
cached.controllers = controllers
|
319
|
+
}
|
270
320
|
if (shouldReattach === true && node != null) parentElement.insertBefore(node, parentElement.childNodes[index] || null)
|
271
321
|
}
|
272
322
|
//schedule configs to be called. They are called after `build` finishes running
|
@@ -282,7 +332,7 @@ var m = (function app(window, undefined) {
|
|
282
332
|
configs.push(callback(data, [node, !isNew, context, cached]))
|
283
333
|
}
|
284
334
|
}
|
285
|
-
else if (typeof
|
335
|
+
else if (typeof data != FUNCTION) {
|
286
336
|
//handle text nodes
|
287
337
|
var nodes;
|
288
338
|
if (cached.nodes.length === 0) {
|
@@ -358,7 +408,7 @@ var m = (function app(window, undefined) {
|
|
358
408
|
//handle cases that are properties (but ignore cases where we should use setAttribute instead)
|
359
409
|
//- list and form are typically used as strings, but are DOM element references in js
|
360
410
|
//- when using CSS selectors (e.g. `m("[style='']")`), style is used as a string, but it's an object in js
|
361
|
-
else if (attrName in node && !(attrName === "list" || attrName === "style" || attrName === "form" || attrName === "type")) {
|
411
|
+
else if (attrName in node && !(attrName === "list" || attrName === "style" || attrName === "form" || attrName === "type" || attrName === "width" || attrName === "height")) {
|
362
412
|
//#348 don't set the value if not needed otherwise cursor placement breaks in Chrome
|
363
413
|
if (tag !== "input" || node[attrName] !== dataAttr) node[attrName] = dataAttr
|
364
414
|
}
|
@@ -388,7 +438,15 @@ var m = (function app(window, undefined) {
|
|
388
438
|
if (nodes.length != 0) nodes.length = 0
|
389
439
|
}
|
390
440
|
function unload(cached) {
|
391
|
-
if (cached.configContext && typeof cached.configContext.onunload === FUNCTION)
|
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
|
+
}
|
392
450
|
if (cached.children) {
|
393
451
|
if (type.call(cached.children) === ARRAY) {
|
394
452
|
for (var i = 0, child; child = cached.children[i]; i++) unload(child)
|
@@ -446,7 +504,7 @@ var m = (function app(window, undefined) {
|
|
446
504
|
var nodeCache = [], cellCache = {};
|
447
505
|
m.render = function(root, cell, forceRecreation) {
|
448
506
|
var configs = [];
|
449
|
-
if (!root) throw new Error("
|
507
|
+
if (!root) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");
|
450
508
|
var id = getCellCacheKey(root);
|
451
509
|
var isDocumentRoot = root === $document;
|
452
510
|
var node = isDocumentRoot || root === $document.documentElement ? documentNode : root;
|
@@ -489,42 +547,75 @@ var m = (function app(window, undefined) {
|
|
489
547
|
return gettersetter(store)
|
490
548
|
};
|
491
549
|
|
492
|
-
var roots = [],
|
550
|
+
var roots = [], components = [], controllers = [], lastRedrawId = null, lastRedrawCallTime = 0, computePreRedrawHook = null, computePostRedrawHook = null, prevented = false, topComponent, unloaders = [];
|
493
551
|
var FRAME_BUDGET = 16; //60 frames per second = 1 call per 16 ms
|
494
|
-
|
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) {
|
495
569
|
if (!root) throw new Error("Please ensure the DOM element exists before rendering a template into it.");
|
496
570
|
var index = roots.indexOf(root);
|
497
571
|
if (index < 0) index = roots.length;
|
572
|
+
|
498
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
|
+
|
499
587
|
if (controllers[index] && typeof controllers[index].onunload === FUNCTION) {
|
500
|
-
var event = {
|
501
|
-
preventDefault: function() {isPrevented = true}
|
502
|
-
};
|
503
588
|
controllers[index].onunload(event)
|
504
589
|
}
|
590
|
+
|
505
591
|
if (!isPrevented) {
|
506
592
|
m.redraw.strategy("all");
|
507
593
|
m.startComputation();
|
508
594
|
roots[index] = root;
|
509
|
-
|
510
|
-
var
|
511
|
-
|
512
|
-
|
513
|
-
|
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) {
|
514
602
|
controllers[index] = controller;
|
515
|
-
|
603
|
+
components[index] = component
|
516
604
|
}
|
517
605
|
endFirstComputation();
|
518
606
|
return controllers[index]
|
519
607
|
}
|
520
608
|
};
|
609
|
+
var redrawing = false
|
521
610
|
m.redraw = function(force) {
|
611
|
+
if (redrawing) return
|
612
|
+
redrawing = true
|
522
613
|
//lastRedrawId is a positive number if a second redraw is requested before the next animation frame
|
523
614
|
//lastRedrawID is null if it's the first redraw and not an event handler
|
524
615
|
if (lastRedrawId && force !== true) {
|
525
616
|
//when setTimeout: only reschedule redraw if time between now and previous redraw is bigger than a frame, otherwise keep currently scheduled timeout
|
526
617
|
//when rAF: always reschedule redraw
|
527
|
-
if (new Date - lastRedrawCallTime > FRAME_BUDGET
|
618
|
+
if ($requestAnimationFrame === window.requestAnimationFrame || new Date - lastRedrawCallTime > FRAME_BUDGET) {
|
528
619
|
if (lastRedrawId > 0) $cancelAnimationFrame(lastRedrawId);
|
529
620
|
lastRedrawId = $requestAnimationFrame(redraw, FRAME_BUDGET)
|
530
621
|
}
|
@@ -533,13 +624,18 @@ var m = (function app(window, undefined) {
|
|
533
624
|
redraw();
|
534
625
|
lastRedrawId = $requestAnimationFrame(function() {lastRedrawId = null}, FRAME_BUDGET)
|
535
626
|
}
|
627
|
+
redrawing = false
|
536
628
|
};
|
537
629
|
m.redraw.strategy = m.prop();
|
538
630
|
function redraw() {
|
539
|
-
|
631
|
+
if (computePreRedrawHook) {
|
632
|
+
computePreRedrawHook()
|
633
|
+
computePreRedrawHook = null
|
634
|
+
}
|
540
635
|
for (var i = 0, root; root = roots[i]; i++) {
|
541
636
|
if (controllers[i]) {
|
542
|
-
|
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) : "")
|
543
639
|
}
|
544
640
|
}
|
545
641
|
//after rendering within a routed context, we need to scroll back to the top, and fetch the document title for history.pushState
|
@@ -576,7 +672,7 @@ var m = (function app(window, undefined) {
|
|
576
672
|
|
577
673
|
//routing
|
578
674
|
var modes = {pathname: "", hash: "#", search: "?"};
|
579
|
-
var redirect =
|
675
|
+
var redirect = noop, routeParams, currentRoute, isDefaultRoute = false;
|
580
676
|
m.route = function() {
|
581
677
|
//m.route()
|
582
678
|
if (arguments.length === 0) return currentRoute;
|
@@ -586,29 +682,42 @@ var m = (function app(window, undefined) {
|
|
586
682
|
redirect = function(source) {
|
587
683
|
var path = currentRoute = normalizeRoute(source);
|
588
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
|
589
687
|
m.route(defaultRoute, true)
|
688
|
+
isDefaultRoute = false
|
590
689
|
}
|
591
690
|
};
|
592
691
|
var listener = m.route.mode === "hash" ? "onhashchange" : "onpopstate";
|
593
692
|
window[listener] = function() {
|
594
|
-
|
595
|
-
|
693
|
+
var path = $location[m.route.mode]
|
694
|
+
if (m.route.mode === "pathname") path += $location.search
|
695
|
+
if (currentRoute != normalizeRoute(path)) {
|
696
|
+
redirect(path)
|
596
697
|
}
|
597
698
|
};
|
598
|
-
|
699
|
+
computePreRedrawHook = setScroll;
|
599
700
|
window[listener]()
|
600
701
|
}
|
601
702
|
//config: m.route
|
602
|
-
else if (arguments[0].addEventListener) {
|
703
|
+
else if (arguments[0].addEventListener || arguments[0].attachEvent) {
|
603
704
|
var element = arguments[0];
|
604
705
|
var isInitialized = arguments[1];
|
605
706
|
var context = arguments[2];
|
606
|
-
|
607
|
-
element.
|
608
|
-
element.addEventListener
|
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
|
+
}
|
609
717
|
}
|
610
|
-
//m.route(route, params)
|
718
|
+
//m.route(route, params, shouldReplaceHistoryEntry)
|
611
719
|
else if (type.call(arguments[0]) === STRING) {
|
720
|
+
var oldRoute = currentRoute;
|
612
721
|
currentRoute = arguments[0];
|
613
722
|
var args = arguments[1] || {}
|
614
723
|
var queryIndex = currentRoute.indexOf("?")
|
@@ -618,16 +727,19 @@ var m = (function app(window, undefined) {
|
|
618
727
|
var currentPath = queryIndex > -1 ? currentRoute.slice(0, queryIndex) : currentRoute
|
619
728
|
if (querystring) currentRoute = currentPath + (currentPath.indexOf("?") === -1 ? "?" : "&") + querystring;
|
620
729
|
|
621
|
-
var shouldReplaceHistoryEntry = (arguments.length === 3 ? arguments[2] : arguments[1]) === true;
|
730
|
+
var shouldReplaceHistoryEntry = (arguments.length === 3 ? arguments[2] : arguments[1]) === true || oldRoute === arguments[0];
|
622
731
|
|
623
732
|
if (window.history.pushState) {
|
733
|
+
computePreRedrawHook = setScroll
|
624
734
|
computePostRedrawHook = function() {
|
625
735
|
window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, $document.title, modes[m.route.mode] + currentRoute);
|
626
|
-
setScroll()
|
627
736
|
};
|
628
737
|
redirect(modes[m.route.mode] + currentRoute)
|
629
738
|
}
|
630
|
-
else
|
739
|
+
else {
|
740
|
+
$location[m.route.mode] = currentRoute
|
741
|
+
redirect(modes[m.route.mode] + currentRoute)
|
742
|
+
}
|
631
743
|
}
|
632
744
|
};
|
633
745
|
m.route.param = function(key) {
|
@@ -635,7 +747,9 @@ var m = (function app(window, undefined) {
|
|
635
747
|
return routeParams[key]
|
636
748
|
};
|
637
749
|
m.route.mode = "search";
|
638
|
-
function normalizeRoute(route) {
|
750
|
+
function normalizeRoute(route) {
|
751
|
+
return route.slice(modes[m.route.mode].length)
|
752
|
+
}
|
639
753
|
function routeByValue(root, router, path) {
|
640
754
|
routeParams = {};
|
641
755
|
|
@@ -645,9 +759,18 @@ var m = (function app(window, undefined) {
|
|
645
759
|
path = path.substr(0, queryStart)
|
646
760
|
}
|
647
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
|
+
|
648
771
|
for (var route in router) {
|
649
772
|
if (route === path) {
|
650
|
-
m.
|
773
|
+
m.mount(root, router[route]);
|
651
774
|
return true
|
652
775
|
}
|
653
776
|
|
@@ -658,7 +781,7 @@ var m = (function app(window, undefined) {
|
|
658
781
|
var keys = route.match(/:[^\/]+/g) || [];
|
659
782
|
var values = [].slice.call(arguments, 1, -2);
|
660
783
|
for (var i = 0, len = keys.length; i < len; i++) routeParams[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
|
661
|
-
m.
|
784
|
+
m.mount(root, router[route])
|
662
785
|
});
|
663
786
|
return true
|
664
787
|
}
|
@@ -669,8 +792,9 @@ var m = (function app(window, undefined) {
|
|
669
792
|
if (e.ctrlKey || e.metaKey || e.which === 2) return;
|
670
793
|
if (e.preventDefault) e.preventDefault();
|
671
794
|
else e.returnValue = false;
|
672
|
-
var currentTarget = e.currentTarget ||
|
795
|
+
var currentTarget = e.currentTarget || e.srcElement;
|
673
796
|
var args = m.route.mode === "pathname" && currentTarget.search ? parseQueryString(currentTarget.search.slice(1)) : {};
|
797
|
+
while (currentTarget && currentTarget.nodeName.toUpperCase() != "A") currentTarget = currentTarget.parentNode
|
674
798
|
m.route(currentTarget[m.route.mode].slice(modes[m.route.mode].length), args)
|
675
799
|
}
|
676
800
|
function setScroll() {
|
@@ -678,24 +802,46 @@ var m = (function app(window, undefined) {
|
|
678
802
|
else window.scrollTo(0, 0)
|
679
803
|
}
|
680
804
|
function buildQueryString(object, prefix) {
|
681
|
-
var
|
682
|
-
|
683
|
-
|
684
|
-
|
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)
|
685
823
|
}
|
686
824
|
return str.join("&")
|
687
825
|
}
|
688
826
|
function parseQueryString(str) {
|
827
|
+
if (str.charAt(0) === "?") str = str.substring(1);
|
828
|
+
|
689
829
|
var pairs = str.split("&"), params = {};
|
690
830
|
for (var i = 0, len = pairs.length; i < len; i++) {
|
691
831
|
var pair = pairs[i].split("=");
|
692
|
-
|
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
|
693
839
|
}
|
694
840
|
return params
|
695
841
|
}
|
696
|
-
|
697
|
-
|
698
|
-
|
842
|
+
m.route.buildQueryString = buildQueryString
|
843
|
+
m.route.parseQueryString = parseQueryString
|
844
|
+
|
699
845
|
function reset(root) {
|
700
846
|
var cacheKey = getCellCacheKey(root);
|
701
847
|
clear(root.childNodes, cellCache[cacheKey]);
|
@@ -707,11 +853,11 @@ var m = (function app(window, undefined) {
|
|
707
853
|
deferred.promise = propify(deferred.promise);
|
708
854
|
return deferred
|
709
855
|
};
|
710
|
-
function propify(promise) {
|
711
|
-
var prop = m.prop();
|
856
|
+
function propify(promise, initialValue) {
|
857
|
+
var prop = m.prop(initialValue);
|
712
858
|
promise.then(prop);
|
713
859
|
prop.then = function(resolve, reject) {
|
714
|
-
return propify(promise.then(resolve, reject))
|
860
|
+
return propify(promise.then(resolve, reject), initialValue)
|
715
861
|
};
|
716
862
|
return prop
|
717
863
|
}
|
@@ -877,7 +1023,7 @@ var m = (function app(window, undefined) {
|
|
877
1023
|
var script = $document.createElement("script");
|
878
1024
|
|
879
1025
|
window[callbackKey] = function(resp) {
|
880
|
-
|
1026
|
+
script.parentNode.removeChild(script);
|
881
1027
|
options.onload({
|
882
1028
|
type: "load",
|
883
1029
|
target: {
|
@@ -888,7 +1034,7 @@ var m = (function app(window, undefined) {
|
|
888
1034
|
};
|
889
1035
|
|
890
1036
|
script.onerror = function(e) {
|
891
|
-
|
1037
|
+
script.parentNode.removeChild(script);
|
892
1038
|
|
893
1039
|
options.onerror({
|
894
1040
|
type: "error",
|
@@ -964,20 +1110,21 @@ var m = (function app(window, undefined) {
|
|
964
1110
|
|
965
1111
|
m.request = function(xhrOptions) {
|
966
1112
|
if (xhrOptions.background !== true) m.startComputation();
|
967
|
-
var deferred =
|
1113
|
+
var deferred = new Deferred();
|
968
1114
|
var isJSONP = xhrOptions.dataType && xhrOptions.dataType.toLowerCase() === "jsonp";
|
969
1115
|
var serialize = xhrOptions.serialize = isJSONP ? identity : xhrOptions.serialize || JSON.stringify;
|
970
1116
|
var deserialize = xhrOptions.deserialize = isJSONP ? identity : xhrOptions.deserialize || JSON.parse;
|
971
|
-
var extract = xhrOptions.extract || function(xhr) {
|
1117
|
+
var extract = isJSONP ? function(jsonp) {return jsonp.responseText} : xhrOptions.extract || function(xhr) {
|
972
1118
|
return xhr.responseText.length === 0 && deserialize === JSON.parse ? null : xhr.responseText
|
973
1119
|
};
|
1120
|
+
xhrOptions.method = (xhrOptions.method || 'GET').toUpperCase();
|
974
1121
|
xhrOptions.url = parameterizeUrl(xhrOptions.url, xhrOptions.data);
|
975
1122
|
xhrOptions = bindData(xhrOptions, xhrOptions.data, serialize);
|
976
1123
|
xhrOptions.onload = xhrOptions.onerror = function(e) {
|
977
1124
|
try {
|
978
1125
|
e = e || event;
|
979
1126
|
var unwrap = (e.type === "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity;
|
980
|
-
var response = unwrap(deserialize(extract(e.target, xhrOptions)));
|
1127
|
+
var response = unwrap(deserialize(extract(e.target, xhrOptions)), e.target);
|
981
1128
|
if (e.type === "load") {
|
982
1129
|
if (type.call(response) === ARRAY && xhrOptions.type) {
|
983
1130
|
for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i])
|
@@ -993,7 +1140,7 @@ var m = (function app(window, undefined) {
|
|
993
1140
|
if (xhrOptions.background !== true) m.endComputation()
|
994
1141
|
};
|
995
1142
|
ajax(xhrOptions);
|
996
|
-
deferred.promise(xhrOptions.initialValue);
|
1143
|
+
deferred.promise = propify(deferred.promise, xhrOptions.initialValue);
|
997
1144
|
return deferred.promise
|
998
1145
|
};
|
999
1146
|
|