Last active
August 25, 2017 07:47
-
-
Save zaceno/54297f9c3a3d2443bfb3678427b7b37a to your computer and use it in GitHub Desktop.
Stateful Hyperapp Components
This file contains hidden or 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 function lets you define components you can use in your views, which are | |
stateful, i e, they embed their own hyperapp app. | |
The idea is to let you build apps according to what I, for lack of better terms, | |
call the "Vue architecture" -- where your app is built up of a tree of stateful | |
components. Parent components communicate with children by the props passed in | |
the view. Children may communicate back to parents via events the parents listen | |
to. | |
Example demo: https://codepen.io/zaceno/pen/KveOeQ?editors=1010 | |
Fair warning: It is not yet clear to me, that there is any real benefit to | |
the "Vue architecture" over the Elm-architecture. If there are any cases where | |
this approach is advisable, it may be when creating small, reusable ui elements | |
which need to manage state related to interactions (like for instance, a radio | |
knob, a vertical slider, a datepicker, et c) | |
... I was just curious if this was possible, without too much trouble. | |
Apparently it is :) | |
## mini usage docs: | |
### Nesting Componets | |
1) Define a component, by passing a app as first argument into `nestable`, | |
and as the second argument a function. The function is similar to how mixins | |
are defined in hyperapp, in that it recieves an emit. Unlike mixins, however, | |
you can use all the regular app options (mixins, view, et c) | |
const Counter = nestable(app, emit => ({ | |
state: {...}, | |
actions: {...}, | |
events: {...}, | |
mixins: [...], | |
view: (state, actions) => ... | |
})) | |
2) In the view of the main app, or a parent component, render the previously | |
defined component: | |
app({ | |
... | |
view: (state, actions) => <main> | |
<Counter key="mycounter" value={state.counterValue} /> | |
</main> | |
}) | |
Note: it is important that stateful components are keyed (unique among siblings) | |
### Parent-child communication ### | |
Parents communicate with children via setting the component props. Every time | |
the child is rendered (including the first time), the child component app will | |
recieve the `parent:update` with the props passed to the child on that render. | |
The child should implement some sort of handling of these props in that event. | |
### Child-parent communication ### | |
Props passed to the child beginning with `on` are special. If, in the parent | |
view, there is a function bound to `onfoo`, | |
view: (state, actions) => <main> | |
<Counter key="counter" onchange={val => actions.counterChanged(val)} /> | |
</main> | |
...then this function will be called when the child emits | |
`emit('self:foo', data)`. | |
counter Counter = subapp(emit => ({ | |
actions: { | |
increment: (state, actions) => update => { | |
update({value: state.value + 1}) | |
emit('self:changed', state.value) | |
} | |
} | |
})) | |
The data emitted is passed as first and only argument to the handler | |
**Danger!** When syncing copies of a value between parent and child, be *very careful* | |
you don't accidentally create an infinte loop! It's a good idea to use | |
hyperapp's `update` event to cancel updare & render if the state hasn't | |
changed. | |
*/ | |
const nestable = (app, appDefFn) => props => ({ | |
tag: 'embedded-', | |
children: [], | |
data: { | |
key: props.key || 'defaultkey', | |
onupdate: el => { el.emit('parent:update', props) }, | |
oncreate: el => { | |
const events = {} | |
for (let n in props) { | |
if (n.substr(0,2) === 'on') { | |
events['self:' + n.substr(2)] = (s, a, data) => props[n](data) | |
} | |
} | |
var emit | |
const appOpts = appDefFn((msg, data) => (emit && emit(msg, data))) | |
appOpts.events = Object.assign(appOpts.events, events) | |
appOpts.root = el | |
emit = app(appOpts) | |
el.emit = emit | |
el.emit('parent:update', props) | |
} | |
} | |
}) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment