diff --git a/mithril.js b/mithril.js index cc497e8e..f224d770 100644 --- a/mithril.js +++ b/mithril.js @@ -967,6 +967,56 @@ var parseQueryString = function(string) { return data } requestService.setCompletionCallback(redrawService.publish) +var throttle = function(callback) { + //60fps translates to 16.6ms, round it down since setTimeout requires int + var time = 16 + var last = 0, pending = null + var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout + return function(synchronous) { + var now = Date.now() + if (synchronous === true || last === 0 || now - last >= time) { + last = now + callback() + } + else if (pending === null) { + pending = timeout(function() { + pending = null + callback() + last = Date.now() + }, time - (now - last)) + } + } +} +var autoredraw = function(root, renderer, pubsub, callback) { + var run = throttle(callback) + if (renderer != null) { + renderer.setEventCallback(function(e) { + if (e.redraw !== false) pubsub.publish() + }) + } + if (pubsub != null) { + if (root.redraw) pubsub.unsubscribe(root.redraw) + pubsub.subscribe(run) + } + return root.redraw = run +} +m.mount = function(renderer, pubsub) { + return function(root, component) { + if (component === null) { + renderer.render(root, []) + pubsub.unsubscribe(root.redraw) + delete root.redraw + return + } + var run = autoredraw(root, renderer, pubsub, function() { + renderer.render( + root, + Vnode(component, undefined, undefined, undefined, undefined, undefined) + ) + }) + run() + } +}(renderService, redrawService) var coreRouter = function($window) { var supportsPushState = typeof $window.history.pushState === "function" var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout @@ -977,6 +1027,16 @@ var coreRouter = function($window) { if (fragment === "pathname" && data[0] !== "/") data = "/" + data 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("#") @@ -1022,7 +1082,7 @@ var coreRouter = function($window) { else $window.location.href = prefix + path } 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() @@ -1031,23 +1091,21 @@ var coreRouter = 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, "([^\\/]+)") + "\/?$") - 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 - } + 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 } - reject(path, params) - }) + } + reject(path, params) } return resolveRoute } @@ -1061,89 +1119,52 @@ var coreRouter = function($window) { } return {setPrefix: setPrefix, getPath: getPath, setPath: setPath, defineRoutes: defineRoutes, link: link} } -var throttle = function(callback) { - //60fps translates to 16.6ms, round it down since setTimeout requires int - var time = 16 - var last = 0, pending = null - var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout - return function(synchronous) { - var now = Date.now() - if (synchronous === true || last === 0 || now - last >= time) { - last = now - callback() - } - else if (pending === null) { - pending = timeout(function() { - pending = null - callback() - last = Date.now() - }, time - (now - last)) - } - } -} -var autoredraw = function(root, renderer, pubsub, callback) { - var run = throttle(callback) - if (renderer != null) { - renderer.setEventCallback(function(e) { - if (e.redraw !== false) pubsub.publish() - }) - } - if (pubsub != null) { - if (root.redraw) pubsub.unsubscribe(root.redraw) - pubsub.subscribe(run) - } - return root.redraw = run -} -m.route = function($window, renderer, pubsub) { +m.route = function($window, mount1) { 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 + mount1(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 -}(window, renderService, redrawService) -m.mount = function(renderer, pubsub) { - return function(root, component) { - if (component === null) { - renderer.render(root, []) - pubsub.unsubscribe(root.redraw) - delete root.redraw - return - } - var run = autoredraw(root, renderer, pubsub, function() { - renderer.render( - root, - Vnode(component, undefined, undefined, undefined, undefined, undefined) - ) - }) - run() - } -}(renderService, redrawService) +}(window, m.mount) m.withAttr = function(attrName, callback, context) { return function(e) { return callback.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName)) diff --git a/mithril.min.js b/mithril.min.js index a8f12318..1ceb0460 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,41 +1,42 @@ -new function(){function u(d,e,p,g,k,m){return{tag:d,key:e,attrs:p,children:g,text:k,dom:m,domSize:void 0,state:{},events:void 0,instance:void 0,skip:!1}}function x(d){if(null==d||"string"!==typeof d&&null==d.view)throw Error("The selector must be either a string or a component.");if("string"===typeof d&&void 0===H[d]){for(var e,p,g=[],k={};e=P.exec(d);){var m=e[1],n=e[2];""===m&&""!==n?p=n:"#"===m?k.id=n:"."===m?g.push(n):"["===e[3][0]&&((m=e[6])&&(m=m.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")), -k[e[4]]=m||!0)}0=r&&z>=B;){var v=a[r],w=f[B];if(v===w)r++,B++;else if(null!=v&&null!=w&&v.key===w.key)r++,B++,m(b,v,w,d,l(a,r,h),q,g),q&&v.tag===w.tag&&t(b,n(v),h);else if(v=a[k],v===w)k--,B++;else if(null!=v&&null!=w&&v.key===w.key)m(b,v,w,d,l(a,k+1,h),q,g),B=r&&z>=B;){v=a[k];w=f[z];if(v===w)k--;else if(null!=v&&null!=w&&v.key===w.key)m(b,v,w,d,l(a,k+1,h),q,g),q&&v.tag===w.tag&&t(b,n(v),h),null!=v.dom&&(h=v.dom),k--;else{if(!A){A=a;var v=k,u={},y;for(y=0;yc.indexOf("?")?"?":"&";c+=h+e}return c}function k(c){try{return""!==c?JSON.parse(c):null}catch(d){throw Error(c); -}}function m(c){return c.responseText}function n(c,d){if("function"===typeof c)if(d instanceof Array)for(var e=0;eh.status)l(n(c.type,d));else{var e=Error(h.responseText),g;for(g in d)e[g]=d[g];l.error(e)}}catch(k){l.error(k)}"function"===typeof t&&t()}};A?h.send(c.data):h.send();return l},jsonp:function(c){var k=e();void 0!==c.initialValue&&k(c.initialValue);var m=c.callbackName||"_mithril_"+Math.round(1E16*Math.random())+ -"_"+l++,h=d.document.createElement("script");d[m]=function(e){h.parentNode.removeChild(h);k(n(c.type,e));"function"===typeof t&&t();delete d[m]};h.onerror=function(){h.parentNode.removeChild(h);k.error(Error("JSONP request failed"));"function"===typeof t&&t();delete d[m]};null==c.data&&(c.data={});c.url=p(c.url,c.data);c.data[c.callbackKey||"callback"]=m;h.src=g(c.url,c.data);d.document.documentElement.appendChild(h);return k},setCompletionCallback:function(c){t=c}}}(window,N),G=function(){var d= -[];return{subscribe:d.push.bind(d),unsubscribe:function(e){e=d.indexOf(e);-1=v&&A>=E;){var r=a[v],u=e[E];if(r===u)v++,E++;else if(null!=r&&null!=u&&r.key===u.key)v++,E++,l(c,r,u,b,w(a,v,g),p,h),p&&r.tag===u.tag&&q(c,m(r),g);else if(r=a[k],r===u)k--,E++;else if(null!=r&&null!=u&&r.key===u.key)l(c,r,u,b,w(a,k+1,g),p,h),E=v&&A>=E;){r=a[k];u=e[A];if(r===u)k--;else if(null!=r&&null!=u&&r.key===u.key)l(c,r,u,b,w(a,k+1,g),p,h),p&&r.tag===u.tag&&q(c,m(r),g),null!=r.dom&&(g=r.dom),k--;else{if(!t){t=a;var r=k,C={},z;for(z=0;zb.indexOf("?")?"?":"&";b+=h+f}return b}function k(b){try{return""!==b?JSON.parse(b):null}catch(g){throw Error(b); +}}function l(b){return b.responseText}function m(b,g){if("function"===typeof b)if(g instanceof Array)for(var f=0;ft.status)g(m(d.type,b));else{var f=Error(t.responseText),h;for(h in b)f[h]=b[h];g.error(f)}}catch(k){g.error(k)}"function"===typeof q&&q()}};A?t.send(d.data):t.send();return g},jsonp:function(d){var g=f();void 0!==d.initialValue&&g(d.initialValue);var k=d.callbackName||"_mithril_"+Math.round(1E16*Math.random())+ +"_"+w++,t=b.document.createElement("script");b[k]=function(f){t.parentNode.removeChild(t);g(m(d.type,f));"function"===typeof q&&q();delete b[k]};t.onerror=function(){t.parentNode.removeChild(t);g.error(Error("JSONP request failed"));"function"===typeof q&&q();delete b[k]};null==d.data&&(d.data={});d.url=n(d.url,d.data);d.data[d.callbackKey||"callback"]=k;t.src=h(d.url,d.data);b.document.documentElement.appendChild(t);return g},setCompletionCallback:function(b){q=b}}}(window,O),L=function(){var b= +[];return{subscribe:b.push.bind(b),unsubscribe:function(f){f=b.indexOf(f);-1