Skip to content

Instantly share code, notes, and snippets.

@barneycarroll
Last active August 2, 2019 21:10
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save barneycarroll/a16a46a38f1836a0eb889a6e9e66a339 to your computer and use it in GitHub Desktop.
Save barneycarroll/a16a46a38f1836a0eb889a6e9e66a339 to your computer and use it in GitHub Desktop.
Functor Mithril!
import m from './functor-mithril'
function Async(){
let cache
return ({content, setup, teardown}, dom) => {
if(content && !cache && teardown)
dom(node => {
void node.clientHeight
setup(node)
})
else if(!content && cache && setup){
dom(node => {
teardown(node).then(() => {
cache = undefined
m.redraw()
})
})
return cache
}
return cache = content
}
}
m.mount(document.body, () => {
let show
return () => [
m('h1', 'Welcome to Functor Mithril!'),
m('button', {
onclick: () => show = !show,
}, 'Toggle'),
m(Async, {
content: show &&
m('p[style=opacity:0;transition:1s]', 'Hi!'),
setup: dom => {
dom.style.opacity = 1
},
teardown: dom => new Promise(done => {
dom.addEventListener('transitionend', done)
dom.style.opacity = 0
})
})
]
})

Functor Mithril!

Functor Mithril drastically simplifies Mithril's component API surface. A component now consists of a function, executed on initialisation, which returns another, which is executed every loop, and whose return value is virtual DOM. Both functions receive the first argument supplied via hyperscript, and a function which will execute post draw loop and is supplied with the underlying DOM.

function setup(initialInput, setup){
  setup(dom => {
    dom.style.opacity = 0
    dom.style.transition = '1s'
    
    void dom.clientHeight
    
    dom.style.opacity = 1
  })

  return function view(input, postDraw){
    return m('p', 'Hi')
  } 
}

Follow the link above to see a component, written in Functor Mithril which handles setup and async DOM teardown!

import tick from './tick'
export default definition =>
function FunctorComponentClosure(v){
const setup = []
const post = []
const view = definition(
unV(v),
setup.push.bind(setup),
)
tick(() => {
while(setup.length)
setup.pop()(v.dom)
})
return {
view: function FunctorComponentView(v){
tick(() => {
while(post.length)
post.pop()(v.dom)
})
return view(
unV(v),
post.push.bind(post),
)
}
}
}
function unV(v){
for(let key in v.attrs)
return v.attrs
if(v.children)
return v.children[0]
}
import functor from './functor-components'
import patch from './mithril-patcher'
export default patch(m, functor)
// Monkey-patches Mithril to accept new component interfaces.
// Supply 1) an instance of Mithril & 2) a factory which consumes
// components with the desired interface and returns Mithril-
// compliant components.
// Returns the patched Mithril interface.
import xet from './xet'
export default (m, factory) => {
const components = new WeakMap
const factoryM = component =>
xet(components, component, () => factory(component))
return Object.assign(
function(component, input){
return (
typeof component !== 'function'
?
m.apply(this, arguments)
:
m(factoryM(component), input)
)
},
m,
{
mount: (a, component) =>
m.mount(a, factoryM(component)),
route: (a, b, c) =>
m.route(a, b,
Object.values(c)
.reduce((hash, [path, component]) => (
hash[path] = factoryM(component),
hash
))
)
},
)
}
export default callback => {
const node = document.createTextNode('')
const observer = new MutationObserver(() => {
observer.disconnect()
callback()
})
observer.observe(node, {characterData: true})
node.nodeValue = ''
}
export default (map, key, factory) => {
if(map.has(key))
return map.get(key)
const value = factory()
map.set(key, value)
return value
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment