Skip to content

Instantly share code, notes, and snippets.

@spacejack
Last active July 19, 2021 11:48
Show Gist options
  • Save spacejack/a5e85f2463c8fe10eb511b51c63b690a to your computer and use it in GitHub Desktop.
Save spacejack/a5e85f2463c8fe10eb511b51c63b690a to your computer and use it in GitHub Desktop.
Mithril "Observer" Component
/*
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