From 992aa30ccc7b0678f4ff808a4c02477c1af1d64d Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 6 Dec 2016 23:29:15 -0500 Subject: [PATCH] fix m.route.link and m.route.set history replacement --- README.md | 2 +- api/router.js | 16 +++++- api/tests/test-router.js | 16 +++--- mithril.js | 56 ++++++++++---------- mithril.min.js | 82 ++++++++++++++--------------- router/router.js | 46 ++++++---------- router/tests/index.html | 1 - router/tests/test-defineRoutes.js | 4 +- router/tests/test-getPath.js | 2 +- router/tests/test-link.js | 87 ------------------------------- router/tests/test-setPath.js | 4 +- 11 files changed, 113 insertions(+), 203 deletions(-) delete mode 100644 router/tests/test-link.js diff --git a/README.md b/README.md index 4d6e53b3..11a87ed7 100644 --- a/README.md +++ b/README.md @@ -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 7.46 KB min+gzip +Despite the huge improvements in performance and modularity, the new codebase is smaller than v0.2.x, currently clocking at 7.47 KB 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 diff --git a/api/router.js b/api/router.js index be13d8a4..915739a5 100644 --- a/api/router.js +++ b/api/router.js @@ -41,11 +41,23 @@ module.exports = function($window, redrawService) { redrawService.subscribe(root, run) } route.set = function(path, data, options) { + if (resolve != null) options = {replace: true} resolve = null routeService.setPath(path, data, options) } 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 } diff --git a/api/tests/test-router.js b/api/tests/test-router.js index 1f3020b7..3de4ee54 100644 --- a/api/tests/test-router.js +++ b/api/tests/test-router.js @@ -358,7 +358,7 @@ o.spec("route", function() { }) callAsync(function() { o(oninit.callCount).equals(1) - route.set('/def') + route.set("/def") callAsync(function() { o(oninit.callCount).equals(2) done() @@ -897,12 +897,14 @@ o.spec("route", function() { }, }) - callAsync(function() { - callAsync(function() { - o(rendered).equals(false) - o(resolved).equals("b") - - done() + 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() + }) }) }) }) diff --git a/mithril.js b/mithril.js index e3c8b8e0..d4cb36b8 100644 --- a/mithril.js +++ b/mithril.js @@ -988,8 +988,6 @@ 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 @@ -1020,15 +1018,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) { @@ -1043,15 +1042,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) + 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) @@ -1073,21 +1072,11 @@ 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) @@ -1125,12 +1114,23 @@ var _20 = function($window, redrawService0) { redrawService0.subscribe(root, run1) } route.set = function(path, data, options) { + if (resolve != null) options = {replace: true} resolve = null routeService.setPath(path, data, options) } 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) diff --git a/mithril.min.js b/mithril.min.js index cf44690d..af119c16 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,41 +1,41 @@ -new function(){function r(a,c,k,d,g,l){return{tag:a,key:c,attrs:k,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===M[a]){for(var c,k,d=[],g={};c=R.exec(a);){var l=c[1],n=c[2];""===l&&""!==n?k=n:"#"===l?g.id=n:"."===l?d.push(n):"["===c[3][0]&&((l=c[6])&&(l=l.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")), -"class"===c[4]?d.push(l):g[c[4]]=l||!0)}0a.indexOf("?")?"?":"&";a+=d+b}return a}function n(a){try{return""!==a?JSON.parse(a):null}catch(F){throw Error(a);}}function A(a){return a.responseText}function m(a, -c){if("function"===typeof a)if(c instanceof Array)for(var b=0;bh.status||304===h.status)c(m(b.type,a));else{var g=Error(h.responseText),k;for(k in a)g[k]=a[k];d(g)}}catch(G){d(G)}};k&&null!=b.data?h.send(b.data):h.send()});return!0===b.background?t:u(t)},jsonp:function(b,h){var n=k();b=d(b,h);var t=new c(function(c, -d){var h=b.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+r++,k=a.document.createElement("script");a[h]=function(d){k.parentNode.removeChild(k);c(m(b.type,d));delete a[h]};k.onerror=function(){k.parentNode.removeChild(k);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;k.src=l(b.url,b.data);a.document.documentElement.appendChild(k)});return!0===b.background?t:n(t)},setCompletionCallback:function(a){h=a}}}(window, -J),Q=function(a){function c(e,f,a,b,c,d,h){for(;a=v&&u>=t;){var x=f[v],p=a[t];if(x!==p||q)if(null==x)v++;else if(null==p)t++;else if(x.key===p.key)v++,t++,l(e,x,p,b,A(f,v,d),q,g),q&&x.tag===p.tag&&m(e,n(x),d);else if(x=f[z],x!==p||q)if(null==x)z--;else if(null==p)t++;else if(x.key===p.key)l(e,x,p,b,A(f,z+1,d),q,g),(q||t=v&&u>=t;){x=f[z];p=a[u];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&&m(e,n(x),d),null!=x.dom&&(d=x.dom),z--;else{if(!E){E=f;var x=z,r={},C;for(C=0;Ca.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;bk.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(G){d(G)}};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=k&&t>=A;){var w=f[k],u=a[A];if(w!==u||q)if(null==w)k++;else if(null==u)A++;else if(w.key===u.key)k++,A++,m(e,w,u,b,n(f,k,d),q,g),q&&w.tag===u.tag&&p(e,l(w),d);else if(w=f[x],w!==u||q)if(null==w)x--;else if(null==u)A++;else if(w.key===u.key)m(e,w,u,b,n(f,x+1,d),q,g),(q||A=k&&t>=A;){w=f[x];u=a[t];if(w!==u||q)if(null==w)x--;else{if(null!= +u)if(w.key===u.key)m(e,w,u,b,n(f,x+1,d),q,g),q&&w.tag===u.tag&&p(e,l(w),d),null!=w.dom&&(d=w.dom),x--;else{if(!D){D=f;var w=x,C={},E;for(E=0;E - diff --git a/router/tests/test-defineRoutes.js b/router/tests/test-defineRoutes.js index b00530b7..51b3cc09 100644 --- a/router/tests/test-defineRoutes.js +++ b/router/tests/test-defineRoutes.js @@ -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) diff --git a/router/tests/test-getPath.js b/router/tests/test-getPath.js index 119c1713..6c5dc2b2 100644 --- a/router/tests/test-getPath.js +++ b/router/tests/test-getPath.js @@ -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() }) diff --git a/router/tests/test-link.js b/router/tests/test-link.js deleted file mode 100644 index 3d07af2e..00000000 --- a/router/tests/test-link.js +++ /dev/null @@ -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() - }) - }) - }) - }) - }) - }) -}) diff --git a/router/tests/test-setPath.js b/router/tests/test-setPath.js index 6b817cfb..c957a59a 100644 --- a/router/tests/test-setPath.js +++ b/router/tests/test-setPath.js @@ -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)