|
| 1 | +# Usage Guide |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +`Halogen.VDom` is built on [Mealy machines](https://en.wikipedia.org/wiki/Mealy_machine). |
| 6 | +Given an input `VDom`, the machine yields a `Node`, the next machine step, and a finalizer. |
| 7 | + |
| 8 | +```purescript |
| 9 | +import Halogen.VDom as VDom |
| 10 | +
|
| 11 | +type MyVDom = VDom.VDom ... |
| 12 | +
|
| 13 | +render ∷ MyState → MyVDom |
| 14 | +
|
| 15 | +main = do |
| 16 | + -- Build the initial machine |
| 17 | + machine1 ← V.buildVDom myVDomSpec (render state1) |
| 18 | +
|
| 19 | + -- Attach the output node to the DOM |
| 20 | + appendChildToBody (V.extract machine1) |
| 21 | +
|
| 22 | + -- Patch |
| 23 | + machine2 ← V.step machine1 (render state2) |
| 24 | + machine3 ← V.step machine2 (render state3) |
| 25 | + ... |
| 26 | +``` |
| 27 | + |
| 28 | +Out of the box, only very basic text and node creation is supported. Attributes, |
| 29 | +properties, event listeners, hooks, etc. are left for library implementors to |
| 30 | +plug in as needed. Library authors should likely `newtype` their wrappers to |
| 31 | +get more convenient instances (eg. `map`ping over inputs from event listeners). |
| 32 | + |
| 33 | +## Extending |
| 34 | + |
| 35 | +The core `VDom a w` type is parameterized by the types for element attributes |
| 36 | +and custom widgets. Element attributes will likely be a sum for the usual suspects |
| 37 | +(DOM attributes, properties, event listeners, lifecycle hooks) and mutate a |
| 38 | +given DOM `Element`, while widgets give you complete control over the patching |
| 39 | +and diffing of a tree (eg. thunks, custom components, etc). |
| 40 | + |
| 41 | +When you start your initial machine, you provide a `VDomSpec`, which contains |
| 42 | +the machines for running your attributes and widgets. |
| 43 | + |
| 44 | +```purescript |
| 45 | +import Halogen.VDom as VDom |
| 46 | +
|
| 47 | +data MyAttribute |
| 48 | +data MyWidget |
| 49 | +
|
| 50 | +makeSpec ∷ ∀ eff. DOM.Document → VDom.VDomSpec eff MyAttribute MyWidget |
| 51 | +makeSpec document = |
| 52 | + VDom.VDomSpec |
| 53 | + { buildWidget: ... |
| 54 | + , buildAttributes: ... |
| 55 | + , document |
| 56 | + } |
| 57 | +``` |
| 58 | + |
| 59 | +The type signature for `buildWidget` looks like: |
| 60 | + |
| 61 | +```purescript |
| 62 | +buildWidget |
| 63 | + ∷ ∀ eff a |
| 64 | + . V.VDomSpec eff a MyWidget |
| 65 | + → V.VDomMachine eff MyWidget DOM.Node |
| 66 | +``` |
| 67 | + |
| 68 | +`buildWidget` takes a circular reference to the `VDomSpec` you are building so you |
| 69 | +can have recursive trees. The core though is in the returned `VDomMachine` which |
| 70 | +takes your widget type, and yields a DOM node. |
| 71 | + |
| 72 | +The type signature for `buildAttributes` looks like: |
| 73 | + |
| 74 | +```purescript |
| 75 | +buildAttributes |
| 76 | + ∷ ∀ eff |
| 77 | + . DOM.Element |
| 78 | + → V.VDomMachine eff MyAttribute Unit |
| 79 | +``` |
| 80 | + |
| 81 | +This takes the current `Element` and yields a machine which takes your attribute |
| 82 | +type and yields `Unit`. |
| 83 | + |
| 84 | +If you don't have any custom widgets, you can supply a `Void` machine. |
| 85 | + |
| 86 | +```purescript |
| 87 | +import Halogen.VDom as VDom |
| 88 | +import Halogen.VDom.Machine as Machine |
| 89 | +
|
| 90 | +data MyAttribute |
| 91 | +
|
| 92 | +makeSpec ∷ ∀ eff. DOM.Document → VDom.VDomSpec eff MyAttribute Void |
| 93 | +makeSpec document = |
| 94 | + VDom.VDomSpec |
| 95 | + { buildWidget: const (Machine.never) |
| 96 | + , buildAttributes: ... |
| 97 | + , document |
| 98 | + } |
| 99 | +``` |
| 100 | + |
| 101 | +## Creating Machines |
| 102 | + |
| 103 | +A `Machine`'s type looks like: |
| 104 | + |
| 105 | +```purescript |
| 106 | +type Machine m a b = a → m (Step m a b) |
| 107 | +
|
| 108 | +data Step m a b = Step b (Machine m a b) (m Unit) |
| 109 | +``` |
| 110 | + |
| 111 | +So it is just an effectful function from some input to a `Step`, which is a |
| 112 | +product of an output value `b`, the next transition, and a finalizer. Finalizers |
| 113 | +are useful when your widgets or attributes need to perform cleanup. |
| 114 | + |
| 115 | +The structure of a widget machine will likely follow this pattern: |
| 116 | + |
| 117 | +```purescript |
| 118 | +import Halogen.VDom as V |
| 119 | +
|
| 120 | +createWidgetNode ∷ MyWidget → V.VDomEff eff DOM.Node |
| 121 | +
|
| 122 | +patchWidgetNode ∷ DOM.Node → MyWidget → MyWidget → V.VDomEff eff DOM.Node |
| 123 | +
|
| 124 | +cleanupWidgetNode ∷ DOM.Node → MyWidget → V.VDomEff eff Unit |
| 125 | +
|
| 126 | +buildWidget |
| 127 | + ∷ ∀ eff a |
| 128 | + . V.VDomSpec eff a MyWidget |
| 129 | + → V.VDomMachine eff MyWidget DOM.Node |
| 130 | +buildWidget spec = render |
| 131 | + where |
| 132 | + render ∷ V.VDomMachine eff MyWidget DOM.Node |
| 133 | + render widget = do |
| 134 | + node ← createWidgetNode widget |
| 135 | + pure |
| 136 | + (V.Step node |
| 137 | + (patch node widget) |
| 138 | + (done node widget)) |
| 139 | +
|
| 140 | + patch ∷ DOM.Node → MyWidget → V.VDomMachine eff MyWidget DOM.Node |
| 141 | + patch node1 widget1 widget2 = do |
| 142 | + node2 ← patchWidgetNode node widget1 widget2 |
| 143 | + pure |
| 144 | + (V.Step node2 |
| 145 | + (patch node2 myWidget2) |
| 146 | + (done node2 myWidget2)) |
| 147 | +
|
| 148 | + done ∷ DOM.Node → MyWidget → V.VDomEff eff Unit |
| 149 | + done node widget = cleanupWidgetNode node widget |
| 150 | +``` |
| 151 | + |
| 152 | +Note that `Machine`s can keep any state they need to, it is just passed from |
| 153 | +machine to machine through closures. |
| 154 | + |
| 155 | +The structure of an attribute machine will likely follow this pattern: |
| 156 | + |
| 157 | +```purescript |
| 158 | +import Halogen.VDom as V |
| 159 | +
|
| 160 | +applyAttributes ∷ DOM.Element → MyAttribute → V.VDomEff eff Unit |
| 161 | +
|
| 162 | +patchAttributes ∷ DOM.Element → MyAttribute → MyAttribute → V.VDomEff eff Unit |
| 163 | +
|
| 164 | +cleanupAttributes ∷ DOM.Element → MyAttribute → V.VDomEff eff Unit |
| 165 | +
|
| 166 | +buildAttributes |
| 167 | + ∷ ∀ eff a |
| 168 | + . DOM.Element |
| 169 | + → V.VDomMachine eff MyAttribute Unit |
| 170 | +buildAttribute elem = apply |
| 171 | + where |
| 172 | + apply ∷ V.VDomMachine eff MyAttribute Unit |
| 173 | + apply attrs = do |
| 174 | + applyAttributes elem attrs |
| 175 | + pure |
| 176 | + (V.Step unit |
| 177 | + (patch attrs) |
| 178 | + (done attrs)) |
| 179 | +
|
| 180 | + patch ∷ MyAttribute → V.VDomMachine eff MyAttribute Unit |
| 181 | + patch attrs1 attrs2 = do |
| 182 | + patchAttributes elem attrs1 attrs2 |
| 183 | + pure |
| 184 | + (V.Step unit |
| 185 | + (patch attrs2) |
| 186 | + (done attrs2)) |
| 187 | +
|
| 188 | + done ∷ MyAttribute → V.VDomEff eff Unit |
| 189 | + done attrs = cleanupAttribute elem attrs |
| 190 | +``` |
| 191 | + |
| 192 | +Note that the `Element` is provided on initialization, and there is no meaninful |
| 193 | +output type because it is only effectful. |
| 194 | + |
| 195 | +## Getting Performance |
| 196 | + |
| 197 | +The core of `Halogen.VDom` strives to be as fast as possible. It does this |
| 198 | +through pervasive use of monomorphic `Eff` do-blocks (which are optimized into |
| 199 | +imperative JavaScript) and `Data.Function.Uncurried` (which eliminates the |
| 200 | +overhead of currying). It also provides a few monomorphic utilities in |
| 201 | +`Halogen.VDom.Util` to help cut down on allocations. Additionally there are |
| 202 | +some general purposes utilities to help with faster diffing. |
0 commit comments