Warning: Reason support is experimental. We are looking for beta-tester and contributors.

Writing a client/server Eliom application

The code of this tutorial has been tested against Eliom 4.

In this chapter, we will write a collaborative drawing application. It is a client/server web application displaying an area where users can draw using the mouse, and see what other users are drawing at the same time and in real-time.

The final eliom code is available for download.


To get started, we recommend to use Eliom's distillery, a program which creates scaffolds for Eliom projects. The following command creates a very simplicistic project called "graffiti" in the directory "graffiti" (adapt it to your needs!):

$ eliom-distillery -name graffiti -template basic -target-directory graffiti

My first page

Our web application consists of a single page for now. Let's start by creating a very basic page. We define the service that will implement this page by the following declaration:

open Eliom_content.Html5.D (* provides functions to create HTML nodes *)

let main_service =
    (fun () () ->
           (head (title (pcdata "Page title")) [])
           (body [h1 [pcdata "Graffiti"]])))

If you're using eliom-distillery just replace the content of the eliom-file by the above lines and run

$ make test.byte

This will compile your application and run ocsigenserver on it. (Refer to the manual on how to compile your project "by hand".)

Your page is now available at URL http://localhost:8080/graff.

Execute parts of the program on the client

To create our first service, we used the function Eliom_registration.​Html5.​register_service, as we wanted to return HTML5. But actually we want our service to send an Eliom application. To do so, we will create our own registration module by using the functor Eliom_registration.App:

module My_app =
  Eliom_registration.App (struct
      let application_name = "graffiti"

It is now possible to use My_app for registering our main service (now at URL /):

let main_service =
    (fun () () ->
           (head (title (pcdata "Graffiti")) [])
           (body [h1 [pcdata "Graffiti"]]) ) )

We now want to add some OCaml code to be executed by the browser. For that purpose, Eliom provides a syntax extension to distinguish between server and client code in the same file. We start by a very basic program, that will display a message to the user by calling the JavaScript function alert. Add the following lines to the program,

  let _ = Eliom_lib.alert "Hello!"

After running again make test.byte, and visiting http://localhost:8080/graff, the browser will now load the file graffiti.js, and open an alert-box accordingly.

Accessing server side variables on client side code

The client side process is not really separated from the server side, we can access some server variables from client code. For instance:

let count = ref 0

let main_service =
    (fun () () ->
      let c = incr count; !count in
      ignore {unit{
	  (Printf.sprintf "You came %i times to this page" %c))
           (head (title (pcdata "Graffiti")) [])
           (body [h1 [pcdata "Graffiti"]]) ) )

Here, we are increasing the reference count each time the page is accessed. When the page is loaded and the document is in-place, the client program initialize the value inside {unit{ ... }} and thus triggers an alert window. More specifically, the variable c , in the scope of the client value on the server is made available in the client value using the syntax extension %c. In doing so, the server side value c is displayed in a message box on the client.

Collaborative drawing application

Drawing on a canvas

We now want to draw something on the page using an HTML5 canvas. The drawing primitive is defined in the client-side function called draw that just draws a line between two given points in a canvas.

To start our collaborative drawing application, we define another client-side function init_client , which just draws a single, orange line for now.

Here is the (full) new version of the program:

  (* Modules opened in the shared-section are available in client-
     and server-code *)
  open Eliom_content.Html5.D
  open Lwt

module My_app =
  Eliom_registration.App (
      let application_name = "graffiti"

  let width = 700
  let height = 400

  let draw ctx ((r, g, b), size, (x1, y1), (x2, y2)) =
    let color = CSS.Color.string_of_t (CSS.Color.rgb r g b) in
    ctx##strokeStyle <- (Js.string color);
    ctx##lineWidth <- float size;
    ctx##moveTo(float x1, float y1);
    ctx##lineTo(float x2, float y2);

let canvas_elt =
  canvas ~a:[a_width width; a_height height]
    [pcdata "your browser doesn't support canvas"]

let page =
    (head (title (pcdata "Graffiti")) [])
    (body [h1 [pcdata "Graffiti"];
           canvas_elt] ) )
  let init_client () =
    let canvas = Eliom_content.Html5.To_dom.of_canvas %canvas_elt in
    let ctx = canvas##getContext (Dom_html._2d_) in
    ctx##lineCap <- Js.string "round";
    draw ctx ((0, 0, 0), 12, (10, 10), (200, 100))

let main_service =
  My_app.register_service ~path:[""] ~get_params:Eliom_parameter.unit
    (fun () () ->
      (* Cf. the box "Client side side-effects on the server" *)
      let _ = {unit{ init_client () }} in
      Lwt.return page)

Single user drawing application

We now want to catch mouse events to draw lines with the mouse like with the brush tools of any classical drawing application. One solution would be to mimic classical JavaScript code in OCaml ; for example by using the function Dom_events.​listen that is the Js_of_ocaml's equivalent of addEventListener. However, this solution is at least as much verbose than the JavaScript equivalent, hence not satisfactory. Js_of_ocaml's library provides a much easier way to do that, together with Lwt.

Replace the init_client of the previous example by the following piece of code, then compile and draw!

let init_client () =

  let canvas = Eliom_content.Html5.To_dom.of_canvas %canvas_elt in
  let ctx = canvas##getContext (Dom_html._2d_) in
  ctx##lineCap <- Js.string "round";

  let x = ref 0 and y = ref 0 in

  let set_coord ev =
    let x0, y0 = Dom_html.elementClientPosition canvas in
    x := ev##clientX - x0; y := ev##clientY - y0

  let compute_line ev =
    let oldx = !x and oldy = !y in
    set_coord ev;
    ((0, 0, 0), 5, (oldx, oldy), (!x, !y))

  let line ev = draw ctx (compute_line ev); Lwt.return () in

    (fun () ->
      let open Lwt_js_events in
      mousedowns canvas
        (fun ev _ ->
          set_coord ev; line ev >>= fun () ->
          Lwt.pick [mousemoves Dom_html.document (fun x _ -> line x);
		    mouseup Dom_html.document >>= line]))

We use two references x and y to record the last mouse position. The function set_coord updates those references from mouse event data. The function compute_line computes the coordinates of a line from the initial (old) coordinates to the new coordinates–the event data sent as a parameter.

The last four lines of code, that implement the event handling loop, could be read as: for each mousedown event on the canvas, do set_coord then line (this will draw a dot), then behave as the first of the two following lines to terminate:

  • For each mousemove event on the document, call line (never terminates)
  • If there is a mouseup event on the document, call line.

Collaborative drawing application

In order to see what other users are drawing, we now want to do the following:

  • Send the coordinates to the server when the user draw a line, then
  • Dispatch the coordinates to all connected users.

We first declare a type, shared by the server and the client, describing the color (as RGB values) and coordinates of drawn lines.

  type messages = ((int * int * int) * int * (int * int) * (int * int))
    deriving (Json)

We annotate the type declaration with deriving (Json) to allow type-safe deserialization of this type. Eliom forces you to use this in order to avoid server crashes if a client sends corrupted data. This is defined using as custom version of the Deriving syntax extension. You need to do that for each type of data sent by the client to the server. This annotation can only be added on types containing exclusively basics type or other types annotated with deriving. See the Js_of_ocaml API, for more information on the Deriving_Json module.

Then we create an Eliom bus to broadcast drawing events to all client with the function Eliom_bus.​create. This function take as parameter the type of values carried by the bus.

let bus = Eliom_bus.create Json.t<messages>

To write draw orders into the bus, we just replace the function line in init_client by:

let line ev =
  let v = compute_line ev in
  let _ = Eliom_bus.write %bus v in
  draw ctx v;
  Lwt.return () in

Finally, to interpret the draw orders read on the bus, we add the following line at the end of function init_client:

Lwt.async (fun () -> Lwt_stream.iter (draw ctx) (Eliom_bus.stream %bus))

Now you can try the program using two browser windows to see that the lines are drawn on both windows.

Color and size of the brush

In this section, we add a color picker and slider to choose the size of the brush. For the colorpicker we used a widget available in Ojwidgets Error a_manual: exception Projects.No_such_project("ojwidgets").

In the Makefile.options, created by Eliom's distillery, just add ojwidgets to the (currently empty) CLIENT_PACKAGES:

CLIENT_PACKAGES := ojwidgets

To create the widget, we add the following code in the init_client immediatly after canvas configuration:

(* Color of the brush *)
let colorpicker = Ojw_color_picker.create ~width:150 () in
Ojw_color_picker.append_at (Dom_html.document##body) colorpicker;
Ojw_color_picker.init_handler colorpicker;

Then we'll add a simple Html5 slider to change the size of the brush. Near the canvas_elt definition, add simply this code

let slider = Html5.D.int_input
   ~a:[Html5.D.a_id "slider"; Html5.D.a_input_min 1.; Html5.D.a_input_max 80.]
   ~input_type:`Range ()

Next, add it to the page's body like this

let page =
    (head (title (pcdata "Graffiti")) [])
    (body [h1 [pcdata "Graffiti"];
           canvas_elt;div [slider]] ))

To change the size and the color of the brush, we replace the last line of the function compute_line in init_client by:

let color = Ojw_color_picker.get_rgb colorpicker in
let size_slider = Eliom_content.Html5.To_dom.of_input %slider in
let size = int_of_string (Js.to_string size_slider##value) in
(color, size, (oldx, oldy), (!x, !y))

As last step, we need to add a stylesheet and in the headers of our page. To ease the creation of the head-element in HTML we use the function Eliom_tools.​F.​head:

let page =
    (Eliom_tools.F.head ~title:"Graffiti"
      ~js:[] ())
    (body [h1 [pcdata "Graffiti"]; canvas_elt])

You need to install the corresponding stylesheets and images into your project. The stylesheet files should go in the directory static/css

Then you can test your application (make test.byte).

Sending the initial image

To finish the first part of the tutorial, we want to save the current drawing on server side and send the current image when a new user arrives. To do that, we will use the Cairo binding for OCaml.

For using Cairo, first, make sure that it is installed. If you are using the bundle, it should have been configured with the option --enable-cairo. Second, add it to the SERVER_PACKAGES in your Makefile.options:


The draw_server function below is the equivalent of the draw function on the server side and the image_string function outputs the PNG image in a string.

let draw_server =

  let surface = Cairo.Image.create Cairo.Image.ARGB32 ~width ~height in

  let rgb_floats_from_ints (r, g, b) =
    float r /. 255., float g /. 255., float b /. 255. in

  let ctx = Cairo.create surface in
  (fun (rgb, size, (x1, y1), (x2, y2)) ->

    (* Set thickness of brush *)
    Cairo.set_line_width ctx (float size) ;
    Cairo.set_line_join ctx Cairo.JOIN_ROUND ;
    Cairo.set_line_cap ctx Cairo.ROUND ;
    let red, green, blue =  rgb_floats_from_ints rgb in
    Cairo.set_source_rgb ctx ~red ~green ~blue ;

    Cairo.move_to ctx (float x1) (float y1) ;
    Cairo.line_to ctx (float x2) (float y2) ;
    Cairo.Path.close ctx ;

    (* Apply the ink *)
    Cairo.stroke ctx ;

let image_string =
   (fun () ->
     let b = Buffer.create 10000 in
     (* Output a PNG in a string *)
     Cairo.PNG.write_to_stream surface (Buffer.add_string b);
     Buffer.contents b

let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus)

We also define a service that send the picture:

let imageservice =
    (fun () () -> Lwt.return (image_string (), "image/png"))

We now want to load the initial image once the canvas is created. Add the following lines just between the creation of the canvas context and the creation of the slider:

(* The initial image: *)
let img =
    (img ~alt:"canvas"
       ~src:(make_uri ~service:%imageservice ())
img##onload <- Dom_html.handler
                (fun ev -> ctx##drawImage(img, 0., 0.); Js._false);

After compiling the project (make byte and the call to oclosure_req), you are ready to try your graffiti-application by make test.byte.

Note, that the Makefile from the distillery automatically adds the packages defined in SERVER_PACKAGES as an extension in your configuration file local/etc/graffiti/graffiti-test.conf:

<extension findlib-package="cairo2" />

The first version of the program is now complete. You may want now to install the application by running

$ make all
$ sudo make install
Download the code.


  • Add a button to make possible to download the current image and save it to the hard disk (reuse the service imageservice).
  • Add a button with a color picker to select a color from the drawing. Pressing the button changes the mouse cursor, and disables current mouse events until the next mouse click event on the document. Then the color palette changes to the color of the pixel clicked. (Use the function Dom_html.pixel_get).

prev next