node package manager

msg

Elmish Message data structure

MSG: Message Signal Generator

Exports a single function which takes an object of name -> function pairs and contructs a data structure which can be used to make an Elm Architecture / Redux style app. Inspired by union-type.

295 bytes. No dependencies.

npm i -S msg
import Message from 'msg'
 
const Route = ( model, path ) => { model.route = path; return model }
const Select = ( model, name ) => { model.selected = name; return model }
const Saved = ( model, name ) => { model.saved = true; return model }
 
const actions = { Route, Select, Saved }
const Action = Message( actions )
 
console.log( Action )

console.log(Action.Route( '/' ))

Action.isPrototypeOf( Action.Route() )              //=> true 
Action.isPrototypeOf( Action.Route )                //=> false 
Message.prototype.isPrototypeOf( Action.Route() )   //=> true 
Action.Route() instanceof Message                   //=> true 

Define effects

const Effect = Message(
{ Save: ( message$, index ) => {
  localStorage.setItem( 'selected', index )
  return Action.Saved()
}})

Initialise model

const init = () =>
  ({ route: '/'
   , selected: localStorage.getItem( 'selected' )
   , names: [ 'Johnathan Best', 'Adonai Reynolds', 'Kasandra Ursler', 'Honora Bognár' ]
   , saved: false
   })

Define the view Import update as render so it doesn't clash with our model update function

import yo, { update as render } from 'yo-yo' 
const view = message$ => model => yo`
<div>
    <button onclick=${ e => message$( Effect.Save( model.selected ))}>Save</button>
    <ul>${ model.names.map(( name, index ) => yo`
    <li onclick=${ e => message$( Action.Select( index ))}>
      ${ model.selected == index ? name.toUpperCase() : name }
    </li>`)}
    </ul>
  ${ model.saved ? 'Saved!' : null }
</div>
`

Define flyd streams. Flyd has a good explanation in its README.

const update = ( model, action ) => action.value( model, ...action.args )
 
const instanceOf = type => msg =>
  type.isPrototypeOf( msg )
 
import { stream, scan, map } from 'flyd'
import filter from 'flyd/module/filter'
 
const message$ = stream()
const action$ = filter( instanceOf( Action ), message$ )
const model$ = scan( update, init(), action$ ) // Contains the entire state of the application 
const node$ = map( view( message$ ), model$ )  // Stream of DOM nodes to patch the document 

message$ is short for messageStream. message$ is a function which takes one or zero arguments. If you pass a value to action$, that becomes the value returned next time message$ is called with no argument.

message$( 'example' )
message$()             // => 'example' 

The scan function is like reduce. It returns a new stream based on manipulating the stream passed as its third argument. The second argument is the initial value of the returned stream. Each time something is pushed onto action$ by calling action$( something ), scan runs update and passes it two arguments. 1: model - the last value passed to model$. 2: action - the last value passed to action$. The value returned by update becomes the new value of model$.

const container = document.querySelector( '#app' )
scan( render, container, node$ )
 
const effect$ = filter( instanceOf( Effect ), message$ )
map( effect => message$( effect.value( ...effect.args )), effect$ )