Last active
July 8, 2018 09:07
-
-
Save dead-claudia/90d6107b78d532307d64375e0675a935 to your computer and use it in GitHub Desktop.
Mithril router concept
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// mithril/route.js | |
"use strict" | |
var redrawService = require("./redraw") | |
var register = require("./router/register")(window) | |
module.exports = require("./router/router")(redrawService, register) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// mithril/router/register.js | |
"use strict" | |
module.exports = function($window) { | |
var supportsPushState = $window.history.pushState === "function" | |
return function(callback, prefixType) { | |
var skip | |
function resolveRoute() { | |
var path = "" | |
if (prefixType !== "hash") { | |
if (prefixType !== "query") path = $window.location.pathname | |
path += $window.location.search | |
} | |
path += $window.location.hash | |
var redirecting = path === skip | |
if (redirecting) skip = void 0 | |
return callback( | |
path.replace( | |
/(?:%[a-f89][a-f0-9])+/gim, | |
$window.decodeURIComponent | |
), | |
supportsPushState ? $window.history.state : void 0, | |
redirecting | |
) | |
} | |
if (supportsPushState) $window.onpopstate = resolveRoute | |
else if (prefixType === "hash") $window.onhashchange = resolveRoute | |
resolveRoute() | |
return { | |
decode: $window.decodeURIComponent, | |
supportsState: supportsPushState, | |
unregister: function() { | |
if (supportsPushState) $window.onpopstate = void 0 | |
else if (prefixType === "hash") $window.onhashchange = void 0 | |
}, | |
set: function(path, options, redirecting) { | |
if (!supportsPushState) { | |
if (path == null) { | |
$window.onhashchange = void 0 | |
} else { | |
if (redirecting) skip = path | |
$window.location.href = path | |
} | |
} else if (path == null) { | |
$window.onpopstate = void 0 | |
} else { | |
resolveRoute() | |
// Always replace when redirecting | |
if (redirecting) skip = path | |
if (redirecting || options.replace) { | |
$window.history.replaceState( | |
options.state, options.title, path | |
) | |
} else { | |
$window.history.pushState( | |
options.state, options.title, path | |
) | |
} | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// mithril/router/router.js | |
"use strict" | |
var Promise = require("../promise/promise") | |
var m = require("../render/hyperscript") | |
var buildQueryString = require("../querystring/build") | |
var parseQueryString = require("../querystring/parse") | |
module.exports = function createRouter(redraw, register) { | |
var backend, path, params, prefix, routes, resolve, layout | |
var current, initialRoute, initialExec, bail | |
var hasOwn = {}.hasOwnProperty | |
var loading = false | |
var assign = Object.assign || function(target, source) { | |
for (var key in source) { | |
if (hasOwn.call(source, key)) target[key] = source[key] | |
} | |
} | |
function reset() { | |
if (backend != null) backend.unregister() | |
backend = path = params = prefix = routes = resolve = void 0 | |
current = initialRoute = initialExec = bail = void 0 | |
} | |
function decodeExec(exec) { | |
exec[0] = void 0 | |
for (var i = 1; i < exec.length; i++) exec[i] = backend.decode(exec[i]) | |
} | |
function resolveRoute(depth, current, options) { | |
var ignore = false | |
if (depth === 20) { | |
loading = false | |
throw new Error("Too many redirects!") | |
} | |
function render() { | |
if (ignore) return | |
if (depth !== 0) { | |
if (options.params) params = options.params | |
path = current | |
options.replace = true | |
backend.set(resolvePath(current, options), options, true) | |
} | |
loading = false | |
redraw() | |
} | |
function next(result) { | |
if (ignore) return | |
return result == null ? render() : resolveRoute( | |
depth + 1, result[0], | |
result[1] || {params: options.params} | |
) | |
} | |
var result = resolve(path, options.params) | |
if (result != null && typeof result.then === "function") { | |
Promise.resolve(result).then(next, render) | |
bail = function () { ignore = true } | |
} else { | |
bail = undefined | |
return next(result) | |
} | |
} | |
function update(route, exec, redirecting) { | |
for (var i = 1; i < exec.length; i++) params[route.keys[i]] = exec[i] | |
current = route | |
if (redirecting) return | |
loading = resolve != null | |
try { | |
if (loading) resolveRoute(0, path, {params: params}) | |
} finally { | |
if (!loading) redraw() | |
} | |
} | |
function callback(newPath, state, redirecting) { | |
var newParams = Object.create(null) | |
var queryIndex = newPath.indexOf("?", prefix.length) | |
var hashIndex = newPath.indexOf("#", prefix.length) | |
if (queryIndex >= 0) { | |
assign(newParams, parseQueryString(hashIndex >= 0 | |
? newPath.slice(queryIndex + 1, hashIndex) | |
: newPath.slice(queryIndex + 1) | |
)) | |
} | |
if (hashIndex >= 0) { | |
assign(newParams, parseQueryString(newPath.slice(hashIndex + 1))) | |
} | |
if (state != null) assign(newParams, state) | |
newPath = queryIndex >= 0 | |
? newPath.slice(prefix.length, queryIndex) | |
: newPath.slice(prefix.length) | |
if (newPath[0] !== "/") newPath = "/" + newPath | |
path = newPath | |
params = newParams | |
for (var i = 0; i < routes.length; i++) { | |
var exec = routes[i].regexp.exec(path) | |
if (exec != null) { | |
decodeExec(exec) | |
return update(routes[i], exec, redirecting) | |
} | |
} | |
return update(initialRoute, initialExec, redirecting) | |
} | |
function render(newParams) { | |
return (0, current.route)(newParams != null ? newParams : params) | |
} | |
function resolvePath(path, options) { | |
// Search for a query within the final hash if present, or the raw | |
// URL otherwise. | |
return prefix + path + | |
(path.indexOf("?", path.indexOf("#", prefix.length) + 1) < 0 | |
? "?" : "&") + | |
buildQueryString(options.params) | |
} | |
return { | |
init: function(options) { | |
// For good measure. | |
reset() | |
resolve = options.resolve | |
prefix = options.prefix || "#!" | |
layout = options.layout | |
routes = Object.keys(options.routes).map(function(pattern) { | |
return { | |
regexp: new RegExp( | |
"^" + | |
pattern | |
.replace(/:[^\/]+?\.{3}/g, "(.*?)") | |
.replace(/:[^\/]+/g, "([^\\/]+)") + | |
"\/?$" | |
), | |
callback: options.routes[pattern], | |
// Start with padding, so it's easier to loop them when | |
// adding the exec result to the parameters. | |
keys: [void 0].concat((pattern.match(/:[^\/]+/g) || []) | |
.map(function(key) { | |
return key.replace(/:|\./g, "") | |
}) | |
), | |
} | |
}) | |
for (var i = 0; i < routes.length; i++) { | |
initialExec = routes[i].regexp.exec(options.initial) | |
if (initialExec != null) { | |
initialRoute = routes[i] | |
backend = register(callback, | |
prefix[0] === "#" ? "hash" : | |
prefix[0] === "?" ? "query" : | |
"path" | |
) | |
decodeExec(initialExec) | |
return { | |
view: function() { | |
if (layout == null) { | |
if (loading) return void 0 | |
return (0, current.route)(params) | |
} | |
return layout( | |
loading ? void 0 : render, | |
path, params | |
) | |
}, | |
onremove: reset, | |
} | |
} | |
} | |
reset() | |
throw new Error( | |
"Could not resolve default route " + options.initial | |
) | |
}, | |
set: function(newPath, options) { | |
// Override existing routing when we go to a new route. | |
if (bail != null) { bail(); bail = void 0 } | |
if (options == null) options = {} | |
backend.set(resolvePath(newPath, options), options, false) | |
}, | |
link: function() { | |
var options, href | |
var onclick = function(e) { | |
if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) { | |
return | |
} | |
e.preventDefault() | |
e.redraw = false | |
backend.set(href, options || {}, false) | |
} | |
return { | |
view: function(vnode) { | |
options = vnode.attrs | |
href = resolvePath(vnode.attrs.to, vnode.attrs) | |
var attrs = {href: href, onclick: onclick} | |
if (options.attrs) { | |
assign(attrs, options.attrs) | |
if ("tag" in attrs) attrs.tag = undefined | |
if ("attrs" in attrs) attrs.attrs = undefined | |
} | |
return m(vnode.attrs.tag || "a", attrs, vnode.children) | |
}, | |
} | |
}, | |
create: function(newRedraw, newRegister) { | |
return createRouter(newRedraw || redraw, newRegister || register) | |
}, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment