Merge branch 'rewrite' into array-isArray

This commit is contained in:
Barney Carroll 2016-12-16 10:51:39 +00:00
commit aa72f87408
27 changed files with 1172 additions and 529 deletions

20
.npmignore Normal file
View file

@ -0,0 +1,20 @@
# Configuration files
.deploy.env
.editorconfig
.eslintrc.js
.gitattributes
.gitignore
.travis.yml
# Tests
test-utils/
tests/
# Documentation
docs/
examples/
CONTRIBUTING.md
# Browser stub (use index.js w/ a bundler or mithril.js w/o one instead)
module/
browser.js

View file

@ -34,6 +34,6 @@ There are over 4000 assertions in the test suite, and tests cover even difficult
## Modularity ## Modularity
Despite the huge improvements in performance and modularity, the new codebase is smaller than v0.2.x, currently clocking at <!-- size -->7.43 KB<!-- /size --> min+gzip Despite the huge improvements in performance and modularity, the new codebase is smaller than v0.2.x, currently clocking at <!-- size -->7.47 KB<!-- /size --> min+gzip
In addition, Mithril is now completely modular: you can import only the modules that you need and easily integrate 3rd party modules if you wish to use a different library for routing, ajax, and even rendering In addition, Mithril is now completely modular: you can import only the modules that you need and easily integrate 3rd party modules if you wish to use a different library for routing, ajax, and even rendering

View file

@ -1,47 +1,59 @@
"use strict" "use strict"
var Vnode = require("../render/vnode") var Vnode = require("../render/vnode")
var Promise = require("../promise/promise")
var coreRouter = require("../router/router") var coreRouter = require("../router/router")
module.exports = function($window, redrawService) { module.exports = function($window, redrawService) {
var routeService = coreRouter($window) var routeService = coreRouter($window)
var identity = function(v) {return v} var identity = function(v) {return v}
var resolver, component, attrs, currentPath, resolve var render, component, attrs, currentPath, updatePending = false
var route = function(root, defaultRoute, routes) { var route = function(root, defaultRoute, routes) {
if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined") if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
var update = function(routeResolver, comp, params, path) { var update = function(routeResolver, comp, params, path) {
resolver = routeResolver, component = comp, attrs = params, currentPath = path, resolve = null component = comp != null && typeof comp.view === "function" ? comp : "div", attrs = params, currentPath = path, updatePending = false
resolver.render = routeResolver.render || identity render = (routeResolver.render || identity).bind(routeResolver)
render() run()
} }
var render = function() { var run = function() {
if (resolver != null) redrawService.render(root, resolver.render(Vnode(component, attrs.key, attrs))) if (render != null) redrawService.render(root, render(Vnode(component, attrs.key, attrs)))
}
var bail = function() {
routeService.setPath(defaultRoute)
} }
routeService.defineRoutes(routes, function(payload, params, path) { routeService.defineRoutes(routes, function(payload, params, path) {
if (payload.view) update({}, payload, params, path) if (payload.view) update({}, payload, params, path)
else { else {
if (payload.onmatch) { if (payload.onmatch) {
if (resolve != null) update(payload, component, params, path) updatePending = true
else { Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
resolve = function(resolved) { if (updatePending) update(payload, resolved, params, path)
update(payload, resolved, params, path) }, bail)
}
payload.onmatch(function(resolved) {
if (resolve != null) resolve(resolved)
}, params, path)
}
} }
else update(payload, "div", params, path) else update(payload, "div", params, path)
} }
}, function() { }, bail)
routeService.setPath(defaultRoute) redrawService.subscribe(root, run)
}) }
redrawService.subscribe(root, render) route.set = function(path, data, options) {
if (updatePending) options = {replace: true}
updatePending = false
routeService.setPath(path, data, options)
} }
route.set = routeService.setPath
route.get = function() {return currentPath} route.get = function() {return currentPath}
route.prefix = routeService.setPrefix route.prefix = function(prefix) {routeService.prefix = prefix}
route.link = routeService.link route.link = function(vnode) {
vnode.dom.setAttribute("href", routeService.prefix + vnode.attrs.href)
vnode.dom.onclick = function(e) {
if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return
e.preventDefault()
e.redraw = false
var href = this.getAttribute("href")
if (href.indexOf(routeService.prefix) === 0) href = href.slice(routeService.prefix.length)
route.set(href, undefined, undefined)
}
}
return route return route
} }

View file

@ -5,9 +5,11 @@ var callAsync = require("../../test-utils/callAsync")
var browserMock = require("../../test-utils/browserMock") var browserMock = require("../../test-utils/browserMock")
var m = require("../../render/hyperscript") var m = require("../../render/hyperscript")
var callAsync = require("../../test-utils/callAsync")
var coreRenderer = require("../../render/render") var coreRenderer = require("../../render/render")
var apiRedraw = require("../../api/redraw") var apiRedraw = require("../../api/redraw")
var apiRouter = require("../../api/router") var apiRouter = require("../../api/router")
var Promise = require("../../promise/promise")
o.spec("route", function() { o.spec("route", function() {
void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) { void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) {
@ -73,7 +75,7 @@ o.spec("route", function() {
} }
}) })
setTimeout(function() { callAsync(function() {
o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.nodeName).equals("DIV")
$window.history.back() $window.history.back()
@ -81,7 +83,7 @@ o.spec("route", function() {
o($window.location.pathname).equals("/") o($window.location.pathname).equals("/")
done() done()
}, FRAME_BUDGET) })
}) })
o("default route does not inherit params", function(done) { o("default route does not inherit params", function(done) {
@ -156,11 +158,11 @@ o.spec("route", function() {
o(onclick.args[0].target).equals(root.firstChild) o(onclick.args[0].target).equals(root.firstChild)
// Wrapped to give time for the rate-limited redraw to fire // Wrapped to give time for the rate-limited redraw to fire
setTimeout(function() { callAsync(function() {
o(onupdate.callCount).equals(1) o(onupdate.callCount).equals(1)
done() done()
}, FRAME_BUDGET * 2) })
}) })
o("event handlers can skip redraw", function(done) { o("event handlers can skip redraw", function(done) {
@ -191,11 +193,11 @@ o.spec("route", function() {
o(oninit.callCount).equals(1) o(oninit.callCount).equals(1)
// Wrapped to ensure no redraw fired // Wrapped to ensure no redraw fired
setTimeout(function() { callAsync(function() {
o(onupdate.callCount).equals(0) o(onupdate.callCount).equals(0)
done() done()
}, FRAME_BUDGET) })
}) })
o("changes location on route.link", function() { o("changes location on route.link", function() {
@ -229,23 +231,23 @@ o.spec("route", function() {
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test") o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test")
}) })
o("accepts RouteResolver", function() { o("accepts RouteResolver with onmatch that returns Component", function(done) {
var matchCount = 0 var matchCount = 0
var renderCount = 0 var renderCount = 0
var Component = { var Component = {
view: function() { view: function() {
return m("div") return m("span")
} }
} }
var resolver = { var resolver = {
onmatch: function(resolve, args, requestedPath) { onmatch: function(args, requestedPath) {
matchCount++ matchCount++
o(args.id).equals("abc") o(args.id).equals("abc")
o(requestedPath).equals("/abc") o(requestedPath).equals("/abc")
o(this).equals(resolver) o(this).equals(resolver)
resolve(Component) return Component
}, },
render: function(vnode) { render: function(vnode) {
renderCount++ renderCount++
@ -262,12 +264,175 @@ o.spec("route", function() {
"/:id" : resolver "/:id" : resolver
}) })
o(matchCount).equals(1) callAsync(function() {
o(renderCount).equals(1) o(matchCount).equals(1)
o(root.firstChild.nodeName).equals("DIV") o(renderCount).equals(1)
o(root.firstChild.nodeName).equals("SPAN")
done()
})
}) })
o("accepts RouteResolver without `render` method as payload", function() { o("accepts RouteResolver with onmatch that returns Promise<Component>", function(done) {
var matchCount = 0
var renderCount = 0
var Component = {
view: function() {
return m("span")
}
}
var resolver = {
onmatch: function(args, requestedPath) {
matchCount++
o(args.id).equals("abc")
o(requestedPath).equals("/abc")
o(this).equals(resolver)
return Promise.resolve(Component)
},
render: function(vnode) {
renderCount++
o(vnode.attrs.id).equals("abc")
o(this).equals(resolver)
return vnode
},
}
$window.location.href = prefix + "/abc"
route(root, "/abc", {
"/:id" : resolver
})
callAsync(function() {
o(matchCount).equals(1)
o(renderCount).equals(1)
o(root.firstChild.nodeName).equals("SPAN")
done()
})
})
o("accepts RouteResolver with onmatch that returns Promise<undefined>", function(done) {
var matchCount = 0
var renderCount = 0
var Component = {
view: function() {
return m("span")
}
}
var resolver = {
onmatch: function(args, requestedPath) {
matchCount++
o(args.id).equals("abc")
o(requestedPath).equals("/abc")
o(this).equals(resolver)
return Promise.resolve()
},
render: function(vnode) {
renderCount++
o(vnode.attrs.id).equals("abc")
o(this).equals(resolver)
return vnode
},
}
$window.location.href = prefix + "/abc"
route(root, "/abc", {
"/:id" : resolver
})
callAsync(function() {
o(matchCount).equals(1)
o(renderCount).equals(1)
o(root.firstChild.nodeName).equals("DIV")
done()
})
})
o("accepts RouteResolver with onmatch that returns Promise<any>", function(done) {
var matchCount = 0
var renderCount = 0
var Component = {
view: function() {
return m("span")
}
}
var resolver = {
onmatch: function(args, requestedPath) {
matchCount++
o(args.id).equals("abc")
o(requestedPath).equals("/abc")
o(this).equals(resolver)
return Promise.resolve([])
},
render: function(vnode) {
renderCount++
o(vnode.attrs.id).equals("abc")
o(this).equals(resolver)
return vnode
},
}
$window.location.href = prefix + "/abc"
route(root, "/abc", {
"/:id" : resolver
})
callAsync(function() {
o(matchCount).equals(1)
o(renderCount).equals(1)
o(root.firstChild.nodeName).equals("DIV")
done()
})
})
o("accepts RouteResolver with onmatch that returns rejected Promise", function(done) {
var matchCount = 0
var renderCount = 0
var spy = o.spy()
var Component = {
view: function() {
return m("span")
}
}
var resolver = {
onmatch: function(args, requestedPath) {
matchCount++
return Promise.reject(new Error("error"))
},
render: function(vnode) {
renderCount++
return vnode
},
}
$window.location.href = prefix + "/test/1"
route(root, "/default", {
"/default" : {view: spy},
"/test/:id" : resolver
})
callAsync(function() {
callAsync(function() {
o(matchCount).equals(1)
o(renderCount).equals(0)
o(spy.callCount).equals(1)
done()
})
})
})
o("accepts RouteResolver without `render` method as payload", function(done) {
var matchCount = 0 var matchCount = 0
var Component = { var Component = {
view: function() { view: function() {
@ -278,29 +443,29 @@ o.spec("route", function() {
$window.location.href = prefix + "/abc" $window.location.href = prefix + "/abc"
route(root, "/abc", { route(root, "/abc", {
"/:id" : { "/:id" : {
onmatch: function(resolve, args, requestedPath) { onmatch: function(args, requestedPath) {
matchCount++ matchCount++
o(args.id).equals("abc") o(args.id).equals("abc")
o(requestedPath).equals("/abc") o(requestedPath).equals("/abc")
resolve(Component) return Component
}, },
}, },
}) })
o(matchCount).equals(1) callAsync(function() {
o(matchCount).equals(1)
o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.nodeName).equals("DIV")
done()
})
}) })
o("changing `vnode.key` in `render` resets the component", function(done, timeout){ o("changing `vnode.key` in `render` resets the component", function(done, timeout){
timeout(FRAME_BUDGET * 6)
var oninit = o.spy() var oninit = o.spy()
var Component = { var Component = {
oninit: oninit, oninit: oninit,
view: function(){ view: function() {
return m("div") return m("div")
} }
} }
@ -310,14 +475,14 @@ o.spec("route", function() {
return m(Component, {key: vnode.attrs.id}) return m(Component, {key: vnode.attrs.id})
}} }}
}) })
setTimeout(function(){ callAsync(function() {
o(oninit.callCount).equals(1) o(oninit.callCount).equals(1)
route.set('/def') route.set("/def")
setTimeout(function(){ callAsync(function() {
o(oninit.callCount).equals(2) o(oninit.callCount).equals(2)
done() done()
}, FRAME_BUDGET) })
}, FRAME_BUDGET) })
}) })
o("accepts RouteResolver without `onmatch` method as payload", function() { o("accepts RouteResolver without `onmatch` method as payload", function() {
@ -371,14 +536,14 @@ o.spec("route", function() {
route.set("/b") route.set("/b")
setTimeout(function() { callAsync(function() {
o(root.firstChild).equals(dom) o(root.firstChild).equals(dom)
done() done()
}, FRAME_BUDGET) })
}) })
o("calls onmatch and view correct number of times", function() { o("calls onmatch and view correct number of times", function(done) {
var matchCount = 0 var matchCount = 0
var renderCount = 0 var renderCount = 0
var Component = { var Component = {
@ -390,9 +555,9 @@ o.spec("route", function() {
$window.location.href = prefix + "/" $window.location.href = prefix + "/"
route(root, "/", { route(root, "/", {
"/" : { "/" : {
onmatch: function(resolve) { onmatch: function() {
matchCount++ matchCount++
resolve(Component) return Component
}, },
render: function(vnode) { render: function(vnode) {
renderCount++ renderCount++
@ -401,48 +566,126 @@ o.spec("route", function() {
}, },
}) })
o(matchCount).equals(1) callAsync(function() {
o(renderCount).equals(1) o(matchCount).equals(1)
o(renderCount).equals(1)
redrawService.redraw() redrawService.redraw()
o(matchCount).equals(1) o(matchCount).equals(1)
o(renderCount).equals(2) o(renderCount).equals(2)
done()
})
})
o("calls onmatch and view correct number of times when not onmatch returns undefined", function(done) {
var matchCount = 0
var renderCount = 0
var Component = {
view: function() {
return m("div")
}
}
$window.location.href = prefix + "/"
route(root, "/", {
"/" : {
onmatch: function() {
matchCount++
},
render: function(vnode) {
renderCount++
return {tag: Component}
},
},
})
callAsync(function() {
o(matchCount).equals(1)
o(renderCount).equals(1)
redrawService.redraw()
o(matchCount).equals(1)
o(renderCount).equals(2)
done()
})
}) })
o("onmatch can redirect to another route", function(done) { o("onmatch can redirect to another route", function(done) {
var redirected = false var redirected = false
var render = o.spy()
$window.location.href = prefix + "/a" $window.location.href = prefix + "/a"
route(root, "/a", { route(root, "/a", {
"/a" : { "/a" : {
onmatch: function() { onmatch: function() {
route.set("/b") route.set("/b")
} },
render: render
}, },
"/b" : { "/b" : {
view: function(vnode){ view: function() {
redirected = true redirected = true
} }
} }
}) })
setTimeout(function() { callAsync(function() {
o(render.callCount).equals(0)
o(redirected).equals(true) o(redirected).equals(true)
done() done()
}, FRAME_BUDGET) })
}) })
o("onmatch can redirect to another route that has RouteResolver", function(done) { o("onmatch can redirect to another route that has RouteResolver w/ only onmatch", function(done) {
var redirected = false var redirected = false
var render = o.spy()
var view = o.spy(function() {return m("div")})
$window.location.href = prefix + "/a" $window.location.href = prefix + "/a"
route(root, "/a", { route(root, "/a", {
"/a" : { "/a" : {
onmatch: function() { onmatch: function() {
route.set("/b") route.set("/b")
},
render: render
},
"/b" : {
onmatch: function() {
redirected = true
return {view: view}
} }
}
})
callAsync(function() {
callAsync(function() {
o(render.callCount).equals(0)
o(redirected).equals(true)
o(view.callCount).equals(1)
o(root.childNodes.length).equals(1)
o(root.firstChild.nodeName).equals("DIV")
done()
})
})
})
o("onmatch can redirect to another route that has RouteResolver w/ only render", function(done) {
var redirected = false
var render = o.spy()
$window.location.href = prefix + "/a"
route(root, "/a", {
"/a" : {
onmatch: function() {
route.set("/b")
},
render: render
}, },
"/b" : { "/b" : {
render: function(vnode){ render: function(vnode){
@ -451,45 +694,219 @@ o.spec("route", function() {
} }
}) })
setTimeout(function() { callAsync(function() {
o(render.callCount).equals(0)
o(redirected).equals(true) o(redirected).equals(true)
done() done()
}, FRAME_BUDGET) })
}) })
o("onmatch resolution callback resolves at most once", function(done) { o("onmatch can redirect to another route that has RouteResolver whose onmatch resolves asynchronously", function(done) {
var resolveCount = 0 var redirected = false
var resolvedComponent var render = o.spy()
var A = {view: function() {}} var view = o.spy()
var B = {view: function() {}}
var C = {view: function() {}}
$window.location.href = prefix + "/" $window.location.href = prefix + "/a"
route(root, "/", { route(root, "/a", {
"/": { "/a" : {
onmatch: function(resolve) { onmatch: function() {
resolve(A) route.set("/b")
resolve(B) },
callAsync(function() {resolve(C)}) render: render
},
"/b" : {
onmatch: function() {
redirected = true
return new Promise(function(fulfill){
callAsync(function(){
fulfill({view: view})
})
})
}
}
})
callAsync(function() {
callAsync(function() {
callAsync(function() {
o(render.callCount).equals(0)
o(redirected).equals(true)
o(view.callCount).equals(1)
done()
})
})
})
})
o("onmatch can redirect to another route asynchronously", function(done) {
var redirected = false
var render = o.spy()
var view = o.spy()
$window.location.href = prefix + "/a"
route(root, "/a", {
"/a" : {
onmatch: function() {
callAsync(function() {route.set("/b")})
return new Promise(function() {})
},
render: render
},
"/b" : {
onmatch: function() {
redirected = true
return {view: view}
}
}
})
callAsync(function() {
callAsync(function() {
callAsync(function() {
o(render.callCount).equals(0)
o(redirected).equals(true)
o(view.callCount).equals(1)
done()
})
})
})
})
o("onmatch can redirect w/ window.history.back()", function(done) {
var render = o.spy()
var component = {view: o.spy()}
$window.location.href = prefix + "/a"
route(root, "/a", {
"/a" : {
onmatch: function() {
return component
}, },
render: function(vnode) { render: function(vnode) {
resolveCount++ return vnode
resolvedComponent = vnode.tag
} }
}, },
"/b" : {
onmatch: function() {
$window.history.back()
return new Promise(function() {})
},
render: render
}
}) })
setTimeout(function() {
o(resolveCount).equals(1)
o(resolvedComponent).equals(A)
done() callAsync(function() {
}, FRAME_BUDGET) route.set('/b')
callAsync(function() {
callAsync(function() {
callAsync(function() {
o(render.callCount).equals(0)
o(component.view.callCount).equals(2)
done()
})
})
})
})
})
o("onmatch can redirect to a non-existent route that defaults to a RouteResolver w/ onmatch", function(done) {
var redirected = false
var render = o.spy()
$window.location.href = prefix + "/a"
route(root, "/b", {
"/a" : {
onmatch: function() {
route.set("/c")
},
render: render
},
"/b" : {
onmatch: function(vnode){
redirected = true
return {view: function() {}}
}
}
})
callAsync(function() {
callAsync(function() {
o(render.callCount).equals(0)
o(redirected).equals(true)
done()
})
})
})
o("onmatch can redirect to a non-existent route that defaults to a RouteResolver w/ render", function(done) {
var redirected = false
var render = o.spy()
$window.location.href = prefix + "/a"
route(root, "/b", {
"/a" : {
onmatch: function() {
route.set("/c")
},
render: render
},
"/b" : {
render: function(vnode){
redirected = true
}
}
})
callAsync(function() {
callAsync(function() {
o(render.callCount).equals(0)
o(redirected).equals(true)
done()
})
})
})
o("onmatch can redirect to a non-existent route that defaults to a component", function(done) {
var redirected = false
var render = o.spy()
$window.location.href = prefix + "/a"
route(root, "/b", {
"/a" : {
onmatch: function() {
route.set("/c")
},
render: render
},
"/b" : {
view: function(vnode){
redirected = true
}
}
})
callAsync(function() {
callAsync(function() {
o(render.callCount).equals(0)
o(redirected).equals(true)
done()
})
})
}) })
o("the previous view redraws while onmatch resolution is pending (#1268)", function(done) { o("the previous view redraws while onmatch resolution is pending (#1268)", function(done) {
var view = o.spy() var view = o.spy()
var onmatch = o.spy() var onmatch = o.spy(function() {
return new Promise(function() {})
})
$window.location.href = prefix + "/a" $window.location.href = prefix + "/a"
route(root, "/", { route(root, "/", {
@ -502,7 +919,7 @@ o.spec("route", function() {
route.set("/b") route.set("/b")
setTimeout(function(){ callAsync(function() {
o(view.callCount).equals(1) o(view.callCount).equals(1)
o(onmatch.callCount).equals(1) o(onmatch.callCount).equals(1)
@ -512,12 +929,12 @@ o.spec("route", function() {
o(onmatch.callCount).equals(1) o(onmatch.callCount).equals(1)
done() done()
}, FRAME_BUDGET) })
}) })
o("m.route.set(m.route.get()) re-runs the resolution logic (#1180)", function(done){ o("m.route.set(m.route.get()) re-runs the resolution logic (#1180)", function(done){
var onmatch = o.spy(function(resolve) {resolve()}) var onmatch = o.spy()
var render = o.spy(function(){return m("div")}) var render = o.spy(function() {return m("div")})
$window.location.href = prefix + "/" $window.location.href = prefix + "/"
route(root, '/', { route(root, '/', {
@ -527,25 +944,33 @@ o.spec("route", function() {
} }
}) })
o(onmatch.callCount).equals(1) callAsync(function() {
o(render.callCount).equals(1) o(onmatch.callCount).equals(1)
o(render.callCount).equals(1)
route.set(route.get()) route.set(route.get())
setTimeout(function() { callAsync(function() {
o(onmatch.callCount).equals(2) callAsync(function() {
o(render.callCount).equals(2) o(onmatch.callCount).equals(2)
o(render.callCount).equals(2)
done() done()
}, FRAME_BUDGET) })
})
})
}) })
o("m.route.get() returns the last fully resolved route (#1276)", function(done){ o("m.route.get() returns the last fully resolved route (#1276)", function(done){
$window.location.href = prefix + "/" $window.location.href = prefix + "/"
route(root, "/", { route(root, "/", {
"/": {view: function(){}}, "/": {view: function() {}},
"/2": {onmatch: function(){}} "/2": {
onmatch: function() {
return new Promise(function() {})
}
}
}) })
@ -553,15 +978,13 @@ o.spec("route", function() {
route.set("/2") route.set("/2")
setTimeout(function(){ callAsync(function() {
o(route.get()).equals("/") o(route.get()).equals("/")
done() done()
}, FRAME_BUDGET) })
}) })
o("routing with RouteResolver works more than once", function(done, timeout) { o("routing with RouteResolver works more than once", function(done) {
timeout(200)
$window.location.href = prefix + "/a" $window.location.href = prefix + "/a"
route(root, '/a', { route(root, '/a', {
'/a': { '/a': {
@ -578,44 +1001,96 @@ o.spec("route", function() {
route.set('/b') route.set('/b')
setTimeout(function(){ callAsync(function() {
route.set('/a') route.set('/a')
setTimeout(function(){ callAsync(function() {
o(root.firstChild.nodeName).equals("A") o(root.firstChild.nodeName).equals("A")
done() done()
}, FRAME_BUDGET) })
}, FRAME_BUDGET) })
}) })
o("calling route.set invalidates pending onmatch resolution", function(done, timeout) { o("calling route.set invalidates pending onmatch resolution", function(done) {
timeout(200) var rendered = false
var resolved var resolved
$window.location.href = prefix + "/a" $window.location.href = prefix + "/a"
route(root, "/a", { route(root, "/a", {
"/a": { "/a": {
onmatch: function(resolve) { onmatch: function() {
setTimeout(resolve, 20) return new Promise(function(resolve) {
callAsync(function() {
callAsync(function() {
resolve({view: function() {}})
})
})
})
}, },
render: function(vnode) {resolved = "a"} render: function(vnode) {
rendered = true
resolved = "a"
}
}, },
"/b": { "/b": {
view: function() {resolved = "b"} view: function() {
resolved = "b"
}
} }
}) })
route.set("/b") route.set("/b")
setTimeout(function() { callAsync(function() {
o(rendered).equals(false)
o(resolved).equals("b") o(resolved).equals("b")
done() done()
}, 30) })
}) })
o("route changes activate onbeforeremove", function(done, timeout) { o("calling route.set invalidates pending onmatch resolution", function(done) {
var rendered = false
var resolved
$window.location.href = prefix + "/a"
route(root, "/a", {
"/a": {
onmatch: function() {
return new Promise(function(resolve) {
callAsync(function() {
callAsync(function() {
resolve({view: function() {rendered = true}})
})
})
})
},
render: function(vnode) {
rendered = true
resolved = "a"
}
},
"/b": {
view: function() {
resolved = "b"
}
}
})
route.set("/b")
callAsync(function() {
o(rendered).equals(false)
o(resolved).equals("b")
callAsync(function() {
o(rendered).equals(false)
o(resolved).equals("b")
done()
})
})
})
o("route changes activate onbeforeremove", function(done) {
var spy = o.spy() var spy = o.spy()
$window.location.href = prefix + "/a" $window.location.href = prefix + "/a"
@ -631,13 +1106,46 @@ o.spec("route", function() {
route.set("/b") route.set("/b")
setTimeout(function() { callAsync(function() {
o(spy.callCount).equals(1) o(spy.callCount).equals(1)
done() done()
}, 30) })
}) })
o("asynchronous route.set in onmatch works", function(done) {
var rendered = false, resolved
route(root, "/a", {
"/a": {
onmatch: function() {
return Promise.resolve().then(function() {
route.set("/b")
})
},
render: function(vnode) {
rendered = true
resolved = "a"
}
},
"/b": {
view: function() {
resolved = "b"
}
},
})
callAsync(function() { // tick for popstate for /a
callAsync(function() { // tick for promise in onmatch
callAsync(function() { // tick for onpopstate for /b
o(rendered).equals(false)
o(resolved).equals("b")
done()
})
})
})
})
o("throttles", function(done, timeout) { o("throttles", function(done, timeout) {
timeout(200) timeout(200)
@ -654,12 +1162,12 @@ o.spec("route", function() {
redrawService.redraw() redrawService.redraw()
var after = i var after = i
setTimeout(function(){ setTimeout(function() {
o(before).equals(1) // routes synchronously o(before).equals(1) // routes synchronously
o(after).equals(2) // redraws synchronously o(after).equals(2) // redraws synchronously
o(i).equals(3) // throttles rest o(i).equals(3) // throttles rest
done() done()
},40) }, FRAME_BUDGET * 2)
}) })
}) })
}) })

View file

@ -126,7 +126,7 @@ var querystring = m.buildQueryString({a: "1", b: "2"})
```javascript ```javascript
var state = { var state = {
value: "", value: "",
setValue: function(v) {value = v} setValue: function(v) {state.value = v}
} }
var Component = { var Component = {

View file

@ -13,7 +13,9 @@ If you are migrating, consider using the [mithril-codemods](https://www.npmjs.co
- [`m.prop` removed](#mprop-removed) - [`m.prop` removed](#mprop-removed)
- [`m.component` removed](#mcomponent-removed) - [`m.component` removed](#mcomponent-removed)
- [`config` function](#config-function) - [`config` function](#config-function)
- [Cancelling redraw from event handlers](#cancelling-redraw-from-event-handlers) - [Changes in redraw behaviour](#changes-in-redraw-behaviour)
- [No more redraw locks](#no-more-redraw-locks)
- [Cancelling redraw from event handlers](#cancelling-redraw-from-event-handlers)
- [Component `controller` function](#component-controller-function) - [Component `controller` function](#component-controller-function)
- [Component arguments](#component-arguments) - [Component arguments](#component-arguments)
- [`view()` parameters](#view-parameters) - [`view()` parameters](#view-parameters)
@ -25,6 +27,7 @@ If you are migrating, consider using the [mithril-codemods](https://www.npmjs.co
- [Accessing route params](#accessing-route-params) - [Accessing route params](#accessing-route-params)
- [Preventing unmounting](#preventing-unmounting) - [Preventing unmounting](#preventing-unmounting)
- [`m.request`](#mrequest) - [`m.request`](#mrequest)
- [`m.deferred` removed](#mdeferred-removed)
- [`m.sync` removed](#msync-removed) - [`m.sync` removed](#msync-removed)
- [`xlink` namespace required](#xlink-namespace-required) - [`xlink` namespace required](#xlink-namespace-required)
- [Nested arrays in views](#nested-arrays-in-views) - [Nested arrays in views](#nested-arrays-in-views)
@ -118,7 +121,15 @@ If available the DOM-Element of the vnode can be accessed at `vnode.dom`.
--- ---
## Cancelling redraw from event handlers ## Changes in redraw behaviour
Mithril's rendering engine still operates on the basis of semi-automated global redraws, but some APIs and behaviours differ:
### No more redraw locks
In v0.2.x, Mithril allowed 'redraw locks' which temporarily prevented blocked draw logic: by default, `m.request` would lock the draw loop on execution and unlock when all pending requests had resolved - the same behaviour could be invoked manually using `m.startComputation()` and `m.endComputation()`. The latter APIs and the associated behaviour has been removed in v1.x. Redraw locking can lead to buggy UIs: the concerns of one part of the application should not be allowed to prevent other parts of the view from updating to reflect change.
### Cancelling redraw from event handlers
`m.mount()` and `m.route()` still automatically redraw after a DOM event handler runs. Cancelling these redraws from within your event handlers is now done by setting the `redraw` property on the passed-in event object to `false`. `m.mount()` and `m.route()` still automatically redraw after a DOM event handler runs. Cancelling these redraws from within your event handlers is now done by setting the `redraw` property on the passed-in event object to `false`.
@ -492,9 +503,45 @@ Additionally, if the `extract` option is passed to `m.request` the return value
--- ---
## `m.deferred` removed
`v0.2.x` used its own custom asynchronous contract object, exposed as `m.deferred`, which was used as the basis for `m.request`. `v1.x` uses Promises instead, and implements a [polyfill](promises.md) in non-supporting environments. In situations where you would have used `m.deferred`, you should use Promises instead.
### `v0.2.x`
```javascript
var greetAsync = function() {
var deferred = m.deferred();
setTimeout(function() {
deferred.resolve("hello");
}, 1000);
return deferred.promise;
};
greetAsync()
.then(function(value) {return value + " world"})
.then(function(value) {console.log(value)}); //logs "hello world" after 1 second
```
### `v1.x`
```javascript
var greetAsync = new Promise(function(resolve){
setTimeout(function() {
resolve("hello");
}, 1000);
});
greetAsync()
.then(function(value) {return value + " world"})
.then(function(value) {console.log(value)}); //logs "hello world" after 1 second
```
---
## `m.sync` removed ## `m.sync` removed
`m.sync` has been removed in favor of `Promise.all` Since `v1.x` uses standards-compliant Promises, `m.sync` is redundant. Use `Promise.all` instead.
### `v0.2.x` ### `v0.2.x`
@ -560,7 +607,7 @@ If a vnode is strictly equal to the vnode occupying its place in the last draw,
## `m.startComputation`/`m.endComputation` removed ## `m.startComputation`/`m.endComputation` removed
They are considered anti-patterns and have a number of problematic edge cases, so they no longer exist in v1.x They are considered anti-patterns and have a number of problematic edge cases, so they no longer exist in v1.x.
--- ---

View file

@ -4,7 +4,7 @@
- [Signature](#signature) - [Signature](#signature)
- [How it works](#how-it-works) - [How it works](#how-it-works)
- [Performance considerations](#performance-considerations) - [Performance considerations](#performance-considerations)
- [Differences from m.render](#differences-from-m-render) - [Differences from m.render](#differences-from-mrender)
--- ---
@ -73,4 +73,4 @@ A component rendered via `m.mount` automatically auto-redraws in response to vie
`m.mount()` is suitable for application developers integrating Mithril widgets into existing codebases where routing is handled by another library or framework, while still enjoying Mithril's auto-redrawing facilities. `m.mount()` is suitable for application developers integrating Mithril widgets into existing codebases where routing is handled by another library or framework, while still enjoying Mithril's auto-redrawing facilities.
`m.render()` is suitable for library authors who wish to manually control rendering (e.g. when hooking to a third party router, or using third party data-layer libraries like Redux). `m.render()` is suitable for library authors who wish to manually control rendering (e.g. when hooking to a third party router, or using third party data-layer libraries like Redux).

View file

@ -76,7 +76,7 @@ m.request({
}) })
``` ```
Calls to `m.request` return a [promise](promise.md). A call to `m.request` return a [promise](promise.md) and trigger a redraw upon completion of its promise chain.
By default, `m.request` assumes the response is in JSON format and parses it into a Javascript object (or array). By default, `m.request` assumes the response is in JSON format and parses it into a Javascript object (or array).

View file

@ -16,9 +16,10 @@
- [Routing parameters](#routing-parameters) - [Routing parameters](#routing-parameters)
- [Changing router prefix](#changing-router-prefix) - [Changing router prefix](#changing-router-prefix)
- [Advanced component resolution](#advanced-component-resolution) - [Advanced component resolution](#advanced-component-resolution)
- [Wrapping a layout component](#wrapping-a-layout-component) - [Wrapping a layout component](#wrapping-a-layout-component)
- [Authentication](#authentication) - [Authentication](#authentication)
- [Code splitting](#code-splitting) - [Preloading data](#preloading-data)
- [Code splitting](#code-splitting)
--- ---
@ -107,18 +108,23 @@ A RouterResolver is an object that contains an `onmatch` method and/or a `render
##### routeResolver.onmatch ##### routeResolver.onmatch
The `onmatch` hook is called when the router needs to find a component to render. It is called once when a router path changes, but not on subsequent redraws. It can be used to run logic before a component initializes (for example authentication logic) The `onmatch` hook is called when the router needs to find a component to render. It is called once per router path changes, but not on subsequent redraws while on the same path. It can be used to run logic before a component initializes (for example authentication logic, data preloading, redirection analytics tracking, etc)
This method also allows you to asynchronously define what component will be rendered, making it suitable for code splitting and asynchronous module loading. This method also allows you to asynchronously define what component will be rendered, making it suitable for code splitting and asynchronous module loading. To render a component asynchronously return a promise that resolves to a component.
`routeResolver.onmatch(resolve, args, requestedPath)` For more information on `onmatch`, see the [advanced component resolution](#advanced-component-resolution) section
Argument | Type | Description `routeResolver.onmatch(args, requestedPath)`
--------------- | ------------------------ | ---
`resolve` | `Component -> undefined` | Call this function with a component as the first argument to use it as the route's component Argument | Type | Description
`args` | `Object` | The [routing parameters](#routing-parameters) --------------- | ------------------------------ | ---
`requestedPath` | `String` | The router path requested by the last routing action, including interpolated routing parameter values, but without the prefix. When `onmatch` is called, the resolution for this path is not complete and `m.route.get()` still returns the previous path. `args` | `Object` | The [routing parameters](#routing-parameters)
**returns** | | Returns `undefined` `requestedPath` | `String` | The router path requested by the last routing action, including interpolated routing parameter values, but without the prefix. When `onmatch` is called, the resolution for this path is not complete and `m.route.get()` still returns the previous path.
**returns** | `Component|Promise<Component>` | Returns a component or a promise that resolves to a component
If `onmatch` returns a component or a promise that resolves to a component, this component is used as the `vnode.tag` for the first argument in the RouteResolver's `render` method. Otherwise, `vnode.tag` is set to `"div"`. Similarly, if the `onmatch` method is omitted, `vnode.tag` is also `"div"`.
If `onmatch` returns a promise that gets rejected, the router redirects back to `defaultRoute`. You may override this behavior by calling `.catch` on the promise chain before returning it.
##### routeResolver.render ##### routeResolver.render
@ -128,8 +134,8 @@ The `render` method is called on every redraw for a matching route. It is simila
Argument | Type | Description Argument | Type | Description
------------------- | --------------- | ----------- ------------------- | --------------- | -----------
`vnode` | `Object` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If the routeResolver does not have a `resolve` method, the vnode's `tag` field defaults to a `div` `vnode` | `Object` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If onmatch does not return a component or a promise that resolves to a component, the vnode's `tag` field defaults to `"div"`
`vnode.attrs` | `Object` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If the routeResolver does not have a `resolve` method, the vnode defaults to a `div` `vnode.attrs` | `Object` | A map of URL parameter values
**returns** | `Vnode` | Returns a vnode **returns** | `Vnode` | Returns a vnode
--- ---
@ -258,7 +264,7 @@ m.route(document.body, "/edit/pictures/image.jpg", {
--- ---
### Changing route prefix ### Changing router prefix
The router prefix is a fragment of the URL that dictates the underlying [strategy](routing-strategies.md) used by the router. The router prefix is a fragment of the URL that dictates the underlying [strategy](routing-strategies.md) used by the router.
@ -286,8 +292,8 @@ Instead of mapping a component to a route, you can specify a RouteResolver objec
```javascript ```javascript
m.route(document.body, "/", { m.route(document.body, "/", {
"/": { "/": {
onmatch: function(resolve, args, requestedPath) { onmatch: function(args, requestedPath) {
resolve(Home) return Home
}, },
render: function(vnode) { render: function(vnode) {
return vnode // equivalent to m(Home) return vnode // equivalent to m(Home)
@ -300,7 +306,7 @@ RouteResolvers are useful for implementing a variety of advanced routing use cas
--- ---
### Wrapping a layout component #### Wrapping a layout component
It's often desirable to wrap all or most of the routed components in a reusable shell (often called a "layout"). In order to do that, you first need to create a component that contains the common markup that will wrap around the various different components: It's often desirable to wrap all or most of the routed components in a reusable shell (often called a "layout"). In order to do that, you first need to create a component that contains the common markup that will wrap around the various different components:
@ -344,7 +350,7 @@ Note that in this case, if the Layout component the `oninit` and `oncreate` life
--- ---
### Authentication #### Authentication
The RouterResolver's `onmatch` hook can be used to run logic before the top level component in a route is initializated. The example below shows how to implement a login wall that prevents users from seeing the `/secret` page unless they login. The RouterResolver's `onmatch` hook can be used to run logic before the top level component in a route is initializated. The example below shows how to implement a login wall that prevents users from seeing the `/secret` page unless they login.
@ -366,10 +372,12 @@ var Login = {
m.route(document.body, "/secret", { m.route(document.body, "/secret", {
"/secret": { "/secret": {
onmatch: function(resolve) { onmatch: function() {
if (isLoggedIn) resolve(Home) if (!isLoggedIn) m.route.set("/login")
else m.route.set("/login")
}, },
render: function() {
return m(Home)
}
}, },
"/login": Login "/login": Login
}) })
@ -381,11 +389,67 @@ For the sake of simplicity, in the example above, the user's logged in status is
--- ---
### Code splitting #### Preloading data
In a large application, it may be desirable to download the code for each route on demand, rather than upfront. Dividing the codebase this way is known as code splitting or lazy loading. In Mithril, this can be accomplished by calling the `resolve` callback of the `onmatch` hook asynchronously: Typically, a component can load data upon initialization. Loading data this way renders the component twice (once upon routing, and once after the request completes).
At its simplest form, one could do the following: ```javascript
var state = {
users: [],
loadUsers: function() {
return m.request("/api/v1/users").then(function(users) {
state.users = users
})
}
}
m.route(document.body, "/user/list", {
"/user/list": {
oninit: state.loadUsers,
view: function() {
return state.users.length > 0 ? state.users.map(function() {
return m("div", user.id)
}) : "loading"
}
},
})
```
In the example above, on the first render, the UI displays `"loading"` since `state.users` is an empty array before the request completes. Then, once data is available, the UI redraws and a list of user ids is shown.
RouteResolvers can be used as a mechanism to preload data before rendering a component in order to avoid UI flickering and thus bypassing the need for a loading indicator:
```javascript
var state = {
users: [],
loadUsers: function() {
return m.request("/api/v1/users").then(function(users) {
state.users = users
})
}
}
m.route(document.body, "/user/list", {
"/user/list": {
onmatch: state.loadUsers,
render: function() {
return state.users.length > 0 ? state.users.map(function() {
return m("div", user.id)
}) : "loading"
}
},
})
```
Above, `render` only runs after the request completes, making the ternary operator redundant.
---
#### Code splitting
In a large application, it may be desirable to download the code for each route on demand, rather than upfront. Dividing the codebase this way is known as code splitting or lazy loading. In Mithril, this can be accomplished by returning a promise from the `onmatch` hook:
At its most basic form, one could do the following:
```javascript ```javascript
// Home.js // Home.js
@ -401,21 +465,20 @@ module.export = {
```javascript ```javascript
// index.js // index.js
function load(file, done) { function load(file) {
m.request({ return m.request({
method: "GET", method: "GET",
url: file, url: file,
extract: function(xhr) { extract: function(xhr) {
return new Function("var module = {};" + xhr.responseText + ";return module.exports;") return new Function("var module = {};" + xhr.responseText + ";return module.exports;")
} }
}) })
.run(done)
} }
m.route(document.body, "/", { m.route(document.body, "/", {
"/": { "/": {
onmatch: function(resolve) { onmatch: function() {
load("Home.js", resolve) return load("Home.js")
}, },
}, },
}) })
@ -428,9 +491,11 @@ Fortunately, there are a number of tools that facilitate the task of bundling mo
```javascript ```javascript
m.route(document.body, "/", { m.route(document.body, "/", {
"/": { "/": {
onmatch: function(resolve) { onmatch: function() {
// using Webpack async code splitting // using Webpack async code splitting
require(['./Home.js'], resolve) return new Promise(function(resolve) {
require(['./Home.js'], resolve)
})
}, },
}, },
}) })

View file

@ -15,7 +15,7 @@ Returns an event handler that runs `callback` with the value of the specified DO
```javascript ```javascript
var state = { var state = {
value: "", value: "",
setValue: function(v) {value = v} setValue: function(v) {state.value = v}
} }
var Component = { var Component = {
@ -63,18 +63,18 @@ document.body.onclick = m.withAttr("title", function(value) {
Typically, `m.withAttr()` can be used in Mithril component views to avoid polluting the data layer with DOM event model concerns: Typically, `m.withAttr()` can be used in Mithril component views to avoid polluting the data layer with DOM event model concerns:
```javascript ```javascript
var Data = { var state = {
email: "", email: "",
setEmail: function(email) { setEmail: function(email) {
Data.email = email.toLowerCase() state.email = email.toLowerCase()
} }
} }
var MyComponent = { var MyComponent = {
view: function() { view: function() {
return m("input", { return m("input", {
oninput: m.withAttr("value", Data.setEmail), oninput: m.withAttr("value", state.setEmail),
value: Data.email value: state.email
}) })
} }
} }
@ -89,15 +89,15 @@ m.mount(document.body, MyComponent)
The `m.withAttr()` helper reads the value of the element to which the event handler is bound, which is not necessarily the same as the element where the event originated. The `m.withAttr()` helper reads the value of the element to which the event handler is bound, which is not necessarily the same as the element where the event originated.
```javascript ```javascript
var Data = { var state = {
url: "", url: "",
setURL: function(url) {Data.url = url} setURL: function(url) {state.url = url}
} }
var MyComponent = { var MyComponent = {
view: function() { view: function() {
return m("a[href='/foo']", {onclick: m.withAttr("href", Data.setURL)}, [ return m("a[href='/foo']", {onclick: m.withAttr("href", state.setURL)}, [
m("span", Data.url) m("span", state.url)
]) ])
} }
} }
@ -117,21 +117,21 @@ The first argument of `m.withAttr()` can be either an attribute or a property.
```javascript ```javascript
// reads from `select.selectedIndex` property // reads from `select.selectedIndex` property
var Data = { var state = {
index: 0, index: 0,
setIndex: function(index) {Data.index = index} setIndex: function(index) {state.index = index}
} }
m("select", {onclick: m.withAttr("selectedIndex", Data.setIndex)}) m("select", {onclick: m.withAttr("selectedIndex", state.setIndex)})
``` ```
If a value can be both an attribute *and* a property, the property value is used. If a value can be both an attribute *and* a property, the property value is used.
```javascript ```javascript
// value is a boolean, because the `input.checked` property is boolean // value is a boolean, because the `input.checked` property is boolean
var Data = { var state = {
selected: false, selected: false,
setSelected: function(selected) {Data.selected = selected} setSelected: function(selected) {state.selected = selected}
} }
m("input[type=checkbox]", {onclick: m.withAttr("checked", Data.setSelected)}) m("input[type=checkbox]", {onclick: m.withAttr("checked", state.setSelected)})
``` ```

View file

@ -214,7 +214,7 @@ var _8 = function($window, Promise) {
var next = then0.apply(promise0, arguments) var next = then0.apply(promise0, arguments)
next.then(complete, function(e) { next.then(complete, function(e) {
complete() complete()
throw e if (count === 0) throw e
}) })
return finalize(next) return finalize(next)
} }
@ -953,6 +953,7 @@ var _16 = function(redrawService0) {
} }
} }
m.mount = _16(redrawService) m.mount = _16(redrawService)
var Promise = PromisePolyfill
var parseQueryString = function(string) { var parseQueryString = function(string) {
if (string === "" || string == null) return {} if (string === "" || string == null) return {}
if (string.charAt(0) === "?") string = string.slice(1) if (string.charAt(0) === "?") string = string.slice(1)
@ -986,20 +987,18 @@ var parseQueryString = function(string) {
var coreRouter = function($window) { var coreRouter = function($window) {
var supportsPushState = typeof $window.history.pushState === "function" var supportsPushState = typeof $window.history.pushState === "function"
var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout
var prefix1 = "#!"
function setPrefix(value) {prefix1 = value}
function normalize1(fragment0) { function normalize1(fragment0) {
var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data
return data return data
} }
var asyncId var asyncId
function debounceAsync(f) { function debounceAsync(callback0) {
return function() { return function() {
if (asyncId != null) return if (asyncId != null) return
asyncId = callAsync0(function() { asyncId = callAsync0(function() {
asyncId = null asyncId = null
f() callback0()
}) })
} }
} }
@ -1018,15 +1017,16 @@ var coreRouter = function($window) {
} }
return path.slice(0, pathEnd) return path.slice(0, pathEnd)
} }
function getPath() { var router = {prefix: "#!"}
var type2 = prefix1.charAt(0) router.getPath = function() {
var type2 = router.prefix.charAt(0)
switch (type2) { switch (type2) {
case "#": return normalize1("hash").slice(prefix1.length) case "#": return normalize1("hash").slice(router.prefix.length)
case "?": return normalize1("search").slice(prefix1.length) + normalize1("hash") case "?": return normalize1("search").slice(router.prefix.length) + normalize1("hash")
default: return normalize1("pathname").slice(prefix1.length) + normalize1("search") + normalize1("hash") default: return normalize1("pathname").slice(router.prefix.length) + normalize1("search") + normalize1("hash")
} }
} }
function setPath(path, data, options) { router.setPath = function(path, data, options) {
var queryData = {}, hashData = {} var queryData = {}, hashData = {}
path = parsePath(path, queryData, hashData) path = parsePath(path, queryData, hashData)
if (data != null) { if (data != null) {
@ -1041,15 +1041,15 @@ var coreRouter = function($window) {
var hash = buildQueryString(hashData) var hash = buildQueryString(hashData)
if (hash) path += "#" + hash if (hash) path += "#" + hash
if (supportsPushState) { if (supportsPushState) {
if (options && options.replace) $window.history.replaceState(null, null, prefix1 + path) if (options && options.replace) $window.history.replaceState(null, null, router.prefix + path)
else $window.history.pushState(null, null, prefix1 + path) else $window.history.pushState(null, null, router.prefix + path)
$window.onpopstate(true) $window.onpopstate()
} }
else $window.location.href = prefix1 + path else $window.location.href = router.prefix + path
} }
function defineRoutes(routes, resolve, reject) { router.defineRoutes = function(routes, resolve, reject) {
function resolveRoute() { function resolveRoute() {
var path = getPath() var path = router.getPath()
var params = {} var params = {}
var pathname = parsePath(path, params, params) var pathname = parsePath(path, params, params)
@ -1071,72 +1071,71 @@ var coreRouter = function($window) {
} }
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute) if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
else if (prefix1.charAt(0) === "#") $window.onhashchange = resolveRoute else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
resolveRoute() resolveRoute()
} }
function link(vnode2) {
vnode2.dom.setAttribute("href", prefix1 + vnode2.attrs.href) return router
vnode2.dom.onclick = function(e) {
if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return
e.preventDefault()
e.redraw = false
var href = this.getAttribute("href")
if (href.indexOf(prefix1) === 0) href = href.slice(prefix1.length)
setPath(href, undefined, undefined)
}
}
return {setPrefix: setPrefix, getPath: getPath, setPath: setPath, defineRoutes: defineRoutes, link: link}
} }
var _20 = function($window, redrawService0) { var _20 = function($window, redrawService0) {
var routeService = coreRouter($window) var routeService = coreRouter($window)
var identity = function(v) {return v} var identity = function(v) {return v}
var resolver, component, attrs3, currentPath, resolve var render1, component, attrs3, currentPath, updatePending = false
var route = function(root, defaultRoute, routes) { var route = function(root, defaultRoute, routes) {
if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined") if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
var update = function(routeResolver, comp, params, path) { var update = function(routeResolver, comp, params, path) {
resolver = routeResolver, component = comp, attrs3 = params, currentPath = path, resolve = null component = comp != null && typeof comp.view === "function" ? comp : "div", attrs3 = params, currentPath = path, updatePending = false
resolver.render = routeResolver.render || identity render1 = (routeResolver.render || identity).bind(routeResolver)
render1() run1()
} }
var render1 = function() { var run1 = function() {
if (resolver != null) redrawService0.render(root, resolver.render(Vnode(component, attrs3.key, attrs3))) if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3)))
}
var bail = function() {
routeService.setPath(defaultRoute)
} }
routeService.defineRoutes(routes, function(payload, params, path) { routeService.defineRoutes(routes, function(payload, params, path) {
if (payload.view) update({}, payload, params, path) if (payload.view) update({}, payload, params, path)
else { else {
if (payload.onmatch) { if (payload.onmatch) {
if (resolve != null) update(payload, component, params, path) updatePending = true
else { Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
resolve = function(resolved) { if (updatePending) update(payload, resolved, params, path)
update(payload, resolved, params, path) }, bail)
}
payload.onmatch(function(resolved) {
if (resolve != null) resolve(resolved)
}, params, path)
}
} }
else update(payload, "div", params, path) else update(payload, "div", params, path)
} }
}, function() { }, bail)
routeService.setPath(defaultRoute) redrawService0.subscribe(root, run1)
}) }
redrawService0.subscribe(root, render1) route.set = function(path, data, options) {
if (updatePending) options = {replace: true}
updatePending = false
routeService.setPath(path, data, options)
} }
route.set = routeService.setPath
route.get = function() {return currentPath} route.get = function() {return currentPath}
route.prefix = routeService.setPrefix route.prefix = function(prefix0) {routeService.prefix = prefix0}
route.link = routeService.link route.link = function(vnode1) {
vnode1.dom.setAttribute("href", routeService.prefix + vnode1.attrs.href)
vnode1.dom.onclick = function(e) {
if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return
e.preventDefault()
e.redraw = false
var href = this.getAttribute("href")
if (href.indexOf(routeService.prefix) === 0) href = href.slice(routeService.prefix.length)
route.set(href, undefined, undefined)
}
}
return route return route
} }
m.route = _20(window, redrawService) m.route = _20(window, redrawService)
m.withAttr = function(attrName, callback0, context) { m.withAttr = function(attrName, callback1, context) {
return function(e) { return function(e) {
return callback0.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName)) return callback1.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName))
} }
} }
var _27 = coreRenderer(window) var _28 = coreRenderer(window)
m.render = _27.render m.render = _28.render
m.redraw = redrawService.redraw m.redraw = redrawService.redraw
m.request = requestService.request m.request = requestService.request
m.jsonp = requestService.jsonp m.jsonp = requestService.jsonp

81
mithril.min.js vendored
View file

@ -1,40 +1,41 @@
new function(){function r(a,c,h,d,g,l){return{tag:a,key:c,attrs:h,children:d,text:g,dom:l,domSize:void 0,state:{},events:void 0,instance:void 0,skip:!1}}function y(a){if(null==a||"string"!==typeof a&&null==a.view)throw Error("The selector must be either a string or a component.");if("string"===typeof a&&void 0===I[a]){for(var c,h,d=[],g={};c=P.exec(a);){var l=c[1],m=c[2];""===l&&""!==m?h=m:"#"===l?g.id=m:"."===l?d.push(m):"["===c[3][0]&&((l=c[6])&&(l=l.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")), new function(){function w(a,c,h,d,g,m){return{tag:a,key:c,attrs:h,children:d,text:g,dom:m,domSize:void 0,state:{},events:void 0,instance:void 0,skip:!1}}function z(a){if(null==a||"string"!==typeof a&&null==a.view)throw Error("The selector must be either a string or a component.");if("string"===typeof a&&void 0===K[a]){for(var c,h,d=[],g={};c=P.exec(a);){var m=c[1],l=c[2];""===m&&""!==l?h=l:"#"===m?g.id=l:"."===m?d.push(l):"["===c[3][0]&&((m=c[6])&&(m=m.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),
"class"===c[4]?d.push(l):g[c[4]]=l||!0)}0<d.length&&(g.className=d.join(" "));I[a]=function(a,c){var l=!1,b,d,v=a.className||a["class"],u;for(u in g)a[u]=g[u];void 0!==v&&(void 0!==a["class"]&&(a["class"]=void 0,a.className=v),void 0!==g.className&&(a.className=g.className+" "+v));for(u in a)if("key"!==u){l=!0;break}Array.isArray(c)&&1==c.length&&null!=c[0]&&"#"===c[0].tag?d=c[0].children:b=c;return r(h||"div",a.key,l?a:void 0,b,d,void 0)}}var A;null==arguments[1]||"object"===typeof arguments[1]&& "class"===c[4]?d.push(m):g[c[4]]=m||!0)}0<d.length&&(g.className=d.join(" "));K[a]=function(a,c){var m=!1,b,p,d=a.className||a["class"],t;for(t in g)a[t]=g[t];void 0!==d&&(void 0!==a["class"]&&(a["class"]=void 0,a.className=d),void 0!==g.className&&(a.className=g.className+" "+d));for(t in a)if("key"!==t){m=!0;break}c instanceof Array&&1==c.length&&null!=c[0]&&"#"===c[0].tag?p=c[0].children:b=c;return w(h||"div",a.key,m?a:void 0,b,p,void 0)}}var n;null!=arguments[1]&&("object"!==typeof arguments[1]||
void 0===arguments[1].tag&&!Array.isArray(arguments[1])?(A=arguments[1],d=2):d=1;if(arguments.length===d+1)c=Array.isArray(arguments[d])?arguments[d]:[arguments[d]];else for(c=[];d<arguments.length;d++)c.push(arguments[d]);return"string"===typeof a?I[a](A||{},r.normalizeChildren(c)):r(a,A&&A.key,A||{},r.normalizeChildren(c),void 0,void 0)}function Q(a){var c=0,h=null,d="function"===typeof requestAnimationFrame?requestAnimationFrame:setTimeout;return function(){var g=Date.now();0===c||16<=g-c?(c=g, void 0!==arguments[1].tag||arguments[1]instanceof Array)?d=1:(n=arguments[1],d=2);if(arguments.length===d+1)c=arguments[d]instanceof Array?arguments[d]:[arguments[d]];else for(c=[];d<arguments.length;d++)c.push(arguments[d]);return"string"===typeof a?K[a](n||{},w.normalizeChildren(c)):w(a,n&&n.key,n||{},w.normalizeChildren(c),void 0,void 0)}function Q(a){var c=0,h=null,d="function"===typeof requestAnimationFrame?requestAnimationFrame:setTimeout;return function(){var g=Date.now();0===c||16<=g-c?(c=
a()):null===h&&(h=d(function(){h=null;a();c=Date.now()},16-(g-c)))}}r.normalize=function(a){return Array.isArray(a)?r("[",void 0,void 0,r.normalizeChildren(a),void 0,void 0):null!=a&&"object"!==typeof a?r("#",void 0,void 0,a,void 0,void 0):a};r.normalizeChildren=function(a){for(var c=0;c<a.length;c++)a[c]=r.normalize(a[c]);return a};var P=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,I={};y.trust=function(a){null==a&&(a="");return r("<",void 0,void 0,a,void 0,void 0)}; g,a()):null===h&&(h=d(function(){h=null;a();c=Date.now()},16-(g-c)))}}w.normalize=function(a){return a instanceof Array?w("[",void 0,void 0,w.normalizeChildren(a),void 0,void 0):null!=a&&"object"!==typeof a?w("#",void 0,void 0,a,void 0,void 0):a};w.normalizeChildren=function(a){for(var c=0;c<a.length;c++)a[c]=w.normalize(a[c]);return a};var P=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,K={};z.trust=function(a){null==a&&(a="");return w("<",void 0,void 0,a,void 0,
y.fragment=function(a,c){return r("[",a.key,a,r.normalizeChildren(c),void 0,void 0)};var t=function(a){function c(a,b){return function v(c){var k;try{if(!b||null==c||"object"!==typeof c&&"function"!==typeof c||"function"!==typeof(k=c.then))G(function(){b||0!==a.length||console.error("Possible unhandled promise rejection:",c);for(var d=0;d<a.length;d++)a[d](c);g.length=0;l.length=0;n.state=b;n.retry=function(){v(c)}});else{if(c===d)throw new TypeError("Promise can't be resolved w/ itself");h(k.bind(c))}}catch(C){A(C)}}} void 0)};z.fragment=function(a,c){return w("[",a.key,a,w.normalizeChildren(c),void 0,void 0)};var v=function(a){function c(a,b){return function C(c){var l;try{if(!b||null==c||"object"!==typeof c&&"function"!==typeof c||"function"!==typeof(l=c.then))k(function(){b||0!==a.length||console.error("Possible unhandled promise rejection:",c);for(var d=0;d<a.length;d++)a[d](c);g.length=0;m.length=0;p.state=b;p.retry=function(){C(c)}});else{if(c===d)throw new TypeError("Promise can't be resolved w/ itself");
function h(a){function b(b){return function(a){0<c++||b(a)}}var c=0,d=b(A);try{a(b(m),d)}catch(u){d(u)}}if(!(this instanceof t))throw Error("Promise must be called with `new`");if("function"!==typeof a)throw new TypeError("executor must be a function");var d=this,g=[],l=[],m=c(g,!0),A=c(l,!1),n=d._instance={resolvers:g,rejectors:l},G="function"===typeof setImmediate?setImmediate:setTimeout;h(a)};t.prototype.then=function(a,c){function h(a,c,h,k){c.push(function(b){if("function"!==typeof a)h(b);else try{g(a(b))}catch(M){l&& h(l.bind(c))}}catch(G){n(G)}}}function h(a){function b(b){return function(a){0<c++||b(a)}}var c=0,d=b(n);try{a(b(l),d)}catch(t){d(t)}}if(!(this instanceof v))throw Error("Promise must be called with `new`");if("function"!==typeof a)throw new TypeError("executor must be a function");var d=this,g=[],m=[],l=c(g,!0),n=c(m,!1),p=d._instance={resolvers:g,rejectors:m},k="function"===typeof setImmediate?setImmediate:setTimeout;h(a)};v.prototype.then=function(a,c){function h(a,c,h,l){c.push(function(b){if("function"!==
l(M)}});"function"===typeof d.retry&&k===d.state&&d.retry()}var d=this._instance,g,l,m=new t(function(a,c){g=a;l=c});h(a,d.resolvers,g,!0);h(c,d.rejectors,l,!1);return m};t.prototype["catch"]=function(a){return this.then(null,a)};t.resolve=function(a){return a instanceof t?a:new t(function(c){c(a)})};t.reject=function(a){return new t(function(c,h){h(a)})};t.all=function(a){return new t(function(c,h){var d=a.length,g=0,l=[];if(0===a.length)c([]);else for(var m=0;m<a.length;m++)(function(m){function n(a){g++; typeof a)h(b);else try{g(a(b))}catch(D){m&&m(D)}});"function"===typeof d.retry&&l===d.state&&d.retry()}var d=this._instance,g,m,l=new v(function(a,c){g=a;m=c});h(a,d.resolvers,g,!0);h(c,d.rejectors,m,!1);return l};v.prototype["catch"]=function(a){return this.then(null,a)};v.resolve=function(a){return a instanceof v?a:new v(function(c){c(a)})};v.reject=function(a){return new v(function(c,h){h(a)})};v.all=function(a){return new v(function(c,h){var d=a.length,g=0,m=[];if(0===a.length)c([]);else for(var l=
l[m]=a;g===d&&c(l)}null==a[m]||"object"!==typeof a[m]&&"function"!==typeof a[m]||"function"!==typeof a[m].then?n(a[m]):a[m].then(n,h)})(m)})};t.race=function(a){return new t(function(c,h){for(var d=0;d<a.length;d++)a[d].then(c,h)})};"undefined"===typeof Promise&&("undefined"!==typeof window?window.Promise=t:"undefined"!==typeof global&&(global.Promise=t));var E=function(a){function c(a,d){if(Array.isArray(d))for(var g=0;g<d.length;g++)c(a+"["+g+"]",d[g]);else if("[object Object]"===Object.prototype.toString.call(d))for(g in d)c(a+ 0;l<a.length;l++)(function(l){function p(a){g++;m[l]=a;g===d&&c(m)}null==a[l]||"object"!==typeof a[l]&&"function"!==typeof a[l]||"function"!==typeof a[l].then?p(a[l]):a[l].then(p,h)})(l)})};v.race=function(a){return new v(function(c,h){for(var d=0;d<a.length;d++)a[d].then(c,h)})};"undefined"===typeof E&&("undefined"!==typeof window?window.Promise=v:"undefined"!==typeof global&&(global.Promise=v));var H="undefined"!==typeof E?E:v,I=function(a){function c(a,d){if(d instanceof Array)for(var g=0;g<d.length;g++)c(a+
"["+g+"]",d[g]);else h.push(encodeURIComponent(a)+(null!=d&&""!==d?"="+encodeURIComponent(d):""))}if("[object Object]"!==Object.prototype.toString.call(a))return"";var h=[],d;for(d in a)c(d,a[d]);return h.join("&")},K=function(a,c){function h(){function b(){0===--a&&"function"===typeof k&&k()}var a=0;return function u(c){var d=c.then;c.then=function(){a++;var g=d.apply(c,arguments);g.then(b,function(a){b();throw a;});return u(g)};return c}}function d(b,a){if("string"===typeof b){var c=b;b=a||{};null== "["+g+"]",d[g]);else if("[object Object]"===Object.prototype.toString.call(d))for(g in d)c(a+"["+g+"]",d[g]);else h.push(encodeURIComponent(a)+(null!=d&&""!==d?"="+encodeURIComponent(d):""))}if("[object Object]"!==Object.prototype.toString.call(a))return"";var h=[],d;for(d in a)c(d,a[d]);return h.join("&")},M=function(a,c){function h(){function b(){0===--a&&"function"===typeof r&&r()}var a=0;return function t(c){var d=c.then;c.then=function(){a++;var g=d.apply(c,arguments);g.then(b,function(c){b();
b.url&&(b.url=c)}return b}function g(b,a){if(null==a)return b;for(var c=b.match(/:[^\/]+/gi)||[],d=0;d<c.length;d++){var g=c[d].slice(1);null!=a[g]&&(b=b.replace(c[d],a[g]),delete a[g])}return b}function l(a,c){var b=E(c);if(""!==b){var d=0>a.indexOf("?")?"?":"&";a+=d+b}return a}function m(a){try{return""!==a?JSON.parse(a):null}catch(M){throw Error(a);}}function A(a){return a.responseText}function n(a,c){if("function"===typeof a)if(Array.isArray(c))for(var b=0;b<c.length;b++)c[b]=new a(c[b]);else return new a(c); if(0===a)throw c;});return t(g)};return c}}function d(b,a){if("string"===typeof b){var c=b;b=a||{};null==b.url&&(b.url=c)}return b}function g(b,a){if(null==a)return b;for(var c=b.match(/:[^\/]+/gi)||[],d=0;d<c.length;d++){var g=c[d].slice(1);null!=a[g]&&(b=b.replace(c[d],a[g]),delete a[g])}return b}function m(a,c){var b=I(c);if(""!==b){var d=0>a.indexOf("?")?"?":"&";a+=d+b}return a}function l(a){try{return""!==a?JSON.parse(a):null}catch(D){throw Error(a);}}function n(a){return a.responseText}function p(a,
return c}var r=0,k;return{request:function(b,k){var v=h();b=d(b,k);var u=new c(function(c,d){null==b.method&&(b.method="GET");b.method=b.method.toUpperCase();var h="boolean"===typeof b.useBody?b.useBody:"GET"!==b.method&&"TRACE"!==b.method;"function"!==typeof b.serialize&&(b.serialize="undefined"!==typeof FormData&&b.data instanceof FormData?function(a){return a}:JSON.stringify);"function"!==typeof b.deserialize&&(b.deserialize=m);"function"!==typeof b.extract&&(b.extract=A);b.url=g(b.url,b.data); c){if("function"===typeof a)if(c instanceof Array)for(var b=0;b<c.length;b++)c[b]=new a(c[b]);else return new a(c);return c}var k=0,r;return{request:function(b,k){var r=h();b=d(b,k);var t=new c(function(c,d){null==b.method&&(b.method="GET");b.method=b.method.toUpperCase();var h="boolean"===typeof b.useBody?b.useBody:"GET"!==b.method&&"TRACE"!==b.method;"function"!==typeof b.serialize&&(b.serialize="undefined"!==typeof FormData&&b.data instanceof FormData?function(a){return a}:JSON.stringify);"function"!==
h?b.data=b.serialize(b.data):b.url=l(b.url,b.data);var k=new a.XMLHttpRequest;k.open(b.method,b.url,"boolean"===typeof b.async?b.async:!0,"string"===typeof b.user?b.user:void 0,"string"===typeof b.password?b.password:void 0);b.serialize===JSON.stringify&&h&&k.setRequestHeader("Content-Type","application/json; charset=utf-8");b.deserialize===m&&k.setRequestHeader("Accept","application/json, text/*");b.withCredentials&&(k.withCredentials=b.withCredentials);"function"===typeof b.config&&(k=b.config(k, typeof b.deserialize&&(b.deserialize=l);"function"!==typeof b.extract&&(b.extract=n);b.url=g(b.url,b.data);h?b.data=b.serialize(b.data):b.url=m(b.url,b.data);var k=new a.XMLHttpRequest;k.open(b.method,b.url,"boolean"===typeof b.async?b.async:!0,"string"===typeof b.user?b.user:void 0,"string"===typeof b.password?b.password:void 0);b.serialize===JSON.stringify&&h&&k.setRequestHeader("Content-Type","application/json; charset=utf-8");b.deserialize===l&&k.setRequestHeader("Accept","application/json, text/*");
b)||k);k.onreadystatechange=function(){if(4===k.readyState)try{var a=b.extract!==A?b.extract(k,b):b.deserialize(b.extract(k,b));if(200<=k.status&&300>k.status||304===k.status)c(n(b.type,a));else{var g=Error(k.responseText),h;for(h in a)g[h]=a[h];d(g)}}catch(F){d(F)}};h&&null!=b.data?k.send(b.data):k.send()});return!0===b.background?u:v(u)},jsonp:function(b,k){var m=h();b=d(b,k);var u=new c(function(c,d){var k=b.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+r++,h=a.document.createElement("script"); b.withCredentials&&(k.withCredentials=b.withCredentials);"function"===typeof b.config&&(k=b.config(k,b)||k);k.onreadystatechange=function(){if(4===k.readyState)try{var a=b.extract!==n?b.extract(k,b):b.deserialize(b.extract(k,b));if(200<=k.status&&300>k.status||304===k.status)c(p(b.type,a));else{var g=Error(k.responseText),h;for(h in a)g[h]=a[h];d(g)}}catch(F){d(F)}};h&&null!=b.data?k.send(b.data):k.send()});return!0===b.background?t:r(t)},jsonp:function(b,l){var n=h();b=d(b,l);var t=new c(function(c,
a[k]=function(d){h.parentNode.removeChild(h);c(n(b.type,d));delete a[k]};h.onerror=function(){h.parentNode.removeChild(h);d(Error("JSONP request failed"));delete a[k]};null==b.data&&(b.data={});b.url=g(b.url,b.data);b.data[b.callbackKey||"callback"]=k;h.src=l(b.url,b.data);a.document.documentElement.appendChild(h)});return!0===b.background?u:m(u)},setCompletionCallback:function(a){k=a}}}(window,"undefined"!==typeof Promise?Promise:t),O=function(a){function c(e,f,a,b,c,d,g){for(;a<b;a++){var q=f[a]; d){var h=b.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+k++,l=a.document.createElement("script");a[h]=function(d){l.parentNode.removeChild(l);c(p(b.type,d));delete a[h]};l.onerror=function(){l.parentNode.removeChild(l);d(Error("JSONP request failed"));delete a[h]};null==b.data&&(b.data={});b.url=g(b.url,b.data);b.data[b.callbackKey||"callback"]=h;l.src=m(b.url,b.data);a.document.documentElement.appendChild(l)});return!0===b.background?t:n(t)},setCompletionCallback:function(a){r=a}}}(window,
null!=q&&n(e,h(q,c,g),d)}}function h(e,f,a){var q=e.tag;null!=e.attrs&&J(e.attrs,e,f);if("string"===typeof q)switch(q){case "#":return e.dom=B.createTextNode(e.children);case "<":return d(e);case "[":var b=B.createDocumentFragment();null!=e.children&&(q=e.children,c(b,q,0,q.length,f,null,a));e.dom=b.firstChild;e.domSize=b.childNodes.length;return b;default:var g=e.tag;switch(e.tag){case "svg":a="http://www.w3.org/2000/svg";break;case "math":a="http://www.w3.org/1998/Math/MathML"}var k=(q=e.attrs)&& H),O=function(a){function c(e,f,a,b,c,d,g){for(;a<b;a++){var q=f[a];null!=q&&p(e,h(q,c,g),d)}}function h(e,f,a){var q=e.tag;null!=e.attrs&&L(e.attrs,e,f);if("string"===typeof q)switch(q){case "#":return e.dom=B.createTextNode(e.children);case "<":return d(e);case "[":var b=B.createDocumentFragment();null!=e.children&&(q=e.children,c(b,q,0,q.length,f,null,a));e.dom=b.firstChild;e.domSize=b.childNodes.length;return b;default:var g=e.tag;switch(e.tag){case "svg":a="http://www.w3.org/2000/svg";break;
q.is,g=a?k?B.createElementNS(a,g,{is:k}):B.createElementNS(a,g):k?B.createElement(g,{is:k}):B.createElement(g);e.dom=g;if(null!=q)for(b in k=a,q)u(e,b,null,q[b],k);null!=e.attrs&&null!=e.attrs.contenteditable?G(e):(null!=e.text&&(""!==e.text?g.textContent=e.text:e.children=[r("#",void 0,void 0,e.text,void 0,void 0)]),null!=e.children&&(b=e.children,c(g,b,0,b.length,f,null,a),f=e.attrs,"select"===e.tag&&null!=f&&("value"in f&&u(e,"value",null,f.value,void 0),"selectedIndex"in f&&u(e,"selectedIndex", case "math":a="http://www.w3.org/1998/Math/MathML"}var p=(q=e.attrs)&&q.is,g=a?p?B.createElementNS(a,g,{is:p}):B.createElementNS(a,g):p?B.createElement(g,{is:p}):B.createElement(g);e.dom=g;if(null!=q)for(b in p=a,q)t(e,b,null,q[b],p);null!=e.attrs&&null!=e.attrs.contenteditable?k(e):(null!=e.text&&(""!==e.text?g.textContent=e.text:e.children=[w("#",void 0,void 0,e.text,void 0,void 0)]),null!=e.children&&(b=e.children,c(g,b,0,b.length,f,null,a),f=e.attrs,"select"===e.tag&&null!=f&&("value"in f&&t(e,
null,f.selectedIndex,void 0))));return g}else{e.state||(e.state={});E(e.state,e.tag);b=e.tag.view;if(null!=b.reentrantLock)e=N;else if(b.reentrantLock=!0,J(e.tag,e,f),e.instance=r.normalize(b.call(e.state,e)),b.reentrantLock=null,null!=e.instance){if(e.instance===e)throw Error("A view cannot return the vnode it received as arguments");f=h(e.instance,f,a);e.dom=e.instance.dom;e.domSize=null!=e.dom?e.instance.domSize:0;e=f}else e.domSize=0,e=N;return e}}function d(e){var f={caption:"table",thead:"table", "value",null,f.value,void 0),"selectedIndex"in f&&t(e,"selectedIndex",null,f.selectedIndex,void 0))));return g}else{e.state||(e.state={});R(e.state,e.tag);b=e.tag.view;if(null!=b.reentrantLock)e=E;else if(b.reentrantLock=!0,L(e.tag,e,f),e.instance=w.normalize(b.call(e.state,e)),b.reentrantLock=null,null!=e.instance){if(e.instance===e)throw Error("A view cannot return the vnode it received as arguments");f=h(e.instance,f,a);e.dom=e.instance.dom;e.domSize=null!=e.dom?e.instance.domSize:0;e=f}else e.domSize=
tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"}[(e.children.match(/^\s*?<(\w+)/im)||[])[1]]||"div",f=B.createElement(f);f.innerHTML=e.children;e.dom=f.firstChild;e.domSize=f.childNodes.length;e=B.createDocumentFragment();for(var a;a=f.firstChild;)e.appendChild(a);return e}function g(e,f,a,b,d,g){if(f!==a&&(null!=f||null!=a))if(null==f)c(e,a,0,a.length,b,d,void 0);else if(null==a)k(f,0,f.length,a);else{for(var q=!1,w=0;w<a.length;w++)if(null!=a[w]){q=null==a[w].key; 0,e=E;return e}}function d(e){var f={caption:"table",thead:"table",tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"}[(e.children.match(/^\s*?<(\w+)/im)||[])[1]]||"div",f=B.createElement(f);f.innerHTML=e.children;e.dom=f.firstChild;e.domSize=f.childNodes.length;e=B.createDocumentFragment();for(var a;a=f.firstChild;)e.appendChild(a);return e}function g(e,f,a,b,d,g){if(f!==a&&(null!=f||null!=a))if(null==f)c(e,a,0,a.length,b,d,void 0);else if(null==a)r(f,0,f.length,
break}if(f.length===a.length&&q)for(w=0;w<f.length;w++)f[w]!==a[w]&&(null==f[w]?n(e,h(a[w],b,g),A(f,w+1,d)):null==a[w]?k(f,w,w+1,a):l(e,f[w],a[w],b,A(f,w+1,d),!1,g));else{a:{if(null!=f.pool&&Math.abs(f.pool.length-a.length)<=Math.abs(f.length-a.length)&&(q=a[0]&&a[0].children&&a[0].children.length||0,Math.abs((f.pool[0]&&f.pool[0].children&&f.pool[0].children.length||0)-q)<=Math.abs((f[0]&&f[0].children&&f[0].children.length||0)-q))){q=!0;break a}q=!1}q&&(f=f.concat(f.pool));for(var u=w=0,z=f.length- a);else{for(var q=!1,k=0;k<a.length;k++)if(null!=a[k]){q=null==a[k].key;break}if(f.length===a.length&&q)for(k=0;k<f.length;k++)f[k]!==a[k]&&(null==f[k]?p(e,h(a[k],b,g),n(f,k+1,d)):null==a[k]?r(f,k,k+1,a):m(e,f[k],a[k],b,n(f,k+1,d),!1,g));else{a:{if(null!=f.pool&&Math.abs(f.pool.length-a.length)<=Math.abs(f.length-a.length)&&(q=a[0]&&a[0].children&&a[0].children.length||0,Math.abs((f.pool[0]&&f.pool[0].children&&f.pool[0].children.length||0)-q)<=Math.abs((f[0]&&f[0].children&&f[0].children.length||
1,v=a.length-1,D;z>=w&&v>=u;){var x=f[w],p=a[u];if(x!==p||q)if(null==x)w++;else if(null==p)u++;else if(x.key===p.key)w++,u++,l(e,x,p,b,A(f,w,d),q,g),q&&x.tag===p.tag&&n(e,m(x),d);else if(x=f[z],x!==p||q)if(null==x)z--;else if(null==p)u++;else if(x.key===p.key)l(e,x,p,b,A(f,z+1,d),q,g),(q||u<v)&&n(e,m(x),A(f,w,d)),z--,u++;else break;else z--,u++;else w++,u++}for(;z>=w&&v>=u;){x=f[z];p=a[v];if(x!==p||q)if(null==x)z--;else{if(null!=p)if(x.key===p.key)l(e,x,p,b,A(f,z+1,d),q,g),q&&x.tag===p.tag&&n(e,m(x), 0)-q))){q=!0;break a}q=!1}q&&(f=f.concat(f.pool));for(var A=k=0,y=f.length-1,t=a.length-1,D;y>=k&&t>=A;){var x=f[k],u=a[A];if(x!==u||q)if(null==x)k++;else if(null==u)A++;else if(x.key===u.key)k++,A++,m(e,x,u,b,n(f,k,d),q,g),q&&x.tag===u.tag&&p(e,l(x),d);else if(x=f[y],x!==u||q)if(null==x)y--;else if(null==u)A++;else if(x.key===u.key)m(e,x,u,b,n(f,y+1,d),q,g),(q||A<t)&&p(e,l(x),n(f,k,d)),y--,A++;else break;else y--,A++;else k++,A++}for(;y>=k&&t>=A;){x=f[y];u=a[t];if(x!==u||q)if(null==x)y--;else{if(null!=
d),null!=x.dom&&(d=x.dom),z--;else{if(!D){D=f;var x=z,r={},C;for(C=0;C<x;C++){var t=D[C];null!=t&&(t=t.key,null!=t&&(r[t]=C))}D=r}null!=p&&(x=D[p.key],null!=x?(r=f[x],l(e,r,p,b,A(f,z+1,d),q,g),n(e,m(r),d),f[x].skip=!0,null!=r.dom&&(d=r.dom)):(p=h(p,b,void 0),n(e,p,d),d=p))}v--}else z--,v--;if(v<u)break}c(e,a,u,v+1,b,d,g);k(f,w,z+1,a)}}}function l(a,f,b,c,k,z,v){var e=f.tag;if(e===b.tag){b.state=f.state;b.events=f.events;var q;var w;null!=b.attrs&&"function"===typeof b.attrs.onbeforeupdate&&(q=b.attrs.onbeforeupdate.call(b.state, u)if(x.key===u.key)m(e,x,u,b,n(f,y+1,d),q,g),q&&x.tag===u.tag&&p(e,l(x),d),null!=x.dom&&(d=x.dom),y--;else{if(!D){D=f;var x=y,C={},w;for(w=0;w<x;w++){var v=D[w];null!=v&&(v=v.key,null!=v&&(C[v]=w))}D=C}null!=u&&(x=D[u.key],null!=x?(C=f[x],m(e,C,u,b,n(f,y+1,d),q,g),p(e,l(C),d),f[x].skip=!0,null!=C.dom&&(d=C.dom)):(u=h(u,b,void 0),p(e,u,d),d=u))}t--}else y--,t--;if(t<A)break}c(e,a,A,t+1,b,d,g);r(f,k,y+1,a)}}}function m(a,f,b,c,u,y,n){var e=f.tag;if(e===b.tag){b.state=f.state;b.events=f.events;var q;
b,f));"string"!==typeof b.tag&&"function"===typeof b.tag.onbeforeupdate&&(w=b.tag.onbeforeupdate.call(b.state,b,f));void 0===q&&void 0===w||q||w?q=!1:(b.dom=f.dom,b.domSize=f.domSize,b.instance=f.instance,q=!0);if(!q)if(null!=b.attrs&&y(b.attrs,b,c,z),"string"===typeof e)switch(e){case "#":f.children.toString()!==b.children.toString()&&(f.dom.nodeValue=b.children);b.dom=f.dom;break;case "<":f.children!==b.children?(m(f),n(a,d(b),k)):(b.dom=f.dom,b.domSize=f.domSize);break;case "[":g(a,f.children, var A;null!=b.attrs&&"function"===typeof b.attrs.onbeforeupdate&&(q=b.attrs.onbeforeupdate.call(b.state,b,f));"string"!==typeof b.tag&&"function"===typeof b.tag.onbeforeupdate&&(A=b.tag.onbeforeupdate.call(b.state,b,f));void 0===q&&void 0===A||q||A?q=!1:(b.dom=f.dom,b.domSize=f.domSize,b.instance=f.instance,q=!0);if(!q)if(null!=b.attrs&&z(b.attrs,b,c,y),"string"===typeof e)switch(e){case "#":f.children.toString()!==b.children.toString()&&(f.dom.nodeValue=b.children);b.dom=f.dom;break;case "<":f.children!==
b.children,c,k,v);f=0;c=b.children;b.dom=null;if(null!=c){for(var p=0;p<c.length;p++)a=c[p],null!=a&&null!=a.dom&&(null==b.dom&&(b.dom=a.dom),f+=a.domSize||1);1!==f&&(b.domSize=f)}break;default:a=v;k=b.dom=f.dom;switch(b.tag){case "svg":a="http://www.w3.org/2000/svg";break;case "math":a="http://www.w3.org/1998/Math/MathML"}"textarea"===b.tag&&(null==b.attrs&&(b.attrs={}),null!=b.text&&(b.attrs.value=b.text,b.text=void 0));z=f.attrs;v=b.attrs;e=a;if(null!=v)for(p in v)u(b,p,z&&z[p],v[p],e);if(null!= b.children?(l(f),p(a,d(b),u)):(b.dom=f.dom,b.domSize=f.domSize);break;case "[":g(a,f.children,b.children,c,u,n);f=0;c=b.children;b.dom=null;if(null!=c){for(var r=0;r<c.length;r++)a=c[r],null!=a&&null!=a.dom&&(null==b.dom&&(b.dom=a.dom),f+=a.domSize||1);1!==f&&(b.domSize=f)}break;default:a=n;u=b.dom=f.dom;switch(b.tag){case "svg":a="http://www.w3.org/2000/svg";break;case "math":a="http://www.w3.org/1998/Math/MathML"}"textarea"===b.tag&&(null==b.attrs&&(b.attrs={}),null!=b.text&&(b.attrs.value=b.text,
z)for(p in z)null!=v&&p in v||("className"===p&&(p="class"),"o"!==p[0]||"n"!==p[1]||D(p)?"key"!==p&&b.dom.removeAttribute(p):C(b,p,void 0));null!=b.attrs&&null!=b.attrs.contenteditable?G(b):null!=f.text&&null!=b.text&&""!==b.text?f.text.toString()!==b.text.toString()&&(f.dom.firstChild.nodeValue=b.text):(null!=f.text&&(f.children=[r("#",void 0,void 0,f.text,void 0,f.dom.firstChild)]),null!=b.text&&(b.children=[r("#",void 0,void 0,b.text,void 0,void 0)]),g(k,f.children,b.children,c,null,a))}else b.instance= b.text=void 0));y=f.attrs;n=b.attrs;e=a;if(null!=n)for(r in n)t(b,r,y&&y[r],n[r],e);if(null!=y)for(r in y)null!=n&&r in n||("className"===r&&(r="class"),"o"!==r[0]||"n"!==r[1]||v(r)?"key"!==r&&b.dom.removeAttribute(r):G(b,r,void 0));null!=b.attrs&&null!=b.attrs.contenteditable?k(b):null!=f.text&&null!=b.text&&""!==b.text?f.text.toString()!==b.text.toString()&&(f.dom.firstChild.nodeValue=b.text):(null!=f.text&&(f.children=[w("#",void 0,void 0,f.text,void 0,f.dom.firstChild)]),null!=b.text&&(b.children=
r.normalize(b.tag.view.call(b.state,b)),y(b.tag,b,c,z),null!=b.instance?(null==f.instance?n(a,h(b.instance,c,v),k):l(a,f.instance,b.instance,c,k,z,v),b.dom=b.instance.dom,b.domSize=b.instance.domSize):null!=f.instance?(t(f.instance,null),b.dom=void 0,b.domSize=0):(b.dom=f.dom,b.domSize=f.domSize)}else t(f,null),n(a,h(b,c,v),k)}function m(b){var a=b.domSize;if(null!=a||null==b.dom){var e=B.createDocumentFragment();if(0<a){for(b=b.dom;--a;)e.appendChild(b.nextSibling);e.insertBefore(b,e.firstChild)}return e}return b.dom} [w("#",void 0,void 0,b.text,void 0,void 0)]),g(u,f.children,b.children,c,null,a))}else b.instance=w.normalize(b.tag.view.call(b.state,b)),z(b.tag,b,c,y),null!=b.instance?(null==f.instance?p(a,h(b.instance,c,n),u):m(a,f.instance,b.instance,c,u,y,n),b.dom=b.instance.dom,b.domSize=b.instance.domSize):null!=f.instance?(D(f.instance,null),b.dom=void 0,b.domSize=0):(b.dom=f.dom,b.domSize=f.domSize)}else D(f,null),p(a,h(b,c,n),u)}function l(b){var a=b.domSize;if(null!=a||null==b.dom){var e=B.createDocumentFragment();
function A(b,a,c){for(;a<b.length;a++)if(null!=b[a]&&null!=b[a].dom)return b[a].dom;return c}function n(b,a,c){c&&c.parentNode?b.insertBefore(a,c):b.appendChild(a)}function G(b){var a=b.children;if(null!=a&&1===a.length&&"<"===a[0].tag)a=a[0].children,b.dom.innerHTML!==a&&(b.dom.innerHTML=a);else if(null!=b.text||null!=a&&0!==a.length)throw Error("Child node of a contenteditable must be trusted");}function k(b,a,c,d){for(;a<c;a++){var e=b[a];null!=e&&(e.skip?e.skip=!1:t(e,d))}}function b(b){var a= if(0<a){for(b=b.dom;--a;)e.appendChild(b.nextSibling);e.insertBefore(b,e.firstChild)}return e}return b.dom}function n(b,a,c){for(;a<b.length;a++)if(null!=b[a]&&null!=b[a].dom)return b[a].dom;return c}function p(b,a,c){c&&c.parentNode?b.insertBefore(a,c):b.appendChild(a)}function k(b){var a=b.children;if(null!=a&&1===a.length&&"<"===a[0].tag)a=a[0].children,b.dom.innerHTML!==a&&(b.dom.innerHTML=a);else if(null!=b.text||null!=a&&0!==a.length)throw Error("Child node of a contenteditable must be trusted");
!1;return function(){a||(a=!0,b())}}function t(a,f){function e(){if(++d===c&&(v(a),a.dom)){var b=a.domSize||1;if(1<b)for(var e=a.dom;--b;){var g=e.nextSibling,k=g.parentNode;null!=k&&k.removeChild(g)}b=a.dom;e=b.parentNode;null!=e&&e.removeChild(b);if(b=null!=f&&null==a.domSize)b=a.attrs,b=!(null!=b&&(b.oncreate||b.onupdate||b.onbeforeremove||b.onremove));b&&"string"===typeof a.tag&&(f.pool?f.pool.push(a):f.pool=[a])}}var c=1,d=0;a.attrs&&a.attrs.onbeforeremove&&(c++,a.attrs.onbeforeremove.call(a.state, }function r(b,a,c,d){for(;a<c;a++){var e=b[a];null!=e&&(e.skip?e.skip=!1:D(e,d))}}function b(b){var a=!1;return function(){a||(a=!0,b())}}function D(a,f){function e(){if(++d===c&&(C(a),a.dom)){var b=a.domSize||1;if(1<b)for(var e=a.dom;--b;){var g=e.nextSibling,k=g.parentNode;null!=k&&k.removeChild(g)}b=a.dom;e=b.parentNode;null!=e&&e.removeChild(b);if(b=null!=f&&null==a.domSize)b=a.attrs,b=!(null!=b&&(b.oncreate||b.onupdate||b.onbeforeremove||b.onremove));b&&"string"===typeof a.tag&&(f.pool?f.pool.push(a):
a,b(e)));"string"!==typeof a.tag&&a.tag.onbeforeremove&&(c++,a.tag.onbeforeremove.call(a.state,a,b(e)));e()}function v(a){a.attrs&&a.attrs.onremove&&a.attrs.onremove.call(a.state,a);"string"!==typeof a.tag&&a.tag.onremove&&a.tag.onremove.call(a.state,a);if(null!=a.instance)v(a.instance);else if(a=a.children,Array.isArray(a))for(var b=0;b<a.length;b++){var e=a[b];null!=e&&v(e)}}function u(a,b,c,d,g){var e=a.dom;if("key"!==b&&(c!==d||"value"===b||"checked"===b||"selectedIndex"===b||"selected"===b&& f.pool=[a])}}var c=1,d=0;a.attrs&&a.attrs.onbeforeremove&&(c++,a.attrs.onbeforeremove.call(a.state,a,b(e)));"string"!==typeof a.tag&&a.tag.onbeforeremove&&(c++,a.tag.onbeforeremove.call(a.state,a,b(e)));e()}function C(b){b.attrs&&b.attrs.onremove&&b.attrs.onremove.call(b.state,b);"string"!==typeof b.tag&&b.tag.onremove&&b.tag.onremove.call(b.state,b);if(null!=b.instance)C(b.instance);else if(b=b.children,b instanceof Array)for(var a=0;a<b.length;a++){var e=b[a];null!=e&&C(e)}}function t(b,a,c,d,g){var e=
a.dom===B.activeElement||"object"===typeof d)&&"undefined"!==typeof d&&!D(b)){var f=b.indexOf(":");if(-1<f&&"xlink"===b.substr(0,f))e.setAttributeNS("http://www.w3.org/1999/xlink",b.slice(f+1),d);else if("o"===b[0]&&"n"===b[1]&&"function"===typeof d)C(a,b,d);else if("style"===b)if(a=c,a===d&&(e.style.cssText="",a=null),null==d)e.style.cssText="";else if("string"===typeof d)e.style.cssText=d;else{"string"===typeof a&&(e.style.cssText="");for(var k in d)e.style[k]=d[k];if(null!=a&&"string"!==typeof a)for(k in a)k in b.dom;if("key"!==a&&(c!==d||"value"===a||"checked"===a||"selectedIndex"===a||"selected"===a&&b.dom===B.activeElement||"object"===typeof d)&&"undefined"!==typeof d&&!v(a)){var f=a.indexOf(":");if(-1<f&&"xlink"===a.substr(0,f))e.setAttributeNS("http://www.w3.org/1999/xlink",a.slice(f+1),d);else if("o"===a[0]&&"n"===a[1]&&"function"===typeof d)G(b,a,d);else if("style"===a)if(b=c,b===d&&(e.style.cssText="",b=null),null==d)e.style.cssText="";else if("string"===typeof d)e.style.cssText=d;else{"string"===
d||(e.style[k]="")}else b in e&&"href"!==b&&"list"!==b&&"form"!==b&&"width"!==b&&"height"!==b&&void 0===g?"input"===a.tag&&"value"===b&&a.dom.value===d&&a.dom===B.activeElement||"select"===a.tag&&"value"===b&&a.dom.value===d&&a.dom===B.activeElement||"option"===a.tag&&"value"===b&&a.dom.value===d||(e[b]=d):"boolean"===typeof d?d?e.setAttribute(b,""):e.removeAttribute(b):e.setAttribute("className"===b?"class":b,d)}}function D(a){return"oninit"===a||"oncreate"===a||"onupdate"===a||"onremove"===a||"onbeforeremove"=== typeof b&&(e.style.cssText="");for(var k in d)e.style[k]=d[k];if(null!=b&&"string"!==typeof b)for(k in b)k in d||(e.style[k]="")}else a in e&&"href"!==a&&"list"!==a&&"form"!==a&&"width"!==a&&"height"!==a&&void 0===g?"input"===b.tag&&"value"===a&&b.dom.value===d&&b.dom===B.activeElement||"select"===b.tag&&"value"===a&&b.dom.value===d&&b.dom===B.activeElement||"option"===b.tag&&"value"===a&&b.dom.value===d||(e[a]=d):"boolean"===typeof d?d?e.setAttribute(a,""):e.removeAttribute(a):e.setAttribute("className"===
a||"onbeforeupdate"===a}function C(a,b,c){var d=a.dom,e="function"!==typeof F?c:function(a){var b=c.call(d,a);F.call(d,a);return b};if(b in d)d[b]="function"===typeof c?e:null;else{var f=b.slice(2);void 0===a.events&&(a.events={});a.events[b]!==e&&(null!=a.events[b]&&d.removeEventListener(f,a.events[b],!1),"function"===typeof c&&(a.events[b]=e,d.addEventListener(f,a.events[b],!1)))}}function J(a,b,c){"function"===typeof a.oninit&&a.oninit.call(b.state,b);"function"===typeof a.oncreate&&c.push(a.oncreate.bind(b.state, a?"class":a,d)}}function v(b){return"oninit"===b||"oncreate"===b||"onupdate"===b||"onremove"===b||"onbeforeremove"===b||"onbeforeupdate"===b}function G(b,a,c){var d=b.dom,e="function"!==typeof F?c:function(b){var a=c.call(d,b);F.call(d,b);return a};if(a in d)d[a]="function"===typeof c?e:null;else{var f=a.slice(2);void 0===b.events&&(b.events={});b.events[a]!==e&&(null!=b.events[a]&&d.removeEventListener(f,b.events[a],!1),"function"===typeof c&&(b.events[a]=e,d.addEventListener(f,b.events[a],!1)))}}
b))}function y(a,b,c,d){d?J(a,b,c):"function"===typeof a.onupdate&&c.push(a.onupdate.bind(b.state,b))}function E(a,b){Object.keys(b).forEach(function(c){a[c]=b[c]})}var B=a.document,N=B.createDocumentFragment(),F;return{render:function(a,b){if(!a)throw Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var c=[],d=B.activeElement;null==a.vnodes&&(a.textContent="");Array.isArray(b)||(b=[b]);g(a,a.vnodes,r.normalizeChildren(b),c,null,void 0);a.vnodes=b;for(var e= function L(b,a,c){"function"===typeof b.oninit&&b.oninit.call(a.state,a);"function"===typeof b.oncreate&&c.push(b.oncreate.bind(a.state,a))}function z(b,a,c,d){d?L(b,a,c):"function"===typeof b.onupdate&&c.push(b.onupdate.bind(a.state,a))}function R(b,a){Object.keys(a).forEach(function(c){b[c]=a[c]})}var B=a.document,E=B.createDocumentFragment(),F;return{render:function(b,a){if(!b)throw Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var c=[],d=B.activeElement;
0;e<c.length;e++)c[e]();B.activeElement!==d&&d.focus()},setEventCallback:function(a){return F=a}}},H=function(a){function c(a){a=d.indexOf(a);-1<a&&d.splice(a,2)}function h(){for(var a=1;a<d.length;a+=2)d[a]()}a=O(a);a.setEventCallback(function(a){!1!==a.redraw&&h()});var d=[];return{subscribe:function(a,h){c(a);d.push(a,Q(h))},unsubscribe:c,redraw:h,render:a.render}}(window);K.setCompletionCallback(H.redraw);y.mount=function(a){return function(c,h){if(null===h)a.render(c,[]),a.unsubscribe(c);else{if(null== null==b.vnodes&&(b.textContent="");a instanceof Array||(a=[a]);g(b,b.vnodes,w.normalizeChildren(a),c,null,void 0);b.vnodes=a;for(var e=0;e<c.length;e++)c[e]();B.activeElement!==d&&d.focus()},setEventCallback:function(b){return F=b}}},J=function(a){function c(a){a=d.indexOf(a);-1<a&&d.splice(a,2)}function h(){for(var a=1;a<d.length;a+=2)d[a]()}a=O(a);a.setEventCallback(function(a){!1!==a.redraw&&h()});var d=[];return{subscribe:function(a,h){c(a);d.push(a,Q(h))},unsubscribe:c,redraw:h,render:a.render}}(window);
h.view)throw Error("m.mount(element, component) expects a component, not a vnode");a.subscribe(c,function(){a.render(c,r(h))});a.redraw()}}}(H);var L=function(a){if(""===a||null==a)return{};"?"===a.charAt(0)&&(a=a.slice(1));a=a.split("&");for(var c={},h={},d=0;d<a.length;d++){var g=a[d].split("="),l=decodeURIComponent(g[0]),g=2===g.length?decodeURIComponent(g[1]):"";"true"===g?g=!0:"false"===g&&(g=!1);var m=l.split(/\]\[?|\[/),r=c;-1<l.indexOf("[")&&m.pop();for(var n=0;n<m.length;n++){var l=m[n], M.setCompletionCallback(J.redraw);z.mount=function(a){return function(c,h){if(null===h)a.render(c,[]),a.unsubscribe(c);else{if(null==h.view)throw Error("m.mount(element, component) expects a component, not a vnode");a.subscribe(c,function(){a.render(c,w(h))});a.redraw()}}}(J);var E=H,N=function(a){if(""===a||null==a)return{};"?"===a.charAt(0)&&(a=a.slice(1));a=a.split("&");for(var c={},h={},d=0;d<a.length;d++){var g=a[d].split("="),m=decodeURIComponent(g[0]),g=2===g.length?decodeURIComponent(g[1]):
t=m[n+1],t=""==t||!isNaN(parseInt(t,10)),k=n===m.length-1;""===l&&(l=m.slice(0,n).join(),null==h[l]&&(h[l]=0),l=h[l]++);null==r[l]&&(r[l]=k?g:t?[]:{});r=r[l]}}return c},R=function(a){function c(c){var b=a.location[c].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);"pathname"===c&&"/"!==b[0]&&(b="/"+b);return b}function h(a){return function(){null==t&&(t=r(function(){t=null;a()}))}}function d(a,b,c){var d=a.indexOf("?"),g=a.indexOf("#"),k=-1<d?d:-1<g?g:a.length;if(-1<d){var d=L(a.slice(d+1, "";"true"===g?g=!0:"false"===g&&(g=!1);var l=m.split(/\]\[?|\[/),n=c;-1<m.indexOf("[")&&l.pop();for(var p=0;p<l.length;p++){var m=l[p],k=l[p+1],k=""==k||!isNaN(parseInt(k,10)),r=p===l.length-1;""===m&&(m=l.slice(0,p).join(),null==h[m]&&(h[m]=0),m=h[m]++);null==n[m]&&(n[m]=r?g:k?[]:{});n=n[m]}}return c},S=function(a){function c(c){var d=a.location[c].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);"pathname"===c&&"/"!==d[0]&&(d="/"+d);return d}function h(a){return function(){null==l&&(l=m(function(){l=
-1<g?g:a.length)),h;for(h in d)b[h]=d[h]}if(-1<g)for(h in b=L(a.slice(g+1)),b)c[h]=b[h];return a.slice(0,k)}function g(){switch(n.charAt(0)){case "#":return c("hash").slice(n.length);case "?":return c("search").slice(n.length)+c("hash");default:return c("pathname").slice(n.length)+c("search")+c("hash")}}function l(c,b,g){var k={},h={};c=d(c,k,h);if(null!=b){for(var l in b)k[l]=b[l];c=c.replace(/:([^\/]+)/g,function(a,c){delete k[c];return b[c]})}(l=E(k))&&(c+="?"+l);(h=E(h))&&(c+="#"+h);m?(g&&g.replace? null;a()}))}}function d(a,c,d){var b=a.indexOf("?"),g=a.indexOf("#"),k=-1<b?b:-1<g?g:a.length;if(-1<b){var b=N(a.slice(b+1,-1<g?g:a.length)),h;for(h in b)c[h]=b[h]}if(-1<g)for(h in c=N(a.slice(g+1)),c)d[h]=c[h];return a.slice(0,k)}var g="function"===typeof a.history.pushState,m="function"===typeof setImmediate?setImmediate:setTimeout,l,n={prefix:"#!",getPath:function(){switch(n.prefix.charAt(0)){case "#":return c("hash").slice(n.prefix.length);case "?":return c("search").slice(n.prefix.length)+c("hash");
a.history.replaceState(null,null,n+c):a.history.pushState(null,null,n+c),a.onpopstate(!0)):a.location.href=n+c}var m="function"===typeof a.history.pushState,r="function"===typeof setImmediate?setImmediate:setTimeout,n="#!",t;return{setPrefix:function(a){n=a},getPath:g,setPath:l,defineRoutes:function(c,b,l){function k(){var a=g(),h={},k=d(a,h,h),m;for(m in c){var n=new RegExp("^"+m.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(n.test(k)){k.replace(n,function(){for(var d= default:return c("pathname").slice(n.prefix.length)+c("search")+c("hash")}},setPath:function(c,k,h){var b={},l={};c=d(c,b,l);if(null!=k){for(var m in k)b[m]=k[m];c=c.replace(/:([^\/]+)/g,function(a,c){delete b[c];return k[c]})}(m=I(b))&&(c+="?"+m);(l=I(l))&&(c+="#"+l);g?(h&&h.replace?a.history.replaceState(null,null,n.prefix+c):a.history.pushState(null,null,n.prefix+c),a.onpopstate()):a.location.href=n.prefix+c},defineRoutes:function(c,k,l){function b(){var a=n.getPath(),b={},g=d(a,b,b),h;for(h in c){var m=
m.match(/:[^\/]+/g)||[],g=[].slice.call(arguments,1,-2),k=0;k<d.length;k++)h[d[k].replace(/:|\./g,"")]=decodeURIComponent(g[k]);b(c[m],h,a,m)});return}}l(a,h)}m?a.onpopstate=h(k):"#"===n.charAt(0)&&(a.onhashchange=k);k()},link:function(a){a.dom.setAttribute("href",n+a.attrs.href);a.dom.onclick=function(a){a.ctrlKey||a.metaKey||a.shiftKey||2===a.which||(a.preventDefault(),a.redraw=!1,a=this.getAttribute("href"),0===a.indexOf(n)&&(a=a.slice(n.length)),l(a,void 0,void 0))}}}};y.route=function(a,c){var h= new RegExp("^"+h.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(m.test(g)){g.replace(m,function(){for(var d=h.match(/:[^\/]+/g)||[],g=[].slice.call(arguments,1,-2),l=0;l<d.length;l++)b[d[l].replace(/:|\./g,"")]=decodeURIComponent(g[l]);k(c[h],b,a,h)});return}}l(a,b)}g?a.onpopstate=h(b):"#"===n.prefix.charAt(0)&&(a.onhashchange=b);b()}};return n};z.route=function(a,c){var h=S(a),d=function(a){return a},g,m,l,n,p=!1,k=function(a,b,k){if(null==a)throw Error("Ensure the DOM element that was passed to `m.route` is not undefined");
R(a),d=function(a){return a},g,l,m,t,n,y=function(a,b,y){if(null==a)throw Error("Ensure the DOM element that was passed to `m.route` is not undefined");var k=function(a,b,c,h){g=a;l=b;m=c;t=h;n=null;g.render=a.render||d;u()},u=function(){null!=g&&c.render(a,g.render(r(l,m.key,m)))};h.defineRoutes(y,function(a,b,c){a.view?k({},a,b,c):a.onmatch?null!=n?k(a,l,b,c):(n=function(d){k(a,d,b,c)},a.onmatch(function(a){null!=n&&n(a)},b,c)):k(a,"div",b,c)},function(){h.setPath(b)});c.subscribe(a,u)};y.set=h.setPath; var r=function(a,b,c,h){m=null!=b&&"function"===typeof b.view?b:"div";l=c;n=h;p=!1;g=(a.render||d).bind(a);t()},t=function(){null!=g&&c.render(a,g(w(m,l.key,l)))},v=function(){h.setPath(b)};h.defineRoutes(k,function(a,b,c){a.view?r({},a,b,c):a.onmatch?(p=!0,E.resolve(a.onmatch(b,c)).then(function(d){p&&r(a,d,b,c)},v)):r(a,"div",b,c)},v);c.subscribe(a,t)};k.set=function(a,b,c){p&&(c={replace:!0});p=!1;h.setPath(a,b,c)};k.get=function(){return n};k.prefix=function(a){h.prefix=a};k.link=function(a){a.dom.setAttribute("href",
y.get=function(){return t};y.prefix=h.setPrefix;y.link=h.link;return y}(window,H);y.withAttr=function(a,c,h){return function(d){return c.call(h||this,a in d.currentTarget?d.currentTarget[a]:d.currentTarget.getAttribute(a))}};var S=O(window);y.render=S.render;y.redraw=H.redraw;y.request=K.request;y.jsonp=K.jsonp;y.parseQueryString=L;y.buildQueryString=E;y.version="1.0.0-rc.6";"undefined"!==typeof module?module.exports=y:window.m=y}; h.prefix+a.attrs.href);a.dom.onclick=function(a){a.ctrlKey||a.metaKey||a.shiftKey||2===a.which||(a.preventDefault(),a.redraw=!1,a=this.getAttribute("href"),0===a.indexOf(h.prefix)&&(a=a.slice(h.prefix.length)),k.set(a,void 0,void 0))}};return k}(window,J);z.withAttr=function(a,c,h){return function(d){return c.call(h||this,a in d.currentTarget?d.currentTarget[a]:d.currentTarget.getAttribute(a))}};H=O(window);z.render=H.render;z.redraw=J.redraw;z.request=M.request;z.jsonp=M.jsonp;z.parseQueryString=
N;z.buildQueryString=I;z.version="1.0.0-rc.6";"undefined"!==typeof module?module.exports=z:window.m=z};

View file

@ -202,7 +202,10 @@ module.exports = new function init() {
status = 1 status = 1
} }
} }
console.log(results.length + " assertions completed in " + Math.round(new Date - start) + "ms") console.log(
results.length + " assertions completed in " + Math.round(new Date - start) + "ms, " +
"of which " + results.filter(function(result){return result.error}).length + " failed"
)
if (hasProcess && status === 1) process.exit(1) if (hasProcess && status === 1) process.exit(1)
} }

View file

@ -403,6 +403,27 @@ o.spec("promise", function() {
done() done()
}) })
}) })
o("triggers all branched rejection handlers upon rejection", function(done) {
var promise = Promise.reject()
var then = o.spy()
var catch1 = o.spy()
var catch2 = o.spy()
var catch3 = o.spy()
promise.catch(catch1)
promise.then(then, catch2)
promise.then(then).catch(catch3)
callAsync(function() {
callAsync(function() {
o(catch1.callCount).equals(1)
o(then.callCount).equals(0)
o(catch2.callCount).equals(1)
o(catch3.callCount).equals(1)
done()
})
})
})
o("does not absorb resolved promise via static rejector", function(done) { o("does not absorb resolved promise via static rejector", function(done) {
var promise = Promise.reject(Promise.resolve(1)) var promise = Promise.reject(Promise.resolve(1))

View file

@ -18,7 +18,7 @@ module.exports = function($window, Promise) {
var next = then.apply(promise, arguments) var next = then.apply(promise, arguments)
next.then(complete, function(e) { next.then(complete, function(e) {
complete() complete()
throw e if (count === 0) throw e
}) })
return finalize(next) return finalize(next)
} }

View file

@ -6,6 +6,7 @@
<body> <body>
<script src="../../module/module.js"></script> <script src="../../module/module.js"></script>
<script src="../../ospec/ospec.js"></script> <script src="../../ospec/ospec.js"></script>
<script src="../../test-utils/callAsync.js"></script>
<script src="../../querystring/parse.js"></script> <script src="../../querystring/parse.js"></script>
<script src="../../test-utils/parseURL.js"></script> <script src="../../test-utils/parseURL.js"></script>
<script src="../../test-utils/callAsync.js"></script> <script src="../../test-utils/callAsync.js"></script>

View file

@ -1,6 +1,7 @@
"use strict" "use strict"
var o = require("../../ospec/ospec") var o = require("../../ospec/ospec")
var callAsync = require("../../test-utils/callAsync")
var xhrMock = require("../../test-utils/xhrMock") var xhrMock = require("../../test-utils/xhrMock")
var Request = require("../../request/request") var Request = require("../../request/request")
var Promise = require("../../promise/promise") var Promise = require("../../promise/promise")
@ -336,7 +337,7 @@ o.spec("xhr", function() {
} }
}) })
var promise = xhr("/item", {background: true}).then(function() {}) var promise = xhr("/item", {background: true}).then(function() {})
setTimeout(function() { setTimeout(function() {
o(complete.callCount).equals(0) o(complete.callCount).equals(0)
done() done()
@ -377,5 +378,31 @@ o.spec("xhr", function() {
o(e.message).equals("error") o(e.message).equals("error")
}).then(done) }).then(done)
}) })
o("triggers all branched catches upon rejection", function(done) {
mock.$defineRoutes({
"GET /item": function(request) {
return {status: 500, responseText: "error"}
}
})
var request = xhr({method: "GET", url: "/item"})
var then = o.spy()
var catch1 = o.spy()
var catch2 = o.spy()
var catch3 = o.spy()
request.catch(catch1)
request.then(then, catch2)
request.then(then).catch(catch3)
callAsync(function() {
callAsync(function() {
o(catch1.callCount).equals(1)
o(then.callCount).equals(0)
o(catch2.callCount).equals(1)
o(catch3.callCount).equals(1)
done()
})
})
})
}) })
}) })

View file

@ -7,9 +7,6 @@ module.exports = function($window) {
var supportsPushState = typeof $window.history.pushState === "function" var supportsPushState = typeof $window.history.pushState === "function"
var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
var prefix = "#!"
function setPrefix(value) {prefix = value}
function normalize(fragment) { function normalize(fragment) {
var data = $window.location[fragment].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) var data = $window.location[fragment].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
if (fragment === "pathname" && data[0] !== "/") data = "/" + data if (fragment === "pathname" && data[0] !== "/") data = "/" + data
@ -17,14 +14,13 @@ module.exports = function($window) {
} }
var asyncId var asyncId
function debounceAsync(f) { function debounceAsync(callback) {
return function() { return function() {
if (asyncId != null) return if (asyncId != null) return
asyncId = callAsync(function() { asyncId = callAsync(function() {
asyncId = null asyncId = null
f() callback()
}) })
} }
} }
@ -44,16 +40,16 @@ module.exports = function($window) {
return path.slice(0, pathEnd) return path.slice(0, pathEnd)
} }
function getPath() { var router = {prefix: "#!"}
var type = prefix.charAt(0) router.getPath = function() {
var type = router.prefix.charAt(0)
switch (type) { switch (type) {
case "#": return normalize("hash").slice(prefix.length) case "#": return normalize("hash").slice(router.prefix.length)
case "?": return normalize("search").slice(prefix.length) + normalize("hash") case "?": return normalize("search").slice(router.prefix.length) + normalize("hash")
default: return normalize("pathname").slice(prefix.length) + normalize("search") + normalize("hash") default: return normalize("pathname").slice(router.prefix.length) + normalize("search") + normalize("hash")
} }
} }
router.setPath = function(path, data, options) {
function setPath(path, data, options) {
var queryData = {}, hashData = {} var queryData = {}, hashData = {}
path = parsePath(path, queryData, hashData) path = parsePath(path, queryData, hashData)
if (data != null) { if (data != null) {
@ -71,16 +67,15 @@ module.exports = function($window) {
if (hash) path += "#" + hash if (hash) path += "#" + hash
if (supportsPushState) { if (supportsPushState) {
if (options && options.replace) $window.history.replaceState(null, null, prefix + path) if (options && options.replace) $window.history.replaceState(null, null, router.prefix + path)
else $window.history.pushState(null, null, prefix + path) else $window.history.pushState(null, null, router.prefix + path)
$window.onpopstate(true) $window.onpopstate()
} }
else $window.location.href = prefix + path else $window.location.href = router.prefix + path
} }
router.defineRoutes = function(routes, resolve, reject) {
function defineRoutes(routes, resolve, reject) {
function resolveRoute() { function resolveRoute() {
var path = getPath() var path = router.getPath()
var params = {} var params = {}
var pathname = parsePath(path, params, params) var pathname = parsePath(path, params, params)
@ -104,21 +99,9 @@ module.exports = function($window) {
} }
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute) if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
else if (prefix.charAt(0) === "#") $window.onhashchange = resolveRoute else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
resolveRoute() resolveRoute()
} }
function link(vnode) { return router
vnode.dom.setAttribute("href", prefix + vnode.attrs.href)
vnode.dom.onclick = function(e) {
if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return
e.preventDefault()
e.redraw = false
var href = this.getAttribute("href")
if (href.indexOf(prefix) === 0) href = href.slice(prefix.length)
setPath(href, undefined, undefined)
}
}
return {setPrefix: setPrefix, getPath: getPath, setPath: setPath, defineRoutes: defineRoutes, link: link}
} }

View file

@ -11,6 +11,7 @@
<script src="../../test-utils/pushStateMock.js"></script> <script src="../../test-utils/pushStateMock.js"></script>
<script src="../../test-utils/domMock.js"></script> <script src="../../test-utils/domMock.js"></script>
<script src="../../promise/promise.js"></script>
<script src="../../render/vnode.js"></script> <script src="../../render/vnode.js"></script>
<script src="../../render/render.js"></script> <script src="../../render/render.js"></script>
<script src="../../querystring/build.js"></script> <script src="../../querystring/build.js"></script>
@ -19,7 +20,6 @@
<script src="test-defineRoutes.js"></script> <script src="test-defineRoutes.js"></script>
<script src="test-getPath.js"></script> <script src="test-getPath.js"></script>
<script src="test-setPath.js"></script> <script src="test-setPath.js"></script>
<script src="test-link.js"></script>
<script>require("../../ospec/ospec").run()</script> <script>require("../../ospec/ospec").run()</script>
</body> </body>

View file

@ -14,7 +14,7 @@ o.spec("Router.defineRoutes", function() {
o.beforeEach(function() { o.beforeEach(function() {
$window = pushStateMock(env) $window = pushStateMock(env)
router = new Router($window) router = new Router($window)
router.setPrefix(prefix) router.prefix = prefix
onRouteChange = o.spy() onRouteChange = o.spy()
onFail = o.spy() onFail = o.spy()
}) })
@ -73,7 +73,7 @@ o.spec("Router.defineRoutes", function() {
$window.location.href = "file://" + prefix + "/test" $window.location.href = "file://" + prefix + "/test"
router = new Router($window) router = new Router($window)
router.setPrefix(prefix) router.prefix = prefix
router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail)

View file

@ -13,7 +13,7 @@ o.spec("Router.getPath", function() {
o.beforeEach(function() { o.beforeEach(function() {
$window = pushStateMock(env) $window = pushStateMock(env)
router = new Router($window) router = new Router($window)
router.setPrefix(prefix) router.prefix = prefix
onRouteChange = o.spy() onRouteChange = o.spy()
onFail = o.spy() onFail = o.spy()
}) })

View file

@ -1,87 +0,0 @@
"use strict"
var o = require("../../ospec/ospec")
var renderService = require("../../render/render")
var callAsync = require("../../test-utils/callAsync")
var pushStateMock = require("../../test-utils/pushStateMock")
var domMock = require("../../test-utils/domMock")
var Router = require("../../router/router")
o.spec("Router.link", function() {
void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) {
void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) {
o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() {
var $window, dom, root, router, onRouteChange, onFail, render
o.beforeEach(function() {
$window = pushStateMock(env)
dom = domMock()
root = dom.document.body
router = new Router($window)
router.setPrefix(prefix)
onRouteChange = o.spy()
onFail = o.spy()
render = renderService(dom).render
})
o("works", function(done) {
var A = {
view: function() {
return {tag: "a", attrs: {href: "/b", oncreate: router.link}}
}
}
var B = {
view: function() {
return {tag: "a", attrs: {href: "/a", oncreate: router.link}}
}
}
$window.location.href = prefix + "/a"
router.defineRoutes({"/a": {tag: A}, "/b": {tag: B}}, function(component) {
render(root, component)
})
callAsync(function() {
var e = dom.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
root.firstChild.dispatchEvent(e)
callAsync(function() {
o(router.getPath()).equals("/b")
done()
})
})
})
o("works after update", function(done) {
var id = "a"
var A = {
view: function() {
return {tag: "a", attrs: {href: "/" + id, oncreate: router.link}}
}
}
$window.location.href = prefix + "/a"
router.defineRoutes({"/a": {tag: A}, "/b": {tag: A}}, function(component) {
render(root, {tag: A})
id = "b"
render(root, {tag: A})
})
callAsync(function() {
var e = dom.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
root.firstChild.dispatchEvent(e)
callAsync(function() {
o(router.getPath()).equals("/b")
done()
})
})
})
})
})
})
})

View file

@ -14,7 +14,7 @@ o.spec("Router.setPath", function() {
o.beforeEach(function() { o.beforeEach(function() {
$window = pushStateMock(env) $window = pushStateMock(env)
router = new Router($window) router = new Router($window)
router.setPrefix(prefix) router.prefix = prefix
onRouteChange = o.spy() onRouteChange = o.spy()
onFail = o.spy() onFail = o.spy()
}) })
@ -88,7 +88,7 @@ o.spec("Router.setPath", function() {
$window.location.href = "file://" + prefix + "/test" $window.location.href = "file://" + prefix + "/test"
router = new Router($window) router = new Router($window)
router.setPrefix(prefix) router.prefix = prefix
router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail) router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail)

View file

@ -5,14 +5,14 @@ var domMock = require("./domMock")
var xhrMock = require("./xhrMock") var xhrMock = require("./xhrMock")
module.exports = function(env) { module.exports = function(env) {
var $window = {} env = env || {}
var $window = env.window = {}
var dom = domMock() var dom = domMock()
var xhr = xhrMock() var xhr = xhrMock()
var ps = pushStateMock(env)
for (var key in dom) if (!$window[key]) $window[key] = dom[key] for (var key in dom) if (!$window[key]) $window[key] = dom[key]
for (var key in xhr) if (!$window[key]) $window[key] = xhr[key] for (var key in xhr) if (!$window[key]) $window[key] = xhr[key]
for (var key in ps) if (!$window[key]) $window[key] = ps[key] pushStateMock(env)
return $window return $window
} }

View file

@ -5,6 +5,7 @@ var parseURL = require("../test-utils/parseURL")
module.exports = function(options) { module.exports = function(options) {
if (options == null) options = {} if (options == null) options = {}
var $window = options.window || {}
var protocol = options.protocol || "http:" var protocol = options.protocol || "http:"
var hostname = options.hostname || "localhost" var hostname = options.hostname || "localhost"
var port = "" var port = ""
@ -32,7 +33,7 @@ module.exports = function(options) {
} }
return isNew return isNew
} }
function prefix(prefix, value) { function prefix(prefix, value) {
if (value === "") return "" if (value === "") return ""
return (value.charAt(0) !== prefix ? prefix : "") + value return (value.charAt(0) !== prefix ? prefix : "") + value
@ -46,125 +47,125 @@ module.exports = function(options) {
function unload() { function unload() {
if (typeof $window.onunload === "function") $window.onunload({type: "unload"}) if (typeof $window.onunload === "function") $window.onunload({type: "unload"})
} }
var $window = {
location: {
get protocol() {
return protocol
},
get hostname() {
return hostname
},
get port() {
return port
},
get pathname() {
return pathname
},
get search() {
return search
},
get hash() {
return hash
},
get origin() {
if (protocol === "file:") return "null"
return protocol + "//" + hostname + prefix(":", port)
},
get host() {
if (protocol === "file:") return ""
return hostname + prefix(":", port)
},
get href() {
return getURL()
},
set protocol(value) { $window.location = {
throw new Error("Protocol is read-only") get protocol() {
}, return protocol
set hostname(value) { },
unload() get hostname() {
past.push({url: getURL(), isNew: true}) return hostname
future = [] },
hostname = value get port() {
}, return port
set port(value) { },
if (protocol === "file:") throw new Error("Port is read-only under `file://` protocol") get pathname() {
unload() return pathname
past.push({url: getURL(), isNew: true}) },
future = [] get search() {
port = value return search
}, },
set pathname(value) { get hash() {
if (protocol === "file:") throw new Error("Pathname is read-only under `file://` protocol") return hash
unload() },
past.push({url: getURL(), isNew: true}) get origin() {
future = [] if (protocol === "file:") return "null"
pathname = prefix("/", value) return protocol + "//" + hostname + prefix(":", port)
}, },
set search(value) { get host() {
unload() if (protocol === "file:") return ""
past.push({url: getURL(), isNew: true}) return hostname + prefix(":", port)
future = [] },
search = prefix("?", value) get href() {
}, return getURL()
set hash(value) { },
var oldHash = hash
past.push({url: getURL(), isNew: false})
future = []
hash = prefix("#", value)
if (oldHash != hash) hashchange()
},
set origin(value) { set protocol(value) {
//origin is writable but ignored throw new Error("Protocol is read-only")
},
set host(value) {
//host is writable but ignored in Chrome
},
set href(value) {
var url = getURL()
var isNew = setURL(value)
if (isNew) {
setURL(url)
unload()
setURL(value)
}
past.push({url: url, isNew: isNew})
future = []
},
}, },
history: { set hostname(value) {
pushState: function(data, title, url) { unload()
past.push({url: getURL(), isNew: false}) past.push({url: getURL(), isNew: true})
future = [] future = []
setURL(url) hostname = value
}, },
replaceState: function(data, title, url) { set port(value) {
future = [] if (protocol === "file:") throw new Error("Port is read-only under `file://` protocol")
setURL(url) unload()
}, past.push({url: getURL(), isNew: true})
back: function() { future = []
var entry = past.pop() port = value
if (entry != null) { },
if (entry.isNew) unload() set pathname(value) {
future.push({url: getURL(), isNew: false}) if (protocol === "file:") throw new Error("Pathname is read-only under `file://` protocol")
setURL(entry.url) unload()
if (!entry.isNew) popstate() past.push({url: getURL(), isNew: true})
} future = []
}, pathname = prefix("/", value)
forward: function() { },
var entry = future.pop() set search(value) {
if (entry != null) { unload()
if (entry.isNew) unload() past.push({url: getURL(), isNew: true})
past.push({url: getURL(), isNew: false}) future = []
setURL(entry.url) search = prefix("?", value)
if (!entry.isNew) popstate() },
} set hash(value) {
}, var oldHash = hash
past.push({url: getURL(), isNew: false})
future = []
hash = prefix("#", value)
if (oldHash != hash) hashchange()
},
set origin(value) {
//origin is writable but ignored
},
set host(value) {
//host is writable but ignored in Chrome
},
set href(value) {
var url = getURL()
var isNew = setURL(value)
if (isNew) {
setURL(url)
unload()
setURL(value)
}
past.push({url: url, isNew: isNew})
future = []
}, },
onpopstate: null,
onhashchange: null,
onunload: null,
} }
$window.history = {
pushState: function(data, title, url) {
past.push({url: getURL(), isNew: false})
future = []
setURL(url)
},
replaceState: function(data, title, url) {
future = []
setURL(url)
},
back: function() {
var entry = past.pop()
if (entry != null) {
if (entry.isNew) unload()
future.push({url: getURL(), isNew: false})
setURL(entry.url)
if (!entry.isNew) popstate()
}
},
forward: function() {
var entry = future.pop()
if (entry != null) {
if (entry.isNew) unload()
past.push({url: getURL(), isNew: false})
setURL(entry.url)
if (!entry.isNew) popstate()
}
},
}
$window.onpopstate = null,
$window.onhashchange = null,
$window.onunload = null
return $window return $window
} }

View file

@ -13,11 +13,13 @@
<script src="../../test-utils/pushStateMock.js"></script> <script src="../../test-utils/pushStateMock.js"></script>
<script src="../../test-utils/xhrMock.js"></script> <script src="../../test-utils/xhrMock.js"></script>
<script src="../../test-utils/domMock.js"></script> <script src="../../test-utils/domMock.js"></script>
<script src="../../test-utils/browserMock.js"></script>
<script src="test-callAsync.js"></script> <script src="test-callAsync.js"></script>
<script src="test-parseURL.js"></script> <script src="test-parseURL.js"></script>
<script src="test-pushStateMock.js"></script> <script src="test-pushStateMock.js"></script>
<script src="test-xhrMock.js"></script> <script src="test-xhrMock.js"></script>
<script src="test-domMock.js"></script> <script src="test-domMock.js"></script>
<script src="test-browserMock.js"></script>
<script>require("../../ospec/ospec").run()</script> <script>require("../../ospec/ospec").run()</script>
</body> </body>

View file

@ -0,0 +1,40 @@
"use strict"
var o = require("../../ospec/ospec")
var browserMock = require("../../test-utils/browserMock")
var callAsync = require("../../test-utils/callAsync")
o.spec("browserMock", function() {
var $window
o.beforeEach(function() {
$window = browserMock()
})
o("Mocks DOM, pushState and XHR", function() {
o($window.location).notEquals(undefined)
o($window.document).notEquals(undefined)
o($window.XMLHttpRequest).notEquals(undefined)
})
o("$window.onhashchange can be reached from the pushStateMock functions", function(done) {
$window.onhashchange = o.spy()
$window.location.hash = '#a'
callAsync(function(){
o($window.onhashchange.callCount).equals(1)
done()
})
})
o("$window.onpopstate can be reached from the pushStateMock functions", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "#a")
$window.history.back()
o($window.onpopstate.callCount).equals(1)
})
o("$window.onunload can be reached from the pushStateMock functions", function() {
$window.onunload = o.spy()
$window.location.href = '/a'
o($window.onunload.callCount).equals(1)
})
})