Merge branch 'rewrite' into array-isArray
This commit is contained in:
commit
aa72f87408
27 changed files with 1172 additions and 529 deletions
20
.npmignore
Normal file
20
.npmignore
Normal 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
|
||||
|
|
@ -34,6 +34,6 @@ There are over 4000 assertions in the test suite, and tests cover even difficult
|
|||
|
||||
## 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
|
||||
|
|
|
|||
|
|
@ -1,47 +1,59 @@
|
|||
"use strict"
|
||||
|
||||
var Vnode = require("../render/vnode")
|
||||
var Promise = require("../promise/promise")
|
||||
var coreRouter = require("../router/router")
|
||||
|
||||
module.exports = function($window, redrawService) {
|
||||
var routeService = coreRouter($window)
|
||||
|
||||
|
||||
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) {
|
||||
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) {
|
||||
resolver = routeResolver, component = comp, attrs = params, currentPath = path, resolve = null
|
||||
resolver.render = routeResolver.render || identity
|
||||
render()
|
||||
component = comp != null && typeof comp.view === "function" ? comp : "div", attrs = params, currentPath = path, updatePending = false
|
||||
render = (routeResolver.render || identity).bind(routeResolver)
|
||||
run()
|
||||
}
|
||||
var render = function() {
|
||||
if (resolver != null) redrawService.render(root, resolver.render(Vnode(component, attrs.key, attrs)))
|
||||
var run = function() {
|
||||
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) {
|
||||
if (payload.view) update({}, payload, params, path)
|
||||
else {
|
||||
if (payload.onmatch) {
|
||||
if (resolve != null) update(payload, component, params, path)
|
||||
else {
|
||||
resolve = function(resolved) {
|
||||
update(payload, resolved, params, path)
|
||||
}
|
||||
payload.onmatch(function(resolved) {
|
||||
if (resolve != null) resolve(resolved)
|
||||
}, params, path)
|
||||
}
|
||||
updatePending = true
|
||||
Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
|
||||
if (updatePending) update(payload, resolved, params, path)
|
||||
}, bail)
|
||||
}
|
||||
else update(payload, "div", params, path)
|
||||
}
|
||||
}, function() {
|
||||
routeService.setPath(defaultRoute)
|
||||
})
|
||||
redrawService.subscribe(root, render)
|
||||
}, bail)
|
||||
redrawService.subscribe(root, run)
|
||||
}
|
||||
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.prefix = routeService.setPrefix
|
||||
route.link = routeService.link
|
||||
route.prefix = function(prefix) {routeService.prefix = prefix}
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ var callAsync = require("../../test-utils/callAsync")
|
|||
var browserMock = require("../../test-utils/browserMock")
|
||||
|
||||
var m = require("../../render/hyperscript")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var coreRenderer = require("../../render/render")
|
||||
var apiRedraw = require("../../api/redraw")
|
||||
var apiRouter = require("../../api/router")
|
||||
var Promise = require("../../promise/promise")
|
||||
|
||||
o.spec("route", function() {
|
||||
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")
|
||||
|
||||
$window.history.back()
|
||||
|
|
@ -81,7 +83,7 @@ o.spec("route", function() {
|
|||
o($window.location.pathname).equals("/")
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
callAsync(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET * 2)
|
||||
})
|
||||
})
|
||||
|
||||
o("event handlers can skip redraw", function(done) {
|
||||
|
|
@ -191,11 +193,11 @@ o.spec("route", function() {
|
|||
o(oninit.callCount).equals(1)
|
||||
|
||||
// Wrapped to ensure no redraw fired
|
||||
setTimeout(function() {
|
||||
callAsync(function() {
|
||||
o(onupdate.callCount).equals(0)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
})
|
||||
|
||||
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("accepts RouteResolver", function() {
|
||||
o("accepts RouteResolver with onmatch that returns Component", function(done) {
|
||||
var matchCount = 0
|
||||
var renderCount = 0
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("div")
|
||||
return m("span")
|
||||
}
|
||||
}
|
||||
|
||||
var resolver = {
|
||||
onmatch: function(resolve, args, requestedPath) {
|
||||
onmatch: function(args, requestedPath) {
|
||||
matchCount++
|
||||
|
||||
o(args.id).equals("abc")
|
||||
o(requestedPath).equals("/abc")
|
||||
o(this).equals(resolver)
|
||||
resolve(Component)
|
||||
return Component
|
||||
},
|
||||
render: function(vnode) {
|
||||
renderCount++
|
||||
|
|
@ -262,12 +264,175 @@ o.spec("route", function() {
|
|||
"/:id" : resolver
|
||||
})
|
||||
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(1)
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
callAsync(function() {
|
||||
o(matchCount).equals(1)
|
||||
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 Component = {
|
||||
view: function() {
|
||||
|
|
@ -278,29 +443,29 @@ o.spec("route", function() {
|
|||
$window.location.href = prefix + "/abc"
|
||||
route(root, "/abc", {
|
||||
"/:id" : {
|
||||
onmatch: function(resolve, args, requestedPath) {
|
||||
onmatch: function(args, requestedPath) {
|
||||
matchCount++
|
||||
|
||||
o(args.id).equals("abc")
|
||||
o(requestedPath).equals("/abc")
|
||||
|
||||
resolve(Component)
|
||||
return Component
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
o(matchCount).equals(1)
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
callAsync(function() {
|
||||
o(matchCount).equals(1)
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("changing `vnode.key` in `render` resets the component", function(done, timeout){
|
||||
timeout(FRAME_BUDGET * 6)
|
||||
|
||||
var oninit = o.spy()
|
||||
var Component = {
|
||||
oninit: oninit,
|
||||
view: function(){
|
||||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
}
|
||||
|
|
@ -310,14 +475,14 @@ o.spec("route", function() {
|
|||
return m(Component, {key: vnode.attrs.id})
|
||||
}}
|
||||
})
|
||||
setTimeout(function(){
|
||||
callAsync(function() {
|
||||
o(oninit.callCount).equals(1)
|
||||
route.set('/def')
|
||||
setTimeout(function(){
|
||||
route.set("/def")
|
||||
callAsync(function() {
|
||||
o(oninit.callCount).equals(2)
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
o("accepts RouteResolver without `onmatch` method as payload", function() {
|
||||
|
|
@ -371,14 +536,14 @@ o.spec("route", function() {
|
|||
|
||||
route.set("/b")
|
||||
|
||||
setTimeout(function() {
|
||||
callAsync(function() {
|
||||
o(root.firstChild).equals(dom)
|
||||
|
||||
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 renderCount = 0
|
||||
var Component = {
|
||||
|
|
@ -390,9 +555,9 @@ o.spec("route", function() {
|
|||
$window.location.href = prefix + "/"
|
||||
route(root, "/", {
|
||||
"/" : {
|
||||
onmatch: function(resolve) {
|
||||
onmatch: function() {
|
||||
matchCount++
|
||||
resolve(Component)
|
||||
return Component
|
||||
},
|
||||
render: function(vnode) {
|
||||
renderCount++
|
||||
|
|
@ -401,48 +566,126 @@ o.spec("route", function() {
|
|||
},
|
||||
})
|
||||
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(1)
|
||||
callAsync(function() {
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(2)
|
||||
o(matchCount).equals(1)
|
||||
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) {
|
||||
var redirected = false
|
||||
|
||||
var render = o.spy()
|
||||
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, "/a", {
|
||||
"/a" : {
|
||||
onmatch: function() {
|
||||
route.set("/b")
|
||||
}
|
||||
},
|
||||
render: render
|
||||
},
|
||||
"/b" : {
|
||||
view: function(vnode){
|
||||
view: function() {
|
||||
redirected = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
callAsync(function() {
|
||||
o(render.callCount).equals(0)
|
||||
o(redirected).equals(true)
|
||||
|
||||
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 render = o.spy()
|
||||
var view = o.spy(function() {return m("div")})
|
||||
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, "/a", {
|
||||
"/a" : {
|
||||
onmatch: function() {
|
||||
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" : {
|
||||
render: function(vnode){
|
||||
|
|
@ -451,45 +694,219 @@ o.spec("route", function() {
|
|||
}
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
callAsync(function() {
|
||||
o(render.callCount).equals(0)
|
||||
o(redirected).equals(true)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
})
|
||||
|
||||
o("onmatch resolution callback resolves at most once", function(done) {
|
||||
var resolveCount = 0
|
||||
var resolvedComponent
|
||||
var A = {view: function() {}}
|
||||
var B = {view: function() {}}
|
||||
var C = {view: function() {}}
|
||||
o("onmatch can redirect to another route that has RouteResolver whose onmatch resolves asynchronously", function(done) {
|
||||
var redirected = false
|
||||
var render = o.spy()
|
||||
var view = o.spy()
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, "/", {
|
||||
"/": {
|
||||
onmatch: function(resolve) {
|
||||
resolve(A)
|
||||
resolve(B)
|
||||
callAsync(function() {resolve(C)})
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, "/a", {
|
||||
"/a" : {
|
||||
onmatch: function() {
|
||||
route.set("/b")
|
||||
},
|
||||
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) {
|
||||
resolveCount++
|
||||
resolvedComponent = vnode.tag
|
||||
return vnode
|
||||
}
|
||||
},
|
||||
"/b" : {
|
||||
onmatch: function() {
|
||||
$window.history.back()
|
||||
return new Promise(function() {})
|
||||
},
|
||||
render: render
|
||||
}
|
||||
})
|
||||
setTimeout(function() {
|
||||
o(resolveCount).equals(1)
|
||||
o(resolvedComponent).equals(A)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
callAsync(function() {
|
||||
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) {
|
||||
var view = o.spy()
|
||||
var onmatch = o.spy()
|
||||
var onmatch = o.spy(function() {
|
||||
return new Promise(function() {})
|
||||
})
|
||||
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, "/", {
|
||||
|
|
@ -502,7 +919,7 @@ o.spec("route", function() {
|
|||
|
||||
route.set("/b")
|
||||
|
||||
setTimeout(function(){
|
||||
callAsync(function() {
|
||||
o(view.callCount).equals(1)
|
||||
o(onmatch.callCount).equals(1)
|
||||
|
||||
|
|
@ -512,12 +929,12 @@ o.spec("route", function() {
|
|||
o(onmatch.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
})
|
||||
|
||||
o("m.route.set(m.route.get()) re-runs the resolution logic (#1180)", function(done){
|
||||
var onmatch = o.spy(function(resolve) {resolve()})
|
||||
var render = o.spy(function(){return m("div")})
|
||||
var onmatch = o.spy()
|
||||
var render = o.spy(function() {return m("div")})
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, '/', {
|
||||
|
|
@ -527,25 +944,33 @@ o.spec("route", function() {
|
|||
}
|
||||
})
|
||||
|
||||
o(onmatch.callCount).equals(1)
|
||||
o(render.callCount).equals(1)
|
||||
callAsync(function() {
|
||||
o(onmatch.callCount).equals(1)
|
||||
o(render.callCount).equals(1)
|
||||
|
||||
route.set(route.get())
|
||||
route.set(route.get())
|
||||
|
||||
setTimeout(function() {
|
||||
o(onmatch.callCount).equals(2)
|
||||
o(render.callCount).equals(2)
|
||||
callAsync(function() {
|
||||
callAsync(function() {
|
||||
o(onmatch.callCount).equals(2)
|
||||
o(render.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
o("m.route.get() returns the last fully resolved route (#1276)", function(done){
|
||||
$window.location.href = prefix + "/"
|
||||
|
||||
route(root, "/", {
|
||||
"/": {view: function(){}},
|
||||
"/2": {onmatch: function(){}}
|
||||
"/": {view: function() {}},
|
||||
"/2": {
|
||||
onmatch: function() {
|
||||
return new Promise(function() {})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
|
@ -553,15 +978,13 @@ o.spec("route", function() {
|
|||
|
||||
route.set("/2")
|
||||
|
||||
setTimeout(function(){
|
||||
callAsync(function() {
|
||||
o(route.get()).equals("/")
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
})
|
||||
|
||||
o("routing with RouteResolver works more than once", function(done, timeout) {
|
||||
timeout(200)
|
||||
|
||||
o("routing with RouteResolver works more than once", function(done) {
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, '/a', {
|
||||
'/a': {
|
||||
|
|
@ -578,44 +1001,96 @@ o.spec("route", function() {
|
|||
|
||||
route.set('/b')
|
||||
|
||||
setTimeout(function(){
|
||||
callAsync(function() {
|
||||
route.set('/a')
|
||||
|
||||
setTimeout(function(){
|
||||
callAsync(function() {
|
||||
o(root.firstChild.nodeName).equals("A")
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
o("calling route.set invalidates pending onmatch resolution", function(done, timeout) {
|
||||
timeout(200)
|
||||
|
||||
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(resolve) {
|
||||
setTimeout(resolve, 20)
|
||||
onmatch: function() {
|
||||
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": {
|
||||
view: function() {resolved = "b"}
|
||||
view: function() {
|
||||
resolved = "b"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
route.set("/b")
|
||||
|
||||
setTimeout(function() {
|
||||
callAsync(function() {
|
||||
o(rendered).equals(false)
|
||||
o(resolved).equals("b")
|
||||
|
||||
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()
|
||||
|
||||
$window.location.href = prefix + "/a"
|
||||
|
|
@ -631,13 +1106,46 @@ o.spec("route", function() {
|
|||
|
||||
route.set("/b")
|
||||
|
||||
setTimeout(function() {
|
||||
callAsync(function() {
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
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) {
|
||||
timeout(200)
|
||||
|
||||
|
|
@ -654,12 +1162,12 @@ o.spec("route", function() {
|
|||
redrawService.redraw()
|
||||
var after = i
|
||||
|
||||
setTimeout(function(){
|
||||
setTimeout(function() {
|
||||
o(before).equals(1) // routes synchronously
|
||||
o(after).equals(2) // redraws synchronously
|
||||
o(i).equals(3) // throttles rest
|
||||
done()
|
||||
},40)
|
||||
}, FRAME_BUDGET * 2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ var querystring = m.buildQueryString({a: "1", b: "2"})
|
|||
```javascript
|
||||
var state = {
|
||||
value: "",
|
||||
setValue: function(v) {value = v}
|
||||
setValue: function(v) {state.value = v}
|
||||
}
|
||||
|
||||
var Component = {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ If you are migrating, consider using the [mithril-codemods](https://www.npmjs.co
|
|||
- [`m.prop` removed](#mprop-removed)
|
||||
- [`m.component` removed](#mcomponent-removed)
|
||||
- [`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 arguments](#component-arguments)
|
||||
- [`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)
|
||||
- [Preventing unmounting](#preventing-unmounting)
|
||||
- [`m.request`](#mrequest)
|
||||
- [`m.deferred` removed](#mdeferred-removed)
|
||||
- [`m.sync` removed](#msync-removed)
|
||||
- [`xlink` namespace required](#xlink-namespace-required)
|
||||
- [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`.
|
||||
|
||||
|
|
@ -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` 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`
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
- [Signature](#signature)
|
||||
- [How it works](#how-it-works)
|
||||
- [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.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).
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
129
docs/route.md
129
docs/route.md
|
|
@ -16,9 +16,10 @@
|
|||
- [Routing parameters](#routing-parameters)
|
||||
- [Changing router prefix](#changing-router-prefix)
|
||||
- [Advanced component resolution](#advanced-component-resolution)
|
||||
- [Wrapping a layout component](#wrapping-a-layout-component)
|
||||
- [Authentication](#authentication)
|
||||
- [Code splitting](#code-splitting)
|
||||
- [Wrapping a layout component](#wrapping-a-layout-component)
|
||||
- [Authentication](#authentication)
|
||||
- [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
|
||||
|
||||
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
|
||||
--------------- | ------------------------ | ---
|
||||
`resolve` | `Component -> undefined` | Call this function with a component as the first argument to use it as the route's component
|
||||
`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.
|
||||
**returns** | | Returns `undefined`
|
||||
`routeResolver.onmatch(args, requestedPath)`
|
||||
|
||||
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.
|
||||
**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
|
||||
|
||||
|
|
@ -128,8 +134,8 @@ The `render` method is called on every redraw for a matching route. It is simila
|
|||
|
||||
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.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` | `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 map of URL parameter values
|
||||
**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.
|
||||
|
||||
|
|
@ -286,8 +292,8 @@ Instead of mapping a component to a route, you can specify a RouteResolver objec
|
|||
```javascript
|
||||
m.route(document.body, "/", {
|
||||
"/": {
|
||||
onmatch: function(resolve, args, requestedPath) {
|
||||
resolve(Home)
|
||||
onmatch: function(args, requestedPath) {
|
||||
return Home
|
||||
},
|
||||
render: function(vnode) {
|
||||
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:
|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
@ -366,10 +372,12 @@ var Login = {
|
|||
|
||||
m.route(document.body, "/secret", {
|
||||
"/secret": {
|
||||
onmatch: function(resolve) {
|
||||
if (isLoggedIn) resolve(Home)
|
||||
else m.route.set("/login")
|
||||
onmatch: function() {
|
||||
if (!isLoggedIn) m.route.set("/login")
|
||||
},
|
||||
render: function() {
|
||||
return m(Home)
|
||||
}
|
||||
},
|
||||
"/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
|
||||
// Home.js
|
||||
|
|
@ -401,21 +465,20 @@ module.export = {
|
|||
|
||||
```javascript
|
||||
// index.js
|
||||
function load(file, done) {
|
||||
m.request({
|
||||
function load(file) {
|
||||
return m.request({
|
||||
method: "GET",
|
||||
url: file,
|
||||
extract: function(xhr) {
|
||||
return new Function("var module = {};" + xhr.responseText + ";return module.exports;")
|
||||
}
|
||||
})
|
||||
.run(done)
|
||||
}
|
||||
|
||||
m.route(document.body, "/", {
|
||||
"/": {
|
||||
onmatch: function(resolve) {
|
||||
load("Home.js", resolve)
|
||||
onmatch: function() {
|
||||
return load("Home.js")
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -428,9 +491,11 @@ Fortunately, there are a number of tools that facilitate the task of bundling mo
|
|||
```javascript
|
||||
m.route(document.body, "/", {
|
||||
"/": {
|
||||
onmatch: function(resolve) {
|
||||
onmatch: function() {
|
||||
// using Webpack async code splitting
|
||||
require(['./Home.js'], resolve)
|
||||
return new Promise(function(resolve) {
|
||||
require(['./Home.js'], resolve)
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Returns an event handler that runs `callback` with the value of the specified DO
|
|||
```javascript
|
||||
var state = {
|
||||
value: "",
|
||||
setValue: function(v) {value = v}
|
||||
setValue: function(v) {state.value = v}
|
||||
}
|
||||
|
||||
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:
|
||||
|
||||
```javascript
|
||||
var Data = {
|
||||
var state = {
|
||||
email: "",
|
||||
setEmail: function(email) {
|
||||
Data.email = email.toLowerCase()
|
||||
state.email = email.toLowerCase()
|
||||
}
|
||||
}
|
||||
|
||||
var MyComponent = {
|
||||
view: function() {
|
||||
return m("input", {
|
||||
oninput: m.withAttr("value", Data.setEmail),
|
||||
value: Data.email
|
||||
oninput: m.withAttr("value", state.setEmail),
|
||||
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.
|
||||
|
||||
```javascript
|
||||
var Data = {
|
||||
var state = {
|
||||
url: "",
|
||||
setURL: function(url) {Data.url = url}
|
||||
setURL: function(url) {state.url = url}
|
||||
}
|
||||
|
||||
var MyComponent = {
|
||||
view: function() {
|
||||
return m("a[href='/foo']", {onclick: m.withAttr("href", Data.setURL)}, [
|
||||
m("span", Data.url)
|
||||
return m("a[href='/foo']", {onclick: m.withAttr("href", state.setURL)}, [
|
||||
m("span", state.url)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
@ -117,21 +117,21 @@ The first argument of `m.withAttr()` can be either an attribute or a property.
|
|||
|
||||
```javascript
|
||||
// reads from `select.selectedIndex` property
|
||||
var Data = {
|
||||
var state = {
|
||||
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.
|
||||
|
||||
```javascript
|
||||
// value is a boolean, because the `input.checked` property is boolean
|
||||
var Data = {
|
||||
var state = {
|
||||
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)})
|
||||
```
|
||||
|
||||
|
|
|
|||
113
mithril.js
113
mithril.js
|
|
@ -214,7 +214,7 @@ var _8 = function($window, Promise) {
|
|||
var next = then0.apply(promise0, arguments)
|
||||
next.then(complete, function(e) {
|
||||
complete()
|
||||
throw e
|
||||
if (count === 0) throw e
|
||||
})
|
||||
return finalize(next)
|
||||
}
|
||||
|
|
@ -953,6 +953,7 @@ var _16 = function(redrawService0) {
|
|||
}
|
||||
}
|
||||
m.mount = _16(redrawService)
|
||||
var Promise = PromisePolyfill
|
||||
var parseQueryString = function(string) {
|
||||
if (string === "" || string == null) return {}
|
||||
if (string.charAt(0) === "?") string = string.slice(1)
|
||||
|
|
@ -986,20 +987,18 @@ var parseQueryString = function(string) {
|
|||
var coreRouter = function($window) {
|
||||
var supportsPushState = typeof $window.history.pushState === "function"
|
||||
var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout
|
||||
var prefix1 = "#!"
|
||||
function setPrefix(value) {prefix1 = value}
|
||||
function normalize1(fragment0) {
|
||||
var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
|
||||
if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data
|
||||
return data
|
||||
}
|
||||
var asyncId
|
||||
function debounceAsync(f) {
|
||||
function debounceAsync(callback0) {
|
||||
return function() {
|
||||
if (asyncId != null) return
|
||||
asyncId = callAsync0(function() {
|
||||
asyncId = null
|
||||
f()
|
||||
callback0()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1018,15 +1017,16 @@ var coreRouter = function($window) {
|
|||
}
|
||||
return path.slice(0, pathEnd)
|
||||
}
|
||||
function getPath() {
|
||||
var type2 = prefix1.charAt(0)
|
||||
var router = {prefix: "#!"}
|
||||
router.getPath = function() {
|
||||
var type2 = router.prefix.charAt(0)
|
||||
switch (type2) {
|
||||
case "#": return normalize1("hash").slice(prefix1.length)
|
||||
case "?": return normalize1("search").slice(prefix1.length) + normalize1("hash")
|
||||
default: return normalize1("pathname").slice(prefix1.length) + normalize1("search") + normalize1("hash")
|
||||
case "#": return normalize1("hash").slice(router.prefix.length)
|
||||
case "?": return normalize1("search").slice(router.prefix.length) + 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 = {}
|
||||
path = parsePath(path, queryData, hashData)
|
||||
if (data != null) {
|
||||
|
|
@ -1041,15 +1041,15 @@ var coreRouter = function($window) {
|
|||
var hash = buildQueryString(hashData)
|
||||
if (hash) path += "#" + hash
|
||||
if (supportsPushState) {
|
||||
if (options && options.replace) $window.history.replaceState(null, null, prefix1 + path)
|
||||
else $window.history.pushState(null, null, prefix1 + path)
|
||||
$window.onpopstate(true)
|
||||
if (options && options.replace) $window.history.replaceState(null, null, router.prefix + path)
|
||||
else $window.history.pushState(null, null, router.prefix + path)
|
||||
$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() {
|
||||
var path = getPath()
|
||||
var path = router.getPath()
|
||||
var params = {}
|
||||
var pathname = parsePath(path, params, params)
|
||||
|
||||
|
|
@ -1071,72 +1071,71 @@ var coreRouter = function($window) {
|
|||
}
|
||||
|
||||
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
|
||||
else if (prefix1.charAt(0) === "#") $window.onhashchange = resolveRoute
|
||||
else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
|
||||
resolveRoute()
|
||||
}
|
||||
function link(vnode2) {
|
||||
vnode2.dom.setAttribute("href", prefix1 + vnode2.attrs.href)
|
||||
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}
|
||||
|
||||
return router
|
||||
}
|
||||
var _20 = function($window, redrawService0) {
|
||||
var routeService = coreRouter($window)
|
||||
|
||||
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) {
|
||||
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) {
|
||||
resolver = routeResolver, component = comp, attrs3 = params, currentPath = path, resolve = null
|
||||
resolver.render = routeResolver.render || identity
|
||||
render1()
|
||||
component = comp != null && typeof comp.view === "function" ? comp : "div", attrs3 = params, currentPath = path, updatePending = false
|
||||
render1 = (routeResolver.render || identity).bind(routeResolver)
|
||||
run1()
|
||||
}
|
||||
var render1 = function() {
|
||||
if (resolver != null) redrawService0.render(root, resolver.render(Vnode(component, attrs3.key, attrs3)))
|
||||
var run1 = function() {
|
||||
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) {
|
||||
if (payload.view) update({}, payload, params, path)
|
||||
else {
|
||||
if (payload.onmatch) {
|
||||
if (resolve != null) update(payload, component, params, path)
|
||||
else {
|
||||
resolve = function(resolved) {
|
||||
update(payload, resolved, params, path)
|
||||
}
|
||||
payload.onmatch(function(resolved) {
|
||||
if (resolve != null) resolve(resolved)
|
||||
}, params, path)
|
||||
}
|
||||
updatePending = true
|
||||
Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
|
||||
if (updatePending) update(payload, resolved, params, path)
|
||||
}, bail)
|
||||
}
|
||||
else update(payload, "div", params, path)
|
||||
}
|
||||
}, function() {
|
||||
routeService.setPath(defaultRoute)
|
||||
})
|
||||
redrawService0.subscribe(root, render1)
|
||||
}, bail)
|
||||
redrawService0.subscribe(root, run1)
|
||||
}
|
||||
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.prefix = routeService.setPrefix
|
||||
route.link = routeService.link
|
||||
route.prefix = function(prefix0) {routeService.prefix = prefix0}
|
||||
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
|
||||
}
|
||||
m.route = _20(window, redrawService)
|
||||
m.withAttr = function(attrName, callback0, context) {
|
||||
m.withAttr = function(attrName, callback1, context) {
|
||||
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)
|
||||
m.render = _27.render
|
||||
var _28 = coreRenderer(window)
|
||||
m.render = _28.render
|
||||
m.redraw = redrawService.redraw
|
||||
m.request = requestService.request
|
||||
m.jsonp = requestService.jsonp
|
||||
|
|
|
|||
81
mithril.min.js
vendored
81
mithril.min.js
vendored
|
|
@ -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,"\\")),
|
||||
"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]&&
|
||||
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,
|
||||
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)};
|
||||
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)}}}
|
||||
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&&
|
||||
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++;
|
||||
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+
|
||||
"["+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==
|
||||
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);
|
||||
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);
|
||||
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,
|
||||
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");
|
||||
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];
|
||||
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)&&
|
||||
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",
|
||||
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",
|
||||
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;
|
||||
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-
|
||||
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),
|
||||
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,
|
||||
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,
|
||||
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!=
|
||||
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=
|
||||
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}
|
||||
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=
|
||||
!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,
|
||||
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&&
|
||||
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
|
||||
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"===
|
||||
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,
|
||||
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=
|
||||
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==
|
||||
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],
|
||||
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,
|
||||
-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?
|
||||
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=
|
||||
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=
|
||||
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;
|
||||
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};
|
||||
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(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||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=
|
||||
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,
|
||||
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");
|
||||
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"!==
|
||||
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=
|
||||
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 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();
|
||||
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,
|
||||
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"!==
|
||||
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.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,
|
||||
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,
|
||||
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;
|
||||
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,
|
||||
"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=
|
||||
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,
|
||||
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||
|
||||
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!=
|
||||
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;
|
||||
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?(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,
|
||||
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=
|
||||
[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();
|
||||
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");
|
||||
}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):
|
||||
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=
|
||||
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"===
|
||||
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?"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)))}}
|
||||
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;
|
||||
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);
|
||||
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]):
|
||||
"";"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=
|
||||
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");
|
||||
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=
|
||||
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");
|
||||
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",
|
||||
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};
|
||||
|
|
@ -202,7 +202,10 @@ module.exports = new function init() {
|
|||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -403,6 +403,27 @@ o.spec("promise", function() {
|
|||
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) {
|
||||
var promise = Promise.reject(Promise.resolve(1))
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ module.exports = function($window, Promise) {
|
|||
var next = then.apply(promise, arguments)
|
||||
next.then(complete, function(e) {
|
||||
complete()
|
||||
throw e
|
||||
if (count === 0) throw e
|
||||
})
|
||||
return finalize(next)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
<body>
|
||||
<script src="../../module/module.js"></script>
|
||||
<script src="../../ospec/ospec.js"></script>
|
||||
<script src="../../test-utils/callAsync.js"></script>
|
||||
<script src="../../querystring/parse.js"></script>
|
||||
<script src="../../test-utils/parseURL.js"></script>
|
||||
<script src="../../test-utils/callAsync.js"></script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var xhrMock = require("../../test-utils/xhrMock")
|
||||
var Request = require("../../request/request")
|
||||
var Promise = require("../../promise/promise")
|
||||
|
|
@ -336,7 +337,7 @@ o.spec("xhr", function() {
|
|||
}
|
||||
})
|
||||
var promise = xhr("/item", {background: true}).then(function() {})
|
||||
|
||||
|
||||
setTimeout(function() {
|
||||
o(complete.callCount).equals(0)
|
||||
done()
|
||||
|
|
@ -377,5 +378,31 @@ o.spec("xhr", function() {
|
|||
o(e.message).equals("error")
|
||||
}).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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@ module.exports = function($window) {
|
|||
var supportsPushState = typeof $window.history.pushState === "function"
|
||||
var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
|
||||
|
||||
var prefix = "#!"
|
||||
function setPrefix(value) {prefix = value}
|
||||
|
||||
function normalize(fragment) {
|
||||
var data = $window.location[fragment].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
|
||||
if (fragment === "pathname" && data[0] !== "/") data = "/" + data
|
||||
|
|
@ -17,14 +14,13 @@ module.exports = function($window) {
|
|||
}
|
||||
|
||||
var asyncId
|
||||
function debounceAsync(f) {
|
||||
function debounceAsync(callback) {
|
||||
return function() {
|
||||
if (asyncId != null) return
|
||||
asyncId = callAsync(function() {
|
||||
asyncId = null
|
||||
f()
|
||||
callback()
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,16 +40,16 @@ module.exports = function($window) {
|
|||
return path.slice(0, pathEnd)
|
||||
}
|
||||
|
||||
function getPath() {
|
||||
var type = prefix.charAt(0)
|
||||
var router = {prefix: "#!"}
|
||||
router.getPath = function() {
|
||||
var type = router.prefix.charAt(0)
|
||||
switch (type) {
|
||||
case "#": return normalize("hash").slice(prefix.length)
|
||||
case "?": return normalize("search").slice(prefix.length) + normalize("hash")
|
||||
default: return normalize("pathname").slice(prefix.length) + normalize("search") + normalize("hash")
|
||||
case "#": return normalize("hash").slice(router.prefix.length)
|
||||
case "?": return normalize("search").slice(router.prefix.length) + normalize("hash")
|
||||
default: return normalize("pathname").slice(router.prefix.length) + normalize("search") + normalize("hash")
|
||||
}
|
||||
}
|
||||
|
||||
function setPath(path, data, options) {
|
||||
router.setPath = function(path, data, options) {
|
||||
var queryData = {}, hashData = {}
|
||||
path = parsePath(path, queryData, hashData)
|
||||
if (data != null) {
|
||||
|
|
@ -71,16 +67,15 @@ module.exports = function($window) {
|
|||
if (hash) path += "#" + hash
|
||||
|
||||
if (supportsPushState) {
|
||||
if (options && options.replace) $window.history.replaceState(null, null, prefix + path)
|
||||
else $window.history.pushState(null, null, prefix + path)
|
||||
$window.onpopstate(true)
|
||||
if (options && options.replace) $window.history.replaceState(null, null, router.prefix + path)
|
||||
else $window.history.pushState(null, null, router.prefix + path)
|
||||
$window.onpopstate()
|
||||
}
|
||||
else $window.location.href = prefix + path
|
||||
else $window.location.href = router.prefix + path
|
||||
}
|
||||
|
||||
function defineRoutes(routes, resolve, reject) {
|
||||
router.defineRoutes = function(routes, resolve, reject) {
|
||||
function resolveRoute() {
|
||||
var path = getPath()
|
||||
var path = router.getPath()
|
||||
var params = {}
|
||||
var pathname = parsePath(path, params, params)
|
||||
|
||||
|
|
@ -104,21 +99,9 @@ module.exports = function($window) {
|
|||
}
|
||||
|
||||
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
|
||||
else if (prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
|
||||
else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
|
||||
resolveRoute()
|
||||
}
|
||||
|
||||
function link(vnode) {
|
||||
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}
|
||||
|
||||
return router
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
<script src="../../test-utils/pushStateMock.js"></script>
|
||||
<script src="../../test-utils/domMock.js"></script>
|
||||
|
||||
<script src="../../promise/promise.js"></script>
|
||||
<script src="../../render/vnode.js"></script>
|
||||
<script src="../../render/render.js"></script>
|
||||
<script src="../../querystring/build.js"></script>
|
||||
|
|
@ -19,7 +20,6 @@
|
|||
<script src="test-defineRoutes.js"></script>
|
||||
<script src="test-getPath.js"></script>
|
||||
<script src="test-setPath.js"></script>
|
||||
<script src="test-link.js"></script>
|
||||
|
||||
<script>require("../../ospec/ospec").run()</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ o.spec("Router.defineRoutes", function() {
|
|||
o.beforeEach(function() {
|
||||
$window = pushStateMock(env)
|
||||
router = new Router($window)
|
||||
router.setPrefix(prefix)
|
||||
router.prefix = prefix
|
||||
onRouteChange = o.spy()
|
||||
onFail = o.spy()
|
||||
})
|
||||
|
|
@ -73,7 +73,7 @@ o.spec("Router.defineRoutes", function() {
|
|||
$window.location.href = "file://" + prefix + "/test"
|
||||
|
||||
router = new Router($window)
|
||||
router.setPrefix(prefix)
|
||||
router.prefix = prefix
|
||||
|
||||
router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ o.spec("Router.getPath", function() {
|
|||
o.beforeEach(function() {
|
||||
$window = pushStateMock(env)
|
||||
router = new Router($window)
|
||||
router.setPrefix(prefix)
|
||||
router.prefix = prefix
|
||||
onRouteChange = o.spy()
|
||||
onFail = o.spy()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -14,7 +14,7 @@ o.spec("Router.setPath", function() {
|
|||
o.beforeEach(function() {
|
||||
$window = pushStateMock(env)
|
||||
router = new Router($window)
|
||||
router.setPrefix(prefix)
|
||||
router.prefix = prefix
|
||||
onRouteChange = o.spy()
|
||||
onFail = o.spy()
|
||||
})
|
||||
|
|
@ -88,7 +88,7 @@ o.spec("Router.setPath", function() {
|
|||
$window.location.href = "file://" + prefix + "/test"
|
||||
|
||||
router = new Router($window)
|
||||
router.setPrefix(prefix)
|
||||
router.prefix = prefix
|
||||
|
||||
router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ var domMock = require("./domMock")
|
|||
var xhrMock = require("./xhrMock")
|
||||
|
||||
module.exports = function(env) {
|
||||
var $window = {}
|
||||
env = env || {}
|
||||
var $window = env.window = {}
|
||||
|
||||
var dom = domMock()
|
||||
var xhr = xhrMock()
|
||||
var ps = pushStateMock(env)
|
||||
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 ps) if (!$window[key]) $window[key] = ps[key]
|
||||
pushStateMock(env)
|
||||
|
||||
return $window
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ var parseURL = require("../test-utils/parseURL")
|
|||
module.exports = function(options) {
|
||||
if (options == null) options = {}
|
||||
|
||||
var $window = options.window || {}
|
||||
var protocol = options.protocol || "http:"
|
||||
var hostname = options.hostname || "localhost"
|
||||
var port = ""
|
||||
|
|
@ -32,7 +33,7 @@ module.exports = function(options) {
|
|||
}
|
||||
return isNew
|
||||
}
|
||||
|
||||
|
||||
function prefix(prefix, value) {
|
||||
if (value === "") return ""
|
||||
return (value.charAt(0) !== prefix ? prefix : "") + value
|
||||
|
|
@ -46,125 +47,125 @@ module.exports = function(options) {
|
|||
function 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) {
|
||||
throw new Error("Protocol is read-only")
|
||||
},
|
||||
set hostname(value) {
|
||||
unload()
|
||||
past.push({url: getURL(), isNew: true})
|
||||
future = []
|
||||
hostname = value
|
||||
},
|
||||
set port(value) {
|
||||
if (protocol === "file:") throw new Error("Port is read-only under `file://` protocol")
|
||||
unload()
|
||||
past.push({url: getURL(), isNew: true})
|
||||
future = []
|
||||
port = value
|
||||
},
|
||||
set pathname(value) {
|
||||
if (protocol === "file:") throw new Error("Pathname is read-only under `file://` protocol")
|
||||
unload()
|
||||
past.push({url: getURL(), isNew: true})
|
||||
future = []
|
||||
pathname = prefix("/", value)
|
||||
},
|
||||
set search(value) {
|
||||
unload()
|
||||
past.push({url: getURL(), isNew: true})
|
||||
future = []
|
||||
search = prefix("?", value)
|
||||
},
|
||||
set hash(value) {
|
||||
var oldHash = hash
|
||||
past.push({url: getURL(), isNew: false})
|
||||
future = []
|
||||
hash = prefix("#", value)
|
||||
if (oldHash != hash) hashchange()
|
||||
},
|
||||
$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 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 = []
|
||||
},
|
||||
set protocol(value) {
|
||||
throw new Error("Protocol is read-only")
|
||||
},
|
||||
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()
|
||||
}
|
||||
},
|
||||
set hostname(value) {
|
||||
unload()
|
||||
past.push({url: getURL(), isNew: true})
|
||||
future = []
|
||||
hostname = value
|
||||
},
|
||||
set port(value) {
|
||||
if (protocol === "file:") throw new Error("Port is read-only under `file://` protocol")
|
||||
unload()
|
||||
past.push({url: getURL(), isNew: true})
|
||||
future = []
|
||||
port = value
|
||||
},
|
||||
set pathname(value) {
|
||||
if (protocol === "file:") throw new Error("Pathname is read-only under `file://` protocol")
|
||||
unload()
|
||||
past.push({url: getURL(), isNew: true})
|
||||
future = []
|
||||
pathname = prefix("/", value)
|
||||
},
|
||||
set search(value) {
|
||||
unload()
|
||||
past.push({url: getURL(), isNew: true})
|
||||
future = []
|
||||
search = prefix("?", value)
|
||||
},
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,13 @@
|
|||
<script src="../../test-utils/pushStateMock.js"></script>
|
||||
<script src="../../test-utils/xhrMock.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-parseURL.js"></script>
|
||||
<script src="test-pushStateMock.js"></script>
|
||||
<script src="test-xhrMock.js"></script>
|
||||
<script src="test-domMock.js"></script>
|
||||
<script src="test-browserMock.js"></script>
|
||||
|
||||
<script>require("../../ospec/ospec").run()</script>
|
||||
</body>
|
||||
|
|
|
|||
40
test-utils/tests/test-browserMock.js
Normal file
40
test-utils/tests/test-browserMock.js
Normal 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)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue