Getting Started

Overview Up Event Handler

Installation

The library Fmlib_browser is best installed via opam issuing the command

    opam install fmlib_browser

Write the application

In this section we write first a very simple application with a counter value and two buttons to decrease and increase the counter value. Here is the ocaml source file which implements the application.

(* file: counter.ml *)
open Fmlib_browser

(* The type of the state is [int]. Therefore no type declaration is
   necessary for the state. *)

let type msg =
    | Decrement
    | Increment

let view (counter: int): msg Html.t =
    let open Html in
    let open Attribute in
    div []
        [ button
            [color "blue"; on_click Decrement]
            [text "-"]
        ; span
            [background_color "silver"; font_size "20px"]
            [text (string_of_int counter)]
        ; button
            [color "blue"; on_click Increment]
            [text "+"]
        ]

let update (counter: int): msg -> int = function
    | Decrement ->
        counter - 1
    | Increment ->
        counter + 1

let _ =
    sandbox         (* very simple applications are sandbox applications *)
        0           (* initial state *)
        view        (* view function *)
        update      (* update function *)

Compile the application via dune

The application in the file webapp.ml has to be compiled to javascript. This is done with the help of js_of_ocaml. The following dune file can be used:

    (executable
      (name counter)
      (modes js)                    ; Activate compilation to javascript
      (libraries fmlib_browser)     ; Use the library
    )

    (rule
        (targets counter.js)         ; Generate the file 'counter.js'
        (deps    counter.bc.js)
        (mode   (promote (until-clean)))
        (action (copy counter.bc.js counter.js))
    )

The application is compiled via one of the commands

    dune build ./counter.js

    dune build --profile release ./counter.js

The first one generates the file counter.js with a lot of diagnostic information. The second one generates a much smaller file counter.js with no diagnostic information.

Html file

Furthermore we need a html file.

    <!-- file: index.html -->
    <!DOCTYPE html>
    <html>
        <head>
            <script type="text/javascript" src="counter.js">
            </script>
        </head>
        <body>
        </body>
    </html>

The sandbox application installs itself as an event listener for the onload event and creates and updates the dom directly below the body element of the html document. Everything in the html file below the body element will be overwritten.

Now you have the files webapp.js and index.html in your source directory. By loading index.html into the browser (either from a webserver or from disk), the browser loads the application webapp.js and fires the onload event which starts the application.

A digital clock

In order to demonstrate commands and subscriptions with a simple example we write an application displaying a digital clock. We want the webpage to look like

    10:15:37

to represent the time of 10 o'clock and 15 minutes and 37 seconds. The current time has to be updated each second.

The state of the application contains the posix time and the current time zone.

(* file: clock.ml *)

open Fmlib_browser

type state = {
    time: Time.t;
    zone: Time.Zone.t;
}

The application has to be informed about the current time and time zone and has to receive a notification each second to update the time. The following message type contains the needed information.

type msg =
    | Time of Time.t            (* message for the current time   *)
    | Zone of Time.Zone.t       (* message for the current time zone *)

We use a minimal view function to render the current time in a browser window.

let view (state: state): msg Html.t * string =
    let open Html in
    h2
        []
        [text (
            Printf.sprintf "%02d:%02d:%02d"
                (Time.hour   state.zone state.time)
                (Time.minute state.zone state.time)
                (Time.second state.zone state.time))
        ]
    ,
    "Digital clock"

For a full blown application the view function returns the virtual dom and the title of the page.

The update function is quite straightforward.

let update (state: state): msg -> state * msg Command.t =
function
    | Time time ->
        {state with time}, Command.none
    | Zone zone ->
        {state with zone}, Command.none

We need a subscription function to get each second a notification of the new time.

let subscription (_: state): msg Subscription.t =
    Subscription.every 1000 (fun time -> Time time)

We need a message Time time each second independently from the system state. The time for repeating timers is expressed in milliseconds.

In order to start the application we need an initial state and an initial command.

initial_state: state =
    {time = Time.zero; zone = Time.Zone.utc}

initial_command: msg Command =
    Command.batch [
         Command.perform Task.(map (fun time -> Time time) now)
         ;
         Command.perform Task.(map (fun zone -> Zone zone) time_zone)
    ]

The task now returns the current posix time and the task time_zone returns the current time zone. However the application needs messages which can be fed into the update function. Therefore the results have to be mapped to messages. Unfortunately ocaml does not allow constructors to be used like functions, therefore two small anonymous function are needed to do the mapping.

Initially we set the time to zero and the time zone to utc. This is evidently not correct. Therefore we immediately start commands which get the current time and the current time zone.

The application is started by

let _ =
    basic_application
        initial_state
        initial_command
        view
        subscription
        update

The corresponding dune and html files are the same as for the counter example.