https://github.com/jayrbolton/functional-frontend-architecture
A functional architecture for building UI with FRP (flyd) and Virtual Dom (snabbdom)
https://github.com/jayrbolton/functional-frontend-architecture
Last synced: 2 months ago
JSON representation
A functional architecture for building UI with FRP (flyd) and Virtual Dom (snabbdom)
- Host: GitHub
- URL: https://github.com/jayrbolton/functional-frontend-architecture
- Owner: jayrbolton
- Created: 2016-01-23T04:14:16.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2016-01-26T06:20:40.000Z (over 9 years ago)
- Last Synced: 2025-01-04T23:43:00.662Z (4 months ago)
- Size: 3.23 MB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Functional Frontend Architecture
This is an example of a simple but robust functional frontend architecture, based on the Elm Architecture but further simplified, using only two libs:
- [flyd](https://github.com/paldepind/flyd) for Functional Reactive Programming
- [snabbdom](https://github.com/paldepind/snabbdom) for Virtual DOMMore optional bonuses:
- with plain js data structures, make use of [Ramda](ramdajs.com)
- To have immutable data and vdom-thunking, use [Immutable.js](https://facebook.github.io/immutable-js/docs/)# High-Level Overview
Making a UI module:
- The app consists of independent UI modules
- UI modules can be nested and combined. They are hierarchical, similar to the DOM.
- Each module exports 2 basic components
- View: a main 'view' function that takes a state and renders a Virtual DOM Tree
- Init function: takes some configuration data and returns a state streamTesting your UI:
- Given your module's state stream, view function, and event streams
- Easily unit-test your pure view functions by passing in mocked-up state and querying the resulting HTML
- Easily unit-test your pure update functions by passing in a mockup state and data and checking the resulting state
- Easily integration-test your full UI by pushing test data onto your module's event streams and querying the resulting DOM stream## Some advantages of this architecture
- Much easier to quickly understand in my opinion than many other similar architectures
- Only two libs, and you can very easily use immutable/mori/etc and make use of vdom 'thunking'
- Very easy to test
- Mostly purely functional# Examples
## Counter
A single-module, very simple example to get you started with the idea. The user clicks one of three buttons and watches the number change in the view.
[View the source](examples/counter/index.es6)
# init function
UI modules export an init function, which is a kind of constructor that can
take configuration data. It is also responsible for initializind the default
state, the various event streams, and returning the state stream.# scanMerge-ing events together
Use `flyd/module/scanMerge` to combine events from your view into a single state stream.
The streams on the left can be any flyd stream, and the functions on the right are state-updater functions and take the current state and a value from the stream, and return a new state.
```js
let state$ = flyd.immediate(flyd_scanMerge([
[addTodo$, (state, formObj) => state.set('todos', state.get('todos').add(formObj))]
, [removeTodo$, (state, idx) => state.set('todos', state.get('todos').delete(idx))]
, [toggleTodo$, (state, idx) => state.setIn(['todos', idx, 'finished'], !state.getIn(['todos', idx, 'finished']))]
, [editTodo$, (state, data) => state.setIn(['todos', data.idx, 'name'], data.name)]
], defaultState))
````flyd.immediate` is good to use so that your `defaultState` is pushed to your
state stream immediately and rendered on pageload, rather than waiting around
for an event to occur.# How to nest modules
If you have a parent module and want to embed child modules, you can use flyd/module/lift:
```js
// Embed any number of child module's state streams into the state stream for a parent module:
// (assume $parentState is already initialized)
let parentState$ = flyd.lift(
(s1, s2, s3, parent) => parent.set('child1', s1).set('child2', s2).set('child3', s3)
, parentState$
, childModuleState1$
, childModuleState2$
, childModuleState3$
)
```# Render a module onto the page
To render your module to the page, use the snabbdom patch function in combination with flyd.
```
import snabbdom from 'snabbdom'
let patch = snabbdom.init([
require('snabbdom/modules/class')
, require('snabbdom/modules/props')
, require('snabbdom/modules/style')
, require('snabbdom/modules/eventlisteners')
])let container = document.querySelector('#container')
let vnode$ = flyd.map(view, state$)
let dom$ = flyd.scan(patch, container, vnode$)// inline:
// flyd.scan(patch, document.querySelector('#container'), flyd.map(view, state$))
```