Last active
August 18, 2016 13:44
-
-
Save gilbert/a24c3d0e679de14fee06 to your computer and use it in GitHub Desktop.
Back-button & forward-button compatible Redux-like state management for Mithril.js
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
var BlogComments = {} | |
BlogComments.controller = function (options) { | |
App.state.fetch('blogComments', `/api/blog-post/${ options.blog_id }/comments`) | |
} | |
BlogComments.view = function (ctrl, options) { | |
var comments = App.state.blogComments | |
return m('.blog-comments-component', [ | |
comments.map( c => m('.comment', c.content) ) | |
]) | |
} |
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
var m = require('mithril') | |
exports.mount = function (defaultRoute, App) { | |
App.history = {} | |
pushNewPageState( window.location.pathname ) | |
// | |
// Global route mod | |
// Create new state on route change. | |
// | |
var originalRoute = m.route | |
m.route = function () { | |
if ( arguments.length === 2 && typeof arguments[0] === 'string' ) { | |
handleRouteChange( arguments[0] ) | |
} | |
return originalRoute(...arguments) | |
} | |
m.route.mode = originalRoute.mode | |
m.route.param = originalRoute.param | |
m.route.buildQueryString = originalRoute.buildQueryString | |
m.route.parseQueryString = originalRoute.parseQueryString | |
window.addEventListener('popstate', function () { | |
handleRouteChange( window.location.pathname ) | |
}) | |
function handleRouteChange (route) { | |
// Save scroll position for a better navigation experience | |
App.state.scrollHeight = document.body.scrollHeight | |
App.state.scrollPos = [window.pageXOffset, window.pageYOffset] | |
if ( App.history[route] ) { | |
// TODO: Expire cache based on App.state.loadedAt | |
App.state = App.history[route] | |
} | |
else { | |
pushNewPageState( route ) | |
} | |
} | |
function pushNewPageState (route) { | |
App.state = Object.create(stateMethods) | |
App.history[route] = App.state | |
} | |
}; | |
var stateMethods = { | |
init: function (loader) { | |
if (this.loadedAt) { | |
// Update body height so we can scroll before content renders | |
document.body.style.minHeight = App.state.scrollHeight | |
m.startComputation() | |
return new Promise( (resolve, reject) => { | |
setTimeout( () => { | |
// Update scroll position to last seen | |
scrollTo(...this.scrollPos) | |
resolve() | |
m.endComputation() | |
}, 0) | |
}) | |
} | |
else { | |
return loader().then( () => { | |
this.loadedAt = Date.now() | |
this.scrollPos = [0, 0] | |
}) | |
} | |
}, | |
fetch: function (key, apiUrl) { | |
if ( this[key] ) return m.deferred.resolve( this[key] ); | |
return m.request({ method: 'GET', url: apiUrl }) | |
.then( result => { | |
this[key] = result | |
return this[key] | |
}) | |
} | |
} | |
// | |
// Helper extensions: | |
// Redraw-conscious version of Promise.resolve and Promise.reject | |
// | |
m.deferred.resolve = function (value) { | |
var deferred = m.deferred() | |
deferred.resolve(value) | |
return deferred.promise | |
} | |
m.deferred.reject = function (value) { | |
var deferred = m.deferred() | |
deferred.reject(value) | |
return deferred.promise | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment