Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deservicify core #2458

Merged
merged 7 commits into from
Jul 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions api/mount-redraw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use strict"

var Vnode = require("../render/vnode")

module.exports = function(render, schedule, console) {
var subscriptions = []
var rendering = false
var pending = false

function sync() {
if (rendering) throw new Error("Nested m.redraw.sync() call")
rendering = true
for (var i = 0; i < subscriptions.length; i += 2) {
try { render(subscriptions[i], Vnode(subscriptions[i + 1]), redraw) }
catch (e) { console.error(e) }
}
rendering = false
}

function redraw() {
if (!pending) {
pending = true
schedule(function() {
pending = false
sync()
})
}
}

redraw.sync = sync

function mount(root, component) {
if (component != null && component.view == null && typeof component !== "function") {
throw new TypeError("m.mount(element, component) expects a component, not a vnode")
}

var index = subscriptions.indexOf(root)
if (index >= 0) {
subscriptions.splice(index, 2)
render(root, [], redraw)
}

if (component != null) {
subscriptions.push(root, component)
render(root, Vnode(component), redraw)
}
}

return {mount: mount, redraw: redraw}
}
15 changes: 0 additions & 15 deletions api/mount.js

This file was deleted.

58 changes: 0 additions & 58 deletions api/redraw.js

This file was deleted.

178 changes: 138 additions & 40 deletions api/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,153 @@

var Vnode = require("../render/vnode")
var Promise = require("../promise/promise")
var coreRouter = require("../router/router")

var buildPathname = require("../pathname/build")
var parsePathname = require("../pathname/parse")
var compileTemplate = require("../pathname/compileTemplate")
var assign = require("../pathname/assign")

var sentinel = {}

