Last active
July 19, 2021 11:48
-
-
Save spacejack/a5e85f2463c8fe10eb511b51c63b690a to your computer and use it in GitHub Desktop.
Mithril "Observer" Component
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
/* | |
This 'Observer' component expects the attrs described below. | |
The vdom rendered by `view` will re-render normally within | |
Mithril's redraw cycle. | |
The vdom rendered by `render` will only re-render when the | |
data stream updates. This will not trigger global redraws. | |
`render` renders into the dom node described by `selector`, | |
otherwise defaults to the component's root node. | |
ObserverAttrs<T> { | |
data: Stream<T>, | |
selector?: string, | |
view(vnode: Vnode): Vnode, | |
render(data: T): Vnode | |
} | |
*/ | |
const observer = function({attrs: {data: dataSrc}}) { | |
// Create a dependent stream we can usubscribe to on removal | |
const data = dataSrc.map(v => v) | |
return { | |
oncreate ({dom, attrs: {render, selector}}) { | |
// Did we get a selector or should we render to root node? | |
const el = typeof selector === 'string' | |
? dom.querySelector(selector) | |
: dom | |
// Re-render only on stream updates | |
data.map(d => { | |
m.render(el, render(d)) | |
}) | |
}, | |
onremove() { | |
// Unsubscribe from stream | |
data.end(true) | |
}, | |
view(vnode) { | |
return vnode.attrs.view(vnode) | |
} | |
} | |
} | |
/** | |
* The loadingBar component hands off stream subscribe/unsubscribe | |
* management and manual render to the observer component. | |
* We declare view (outer) and render (inner) functions that | |
* the observer component will invoke. | |
*/ | |
const loading = { | |
view ({attrs: {progress}}) { | |
return m(observer, { | |
// The selector used to find the render target element | |
selector: '.bar-outer', | |
data: progress, | |
render (progress) { | |
return m('.bar', {style: `width: ${progress * 100}%`}) | |
}, | |
view() { | |
return m('.loading-block', | |
m('p', progress() < 1 ? "Loading..." : "Done!"), | |
// The target element to render the progress bar into | |
m('.bar-outer') | |
) | |
} | |
}) | |
} | |
} | |
/* Main app component */ | |
const app = { | |
view() { | |
return m('.app', | |
m('h1', "My App"), | |
m(loading, {progress}), | |
progress() >= 1 && m('p', | |
m('button', | |
{ | |
type: 'button', | |
onclick: start, | |
}, | |
"Restart" | |
) | |
) | |
) | |
} | |
} | |
// Hold progress state in a stream | |
progress = m.stream() | |
// Simulate loading with a RAF loop | |
function loadLoop() { | |
progress(progress() + 0.005) | |
if (progress() < 1) { | |
requestAnimationFrame(loadLoop) | |
} else { | |
// Do full redraw when loaded | |
m.redraw() | |
} | |
} | |
function start() { | |
progress(0) | |
requestAnimationFrame(loadLoop) | |
} | |
start() | |
// Mount the app | |
m.mount(document.body, app) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment