Skip to content

Instantly share code, notes, and snippets.

@gilbert
Last active August 18, 2016 13:44
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gilbert/a24c3d0e679de14fee06 to your computer and use it in GitHub Desktop.
Save gilbert/a24c3d0e679de14fee06 to your computer and use it in GitHub Desktop.
Back-button & forward-button compatible Redux-like state management for Mithril.js
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) )
])
}
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