The Elm architecture using Fable

Fable implementation of the Elm architecture

This demo is an implementation of the Elm architecture using the same virtual-dom as Elm originally used. Contributed by Tomas Jansson.

Architecture overview

The beauty of the architecture Elm is using for their application is its simplicity. You can read and grasp the whole architecture in a matter of minutes here: http://guide.elm-lang.org/architecture/. I won't explain the architecture further, instead I will go straight to the examples.

First example - a simple counter with svg and ajax call simulation

The example below doesn't make any sense except for demonstrating most of the framework. The example consists of a counter that you can increment and decrement by clicking on the labels. The color and some styling (height) changes depending on the counter. Above the counter is a simple svg square that also changes color on based on a threshold. To bootstrap the application a fake ajax call is made using a timeout, that function is also called for each click on any of the labels faking more ajax call.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
open Fable.Core
open Fable.Core.JsInterop
open Fable.Import
open Fable.Import.Browser

open Fable.Helpers.Virtualdom
open Fable.Helpers.Virtualdom.App
open Fable.Helpers.Virtualdom.Html

// model
type Counter = int
let initCounter = 0

The model for the first example is a simple integer that will act as hour counter. We also provide a default value for our counter.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
// Update
type CounterAction =
    | Decrement of int
    | Increment of int

let fakeAjaxCall model (h:CounterAction->unit) = 
    let message = if model < 30 then Increment 10 else Decrement 5 
    if model > 30 && model < 60 then () 
    else window.setTimeout((fun _ -> h (message)), 2000) |> ignore

let counterUpdate model command =
    match command with
    | Decrement x -> model - x
    | Increment x -> model + x
    |> (fun m -> m, (fakeAjaxCall model) |> toActionList) 

The counter can be incremented or decremented in step of x. If you look closely the update function return the new model, a list of something and a second list of functions. The list of something is a list of functions of type unit->unit that will be executed after this version of the model has been rendered. That way you can run some custom js after something has been rendered. The last list of functions is a list of "long" running functions. These functions must be of the type ('TMessage -> unit) -> unit. The first argument is what makes it possible for these long running functions to trigger a new update. In our example we use fakeAjaxCall to simulate a function call that take a while to execute. The type doesn't match completely of what is expected, but after we partially apply it in counterUpdate we do get the right type.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
// View
let counterView model =
    let bgColor =
        match model with
        | x when x > 10 -> "red"
        | x when x < 0 -> "blue"
        | _ -> "green"
    div []
        [
            div [ Style ["width", "120px"; "height", "120px"] ] [
                svg [ width "120"; height "120"; viewBox "0 0 100 100" ]
                    [ rect [width "110"; height "110"; fill bgColor] []]
            ]
            div [ Style ["border","1px solid blue"]
                  onMouseClick (fun x -> (Increment 1))
                  onDblClick (fun x -> ((Increment 10)))]
                [ text (string "Increment")]
            div [ Style ["background-color", bgColor; "color", "white"]]
                [text (string model)]
            div [ Style ["border", "1px solid green";
                         "height", ((string (7 + model)) + "px")]
                  onMouseClick (fun x -> (Decrement 1))
                  onDblClick (fun x -> (Decrement 5))]
                [ text (string "Decrement")]
        ]

The counterView defines how a model should be rendered. The dsl that is used here is quite simple and have helper functions for a majority of the standard HTML elements. It is trivial to add custom tags if you are missing some tag that you would like to use.

You define a svg the same way as you do with any other html element.

1: 
2: 
3: 
4: 
5: 
// Start the application
createApp initCounter counterView counterUpdate
|> withInitMessage (fakeAjaxCall initCounter) 
|> withStartNodeSelector "#counter"
|> start renderer

The dsl has been separated from the actual rendering of the dsl, to allow for future server side rendering as well. In this example we first call the createApp function to create the application, there is a createSimpleApp that requires a simpler version of the update function of you don't do actions from the update function. The withInitMessage takes a function that returns a message to bootstrap the application. The withNodeSelector is used to place the application in the DOM. When we have an application we can call start with a given renderer to start the application.

Second example - nesting

This is just an add-on to the first example to illustrate how you could nest applications together. Nesting basically means re-use the view and update function.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
type NestedModel = { Top: int; Bottom: int}

type NestedAction = 
    | Reset
    | Top of CounterAction
    | Bottom of CounterAction

let nestedUpdate model action = 
    match action with
    | Reset -> {Top = 0; Bottom = 0},[]
    | Top ca -> 
        let (res, action) = (counterUpdate model.Top ca)
        let action' = App.mapActions Top action
        {model with Top = res},action'
    | Bottom ca -> 
        let (res, action) = (counterUpdate model.Bottom ca)
        let action' = App.mapActions Bottom action
        {model with Bottom = res},action'

let nestedView model = 
    div []
        [
            Html.map Top (counterView model.Top)
            Html.map Bottom (counterView model.Bottom)
        ]

let resetEveryTenth h =
    window.setInterval((fun _ -> Reset |> h), 10000) |> ignore

createApp {Top = 0; Bottom = 0} nestedView nestedUpdate
|> withStartNodeSelector "#nested-counter"
|> withProducer resetEveryTenth
|> start renderer

To get this application started you first need to create the application with the createApp function. Then we pass that result to a helper function, withStartNodeSelector to specify where in the document it should be rendered, default is directly in the body. We also want to reset both counters every tenth second to simulate async updates from things that happens outside the application repeatedly, and to do that we use the withProducer helper. The withProducer helper is a function that calls the given handler with a message when needed, in this example it is on a interval. To start the application by making a call to our fakeAjaxCall function, this will result in an update of the model two seconds after the application starts.

When we have defined an application we can call the start function and pass in a renderer. We are using the standard renderer for virtual-dom.js, but this separation makes it a little bit easier to change to another framework in the future.

That's it, the first application is done and we are ready for example 2.

Second example - todomvc

To have something to compare to other js-framework, Elm and whatnot a todomvc app is in its place. If you don't know what todomvc is check it out here: http://todomvc.com/. The app below should have all the features expected from a todomvc app.

We will follow the exact same steps as with the counter example. First implement the model, then the update function that to handle actions and lastly the view. This example is a little bit longer and have some more features. I'll also show how easy it is to store data from the model in the local storage, and that could easily be swapped to server side storage without effecting any of the application code. Let's start.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
// Todo model
type Filter =
    | All
    | Completed
    | Active

type Item =
    {
        Name: string
        Done: bool
        Id: int
        IsEditing: bool
    }

type TodoModel =
    {
        Items: Item list
        Input: string
        Filter: Filter
    }

The model is really simple. It consists of a list of items, which you can edit and they can be marked as done. You also have a input field and something to filter the models with. I use a discriminated union to filter the items, which is a nice feature you don't have in standard js.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
// Todo update
type TodoAction =
    | NoOp
    | AddItem
    | ChangeInput of string
    | MarkAsDone of Item
    | ToggleItem of Item
    | Destroy of Item
    | CheckAll
    | UnCheckAll
    | SetActiveFilter of Filter
    | ClearCompleted
    | EditItem of Item
    | SaveItem of Item*string

First we define the actual actions before moving on to the actual update function.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
let todoUpdate model msg =
    let updateItems model f =
        let items' = f model.Items
        {model with Items = items'}

    let checkAllWith v =
        List.map (fun i -> { i with Done = v })
        |> updateItems model

    let updateItem i model =
        List.map (fun i' ->
                if i'.Id <> i.Id then i' else i)
        |> updateItems model

    let model' =
        match msg with
        | NoOp -> model
        | AddItem ->
            let maxId =
                if model.Items |> List.isEmpty then 1
                else
                    model.Items
                    |> List.map (fun x -> x.Id)
                    |> List.max
            (fun items ->
                items @ [{  Id = maxId + 1
                            Name = model.Input
                            Done = false
                            IsEditing = false}])
            |> updateItems {model with Input = ""}
        | ChangeInput v -> {model with Input = v}
        | MarkAsDone i ->
            updateItem {i with Done = true} model
        | CheckAll -> checkAllWith true
        | UnCheckAll -> checkAllWith false
        | Destroy i ->
            List.filter (fun i' -> i'.Id <> i.Id)
            |> updateItems model
        | ToggleItem i ->
            updateItem {i with Done = not i.Done} model
        | SetActiveFilter f ->
            { model with Filter = f }
        | ClearCompleted ->
            List.filter (fun i -> not i.Done)
            |> updateItems model
        | EditItem i ->
            updateItem { i with IsEditing = true} model
        | SaveItem (i,str) ->
            updateItem { i with Name = str; IsEditing = false} model

    let jsCall =
        match msg with
        | EditItem i -> toActionList <| fun x -> document.getElementById("item-" + (i.Id.ToString())).focus()
        | _ -> []
    model', jsCall

It might seem like a lot of code, but we need to handle all actions and respond to them accordingly, and this is basically all the logic associated with the todo app. I won't go into the detail in all the scenarios, but you should pay attention to the step where jsCalls is defined. Since we are re-rendering the application on changes in the model, or render what has changed as least, we need a way to give an input focus if we start edit it. That is when the list of js function calls come in handy. So the update function returns the new model a long side a list of js function calls if we need to do something after rendering, and that is something we need to do when we start edit an item, we want to give that item focus.

With this done all we need is to define the view.

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
// Todo view
let filterToTextAndUrl = function
    | All -> "All", ""
    | Completed -> "Completed", "completed"
    | Active -> "Active", "active"

let filter activeFilter f =
    let linkClass = if f = activeFilter then "selected" else ""
    let fText,url = f |> filterToTextAndUrl
    li
        [ onMouseClick (fun _ -> SetActiveFilter f)]
        [ a
            [ attribute "href" ("#/" + url); attribute "class" linkClass ]
            [ text fText] ]

let filters model =
    ul
        [ attribute "class" "filters" ]
        ([ All; Active; Completed ] |> List.map (filter model.Filter))

let todoFooter model =
    let clearVisibility =
        if model.Items |> List.exists (fun i -> i.Done)
        then ""
        else "none"
    let activeCount =
        model.Items
        |> List.filter (fun i -> not i.Done)
        |> List.length |> string
    footer
        [   attribute "class" "footer"; Style ["display","block"]]
        [   span
                [   attribute "class" "todo-count" ]
                [   strong [] [text activeCount]
                    text " items left" ]
            (filters model)
            button
                [   attribute "class" "clear-completed"
                    Style [ "display", clearVisibility ]
                    onMouseClick (fun _ -> ClearCompleted)]
                [ text "Clear completed" ] ]

let inline onInput x = onEvent "oninput" (fun e -> x (unbox e?target?value)) 
let onEnter succ nop = onKeyup (fun x -> if (unbox x?keyCode) = 13 then succ else nop)
let todoHeader model =
    header
        [attribute "class" "header"]
        [   h1 [] [text "todos"]
            input [ attribute "class" "new-todo"
                    attribute "id" "new-todo"
                    property "value" model
                    property "placeholder" "What needs to be done?"
                    onInput (fun x -> ChangeInput x)
                    onEnter AddItem NoOp ]]
let listItem item =
    let itemChecked = if item.Done then "true" else ""
    let editClass = if item.IsEditing then "editing" else ""
    li [ attribute "class" ((if item.Done then "completed " else " ") + editClass)]
       [ div [  attribute "class" "view"
                onDblClick (fun x -> EditItem item) ]
             [ input [  property "className" "toggle"
                        property "type" "checkbox"
                        property "checked" itemChecked
                        onMouseClick (fun e -> ToggleItem item) ]
               label [] [ text item.Name ]
               button [ attribute "class" "destroy"
                        onMouseClick (fun e -> Destroy item) ] [] ]
         input [ attribute "class" "edit"
                 attribute "value" item.Name
                 property "id" ("item-"+item.Id.ToString())
                 onBlur (fun e -> SaveItem (item, (unbox e?target?value))) ] ]

let itemList items activeFilter =
    let filterItems i =
        match activeFilter with
        | All -> true
        | Completed -> i.Done
        | Active -> not i.Done

    ul [attribute "class" "todo-list" ]
       (items |> List.filter filterItems |> List.map listItem)

let todoMain model =
    let items = model.Items
    let allChecked = items |> List.exists (fun i -> not i.Done)
    section [  attribute "class" "main"
               Style [ "style", "block" ] ]
            [   input [ property "id" "toggle-all"
                        attribute "class" "toggle-all"
                        property "type" "checkbox"
                        property "checked" (if not allChecked then "true" else "")
                        onMouseClick (fun e ->
                                    if allChecked
                                    then CheckAll
                                    else UnCheckAll) ]
                label [ attribute "for" "toggle-all" ]
                      [ text "Mark all as complete" ]
                (itemList items model.Filter) ]

let todoView model =
    section
        [attribute "class" "todoapp"]
        ((todoHeader model.Input)::(if model.Items |> List.isEmpty
                then []
                else [  (todoMain model)
                        (todoFooter model) ] ))

This view is more complex than the first example, but it also show how easy it is to split a view up into pieces and then combine them together to form a whole. This makes it quite easy to re-use parts in different views.

One thing to notice is that only a few properties are mapped at the moment, but if you know the property name you can use the syntax e?target?value, which will look app the value property on the target property on the e event as in the example above, and that is what is done in the helper function onInput.

Before this is done, there are one hidden gem that is worth knowing, and it will be showed with two examples. We will add local storage support of the items and a logger of all the actions and model changes. First we need a helper for the storage.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
// Storage
module Storage =
    let private STORAGE_KEY = "vdom-storage"
    open Microsoft.FSharp.Core
    let fetch<'T> (): 'T [] =
        Browser.localStorage.getItem(STORAGE_KEY)
        |> function null -> "[]" | x -> unbox x
        |> JS.JSON.parse |> unbox

    let save<'T> (todos: 'T []) =
        Browser.localStorage.setItem(STORAGE_KEY, JS.JSON.stringify todos)

The module above is a simple helper to store a list of items in local storage of the browser, now let's add it and support for logging.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
open Storage
let initList = fetch<Item>() |> List.ofArray
let initModel = {Filter = All; Items = initList; Input = ""}

createApp initModel todoView todoUpdate
|> (withSubscriber "storagesub" (function
        | ModelChanged (newModel,old) ->
            save (newModel.Items |> Array.ofList)
        | _ -> ()))
|> (withSubscriber "modellogger" (printfn "%A"))
|> withStartNodeSelector "#todo"
|> start renderer

First we initiate the model by checking the local storage if there are any items there. The to add support for local storage we add a subscriber. A subscriber is a function that handles AppEvents, they can be ModelChanged or ActionReceived. For the storage we are only interested in model changes, so that is what we act on and store the list of items in the local storage when the model was changed. For the logger we just logs everything.

We also start the application on the #todo element in the document.

Creating custom elements

If some tag or you want to create a custom helper function that represent some html element it is easy to extend the dsl with your needs. To add a custom html node where you set the css class directly you write something like:

1: 
let inline myDiv className = elem "div" [attribute "class" className]

Creating svg nodes are as easy as regular html nodes:

1: 
let inline redRect x = svgElem "rect" ((fill "red")::x)

As you see the only difference is that you use svgElem instead of elem. You do this to add the correct namespace to the svg nodes. To see more example of how to define your own tags just look at the source code, the dsl is not that complex.

namespace Fable
namespace Fable.Core
module JsInterop

from Fable.Core
namespace Fable.Import
module Browser

from Fable.Import
namespace Fable.Helpers
module Virtualdom

from Fable.Helpers
module App

from Fable.Helpers.Virtualdom
module Html

from Fable.Helpers.Virtualdom
type Counter = int

Full name: Virtualdom.Counter
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
val initCounter : int

Full name: Virtualdom.initCounter
type CounterAction =
  | Decrement of int
  | Increment of int

Full name: Virtualdom.CounterAction
union case CounterAction.Decrement: int -> CounterAction
union case CounterAction.Increment: int -> CounterAction
val fakeAjaxCall : model:int -> h:(CounterAction -> unit) -> unit

Full name: Virtualdom.fakeAjaxCall
val model : int
val h : (CounterAction -> unit)
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
val message : CounterAction
val window : Window

Full name: Fable.Import.Browser.window
abstract member WindowTimers.setTimeout : handler:obj * ?timeout:obj * [<System.ParamArray>] args:obj [] -> float
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
val counterUpdate : model:int -> command:CounterAction -> int * ((CounterAction -> unit) -> unit) list

Full name: Virtualdom.counterUpdate
val command : CounterAction
val x : int
val m : int
val toActionList : a:'a -> 'a list

Full name: Fable.Helpers.Virtualdom.App.toActionList
val counterView : model:int -> DomNode<CounterAction>

Full name: Virtualdom.counterView
val bgColor : string
val div : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Tags.div
Multiple items
union case Attribute.Style: Style -> Attribute<'TMessage>

--------------------
type Style = (string * string) list

Full name: Fable.Helpers.Virtualdom.Html.Types.Style
val svg : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Svg.svg
val width : x:string -> Attribute<'a>

Full name: Fable.Helpers.Virtualdom.Html.Svg.width
val height : x:string -> Attribute<'a>

Full name: Fable.Helpers.Virtualdom.Html.Svg.height
val viewBox : x:string -> Attribute<'a>

Full name: Fable.Helpers.Virtualdom.Html.Svg.viewBox
val rect : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Svg.rect
val fill : x:string -> Attribute<'a>

Full name: Fable.Helpers.Virtualdom.Html.Svg.fill
val onMouseClick : x:(obj -> 'a) -> Attribute<'a>

Full name: Fable.Helpers.Virtualdom.Html.Events.onMouseClick
val x : obj
val onDblClick : x:(obj -> 'a) -> Attribute<'a>

Full name: Fable.Helpers.Virtualdom.Html.Events.onDblClick
val text : x:string -> DomNode<'a>

Full name: Fable.Helpers.Virtualdom.Html.Tags.text
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
val createApp : model:'a -> view:('a -> DomNode<'b>) -> update:('a -> 'b -> 'a * Action<'b> list) -> App<'a,'b>

Full name: Fable.Helpers.Virtualdom.App.createApp
val withInitMessage : msg:(('a -> unit) -> unit) -> app:App<'b,'a> -> App<'b,'a>

Full name: Fable.Helpers.Virtualdom.App.withInitMessage
val withStartNodeSelector : selector:string -> app:App<'a,'b> -> App<'a,'b>

Full name: Fable.Helpers.Virtualdom.App.withStartNodeSelector
val start : renderer:Renderer<'a> -> app:App<'b,'a> -> MailboxProcessor<AppMessage<'a>>

Full name: Fable.Helpers.Virtualdom.App.start
val renderer : Renderer<'a>

Full name: Fable.Helpers.Virtualdom.renderer
type NestedModel =
  {Top: int;
   Bottom: int;}

Full name: Virtualdom.NestedModel
NestedModel.Top: int
NestedModel.Bottom: int
type NestedAction =
  | Reset
  | Top of CounterAction
  | Bottom of CounterAction

Full name: Virtualdom.NestedAction
union case NestedAction.Reset: NestedAction
union case NestedAction.Top: CounterAction -> NestedAction
union case NestedAction.Bottom: CounterAction -> NestedAction
val nestedUpdate : model:NestedModel -> action:NestedAction -> NestedModel * Action<NestedAction> list

Full name: Virtualdom.nestedUpdate
val model : NestedModel
val action : NestedAction
val ca : CounterAction
val res : int
val action : ((CounterAction -> unit) -> unit) list
val action' : Action<NestedAction> list
Multiple items
module App

from Fable.Helpers.Virtualdom

--------------------
type App<'TModel,'TMessage> =
  {Model: 'TModel;
   View: 'TModel -> DomNode<'TMessage>;
   Update: 'TModel -> 'TMessage -> 'TModel * Action<'TMessage> list;
   InitMessage: (('TMessage -> unit) -> unit) option;
   Actions: Action<'TMessage> list;
   Producers: Producer<'TMessage> list;
   Node: Node option;
   CurrentTree: obj option;
   Subscribers: Map<string,Subscriber<'TMessage,'TModel>>;
   NodeSelector: string option;
   ...}

Full name: Fable.Helpers.Virtualdom.App.App<_,_>
val mapActions : m:('a -> 'b) -> (Action<'a> list -> Action<'b> list)

Full name: Fable.Helpers.Virtualdom.App.mapActions
val nestedView : model:NestedModel -> DomNode<NestedAction>

Full name: Virtualdom.nestedView
val map : mapping:('T1 -> 'T2) -> node:DomNode<'T1> -> DomNode<'T2>

Full name: Fable.Helpers.Virtualdom.Html.map
val resetEveryTenth : h:(NestedAction -> 'a) -> unit

Full name: Virtualdom.resetEveryTenth
val h : (NestedAction -> 'a)
abstract member WindowTimers.setInterval : handler:obj * ?timeout:obj * [<System.ParamArray>] args:obj [] -> float
val withProducer : p:Producer<'a> -> app:App<'b,'a> -> App<'b,'a>

Full name: Fable.Helpers.Virtualdom.App.withProducer
type Filter =
  | All
  | Completed
  | Active

Full name: Virtualdom.Filter
union case Filter.All: Filter
union case Filter.Completed: Filter
union case Filter.Active: Filter
type Item =
  {Name: string;
   Done: bool;
   Id: int;
   IsEditing: bool;}

Full name: Virtualdom.Item
Item.Name: string
Item.Done: bool
type bool = System.Boolean

Full name: Microsoft.FSharp.Core.bool
Item.Id: int
Item.IsEditing: bool
type TodoModel =
  {Items: Item list;
   Input: string;
   Filter: Filter;}

Full name: Virtualdom.TodoModel
TodoModel.Items: Item list
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
TodoModel.Input: string
Multiple items
TodoModel.Filter: Filter

--------------------
type Filter =
  | All
  | Completed
  | Active

Full name: Virtualdom.Filter
type TodoAction =
  | NoOp
  | AddItem
  | ChangeInput of string
  | MarkAsDone of Item
  | ToggleItem of Item
  | Destroy of Item
  | CheckAll
  | UnCheckAll
  | SetActiveFilter of Filter
  | ClearCompleted
  ...

Full name: Virtualdom.TodoAction
union case TodoAction.NoOp: TodoAction
union case TodoAction.AddItem: TodoAction
union case TodoAction.ChangeInput: string -> TodoAction
union case TodoAction.MarkAsDone: Item -> TodoAction
union case TodoAction.ToggleItem: Item -> TodoAction
union case TodoAction.Destroy: Item -> TodoAction
union case TodoAction.CheckAll: TodoAction
union case TodoAction.UnCheckAll: TodoAction
union case TodoAction.SetActiveFilter: Filter -> TodoAction
union case TodoAction.ClearCompleted: TodoAction
union case TodoAction.EditItem: Item -> TodoAction
union case TodoAction.SaveItem: Item * string -> TodoAction
val todoUpdate : model:TodoModel -> msg:TodoAction -> TodoModel * ('a -> unit) list

Full name: Virtualdom.todoUpdate
val model : TodoModel
val msg : TodoAction
val updateItems : (TodoModel -> (Item list -> Item list) -> TodoModel)
val f : (Item list -> Item list)
val items' : Item list
val checkAllWith : (bool -> TodoModel)
val v : bool
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val i : Item
val updateItem : (Item -> TodoModel -> TodoModel)
val i' : Item
val model' : TodoModel
val maxId : int
val isEmpty : list:'T list -> bool

Full name: Microsoft.FSharp.Collections.List.isEmpty
val x : Item
val max : list:'T list -> 'T (requires comparison)

Full name: Microsoft.FSharp.Collections.List.max
val items : Item list
val v : string
val filter : predicate:('T -> bool) -> list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.filter
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
val f : Filter
val str : string
val jsCall : ('a -> unit) list
val x : 'a
val document : Document

Full name: Fable.Import.Browser.document
abstract member Document.getElementById : elementId:string -> HTMLElement
System.Int32.ToString() : string
System.Int32.ToString(provider: System.IFormatProvider) : string
System.Int32.ToString(format: string) : string
System.Int32.ToString(format: string, provider: System.IFormatProvider) : string
val filterToTextAndUrl : _arg1:Filter -> string * string

Full name: Virtualdom.filterToTextAndUrl
val filter : activeFilter:Filter -> f:Filter -> DomNode<TodoAction>

Full name: Virtualdom.filter
val activeFilter : Filter
val linkClass : string
val fText : string
val url : string
val li : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Tags.li
val a : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Tags.a
val attribute : key:string -> value:string -> Attribute<'a>

Full name: Fable.Helpers.Virtualdom.Html.Attributes.attribute
val filters : model:TodoModel -> DomNode<TodoAction>

Full name: Virtualdom.filters
val ul : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Tags.ul
TodoModel.Filter: Filter
val todoFooter : model:TodoModel -> DomNode<TodoAction>

Full name: Virtualdom.todoFooter
val clearVisibility : string
val exists : predicate:('T -> bool) -> list:'T list -> bool

Full name: Microsoft.FSharp.Collections.List.exists
val activeCount : string
val length : list:'T list -> int

Full name: Microsoft.FSharp.Collections.List.length
val footer : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Tags.footer
val span : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Tags.span
val strong : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Tags.strong
val button : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Tags.button
val onInput : x:('a -> 'b) -> Attribute<'b>

Full name: Virtualdom.onInput
val x : ('a -> 'b)
val onEvent : eventType:string -> f:(obj -> 'c) -> Attribute<'c>

Full name: Fable.Helpers.Virtualdom.Html.Events.onEvent
val e : obj
val unbox : value:obj -> 'T

Full name: Microsoft.FSharp.Core.Operators.unbox
val onEnter : succ:'a -> nop:'a -> Attribute<'a>

Full name: Virtualdom.onEnter
val succ : 'a
val nop : 'a
val onKeyup : x:(obj -> 'b) -> Attribute<'b>

Full name: Fable.Helpers.Virtualdom.Html.Events.onKeyup
val todoHeader : model:string -> DomNode<TodoAction>

Full name: Virtualdom.todoHeader
val model : string
val header : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Tags.header
val h1 : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Tags.h1
val input : x:Attribute<'a> list -> DomNode<'a>

Full name: Fable.Helpers.Virtualdom.Html.Tags.input
val property : key:string -> value:string -> Attribute<'a>

Full name: Fable.Helpers.Virtualdom.Html.Attributes.property
val x : string
val listItem : item:Item -> DomNode<TodoAction>

Full name: Virtualdom.listItem
val item : Item
val itemChecked : string
val editClass : string
val label : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Tags.label
val onBlur : x:(obj -> 'a) -> Attribute<'a>

Full name: Fable.Helpers.Virtualdom.Html.Events.onBlur
val itemList : items:Item list -> activeFilter:Filter -> DomNode<TodoAction>

Full name: Virtualdom.itemList
val filterItems : (Item -> bool)
val todoMain : model:TodoModel -> DomNode<TodoAction>

Full name: Virtualdom.todoMain
val allChecked : bool
val section : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Fable.Helpers.Virtualdom.Html.Tags.section
val todoView : model:TodoModel -> DomNode<TodoAction>

Full name: Virtualdom.todoView
Multiple items
val Storage : StorageType

Full name: Fable.Import.Browser.Storage

--------------------
type Storage =
  interface
    abstract member clear : unit -> unit
    abstract member getItem : key:string -> obj
    abstract member Item : index:int -> string with get
    abstract member Item : key:string -> obj with get
    abstract member length : float
    abstract member key : index:float -> string
    abstract member removeItem : key:string -> unit
    abstract member setItem : key:string * data:string -> unit
    abstract member Item : index:int -> string with set
    abstract member Item : key:string -> obj with set
    ...
  end

Full name: Fable.Import.Browser.Storage
val private STORAGE_KEY : string

Full name: Virtualdom.Storage.STORAGE_KEY
namespace Microsoft
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Core
val fetch : unit -> 'T []

Full name: Virtualdom.Storage.fetch
val localStorage : Storage

Full name: Fable.Import.Browser.localStorage
module JS

from Fable.Import
Multiple items
val JSON : JS.JSON

Full name: Fable.Import.JS.JSON

--------------------
type JSON =
  interface
    abstract member ( [Symbol.toStringTag] ) : obj
    abstract member parse : text:string * ?reviver:Func<obj,obj,obj> -> obj
    abstract member ( [Symbol.toStringTag] ) : obj with set
    abstract member stringify : value:obj * replacer:ResizeArray<obj> * space:U2<string,float> -> string
    abstract member stringify : value:obj * replacer:Func<string,obj,obj> * space:U2<string,float> -> string
    abstract member stringify : value:obj * replacer:ResizeArray<obj> -> string
    abstract member stringify : value:obj * replacer:Func<string,obj,obj> -> string
    abstract member stringify : value:obj -> string
  end

Full name: Fable.Import.JS.JSON
val save : todos:'T [] -> unit

Full name: Virtualdom.Storage.save
val todos : 'T []
Multiple items
val Storage : StorageType

Full name: Fable.Import.Browser.Storage

--------------------
module Storage

from Virtualdom

--------------------
type Storage =
  interface
    abstract member clear : unit -> unit
    abstract member getItem : key:string -> obj
    abstract member Item : index:int -> string with get
    abstract member Item : key:string -> obj with get
    abstract member length : float
    abstract member key : index:float -> string
    abstract member removeItem : key:string -> unit
    abstract member setItem : key:string * data:string -> unit
    abstract member Item : index:int -> string with set
    abstract member Item : key:string -> obj with set
    ...
  end

Full name: Fable.Import.Browser.Storage
val initList : Item list

Full name: Virtualdom.initList
val ofArray : array:'T [] -> 'T list

Full name: Microsoft.FSharp.Collections.List.ofArray
val initModel : TodoModel

Full name: Virtualdom.initModel
val withSubscriber : subscriberId:string -> subscriber:Subscriber<'a,'b> -> app:App<'b,'a> -> App<'b,'a>

Full name: Fable.Helpers.Virtualdom.App.withSubscriber
union case AppEvents.ModelChanged: 'TModel * 'TModel -> AppEvents<'TMessage,'TModel>
val newModel : TodoModel
val old : TodoModel
module Array

from Microsoft.FSharp.Collections
val ofList : list:'T list -> 'T []

Full name: Microsoft.FSharp.Collections.Array.ofList
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
val myDiv : className:string -> (DomNode<'a> list -> DomNode<'a>)

Full name: Virtualdom.myDiv
val className : string
val elem : tagName:string -> attrs:Attribute<'b> list -> children:DomNode<'b> list -> DomNode<'b>

Full name: Fable.Helpers.Virtualdom.Html.Tags.elem
val redRect : x:Attribute<'a> list -> (DomNode<'a> list -> DomNode<'a>)

Full name: Virtualdom.redRect
val x : Attribute<'a> list
val svgElem : tagName:string -> attrs:Attribute<'b> list -> children:DomNode<'b> list -> DomNode<'b>

Full name: Fable.Helpers.Virtualdom.Html.Svg.svgElem
Fork me on GitHub