module.exports = function($window, redrawService) {
var routeService = coreRouter($window)
module.exports = function($window, mountRedraw) {
var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
var supportsPushState = typeof $window.history.pushState === "function"
var routePrefix = "#!"
var fireAsync

function setPath(path, data, options) {
path = buildPathname(path, data)
if (fireAsync != null) {
fireAsync()
var state = options ? options.state : null
var title = options ? options.title : null
if (options && options.replace) $window.history.replaceState(state, title, routePrefix + path)
else $window.history.pushState(state, title, routePrefix + path)
}
else {
$window.location.href = routePrefix + path
}
}

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")
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
// 0 = start
// 1 = init
// 2 = ready
var state = 0

var compiled = Object.keys(routes).map(function(route) {
if (route[0] !== "/") throw new SyntaxError("Routes must start with a `/`")
if ((/:([^\/\.-]+)(\.{3})?:/).test(route)) {
throw new SyntaxError("Route parameter names must be separated with either `/`, `.`, or `-`")
}
return {
route: route,
component: routes[route],
check: compileTemplate(route),
}
})
var onremove, asyncId

fireAsync = null

if (defaultRoute != null) {
var defaultData = parsePathname(defaultRoute)

if (!compiled.some(function (i) { return i.check(defaultData) })) {
throw new ReferenceError("Default route doesn't match any known routes")
}
}
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
currentResolver = routeResolver.render ? routeResolver : null
if (init) redrawService.redraw()
else {
init = true
redrawService.redraw.sync()

function resolveRoute() {
// Consider the pathname holistically. The prefix might even be invalid,
// but that's not our problem.
var prefix = $window.location.hash
if (routePrefix[0] !== "#") {
prefix = $window.location.search + prefix
if (routePrefix[0] !== "?") {
prefix = $window.location.pathname + prefix
if (prefix[0] !== "/") prefix = "/" + prefix
}
}
if (payload.view || typeof payload === "function") update({}, payload)
else {
if (payload.onmatch) {
Promise.resolve(payload.onmatch(params, path, route)).then(function(resolved) {
update(payload, resolved)
}, function () { bail(path) })
// This seemingly useless `.concat()` speeds up the tests quite a bit,
// since the representation is consistently a relatively poorly
// optimized cons string.
var path = prefix.concat()
.replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
.slice(routePrefix.length)
var data = parsePathname(path)

assign(data.params, $window.history.state)

for (var i = 0; i < compiled.length; i++) {
if (compiled[i].check(data)) {
var payload = compiled[i].component
var route = compiled[i].route
var update = lastUpdate = function(routeResolver, comp) {
if (update !== lastUpdate) return
component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div"
attrs = data.params, currentPath = path, lastUpdate = null
currentResolver = routeResolver.render ? routeResolver : null
if (state === 2) mountRedraw.redraw()
else {
state = 2
mountRedraw.redraw.sync()
}
}
if (payload.view || typeof payload === "function") update({}, payload)
else {
if (payload.onmatch) {
Promise.resolve(payload.onmatch(data.params, path, route)).then(function(resolved) {
update(payload, resolved)
}, function () {
if (path === defaultRoute) throw new Error("Could not resolve default route " + defaultRoute)
setPath(defaultRoute, null, {replace: true})
})
}
else update(payload, "div")
}
return
}
else update(payload, "div")
}
}, bail, defaultRoute, function (unsubscribe) {
redrawService.subscribe(root, function(sub) {
sub.c = run
return sub
}, unsubscribe)

if (path === defaultRoute) throw new Error("Could not resolve default route " + defaultRoute)
setPath(defaultRoute, null, {replace: true})
}

if (supportsPushState) {
onremove = function() {
$window.removeEventListener("popstate", fireAsync, false)
}
$window.addEventListener("popstate", fireAsync = function() {
if (asyncId) return
asyncId = callAsync(function() {
asyncId = null
resolveRoute()
})
}, false)
} else if (routePrefix[0] === "#") {
onremove = function() {
$window.removeEventListener("hashchange", resolveRoute, false)
}
$window.addEventListener("hashchange", resolveRoute, false)
}

return mountRedraw.mount(root, {
onbeforeupdate: function() {
state = state ? 2 : 1
return !(!state || sentinel === currentResolver)
},
oncreate: resolveRoute,
onremove: onremove,
view: function() {
if (!state || sentinel === currentResolver) return
// Wrap in a fragment to preserve existing key semantics
var vnode = [Vnode(component, attrs.key, attrs)]
if (currentResolver) vnode = currentResolver.render(vnode[0])
return vnode
},
})
}
route.set = function(path, data, options) {
Expand All @@ -59,18 +157,18 @@ module.exports = function($window, redrawService) {
options.replace = true
}
lastUpdate = null
routeService.setPath(path, data, options)
setPath(path, data, options)
}
route.get = function() {return currentPath}
route.prefix = function(prefix) {routeService.prefix = prefix}
route.prefix = function(prefix) {routePrefix = prefix}
var link = function(options, vnode) {
vnode.dom.setAttribute("href", routeService.prefix + vnode.attrs.href)
vnode.dom.setAttribute("href", routePrefix + 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)
if (href.indexOf(routePrefix) === 0) href = href.slice(routePrefix.length)
route.set(href, undefined, options)
}
}
Expand Down
8 changes: 3 additions & 5 deletions api/tests/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,11 @@
<script src="../../querystring/build.js"></script>
<script src="../../querystring/parse.js"></script>
<script src="../../request/request.js"></script>
<script src="../../router/router.js"></script>
<script src="../../api/redraw.js"></script>
<script src="../../api/mount.js"></script>
<script src="../../api/mount-redraw.js"></script>
<script src="../../api/router.js"></script>
<script src="./test-redraw.js"></script>
<script src="./test-mount.js"></script>
<script src="./test-mountRedraw.js"></script>
<script src="./test-router.js"></script>
<script src="./test-routerGetSet.js"></script>

<script>require("../../ospec/ospec").run()</script>
</body>
Expand Down
Loading