Demo¶
Time Flies Like an Arrow¶
The Timeflies example (source code) implements the classic Time Flies example from RxJS.
In the Time files demo the stream of mouse moves are transformed into a stream of letters where each letter is delayed according to its position.
The Model
type holds data that you want to keep track of while the
application is running.
type Model = {
Letters: Map<int, string * int * int>
}
The Msg
type defines what events/actions can occur while the
application is running. The state of the application changes only in
reaction to these events
type Msg =
| Letter of int * string * int * int
The update function computes the next state of the application based on the current state and the incoming messages
let update (msg : Msg) (currentModel : Model) : Model =
match currentModel.Letters, msg with
| _, Letter (i, c, x, y) ->
{ currentModel with Letters = currentModel.Letters.Add (i, (c, x, y)) }
The view function renders the model and the result will be handled over to React. It produces a div element and then takes each letter in the Map and adds a span element containing the letter. The span element will have the top/left properties set so that the letter is rendered at the correct position on the page.
let view (model : Model) (dispatch : Dispatch<Msg>) =
let letters = model.Letters
div [ Style [ FontFamily "Consolas, monospace"; Height "100%"] ]
[
[ for KeyValue(i, (c, x, y)) in letters do
yield span [ Key (c + string i); Style [Top y; Left x; Position "fixed"] ]
[ str c ] ] |> ofList
]
The init funciton produces an empty model.
let init () : Model =
{ Letters = Map.empty }
Helper code to render the letters at the correct position on the page.
let getOffset (element: Browser.Element) =
let doc = element.ownerDocument
let docElem = doc.documentElement
let clientTop = docElem.clientTop
let clientLeft = docElem.clientLeft
let scrollTop = Browser.window.pageYOffset
let scrollLeft = Browser.window.pageXOffset
int (scrollTop - clientTop), int (scrollLeft - clientLeft)
let container = Browser.document.querySelector "#elmish-app"
let top, left = getOffset container
Message stream (expression style) that transforms the stream of mouse moves into a stream of letters where each letter is delayed according to its position in the stream.
let stream (model : Model) (msgs: IAsyncObservable<Msg>) =
asyncRx {
let chars =
Seq.toList "TIME FLIES LIKE AN ARROW"
|> Seq.mapi (fun i c -> i, c)
let! i, c = AsyncRx.ofSeq chars
yield! AsyncRx.ofMouseMove ()
|> AsyncRx.delay (100 * i)
|> AsyncRx.map (fun m -> Letter (i, string c, int m.clientX + i * 10 + 15 - left, int m.clientY - top))
} |> AsyncRx.tag "msgs"