package fmlib_browser
Install
dune-project
Dependency
Authors
Maintainers
Sources
sha256=650393b6315075780d51cc698e2ee19bc359f114fc39365fbe137b24f663189e
doc/doc_getting_started.html
Getting Started
Overview Up Single Page Application
Installation
Fmlib_browser is best installed via opam using the command
opam install fmlib_browser
Write the application
In this section we write a very simple application with a counter value and two buttons to increase and decrease the counter value. Here is the ocaml source file which implements the application.
(* file: bin/counter.ml *)
open Fmlib_browser
(* Strictly speaking, we could omit that [state] type and replace it with
[int] in the code for a smaller, cleaner example. We use it to demonstrate
how to define and use the state type in larger applications. *)
type state = {
counter: int
}
type msg =
| Decrement
| Increment
let view (s: state): msg Html.t =
let open Html in
let open Attribute in
let counter = s.counter 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 (s: state): msg -> state =
let counter = s.counter in function
| Decrement ->
{counter = counter - 1}
| Increment ->
{counter = counter + 1}
let _ =
sandbox (* very simple applications are
sandbox applications *)
{counter = 0} (* initial state *)
view (* view function *)
update (* update function *)Compile the application via dune
The application in the file bin/counter.ml has to be compiled to javascript. This is done with the help of js_of_ocaml. The following dune file can be used:
(* file: dune *)
(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.jsThe 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 an html file.
<!-- file: index.html -->
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="counter.js">
</script>
</head>
<body>
This gets overwritten!
</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 counter.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 counter.js and fires the onload event which starts the application.
A digital clock
To demonstrate commands and subscriptions we write a simple 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 =
| Got_time of Time.t (* message for the current time *)
| Got_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
let html =
h2
[]
[text (
Printf.sprintf "%02d:%02d:%02d"
(Time.hour state.time state.zone)
(Time.minute state.time state.zone)
(Time.second state.time state.zone))
]
in
(html, "Digital clock")For an 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
| Got_time time ->
({state with time}, Command.none)
| Got_time_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 -> Got_time time)We need a message Got_time time each second independently from the system state. The time for repeating timers is expressed in milliseconds.
For more subscriptions see module Subscription.
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 [
now (fun t -> Got_time t);
time_zone (fun z -> Got_time_zone z)
]The command now returns the current time and the command time_zone returns the current time zone. The commands need functions to map the time and the time zone into a message which can be fed into the update function. Unfortunately ocaml does not allow constructors to be used like functions, therefore a small anonymous function is needed to do the mapping.
For more commands see module Command.
Initially we set the time to zero and the time zone to utc. This is evidently not correct. Therefore we immediately start commands to get the current time and the current time zone.
The application is started by
let _ =
basic_application
initial_state
initial_command
view
subscription
updateThe corresponding dune and html files are the same as for the counter example.