/* 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 { data: Stream, 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)