Deduplicate m.route and m.redraw logic (#2453)
- Remove appropriate route change subcriptions when a root is removed via `m.mount(root, null)`. - Don't pollute `onpopstate` and friends - use standard event listeners instead. - Simplify and streamline subscriptions, in preparation of adding a `remove` parameter to `m.mount`. - Change the redraw internals to redraw immediately, with ability to cancel via returning a sentinel. - Change `"bleeding-edge"` for `m.version` in `next` to instead just be the latest `m.version`. (If you're using `next`, you should know what you're in for.) - Update tests to be aware of these changes. (Some were failing for subtle reasons.) - Drive-by: remove some uses of `string.charAt(n)` and use `string[n]` instead.
This commit is contained in:
parent
6c562d2b9b
commit
90bcff0fa7
18 changed files with 397 additions and 192 deletions
14
api/mount.js
14
api/mount.js
|
|
@ -5,17 +5,11 @@ var Vnode = require("../render/vnode")
|
|||
module.exports = function(redrawService) {
|
||||
return function(root, component) {
|
||||
if (component === null) {
|
||||
redrawService.render(root, [])
|
||||
redrawService.unsubscribe(root)
|
||||
return
|
||||
} else if (component.view == null && typeof component !== "function") {
|
||||
throw new Error("m.mount(element, component) expects a component, not a vnode")
|
||||
} else {
|
||||
redrawService.subscribe(root, function() { return Vnode(component) })
|
||||
}
|
||||
|
||||
if (component.view == null && typeof component !== "function") throw new Error("m.mount(element, component) expects a component, not a vnode")
|
||||
|
||||
var run = function() {
|
||||
redrawService.render(root, Vnode(component))
|
||||
}
|
||||
redrawService.subscribe(root, run)
|
||||
run()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,24 +14,40 @@ function throttle(callback) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = function($window, throttleMock) {
|
||||
var renderService = coreRenderer($window)
|
||||
var callbacks = []
|
||||
var subscriptions = []
|
||||
var rendering = false
|
||||
|
||||
function subscribe(key, callback) {
|
||||
function run(sub) {
|
||||
var vnode = sub.c(sub)
|
||||
if (vnode !== sub) renderService.render(sub.k, vnode)
|
||||
}
|
||||
function subscribe(key, callback, onremove) {
|
||||
var sub = {k: key, c: callback, r: onremove}
|
||||
unsubscribe(key)
|
||||
callbacks.push(key, callback)
|
||||
subscriptions.push(sub)
|
||||
var vnode = sub.c(sub)
|
||||
if (vnode !== sub) renderService.render(sub.k, vnode)
|
||||
}
|
||||
function unsubscribe(key) {
|
||||
var index = callbacks.indexOf(key)
|
||||
if (index > -1) callbacks.splice(index, 2)
|
||||
for (var i = 0; i < subscriptions.length; i++) {
|
||||
var sub = subscriptions[i]
|
||||
if (sub.k === key) {
|
||||
subscriptions.splice(i, 1)
|
||||
renderService.render(sub.k, [])
|
||||
if (typeof sub.r === "function") sub.r()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
function sync() {
|
||||
if (rendering) throw new Error("Nested m.redraw.sync() call")
|
||||
rendering = true
|
||||
for (var i = 1; i < callbacks.length; i+=2) try {callbacks[i]()} catch (e) {if (typeof console !== "undefined") console.error(e)}
|
||||
for (var i = 0; i < subscriptions.length; i++) {
|
||||
try { run(subscriptions[i]) }
|
||||
catch (e) { if (typeof console !== "undefined") console.error(e) }
|
||||
}
|
||||
rendering = false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,32 +4,38 @@ var Vnode = require("../render/vnode")
|
|||
var Promise = require("../promise/promise")
|
||||
var coreRouter = require("../router/router")
|
||||
|
||||
var sentinel = {}
|
||||
|
||||
module.exports = function($window, redrawService) {
|
||||
var routeService = coreRouter($window)
|
||||
|
||||
var identity = function(v) {return v}
|
||||
var render, component, attrs, currentPath, lastUpdate
|
||||
var currentResolver = sentinel, component, attrs, currentPath, lastUpdate
|
||||
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")
|
||||
function run() {
|
||||
if (render != null) redrawService.render(root, render(Vnode(component, attrs.key, attrs)))
|
||||
}
|
||||
var redraw = function() {
|
||||
run()
|
||||
redraw = redrawService.redraw
|
||||
}
|
||||
redrawService.subscribe(root, run)
|
||||
var init = false
|
||||
var bail = function(path) {
|
||||
if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true})
|
||||
else throw new Error("Could not resolve default route " + defaultRoute)
|
||||
}
|
||||
function run() {
|
||||
init = true
|
||||
if (sentinel !== currentResolver) {
|
||||
var vnode = Vnode(component, attrs.key, attrs)
|
||||
if (currentResolver) vnode = currentResolver.render(vnode)
|
||||
return vnode
|
||||
}
|
||||
}
|
||||
routeService.defineRoutes(routes, function(payload, params, path, route) {
|
||||
var update = lastUpdate = function(routeResolver, comp) {
|
||||
if (update !== lastUpdate) return
|
||||
component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div"
|
||||
attrs = params, currentPath = path, lastUpdate = null
|
||||
render = (routeResolver.render || identity).bind(routeResolver)
|
||||
redraw()
|
||||
currentResolver = routeResolver.render ? routeResolver : null
|
||||
if (init) redrawService.redraw()
|
||||
else {
|
||||
init = true
|
||||
redrawService.redraw.sync()
|
||||
}
|
||||
}
|
||||
if (payload.view || typeof payload === "function") update({}, payload)
|
||||
else {
|
||||
|
|
@ -40,7 +46,12 @@ module.exports = function($window, redrawService) {
|
|||
}
|
||||
else update(payload, "div")
|
||||
}
|
||||
}, bail, defaultRoute)
|
||||
}, bail, defaultRoute, function (unsubscribe) {
|
||||
redrawService.subscribe(root, function(sub) {
|
||||
sub.c = run
|
||||
return sub
|
||||
}, unsubscribe)
|
||||
})
|
||||
}
|
||||
route.set = function(path, data, options) {
|
||||
if (lastUpdate != null) {
|
||||
|
|
|
|||
|
|
@ -38,15 +38,15 @@ o.spec("redrawService", function() {
|
|||
|
||||
redrawService.subscribe(root, spy)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.callCount).equals(2)
|
||||
})
|
||||
|
||||
o("should run a single renderer entry", function(done) {
|
||||
|
|
@ -54,15 +54,15 @@ o.spec("redrawService", function() {
|
|||
|
||||
redrawService.subscribe(root, spy)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
o(spy.callCount).equals(1)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
|
|
@ -82,57 +82,67 @@ o.spec("redrawService", function() {
|
|||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
o(spy3.callCount).equals(0)
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
o(spy3.callCount).equals(0)
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
|
||||
setTimeout(function() {
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
o(spy1.callCount).equals(2)
|
||||
o(spy2.callCount).equals(2)
|
||||
o(spy3.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("should stop running after unsubscribe", function(done) {
|
||||
var spy = o.spy(function() {
|
||||
throw new Error("This shouldn't have been called")
|
||||
})
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
redrawService.unsubscribe(root, spy)
|
||||
o(spy.callCount).equals(1)
|
||||
redrawService.unsubscribe(root)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
o(spy.callCount).equals(1)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(0)
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("should invoke remove callback on unsubscribe", function() {
|
||||
var spy = o.spy()
|
||||
var onremove = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy, onremove)
|
||||
o(spy.callCount).equals(1)
|
||||
redrawService.unsubscribe(root)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(onremove.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("should stop running after unsubscribe, even if it occurs after redraw is requested", function(done) {
|
||||
var spy = o.spy(function() {
|
||||
throw new Error("This shouldn't have been called")
|
||||
})
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
redrawService.unsubscribe(root, spy)
|
||||
redrawService.unsubscribe(root)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
o(spy.callCount).equals(1)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(0)
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
|
|
@ -142,12 +152,13 @@ o.spec("redrawService", function() {
|
|||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
redrawService.unsubscribe(null)
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
redrawService.unsubscribe(null)
|
||||
redrawService.redraw()
|
||||
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
|
|
@ -165,12 +176,6 @@ o.spec("redrawService", function() {
|
|||
redrawService.subscribe(el2, spy2)
|
||||
redrawService.subscribe(el3, spy3)
|
||||
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
o(spy3.callCount).equals(0)
|
||||
|
||||
redrawService.redraw.sync()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
|
|
@ -180,5 +185,11 @@ o.spec("redrawService", function() {
|
|||
o(spy1.callCount).equals(2)
|
||||
o(spy2.callCount).equals(2)
|
||||
o(spy3.callCount).equals(2)
|
||||
|
||||
redrawService.redraw.sync()
|
||||
|
||||
o(spy1.callCount).equals(3)
|
||||
o(spy2.callCount).equals(3)
|
||||
o(spy3.callCount).equals(3)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -111,6 +111,25 @@ o.spec("route", function() {
|
|||
o(view.callCount).equals(2)
|
||||
})
|
||||
|
||||
o("subscribes correctly and removes when unmounted", function() {
|
||||
$window.location.href = prefix + "/"
|
||||
|
||||
route(root, "/", {
|
||||
"/" : {
|
||||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
|
||||
// unsubscribe as if via `m.mount(root)`
|
||||
redrawService.unsubscribe(root)
|
||||
|
||||
o(root.childNodes.length).equals(0)
|
||||
})
|
||||
|
||||
o("default route doesn't break back button", function(done) {
|
||||
$window.location.href = "http://old.com"
|
||||
$window.location.href = "http://new.com"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue