Merge pull request #1291 from pygy/debounceAsync
Router: Add debounceAsync to normalize the timing of onhashchange and onpopstate.
This commit is contained in:
commit
6a7b048064
8 changed files with 353 additions and 258 deletions
|
|
@ -2,38 +2,57 @@
|
|||
|
||||
var Vnode = require("../render/vnode")
|
||||
var coreRouter = require("../router/router")
|
||||
var autoredraw = require("../api/autoredraw")
|
||||
|
||||
module.exports = function($window, renderer, pubsub) {
|
||||
module.exports = function($window, mount) {
|
||||
var router = coreRouter($window)
|
||||
var globalId, currentComponent, currentRender, currentArgs, currentPath
|
||||
|
||||
var RouteComponent = {view: function() {
|
||||
return currentRender(Vnode(currentComponent, null, currentArgs, undefined, undefined, undefined))
|
||||
}}
|
||||
function defaultRender(vnode) {
|
||||
return vnode
|
||||
}
|
||||
var route = function(root, defaultRoute, routes) {
|
||||
var current = {path: null, component: "div", resolver: null}, currentResolutionIdentifier = null
|
||||
var replay = router.defineRoutes(routes, function(payload, args, path, route) {
|
||||
var resolutionIdentifier = currentResolutionIdentifier = {}
|
||||
function resolve(component) {
|
||||
if (resolutionIdentifier !== currentResolutionIdentifier) return
|
||||
resolutionIdentifier = null
|
||||
current.path = path, current.component = component
|
||||
renderer.render(root, payload.render(Vnode(component, null, args, undefined, undefined, undefined)))
|
||||
currentComponent = "div"
|
||||
currentRender = defaultRender
|
||||
currentArgs = null
|
||||
|
||||
mount(root, RouteComponent)
|
||||
|
||||
router.defineRoutes(routes, function(payload, args, path) {
|
||||
var resolutionIdentifier = globalId = {}
|
||||
var isResolver = typeof payload.view !== "function"
|
||||
var render = defaultRender
|
||||
|
||||
function resolve (component) {
|
||||
if (resolutionIdentifier !== globalId) return
|
||||
globalId = null
|
||||
|
||||
currentComponent = component != null ? component : isResolver ? "div" : payload
|
||||
currentRender = render
|
||||
currentArgs = args
|
||||
currentPath = path
|
||||
|
||||
root.redraw(true)
|
||||
}
|
||||
if (typeof payload.view !== "function") {
|
||||
if (typeof payload.render !== "function") payload.render = function(vnode) {return vnode}
|
||||
if (typeof payload.onmatch !== "function") payload.onmatch = function() {resolve(current.component)}
|
||||
if (path !== current.path) payload.onmatch(Vnode(payload, null, args, undefined, undefined, undefined), resolve)
|
||||
else resolve(current.component)
|
||||
var onmatch = function() {
|
||||
resolve()
|
||||
}
|
||||
else {
|
||||
renderer.render(root, Vnode(payload, null, args, undefined, undefined, undefined))
|
||||
if (isResolver) {
|
||||
if (typeof payload.render === "function") render = payload.render.bind(payload)
|
||||
if (typeof payload.onmatch === "function") onmatch = payload.onmatch
|
||||
}
|
||||
|
||||
onmatch.call(payload, resolve, args, path)
|
||||
}, function() {
|
||||
router.setPath(defaultRoute, null, {replace: true})
|
||||
})
|
||||
autoredraw(root, renderer, pubsub, replay)
|
||||
}
|
||||
route.link = router.link
|
||||
route.prefix = router.setPrefix
|
||||
route.set = router.setPath
|
||||
route.get = router.getPath
|
||||
|
||||
route.get = function() {return currentPath}
|
||||
|
||||
return route
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,14 @@ var m = require("../../render/hyperscript")
|
|||
var coreRenderer = require("../../render/render")
|
||||
var apiPubSub = require("../../api/pubsub")
|
||||
var apiRouter = require("../../api/router")
|
||||
var apiMounter = require("../../api/mount")
|
||||
|
||||
o.spec("route", 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 FRAME_BUDGET = Math.floor(1000 / 60)
|
||||
var $window, root, redraw, route
|
||||
var $window, root, redraw, mount, route
|
||||
|
||||
o.beforeEach(function() {
|
||||
$window = browserMock(env)
|
||||
|
|
@ -22,11 +23,12 @@ o.spec("route", function() {
|
|||
root = $window.document.body
|
||||
|
||||
redraw = apiPubSub()
|
||||
route = apiRouter($window, coreRenderer($window), redraw)
|
||||
mount = apiMounter(coreRenderer($window), redraw)
|
||||
route = apiRouter($window, mount)
|
||||
route.prefix(prefix)
|
||||
})
|
||||
|
||||
o("renders into `root`", function(done) {
|
||||
o("renders into `root`", function() {
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, "/", {
|
||||
"/" : {
|
||||
|
|
@ -36,11 +38,21 @@ o.spec("route", function() {
|
|||
}
|
||||
})
|
||||
|
||||
callAsync(function() {
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
|
||||
done()
|
||||
})
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
})
|
||||
|
||||
o("routed mount points can redraw synchronoulsy (#1275)", function() {
|
||||
var view = o.spy()
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, "/", {"/":{view:view}})
|
||||
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
redraw.publish(true)
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
|
||||
})
|
||||
|
||||
o("default route doesn't break back button", function(done) {
|
||||
|
|
@ -55,11 +67,11 @@ o.spec("route", function() {
|
|||
|
||||
setTimeout(function() {
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
|
||||
|
||||
$window.history.back()
|
||||
|
||||
|
||||
o($window.location.pathname).equals("/")
|
||||
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
|
@ -77,12 +89,12 @@ o.spec("route", function() {
|
|||
|
||||
function init(vnode) {
|
||||
o(vnode.attrs.foo).equals(undefined)
|
||||
|
||||
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
o("redraws when render function is executed", function(done) {
|
||||
o("redraws when render function is executed", function() {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
|
||||
|
|
@ -98,18 +110,11 @@ o.spec("route", function() {
|
|||
}
|
||||
})
|
||||
|
||||
callAsync(function() {
|
||||
o(oninit.callCount).equals(1)
|
||||
o(oninit.callCount).equals(1)
|
||||
|
||||
redraw.publish()
|
||||
redraw.publish(true)
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
o(onupdate.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("redraws on events", function(done) {
|
||||
|
|
@ -133,23 +138,21 @@ o.spec("route", function() {
|
|||
}
|
||||
})
|
||||
|
||||
callAsync(function() {
|
||||
root.firstChild.dispatchEvent(e)
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o(oninit.callCount).equals(1)
|
||||
o(oninit.callCount).equals(1)
|
||||
|
||||
o(onclick.callCount).equals(1)
|
||||
o(onclick.this).equals(root.firstChild)
|
||||
o(onclick.args[0].type).equals("click")
|
||||
o(onclick.args[0].target).equals(root.firstChild)
|
||||
o(onclick.callCount).equals(1)
|
||||
o(onclick.this).equals(root.firstChild)
|
||||
o(onclick.args[0].type).equals("click")
|
||||
o(onclick.args[0].target).equals(root.firstChild)
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
done()
|
||||
}, FRAME_BUDGET * 2)
|
||||
})
|
||||
|
||||
o("event handlers can skip redraw", function(done) {
|
||||
|
|
@ -175,21 +178,19 @@ o.spec("route", function() {
|
|||
}
|
||||
})
|
||||
|
||||
callAsync(function() {
|
||||
root.firstChild.dispatchEvent(e)
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o(oninit.callCount).equals(1)
|
||||
o(oninit.callCount).equals(1)
|
||||
|
||||
// Wrapped to ensure no redraw fired
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(0)
|
||||
// Wrapped to ensure no redraw fired
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(0)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("changes location on route.link", function(done) {
|
||||
o("changes location on route.link", function() {
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
e.initEvent("click", true, true)
|
||||
|
|
@ -211,20 +212,16 @@ o.spec("route", function() {
|
|||
}
|
||||
})
|
||||
|
||||
callAsync(function() {
|
||||
var slash = prefix[0] === "/" ? "" : "/"
|
||||
var slash = prefix[0] === "/" ? "" : "/"
|
||||
|
||||
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
|
||||
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
|
||||
|
||||
root.firstChild.dispatchEvent(e)
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test")
|
||||
|
||||
done()
|
||||
})
|
||||
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test")
|
||||
})
|
||||
|
||||
o("accepts RouteResolver", function(done) {
|
||||
|
||||
o("accepts RouteResolver", function() {
|
||||
var matchCount = 0
|
||||
var renderCount = 0
|
||||
var Component = {
|
||||
|
|
@ -232,107 +229,93 @@ o.spec("route", function() {
|
|||
return m("div")
|
||||
}
|
||||
}
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
|
||||
$window.location.href = prefix + "/abc"
|
||||
route(root, "/abc", {
|
||||
"/:id" : {
|
||||
onmatch: function(vnode, resolve) {
|
||||
onmatch: function(resolve, args, requestedPath) {
|
||||
matchCount++
|
||||
|
||||
o(vnode.attrs.id).equals("abc")
|
||||
o(route.get()).equals("/abc")
|
||||
|
||||
|
||||
o(args.id).equals("abc")
|
||||
o(requestedPath).equals("/abc")
|
||||
|
||||
resolve(Component)
|
||||
},
|
||||
render: function(vnode) {
|
||||
renderCount++
|
||||
|
||||
|
||||
o(vnode.attrs.id).equals("abc")
|
||||
|
||||
|
||||
return vnode
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(1)
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(1)
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
})
|
||||
|
||||
o("accepts RouteResolver without `render` method as payload", function(done) {
|
||||
|
||||
o("accepts RouteResolver without `render` method as payload", function() {
|
||||
var matchCount = 0
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
}
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
|
||||
$window.location.href = prefix + "/abc"
|
||||
route(root, "/abc", {
|
||||
"/:id" : {
|
||||
onmatch: function(vnode, resolve) {
|
||||
onmatch: function(resolve, args, requestedPath) {
|
||||
matchCount++
|
||||
|
||||
o(vnode.attrs.id).equals("abc")
|
||||
o(route.get()).equals("/abc")
|
||||
|
||||
|
||||
o(args.id).equals("abc")
|
||||
o(requestedPath).equals("/abc")
|
||||
|
||||
resolve(Component)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
o(matchCount).equals(1)
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
|
||||
o(matchCount).equals(1)
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
})
|
||||
|
||||
o("accepts RouteResolver without `onmatch` method as payload", function(done) {
|
||||
|
||||
o("accepts RouteResolver without `onmatch` method as payload", function() {
|
||||
var renderCount = 0
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
}
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
|
||||
$window.location.href = prefix + "/abc"
|
||||
route(root, "/abc", {
|
||||
"/:id" : {
|
||||
render: function(vnode) {
|
||||
renderCount++
|
||||
|
||||
|
||||
o(vnode.attrs.id).equals("abc")
|
||||
|
||||
|
||||
return m(Component)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
})
|
||||
|
||||
o("RouteResolver `render` does not have component semantics", function(done, timeout) {
|
||||
timeout(60)
|
||||
|
||||
|
||||
o("RouteResolver `render` does not have component semantics", function(done) {
|
||||
var renderCount = 0
|
||||
var A = {
|
||||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
}
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, "/a", {
|
||||
"/a" : {
|
||||
render: function(vnode) {
|
||||
|
|
@ -345,22 +328,20 @@ o.spec("route", function() {
|
|||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
var dom = root.firstChild
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
|
||||
route.set("/b")
|
||||
|
||||
setTimeout(function() {
|
||||
var dom = root.firstChild
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
|
||||
route.set("/b")
|
||||
|
||||
setTimeout(function() {
|
||||
o(root.firstChild).equals(dom)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
o(root.firstChild).equals(dom)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("calls onmatch and view correct number of times", function(done) {
|
||||
o("calls onmatch and view correct number of times", function() {
|
||||
var matchCount = 0
|
||||
var renderCount = 0
|
||||
var Component = {
|
||||
|
|
@ -368,11 +349,11 @@ o.spec("route", function() {
|
|||
return m("div")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, "/", {
|
||||
"/" : {
|
||||
onmatch: function(vnode, resolve) {
|
||||
onmatch: function(resolve) {
|
||||
matchCount++
|
||||
resolve(Component)
|
||||
},
|
||||
|
|
@ -383,69 +364,63 @@ o.spec("route", function() {
|
|||
},
|
||||
})
|
||||
|
||||
callAsync(function() {
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(1)
|
||||
|
||||
redraw.publish()
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(1)
|
||||
|
||||
setTimeout(function() {
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(2)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
redraw.publish(true)
|
||||
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(2)
|
||||
})
|
||||
|
||||
|
||||
o("onmatch can redirect to another route", function(done) {
|
||||
var redirected = false
|
||||
var redirected = false
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, "/a", {
|
||||
"/a" : {
|
||||
onmatch: function() {
|
||||
route.set("/b")
|
||||
}
|
||||
},
|
||||
"/b" : {
|
||||
view: function(vnode){
|
||||
redirected = true
|
||||
}
|
||||
}
|
||||
})
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, "/a", {
|
||||
"/a" : {
|
||||
onmatch: function() {
|
||||
route.set("/b")
|
||||
}
|
||||
},
|
||||
"/b" : {
|
||||
view: function(vnode){
|
||||
redirected = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
o(redirected).equals(true)
|
||||
setTimeout(function() {
|
||||
o(redirected).equals(true)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("onmatch can redirect to another route that has RouteResolver", function(done) {
|
||||
var redirected = false
|
||||
var redirected = false
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, "/a", {
|
||||
"/a" : {
|
||||
onmatch: function() {
|
||||
route.set("/b")
|
||||
}
|
||||
},
|
||||
"/b" : {
|
||||
render: function(vnode){
|
||||
redirected = true
|
||||
}
|
||||
}
|
||||
})
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, "/a", {
|
||||
"/a" : {
|
||||
onmatch: function() {
|
||||
route.set("/b")
|
||||
}
|
||||
},
|
||||
"/b" : {
|
||||
render: function(vnode){
|
||||
redirected = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
o(redirected).equals(true)
|
||||
setTimeout(function() {
|
||||
o(redirected).equals(true)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("onmatch resolution callback resolves at most once", function(done) {
|
||||
var resolveCount = 0
|
||||
var resolvedComponent
|
||||
|
|
@ -456,7 +431,7 @@ o.spec("route", function() {
|
|||
$window.location.href = prefix + "/"
|
||||
route(root, "/", {
|
||||
"/": {
|
||||
onmatch: function(vnode, resolve) {
|
||||
onmatch: function(resolve) {
|
||||
resolve(A)
|
||||
resolve(B)
|
||||
callAsync(function() {resolve(C)})
|
||||
|
|
@ -474,15 +449,114 @@ o.spec("route", function() {
|
|||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("calling route.set invalidates pending onmatch resolution", function(done, timeout) {
|
||||
timeout(100)
|
||||
|
||||
var resolved
|
||||
|
||||
o("the previous view redraws while onmatch resolution is pending (#1268)", function(done) {
|
||||
var view = o.spy()
|
||||
var onmatch = o.spy()
|
||||
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, "/", {
|
||||
"/a": {view: view},
|
||||
"/b": {onmatch: onmatch}
|
||||
})
|
||||
|
||||
o(view.callCount).equals(1)
|
||||
o(onmatch.callCount).equals(0)
|
||||
|
||||
route.set("/b")
|
||||
|
||||
setTimeout(function(){
|
||||
o(view.callCount).equals(1)
|
||||
o(onmatch.callCount).equals(1)
|
||||
|
||||
redraw.publish(true)
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
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()})
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, '/', {
|
||||
"/":{
|
||||
onmatch: onmatch,
|
||||
render: function(){return m("div")}
|
||||
}
|
||||
})
|
||||
|
||||
o(onmatch.callCount).equals(1)
|
||||
|
||||
route.set(route.get())
|
||||
|
||||
setTimeout(function() {
|
||||
o(onmatch.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("m.route.get() returns the last fully resolved route (#1276)", function(done){
|
||||
$window.location.href = prefix + "/"
|
||||
|
||||
route(root, "/", {
|
||||
"/": {view: function(){}},
|
||||
"/2": {onmatch: function(){}}
|
||||
})
|
||||
|
||||
|
||||
o(route.get()).equals("/")
|
||||
|
||||
route.set("/2")
|
||||
|
||||
setTimeout(function(){
|
||||
o(route.get()).equals("/")
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("routing with RouteResolver works more than once (#1286)", function(done, timeout){
|
||||
timeout(FRAME_BUDGET * 3)
|
||||
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, '/a', {
|
||||
'/a': {
|
||||
render: function() {
|
||||
return m("a", "a")
|
||||
}
|
||||
},
|
||||
'/b': {
|
||||
render: function() {
|
||||
return m("b", "b")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
route.set('/b')
|
||||
|
||||
setTimeout(function(){
|
||||
route.set('/a')
|
||||
|
||||
setTimeout(function(){
|
||||
o(root.firstChild.nodeName).equals("A")
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("calling route.set invalidates pending onmatch resolution", function(done, timeout) {
|
||||
timeout(50)
|
||||
|
||||
var resolved
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, "/a", {
|
||||
"/a": {
|
||||
onmatch: function(vnode, resolve) {
|
||||
onmatch: function(resolve) {
|
||||
setTimeout(resolve, 20)
|
||||
},
|
||||
render: function(vnode) {resolved = "a"}
|
||||
|
|
@ -491,15 +565,14 @@ o.spec("route", function() {
|
|||
view: function() {resolved = "b"}
|
||||
}
|
||||
})
|
||||
setTimeout(function() {
|
||||
route.set("/b")
|
||||
|
||||
setTimeout(function() {
|
||||
o(resolved).equals("b")
|
||||
|
||||
done()
|
||||
}, 30)
|
||||
}, FRAME_BUDGET)
|
||||
route.set("/b")
|
||||
|
||||
setTimeout(function() {
|
||||
o(resolved).equals("b")
|
||||
|
||||
done()
|
||||
}, 30)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -52,13 +52,13 @@ Argument | Type | Required | Description
|
|||
|
||||
##### route.get
|
||||
|
||||
Returns the current routing path, without the prefix.
|
||||
Returns the last fully resolved routing path, without the prefix. It may differ from the path displayed in the location bar while an asynchronous route is [pending resolution](#code-splitting).
|
||||
|
||||
`path = m.route.get()`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
----------------- | --------- | -------- | ---
|
||||
**returns** | String | | Returns the current path
|
||||
**returns** | String | | Returns the last fully resolved path
|
||||
|
||||
##### route.prefix
|
||||
|
||||
|
|
@ -94,14 +94,12 @@ This method also allows you to asynchronously define what component will be rend
|
|||
|
||||
`routeResolver.onmatch(vnode, resolve)`
|
||||
|
||||
Argument | Type | Description
|
||||
------------------- | --------------------- | ---
|
||||
`vnode` | `Vnode` | 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` | The [routing parameters](#routing-parameters)
|
||||
`vnode.attrs.path` | `String` | The current router path, including interpolated routing parameter values, but without the prefix. Same value as `m.route.get()`
|
||||
`vnode.attrs.route` | `String` | The matched route
|
||||
`resolve` | `Function(Component)` | Call this function with a component as the first argument to use it as the route's component
|
||||
**returns** | | Returns `undefined`
|
||||
Argument | Type | Description
|
||||
--------------- | --------------------- | ---
|
||||
`resolve` | `Function(Component)` | 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.render
|
||||
|
||||
|
|
@ -113,8 +111,6 @@ 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.attrs.path` | `String` | The current router path, including interpolated routing parameter values, but without the prefix. Same value as `m.route.get()`
|
||||
`vnode.attrs.route` | `String` | The matched route
|
||||
**returns** | `Vnode` | Returns a vnode
|
||||
|
||||
---
|
||||
|
|
@ -266,12 +262,12 @@ m.route.prefix("/my-app")
|
|||
|
||||
### Advanced component resolution
|
||||
|
||||
Instead of mapping a component to a route, you can specify a RouteResolver object. A RouteResolver object contains a `onmatch()` method and a optionally a `render()` method.
|
||||
Instead of mapping a component to a route, you can specify a RouteResolver object. A RouteResolver object contains a `onmatch()` method and/or a `render()` method.
|
||||
|
||||
```javascript
|
||||
m.route(document.body, "/", {
|
||||
"/": {
|
||||
onmatch: function(vnode, resolve) {
|
||||
onmatch: function(resolve, args, requestedPath) {
|
||||
resolve(Home)
|
||||
},
|
||||
render: function(vnode) {
|
||||
|
|
@ -329,7 +325,7 @@ var Login = {
|
|||
|
||||
m.route(document.body, "/secret", {
|
||||
"/secret": {
|
||||
onmatch: function(vnode, resolve) {
|
||||
onmatch: function(resolve) {
|
||||
if (isLoggedIn) resolve(Home)
|
||||
else m.route.set("/login")
|
||||
},
|
||||
|
|
@ -377,7 +373,7 @@ function load(file, done) {
|
|||
|
||||
m.route(document.body, "/", {
|
||||
"/": {
|
||||
onmatch: function(vnode, resolve) {
|
||||
onmatch: function(resolve) {
|
||||
load("Home.js", resolve)
|
||||
},
|
||||
},
|
||||
|
|
@ -391,7 +387,7 @@ Fortunately, there are a number of tools that facilitate the task of bundling mo
|
|||
```javascript
|
||||
m.route(document.body, "/", {
|
||||
"/": {
|
||||
onmatch: function(vnode, resolve) {
|
||||
onmatch: function(resolve) {
|
||||
// using Webpack async code splitting
|
||||
require(['./Home.js'], resolve)
|
||||
},
|
||||
|
|
|
|||
2
index.js
2
index.js
|
|
@ -10,8 +10,8 @@ var Stream = require("./stream")
|
|||
|
||||
requestService.setCompletionCallback(redrawService.publish)
|
||||
|
||||
m.route = require("./route")
|
||||
m.mount = require("./mount")
|
||||
m.route = require("./route")
|
||||
m.withAttr = require("./util/withAttr")
|
||||
m.prop = Stream
|
||||
m.render = renderService.render
|
||||
|
|
|
|||
5
route.js
5
route.js
|
|
@ -1,4 +1,3 @@
|
|||
var renderService = require("./render")
|
||||
var redrawService = require("./redraw")
|
||||
var mount = require("./mount")
|
||||
|
||||
module.exports = require("./api/router")(window, renderService, redrawService)
|
||||
module.exports = require("./api/router")(window, mount)
|
||||
|
|
@ -16,6 +16,18 @@ module.exports = function($window) {
|
|||
return data
|
||||
}
|
||||
|
||||
var asyncId
|
||||
function debounceAsync(f) {
|
||||
return function() {
|
||||
if (asyncId != null) return
|
||||
asyncId = callAsync(function() {
|
||||
asyncId = null
|
||||
f()
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function parsePath(path, queryData, hashData) {
|
||||
var queryIndex = path.indexOf("?")
|
||||
var hashIndex = path.indexOf("#")
|
||||
|
|
@ -67,7 +79,7 @@ module.exports = function($window) {
|
|||
}
|
||||
|
||||
function defineRoutes(routes, resolve, reject) {
|
||||
if (supportsPushState) $window.onpopstate = resolveRoute
|
||||
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
|
||||
else if (prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
|
||||
resolveRoute()
|
||||
|
||||
|
|
@ -76,25 +88,23 @@ module.exports = function($window) {
|
|||
var params = {}
|
||||
var pathname = parsePath(path, params, params)
|
||||
|
||||
callAsync(function() {
|
||||
for (var route in routes) {
|
||||
var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$")
|
||||
for (var route in routes) {
|
||||
var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$")
|
||||
|
||||
if (matcher.test(pathname)) {
|
||||
pathname.replace(matcher, function() {
|
||||
var keys = route.match(/:[^\/]+/g) || []
|
||||
var values = [].slice.call(arguments, 1, -2)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
|
||||
}
|
||||
resolve(routes[route], params, path, route)
|
||||
})
|
||||
return
|
||||
}
|
||||
if (matcher.test(pathname)) {
|
||||
pathname.replace(matcher, function() {
|
||||
var keys = route.match(/:[^\/]+/g) || []
|
||||
var values = [].slice.call(arguments, 1, -2)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
|
||||
}
|
||||
resolve(routes[route], params, path, route)
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
reject(path, params)
|
||||
})
|
||||
reject(path, params)
|
||||
}
|
||||
return resolveRoute
|
||||
}
|
||||
|
|
|
|||
|
|
@ -285,18 +285,14 @@ o.spec("Router.defineRoutes", function() {
|
|||
})
|
||||
})
|
||||
|
||||
o("replays", function(done) {
|
||||
o("replays", function() {
|
||||
$window.location.href = prefix + "/test"
|
||||
var replay = router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail)
|
||||
replay()
|
||||
|
||||
callAsync(function() {
|
||||
o(onRouteChange.callCount).equals(2)
|
||||
o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"])
|
||||
o(onFail.callCount).equals(0)
|
||||
|
||||
done()
|
||||
})
|
||||
o(onRouteChange.callCount).equals(2)
|
||||
o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"])
|
||||
o(onFail.callCount).equals(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -164,9 +164,11 @@ o.spec("api", function() {
|
|||
|
||||
setTimeout(function() {
|
||||
m.route.set("/b")
|
||||
o(m.route.get()).equals("/b")
|
||||
setTimeout(function() {
|
||||
o(m.route.get()).equals("/b")
|
||||
|
||||
done()
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue