Creating links and forms

To create a link (<a>), use the Eliom_content.​Html5.​D.​a function, as in these examples:

open Eliom_content

 let links = Eliom_registration.Html5.register_service ["rep";"links"] Eliom_parameter.unit
 (fun () () ->
   Lwt.return
    Html5.D.(
      html
       (head (title (pcdata "Links")) [])
       (body
         [p
          [a coucou [pcdata "coucou"] (); br ();
           a hello [pcdata "hello"] (); br ();
           a default
             [pcdata "default page of the dir"] (); br ();
           a uasuffix
             [pcdata "uasuffix"] (2007,6); br ();
           a coucou_params
             [pcdata "coucou_params"] (42,(22,"ciao")); br ();
           a raw_serv
             [pcdata "raw_serv"] [("sun","yellow");("sea","blue and pink")]; br ();
           a
             (Eliom_service.Http.external_service
                ~prefix:"http://fr.wikipedia.org"
                ~path:["wiki";""]
                ~get_params:(suffix (all_suffix "suff"))
                ())
             [pcdata "OCaml on wikipedia"]
             ["OCaml"]; br ();
           Raw.a
             ~a:[a_href (Raw.uri_of_string "http://en.wikipedia.org/wiki/OCaml")]
             [pcdata "OCaml on wikipedia"]
         ]])))

See http://tests.ocsigen.org/rep/links.

Eliom_content.​Html5.​D.​a takes as first parameter the service you want to link to. The third parameter is the text of the link. The last parameter is for GET parameters you want to put in the link. The type of this parameter and the name of GET parameters depend on the service you link to.

The links to Wikipedia shows how to define an external service (here it uses a suffix URL). For an external service without parameters, you can use the low level function Eliom_content.​Html5.​D.​Raw.​a, if you don't want to create an external service explicitly. Note that the path must be a list of strings: do not write ["foo/bar"], but ["foo";"bar"], otherwise, the "/" will be encoded in the URL.

If you want to create (mutually or not) recursive pages, create the service using Eliom_service.​App.​service or Eliom_service.​Http.​service first, then register it in the table using (for example) Eliom_registration.​Html5.​register:

let linkrec = Eliom_service.Http.service ["linkrec"] Eliom_parameter.unit ()

let _ = Eliom_registration.Html5.register linkrec
    (fun () () ->
      Lwt.return
       (html
        (head (title (pcdata "")) [])
        (body [p [a linkrec [pcdata "click"] ()]])))

Seehttp://tests.ocsigen.org./linkrec.

(But you can also refer to the current service as Eliom_service.​void_coservice'.)

Forms

Forms towards GET services

The function Eliom_content.​Html5.​D.​get_form allows creation of forms that use the GET method (parameters in the URL). It works like Eliom_content.​Html5.​D.​a but takes a function that creates the form from the parameters names as parameter.

let create_form =
  (fun (number_name, (number2_name, string_name)) ->
    Html5.D.(
      [p [pcdata "Write an int: ";
        int_input ~input_type:`Text ~name:number_name ();
        pcdata "Write another int: ";
        int_input ~input_type:`Text ~name:number2_name ();
        pcdata "Write a string: ";
        string_input ~input_type:`Text ~name:string_name ();
        string_input ~input_type:`Submit ~value:"Click" ()]]
    ))

let form = Eliom_registration.Html5.register_service ["form"] unit
  (fun () () ->
     let f = Html5.D.get_form coucou_params create_form in
     Lwt.return
       (html
         (head (title (pcdata "")) [])
         (body [f])))

See http://tests.ocsigen.org/form to see the function form>> in action]].

If you want to use typed parameters, you cannot use functions like Html5.​M.​input to create your forms (but you can use it if you want to use parameters defined with Eliom_parameter.​any). Indeed, parameter names are typed to force them be used properly. In our example, number_name has type int param_name and must be used with int_input (or other widgets), whereas string_name has type string param_name and must be used with string_input (or other widgets). All functions for creating form widgets are detailed in Eliom_content.​Html5.​D (and Eliom_content.​Html5.​D).

For untyped forms, you may use functions from Html5.​M or functions from the module Eliom_content.​Html5.​D.​Raw. Here is a form linking to our (untyped) service raw_serv.

let raw_form =
  Eliom_registration.Html5.register_service
    ~path:["anyform"]
    ~get_params:Eliom_parameter.unit
    (fun () () ->
      Lwt.return
        Html5.D.(html
                   (head (title (pcdata "")) [])
                   (body
                      [h1 [pcdata "Any Form"];
                       get_form raw_serv
                         (fun () ->
                           [p [pcdata "Form to raw_serv: ";
                               raw_input ~input_type:`Text ~name:"plop" ();
                               raw_input ~input_type:`Text ~name:"plip" ();
                               raw_input ~input_type:`Text ~name:"plap" ();
                               raw_input ~input_type:`Submit ~value:"Click" ()]])
                        ])))

Try this form. >>

POST parameters

By default web page parameters are transferred in the URL (GET parameters). A web page may also expect POST parameters (that is, parameters that are not in the URL but in the body of the HTTP request, if the POST method is used).

let no_post_param_service =
  Eliom_registration.Html5.register_service
    ~path:["post"]
    ~get_params:Eliom_parameter.unit
    (fun () () ->
      Lwt.return
        (html
         (head (title (pcdata "")) [])
         (body [h1 [pcdata
                      "Version of the page without POST parameters"]])))

let my_service_with_post_params =
  Eliom_registration.Html5.register_post_service
    ~fallback:no_post_param_service
    ~post_params:Eliom_parameter.(string "value")
    (fun () value ->
      Lwt.return
        (html
         (head (title (pcdata "")) [])
         (body [h1 [pcdata value]])))

Services may take both GET and POST parameters:

let get_no_post_param_service =
  Eliom_registration.Html5.register_service
    ~path:["post2"]
    ~get_params:Eliom_parameter.(int "i")
    (fun i () ->
      Lwt.return
        (html
         (head (title (pcdata "")) [])
         (body [p [pcdata "No POST parameter, i:";
                   em [pcdata (string_of_int i)]]])))

let my_service_with_get_and_post = Eliom_registration.Html5.register_post_service
  ~fallback:get_no_post_param_service
  ~post_params:Eliom_parameter.(string "value")
  (fun i value ->
    Lwt.return
      (html
         (head (title (pcdata "")) [])
         (body [p [pcdata "Value: ";
                   em [pcdata value];
                   pcdata ", i: ";
                   em [pcdata (string_of_int i)]]])))

POST forms

To create a POST form, use the Eliom_content.​Html5.​D.​post_form function. It is similar to Eliom_content.​Html5.​D.​get_form with an additional parameter for the GET parameters you want to put in the URL (if any). Here, form2 is a page containing a form to the service post (using Html5.F's functions) and form3 (defined using the syntax extension) contains a form to post2, with a GET parameter. form4 is a form to an external page.

let form2 = Eliom_registration.Html5.register_service ["form2"] Eliom_parameter.unit
  (fun () () ->
     let f =
       (Eliom_content.Html5.D.post_form my_service_with_post_params
          (fun chaine ->
            [p [pcdata "Write a string: ";
                string_input ~input_type:`Text ~name:chaine ()]]) ()) in
     Lwt.return
       (html
         (head (title (pcdata "form")) [])
         (body [f])))

let form3 = Eliom_registration.Html5.register_service ["form3"] Eliom_parameter.unit
  (fun () () ->
     let module Html5 = Html5.D in
     let f  =
       (Eliom_content.Html5.D.post_form my_service_with_get_and_post
          (fun chaine ->
            <:html5list< <p> Write a string:
                    $string_input ~input_type:`Text ~name:chaine ()$ </p> >>)
          222) in
     Lwt.return
       << <html>
            <head><title></title></head>
            <body>$f$</body></html> >>)

let form4 = Eliom_registration.Html5.register_service ["form4"] Eliom_parameter.unit
  (fun () () ->
     let module Html5 = Eliom_content.Html5.D in
     let f  =
       (Eliom_content.Html5.D.post_form
          (external_post_service
             ~prefix:"http://www.petizomverts.com"
             ~path:["zebulon"]
             ~get_params:(int "i")
             ~post_params:(string "chaine") ())
          (fun chaine ->
            <:html5list< <p> Write a string:
                     $string_input ~input_type:`Text ~name:chaine ()$ </p> >>)
          222) in
     Lwt.return
       (html
        (head (title (pcdata "form")) [])
        (body [f])))

See the urls: post without parameter, post2 without POST parameter, form2, form3, form4.

Advanced forms and parameters

This section shows more advanced use of page parameters and corresponding forms.

Parsing parameters using regular expressions

Eliom_parameter.regexp allows parsing page parameters using (Perl-compatible) regular expressions. We use the module Netstring_pcre, from OCamlnet. See the documentation about OCamlnet for more information. The following example shows a service that accepts only parameters values enclosed between [ and ]:

let r = Netstring_pcre.regexp "\\\\[(.*)\\\\]"

let regexp =
  Eliom_registration.Html5.register_service
    ~path:["regexp"]
    ~get_params:Eliom_parameter.(regexp r "$1" (fun s -> s) "myparam")
    (fun g () ->
      Lwt.return
        Html5.D.(html
                  (head (title (pcdata "")) [])
                  (body [p [pcdata g]])))
let myregexp = Netstring_pcre.regexp "\\[(.*)\\]"

let regexpserv =
  Eliom_registration.Html5.register_service
    ~path:["regexp"]
    ~get_params:Eliom_parameter.(regexp myregexp "$1" (fun s -> s) "myparam")
    (fun g () ->
      Lwt.return
        Html5.D.(html
                  (head (title (pcdata "")) [])
                  (body [p [pcdata g]])))

Try it.

Boolean checkboxes

Page may take parameter of type bool. A possible use of this type is in a form with boolean checkboxes, as in the example below:

(* Form with bool checkbox: *)
let bool_params = Eliom_registration.Html5.register_service
    ~path:["bool"]
    ~get_params:Eliom_parameter.(bool "case")
  (fun case () ->
    let module Html5 = Html5.D in
    Lwt.return
      << <html>
           <head><title></title></head>
           <body>
           <p>
             $pcdata (if case then "checked" else "not checked")$
           </p>
           </body>
         </html> >>)

let create_form_bool casename =
    let module Html5 = Html5.D in
    <:html5list< <p>check? $bool_checkbox ~name:casename ()$ <br/>
      $string_input ~input_type:`Submit ~value:"Click" ()$</p> >>

let form_bool = Eliom_registration.Html5.register_service ["formbool"] unit
  (fun () () ->
     let module Html5 = Html5.D in
     let f = get_form bool_params create_form_bool in
     Lwt.return
       << <html>
            <head><title></title></head>
            <body> $f$ </body>
          </html> >>)

Try it.

Important warning: As you can see, browsers do not send any value for unchecked boxes! An unchecked box is equivalent to no parameter at all! Thus it is not possible to distinguish between a service taking a boolean and a service taking no parameter at all (if they share the same URL). In Eliom services with higher priority are tried first, and then they are tried in order of registration. The first matching service will answer.

Other types similar to bool:

See Eliom_parameter.

Type set

Page may take several parameters of the same name. It is useful when you want to create a form with a variable number of fields. To do that with Eliom, use the type Eliom_parameter.​set. For example set int "val" means that the page will take zero, one or several parameters of name "val", all of type int. The function you register will receive the parameters in a list. Example:

let set = Eliom_registration.Html5.register_service
    ~path:["set"]
    ~get_params:Eliom_parameter.(set string "s")
  (fun l () ->
    let module Html5 = Html5.D in
    let ll =
      List.map
        (fun s -> << <strong>$str:s$ </strong> >>) l
    in
    Lwt.return
    << <html>
         <head><title></title></head>
         <body>
         <p>
           You sent:
           $list:ll$
         </p>
         </body>
       </html> >>)

These parameters may come from several kinds of widgets in forms. Here is an example of a form with several checkboxes, all sharing the same name, but with different values:

(* form to set *)
let setform = Eliom_registration.Html5.register_service
    ~path:["setform"]
    ~get_params:Eliom_parameter.unit
    (fun () () ->
      Lwt.return
        (html
           (head (title (pcdata "")) [])
           (body [h1 [pcdata "Set Form"];
                  get_form set
                    (fun n ->
                      [p [pcdata "Form to set: ";
                          string_checkbox ~name:n ~value:"box1" ();
                          string_checkbox
                            ~name:n ~value:"box2" ~checked:true ();
                          string_checkbox ~name:n ~value:"box3" ();
                          string_checkbox ~name:n ~value:"box4" ();
                          string_input ~input_type:`Submit ~value:"Click" ()]])
                ])))

Try it.

Once again, note that there is no difference between an empty set or no parameter at all. If you register a service without parameters and a service with a set of parameters on the same URL, the service with higher priority, or the firstly registered service that matches, will answer.

Select

Here is an example of a select box.

let select_example_result = Eliom_registration.Html5.register_service
    ~path:["select"]
    ~get_params:Eliom_parameter.(string "s")
    (fun g () ->
      Lwt.return
        (html
           (head (title (pcdata "")) [])
           (body [p [pcdata "You selected: ";
                     strong [pcdata g]]])))

let create_select_form =
  (fun select_name ->
    Html5.D.(
      [p [pcdata "Select something: ";
        string_select ~name:select_name
          (Option ([] (* attributes *),
                   "Bob" (* value *),
                   None (* Content, if different from value *),
                   false (* not selected *))) (* first line *)
          [Option ([], "Marc", None, false);
          (Optgroup
          ([],
           "Girls",
           ([], "Karin", None, false),
           [([a_disabled `Disabled], "Juliette", None, false);
            ([], "Alice", None, true);
            ([], "Germaine", Some (pcdata "Bob's mother"), false)]))]
          ;
        string_input ~input_type:`Submit ~value:"Send" ()]]
    ))

let select_example = Eliom_registration.Html5.register_service ["select"] Eliom_parameter.unit
  (fun () () ->
     let open Html5.D in
     let f =
       get_form
         select_example_result create_select_form
     in
     Lwt.return
       (html
         (head (title (pcdata "")) [])
         (body [f])))

Try it.

To do "multiple" select boxes, use functions like Eliom_content.​Html5.​D.​string_multiple_select. As you can see in the type, the service must be declared with parameters of type Eliom_parameter.​set.

Clickable images

Here is an example of clickable image. You receive the coordinates the user clicked on.

let coord = Eliom_registration.Html5.register_service
    ~path:["coord"]
    ~get_params:Eliom_parameter.(coordinates "coord")
  (fun c () ->
    let module Html5 = Html5.D in
    Lwt.return
      << <html>
           <head><title></title></head>
           <body>
           <p>
             You clicked on coordinates:
             ($str:(string_of_int c.abscissa)$, $str:(string_of_int c.ordinate)$)
           </p>
           </body>
         </html> >>)

(* form to image *)
let imageform = Eliom_registration.Html5.register_service
    ~path:["imageform"]
    ~get_params:Eliom_parameter.unit
    (fun () () ->
      Lwt.return
        (html
           (head (title (pcdata "")) [])
           (body [h1 [pcdata "Image Form"];
                  get_form coord
                    (fun n ->
                      [p [image_input
                            ~src:(make_uri ~service:(Eliom_service.static_dir ()) ["ocsigen5.png"])
                            ~name:n
                            ()]])
                ])))

Try it.

You may also send a value with the coordinates:

let coord2 = Eliom_registration.Html5.register_service
    ~path:["coord2"]
    ~get_params:Eliom_parameter.(int_coordinates "coord")
  (fun (i, c) () ->
    let module Html5 = Html5.D in
    Lwt.return
      << <html>
           <head><title></title></head>
           <body>
           <p>
             You clicked on coordinates:
             ($str:(string_of_int c.abscissa)$, $str:(string_of_int c.ordinate)$)
           </p>
           </body>
         </html> >>)

(* form to image *)
let imageform2 = Eliom_registration.Html5.register_service
    ~path:["imageform2"]
    ~get_params:Eliom_parameter.unit
    (fun () () ->
      Lwt.return
        (html
           (head (title (pcdata "")) [])
           (body [h1 [pcdata "Image Form"];
                  get_form coord2
                    (fun n ->
                      [p [int_image_input
                            ~src:(make_uri ~service:(Eliom_service.static_dir ()) ["ocsigen5.png"])
                            ~name:n
                            ~value:3
                            ()]])
                ])))

Try it.

Type list

Another way (than Eliom_parameter.​set) to do variable length forms is to use indexed lists (using Eliom_parameter.​list). The use of that feature is a bit more complex than set. Here is an example of service taking an indexed list as parameter:

(* lists *)
let coucou_list = Eliom_registration.Html5.register_service
    ~path:["coucou"]
    ~get_params:Eliom_parameter.(list "a" (string "str"))
  (fun l () ->
    let module Html5 = Html5.D in
    let ll =
      List.map (fun s -> << <strong>$str:s$</strong> >>) l in
      Lwt.return
        << <html>
             <head><title></title></head>
             <body>
             <p>
               You sent:
               $list:ll$
             </p>
             </body>
           </html> >>)

Here is an example of link towards this service: coucou?a.str[0]=toto&a.str[1]=titi.

Warning: As for sets or bools, if a request has no parameter, it will be considered as the empty list. Services with higher priority are tried first, otherwise they are tried in order of registration.

As you see, the names of each list element is built from the name of the list, the name of the list element, and an index. To spare you creating yourself these names, Eliom provides you an iterator to create them.

(* Form with list: *)
let create_listform f =
  (* Here, f.it is an iterator like List.map,
     but it must be applied to a function taking 3 arguments
     (unlike 1 in map), the first one being the name of the parameter,
     and the second one the element of list.
     The last parameter of f.it is the code that must be appended at the
     end of the list created
   *)
  let module Html5 = Eliom_content.Html5.D in
  f.it (fun stringname v init ->
    <:html5list< <p>Write the value for $str:v$:
      $string_input ~input_type:`Text ~name:stringname ()$ </p> >>@init)
    ["one";"two";"three";"four"]
    <:html5list< <p>$string_input ~input_type:`Submit ~value:"Click" ()$</p> >>

let listform = Eliom_registration.Html5.register_service ["listform"] Eliom_parameter.unit
  (fun () () ->
     let f = get_form coucou_list create_listform in
     let module Html5 = Eliom_content.Html5.D in
     Lwt.return
       << <html>
            <head><title></title></head>
            <body> $f$ </body>
          </html> >>)

Try it.

Important warning: As we have seen in the section about boolean (or optional) parameters, it is not possible to distinguish between a boolean with value "false", and no parameter at all. This causes problems if you create a list of boolean or optional values, as it is not possible to know the length of the list. In that case, Eliom always takes the shortest possible list.

Forms and suffixes

Service with "suffix" URLs have an equivalent version with usual parameters, allowing creation of forms towards such services. Example:

(* Form for service with suffix: *)
let create_suffixform ((suff, endsuff),i) =
     let module Html5 = Eliom_content.Html5.D in
    <:html5list< <p>Write the suffix:
      $int_input ~input_type:`Text ~name:suff ()$ <br/>
      Write a string: $user_type_input
      (Ocsigen_lib.Url.string_of_url_path ~encode:false)
         ~input_type:`Text ~name:endsuff ()
         $ <br/>
      Write an int: $int_input ~input_type:`Text ~name:i ()$ <br/>
      $string_input ~input_type:`Submit ~value:"Click" ()$</p> >>

let suffixform = Eliom_registration.Html5.register_service ["suffixform"] Eliom_parameter.unit
  (fun () () ->
     let f = get_form isuffix create_suffixform in
     let module Html5 = Eliom_content.Html5.D in
     Lwt.return
      << <html>
           <head><title></title></head>
           <body> $f$ </body>
         </html> >>)

Try it.

Uploading files

The Eliom_parameter.​file parameter type allows files to be sent in your request. The service gets something of type Ocsigen_extensions.​file_info. You can extract information using this using these functions (from Eliom_request_info):

val get_tmp_filename : Ocsigen_extensions.file_info -> string
val get_filesize : Ocsigen_extensions.file_info -> int64
val get_original_filename : Ocsigen_extensions.file_info -> string

Eliom_request_info.​get_tmp_filename returns the actual name of the uploaded file on the hard drive. Eliom_request_info.​get_original_filename gives the original filename.

To make possible the upload of files, you must configure a directory for uploaded files in Ocsigen's configuration file. For example:

<uploaddir>/tmp</uploaddir>

Files are kept in this directory only while processing the request. Then they are automatically cancelled. Thus your services must copy them somewhere else themselves if they want to keep them. In the following example, we create a new hard link to the file to keep it (the destination must be on the same partition of the disk).

let upload = Eliom_service.Http.service
    ~path:["upload"]
    ~get_params:unit
    ()

let upload2 = Eliom_registration.Html5.register_post_service
   ~fallback:upload
   ~post_params:Eliom_parameter.(file "file")
    (fun () file ->
      let to_display =
        let newname = "/tmp/thefile" in
        (try
          Unix.unlink newname;
        with _ -> ());
        Lwt_log.ign_debug (Eliom_request_info.get_tmp_filename file);
        Unix.link (Eliom_request_info.get_tmp_filename file) newname;
        let fd_in = open_in newname in
        try
          let line = input_line fd_in in close_in fd_in; line (*end*)
        with End_of_file -> close_in fd_in; "vide"
      in
      Lwt.return
        (html
           (head (title (pcdata "Upload")) [])
           (body [h1 [pcdata to_display]])))


let uploadform = Eliom_registration.Html5.register upload
    (fun () () ->
      let f =
        (post_form upload2
           (fun file ->
             [p [file_input ~name:file ();
                 br ();
                 string_input ~input_type:`Submit ~value:"Send" ()
               ]]) ()) in
      Lwt.return
        (html
           (head (title (pcdata "form")) [])
           (body [f])))

Try it (warning: uploading on ocsigen.org is forbidden).

Raw POST data (advanced use)

By specifying ~post_params:Eliom_parameter.raw_post_params, it is possible to create a service that takes as parameter any POST data, as a stream. The only restriction is that it does not work if the content-type corresponds to URL encoded form data or multipart data (because in these cases, there are POST parameters, which are decoded by Eliom to find the service).

See the API reference for more information.