Version 0.99.5
This is a preliminary version of the documentation. Help us to improve it by filling tickets. We are looking for native english speakers to proof read the documentation. Contact us!
You now know all Eliom's main concepts. In that part, we'll give more details on some aspects that have been seen before:
The staticmod extension allows to associate
to your site a static directory
where you can put all the static (non generated) parts of your
web-site (for examples images ans stylesheets).
See the default config file ocsigen.conf to
learn how to do that.
A predefined service can be used to make links to static files.
Get it using
(static_dir ~sp).
That service takes as string parameter the name of the file.
For example
Eliom.a
(static_dir ~sp)
sp
[pcdata "download image"]
"ocsigen8-100x30.png"
creates this link: download image
It is now also possible to handle static pages with Eliom, using
Eliompredefmod.Files (see later).
The Eliompredefmod.Blocks module allows to register services that
send portions of pages, of any type that may be contained directly in
a <body> tag (blocks of xhtml DTD).
It is useful to create AJAX pages
(i.e. pages using the XMLHttpRequest Javascript object).
Note that the service returns a list of blocks.
let divpage = Eliompredefmod.Blocks.register_new_service ~path:["div"] ~get_params:unit (fun sp () () -> return [div [h2 [pcdata "Hallo"]; p [pcdata "Blablablabla"] ]])
The Eliompredefmod.SubXhtml module allows to create other modules for
registering portions of pages of other types.
For example, Eliompredefmod.Blocks
is defined by:
module Blocks = SubXhtml(struct type content = Xhtmltypes.body_content end)
The Eliompredefmod.Redirections module allows to register HTTP redirections.
If a request is done towards such a service, the server asks the browser
to retry with another URL. Example:
let redir = Eliompredefmod.Redirections.register_new_service ~path:["redir"] ~get_params:(int "o") (fun sp o () -> return (make_string_uri coucou_params sp (o,(22,"ee"))))
Note that the cost of a redirection is one more query and one more answer.
You may want to register a service that will send files.
To do that, use the Eliompredefmod.Files module. Example:
let sendfile =
Files.register_new_service
~path:["sendfile"]
~get_params:unit
(fun _ () () -> return "filename")
Other example, with suffix URL:
let sendfile2 =
Files.register_new_service
~path:["files"]
~get_params:(suffix (all_suffix "filename"))
(fun _ s () -> return ("path"^(Extensions.string_of_url_path s)))
The extension Staticmod is another way to
handle static files (see the default
configuration file for more information).
You may want to register a service that will send, for instance,
sometimes
an xhtml page, sometimes a file, sometimes something else.
To do that, use the Eliompredefmod.Any module, together
with the send function of the module you want
to use. Example:
let send_any = Eliompredefmod.Any.register_new_service ~path:["sendany"] ~get_params:(string "type") (fun sp s () -> if s = "valid" then Eliompredefmod.Xhtml.send sp (html (head (title (pcdata "")) []) (body [p [pcdata "This page has been statically typechecked. If you change the parameter in the URL you will get an unchecked text page"]])) else Eliompredefmod.HtmlText.send sp "<html><body><p>It is not a valid page. Put type=\"valid\" in the URL to get a typechecked page.</p></body></html>" )
See a valid page, and a non valid page.
You may also use Eliompredefmod.Any to send cookies or to choose a
different charset than the default
(default charset is set in configuration file)
for the page you send. To do that use the optional parameters
?cookies and ?charset of the
send function.
A simplest way to set your own cookies on the client is to use
functions like
Eliompredefmod.Xhtml.Cookies.register instead of
Eliompredefmod.Xhtml.register.
The function you register returns a pair containing the page (as usual)
and a list of cookies, of type Eliomservices.cookie
defined by:
type cookie = | Set of url_path option * float option * string * string | Unset of url_path option * string
The string option is a the path for which you want
to set/unset the cookie (relative to the main directory of your site,
defined
in the configuration file). None means for all your site.
The float option is a the expiration date
(Unix timestamp, in seconds since the epoch).
None means that the cookie will expire when the browser
will be closed.
You can access the cookies sent by the browser using
Eliomsessions.get_cookies sp.
Example:
let cookiename = "mycookie" let cookies = new_service ["cookies"] unit () let _ = Cookies.register cookies (fun sp () () -> return ((html (head (title (pcdata "")) []) (body [p [pcdata (try "cookie value: "^ (Http_frame.Cookievalues.find cookiename (Eliomsessions.get_cookies sp)) with _ -> "<cookie not set>"); br (); a cookies sp [pcdata "send other cookie"] ()]])), [Eliomservices.Set (None, None, cookiename, string_of_int (Random.int 100))]))
Tables of sessions (for data or services) are kept in memory, and thus will disappear if you close the server process. To solve this problem, Ocsigen allows to reload the modules of your configuration file without shutting down the server. Another solution provided by Eliom is to save session data on hard disk.
To reload the modules of the configuration file without
stoping the server, use /etc/init.d/ocsigen reload
for most of the distributions, or do it manually using:
echo reload > /var/run/ocsigen_command.
Only modules loaded inside <site> or
<library> will be reloaded.
Module loaded using <extension> will not.
Have a look at the logs to see if all went well during the reload. If something went wrong, old services may still be reachable.
Note that coservices created with the old modules or URLs that have not been masked by new ones will still reachable after the update.
During the reload, some information of the configuration file will not be re-read (for example port numbers, user and group, etc.).
Eliom allows to use more persistent data, using the module
Ocsipersist. (Ocsipersist is needed in
eliom.cma, thus you need to dynlink it in the
configuration file before Eliom).
There are currently two implementations of Ocsipersist:
ocsipersist-dbm.cma (uses the DBM database) and
ocsipersist-sqlite.cma (uses the SQLite database,
and depends on sqlite3.cma).
These modules allow to:
set_persistent_data, see below).
Note that persistent data are serialized on hard disk using
OCaml's Marshal module:
Suppose for example that you use get/set_persistent_data
(see below) to store a (int, string)
tuple with the user's login credentials. At this point you stop the
server, and change the code such that get/set_persistent_data now to store
a (int, string, string). Now recompile and restart the server. If by any
chance a client with an old cookie reconnects, you get a segfault on the
server, because of the type change in the data stored in the DB backend ...
Ocsipersist allows to create persistent references.
Here is an example of page with a persistent counter:
let mystore = Ocsipersist.open_store "eliomexamplestore2" let count2 = let next = let cthr = Ocsipersist.make_persistent mystore "countpage" 0 in let mutex = Lwt_mutex.create () in (fun () -> cthr >>= (fun c -> Lwt_mutex.lock mutex >>= fun () -> Ocsipersist.get c >>= (fun oldc -> let newc = oldc + 1 in Ocsipersist.set c newc >>= (fun () -> Lwt_mutex.unlock mutex; return newc)))) in register_new_service ~path:["count2"] ~get_params:unit (fun _ () () -> next () >>= (fun n -> return (html (head (title (pcdata "counter")) []) (body [p [pcdata (string_of_int n)]]))))
Ocsipersist also allows to create very basic
persistent tables. Use them if you don't need complex requests
on your tables. Otherwise use a database such as PostgreSQL
or MySQL. Here are the interface you can use:
type 'value table val open_table : string -> 'value table val find : 'value table -> string -> 'value Lwt.t val add : 'value table -> string -> 'value -> unit Lwt.t val remove : 'value table -> string -> unit Lwt.t
As you can see, all these function are cooperative.
Eliom also implements persistent session tables.
You can use them instead of memory tables if you don't need
to register closures.
The following example is a new version of our site
with users, with persistent connections.
(login_box, disconnect_box
and disconnect_action
are the same as
before).
(************************************************************) (************ Connection of users, version 4 ****************) (**************** (persistent sessions) *********************) (************************************************************) let my_persistent_table = create_persistent_table "eliom_example_table" (* -------------------------------------------------------- *) (* We create one main service and two (POST) actions *) (* (for connection and disconnection) *) let persist_session_example = Eliomservices.new_service ~path:["persist"] ~get_params:unit () let persist_session_connect_action = Eliomservices.new_post_service' ~name:"connect4" ~post_params:(string "login") () (* disconnect_action, login_box and disconnect_box have been defined in the section about actions *) (* ----------------------------------------------------------- *) (* Handler for "persist_session_example" service (main page): *) let persist_session_example_handler sp () () = Eliomsessions.get_persistent_session_data ~table:my_persistent_table ~sp () >>= fun sessdat -> return (html (head (title (pcdata "")) []) (body (match sessdat with Data name -> [p [pcdata ("Hello "^name); br ()]; disconnect_box sp "Close session"] Data_session_expired -> [login_box sp true persist_session_connect_action; p [em [pcdata "The only user is 'toto'."]]] No_data -> [login_box sp false persist_session_connect_action; p [em [pcdata "The only user is 'toto'."]]] ))) (* ----------------------------------------------------------- *) (* Handler for persist_session_connect_action (user logs in): *) let persist_session_connect_action_handler sp () login = Eliomsessions.close_session ~sp () >>= fun () -> if login = "toto" (* Check user and password :-) *) then begin Eliomsessions.set_persistent_session_data ~table:my_persistent_table ~sp login >>= fun () -> return [] end else return [Bad_user] (* -------------------------------------------------------- *) (* Registration of main services: *) let () = Eliompredefmod.Xhtml.register ~service:persist_session_example persist_session_example_handler; Eliompredefmod.Actions.register ~service:persist_session_connect_action persist_session_connect_action_handler
As it is not possible to serialize closures, there is no persistent session service table. Be very carefull if you use both persistent session data tables and service session tables, as your session may become inconsistent (use the session service table only for volatile services, like coservices with timeouts).
The idea is complementary to that of
the "session name". While the
optional session_name parameter allows for a single session to have
multiple buckets of data associated with it, a session_group parameter
(also optional) allow multiple sessions to be referenced together.
For most uses, the session group is the user name.
It allows to implement features like "close all sessions" for one user
(even those opened on other browsers), or to limit the number of sessions
one user may open at the same time.
Session groups have been suggested by Dario Teixeira and introduced in Eliom 0.99.5. Dario explains: Consider the following scenario: a user logs in from home using a "Remember me on this computer" feature, which sets a (almost) no-expiration cookie on his browser and session timeouts of infinity on the server. The user goes on vacation, and while logging from a cyber-café, she also sets the "Remember me" option. Back home she realises her mistake, and wishes to do a "global logout", ie, closing all existing sessions associated with her user name.
(************************************************************) (************ Connection of users, version 5 ****************) (************************************************************) (* -------------------------------------------------------- *) (* We create one main service and two (POST) actions *) (* (for connection and disconnection) *) let connect_example5 = Eliomservices.new_service ~path:["groups"] ~get_params:Eliomparameters.unit () let connect_action = Eliomservices.new_post_service' ~name:"connect5" ~post_params:(Eliomparameters.string "login") () (* As the handler is very simple, we register it now: *) let disconnect_action = Eliompredefmod.Actions.register_new_post_service' ~name:"disconnect5" ~post_params:Eliomparameters.unit (fun sp () () -> Eliomsessions.close_session ~sp () >>= fun () -> Lwt.return []) (* -------------------------------------------------------- *) (* login ang logout boxes: *) let disconnect_box sp s = Eliompredefmod.Xhtml.post_form disconnect_action sp (fun _ -> [p [Eliompredefmod.Xhtml.string_input ~input_type:`Submit ~value:s ()]]) () let login_box sp = Eliompredefmod.Xhtml.post_form connect_action sp (fun loginname -> [p (let l = [pcdata "login: "; Eliompredefmod.Xhtml.string_input ~input_type:`Text ~name:loginname ()] in l) ]) () (* -------------------------------------------------------- *) (* Handler for the "connect_example5" service (main page): *) let connect_example5_handler sp () () = let sessdat = Eliomsessions.get_volatile_data_session_group ~sp () in return (html (head (title (pcdata "")) []) (body (match sessdat with Data name -> [p [pcdata ("Hello "^name); br ()]; disconnect_box sp "Close session"] Data_session_expired No_data -> [login_box sp] ))) (* -------------------------------------------------------- *) (* Handler for connect_action (user logs in): *) let connect_action_handler sp () login = Eliomsessions.close_session ~sp () >>= fun () -> Eliomsessions.set_volatile_data_session_group ~set_max:(Some 10) ~sp login; return [] (* -------------------------------------------------------- *) (* Registration of main services: *) let () = Eliompredefmod.Xhtml.register ~service:connect_example5 connect_example5_handler; Eliompredefmod.Actions.register ~service:connect_action connect_action_handler
As we will see later, there are three kinds of sessions (services, volatile data and persistent data). It is highly recommended to set a group for each of them!
Services or coservices with GET parameters can be preapplied to obtain a service without parameters. Example:
let preappl = preapply coucou_params (3,(4,"cinq"))
It is not possible to register something on a preapplied service, but you can use them in links or as fallbacks for coservices.
Fallbacks have access to some information about what succeeded before
they were called. Get this information using
Eliomsessions.get_exn sp; That function returns a list of exceptions.
That list contains Eliommod.Eliom_Link_too_old if the coservice
was not found, and Eliommod.Eliom_Service_session_expired if the "service session" has expired.
It is also possible to tell actions to send information to the page
generated after them. Just place exceptions in the list returned by the
action. These exceptions will also be accessible with
Eliomsessions.get_exn. Try to replace the lines
above (example of session with actions) by:
(************************************************************) (************ Connection of users, version 6 ****************) (************************************************************) (* -------------------------------------------------------- *) (* Actually, no. It's a lie because we don't use the same session name :-) *) (* new disconnect action and box: *) let disconnect_action = Eliompredefmod.Actions.register_new_post_service' ~name:"disconnect6" ~post_params:Eliomparameters.unit (fun sp () () -> Eliomsessions.close_session ~sp () >>= fun () -> return []) let disconnect_box sp s = Eliompredefmod.Xhtml.post_form disconnect_action sp (fun _ -> [p [Eliompredefmod.Xhtml.string_input ~input_type:`Submit ~value:s ()]]) () exception Bad_user (* -------------------------------------------------------- *) (* new login box: *) let login_box sp session_expired action = Eliompredefmod.Xhtml.post_form action sp (fun loginname -> let l = [pcdata "login: "; string_input ~input_type:`Text ~name:loginname ()] in let exnlist = Eliomsessions.get_exn sp in (* If exnlist is not empty, something went wrong during an action. We write an error message: *) [p (if List.mem Bad_user exnlist then (pcdata "Wrong user")::(br ())::l else if session_expired then (pcdata "Session expired")::(br ())::l else l) ]) () (* -------------------------------------------------------- *) (* New handler for connect_action (user logs in): *) let connect_action_handler sp () login = Eliomsessions.close_session ~sp () >>= fun () -> if login = "toto" (* Check user and password :-) *) then begin Eliomsessions.set_volatile_data_session_group ~set_max:(Some 10) ~sp login; return [] end else return [Bad_user]
If the actions raises an exception (with Lwt.fail),
the server will send an error 500 (like for any other service).
Think about catching the exceptions and put them in the list
if they correspond to usual cases you want to handle while
generating the page after the action.
It is possible to set a limit to the number of uses of
(attached or non-attached) coservices. Just give the maximum number
of uses with the optional ?max_use parameter while
creating your coservices. Example
let disposable = new_service ["disposable"] unit () let _ = register disposable (fun sp () () -> let disp_coservice = new_coservice ~max_use:2 ~fallback:disposable ~get_params:unit () in register_for_session sp disp_coservice (fun sp () () -> return (html (head (title (pcdata "")) []) (body [p [pcdata "I am a disposable coservice"; br (); a disp_coservice sp [pcdata "Try me once again"] ()]])) ); return (html (head (title (pcdata "")) []) (body [p [(if List.mem Eliommod.Eliom_Link_too_old (Eliomsessions.get_exn sp) then pcdata "Your link was outdated. I am the fallback. I just created a new disposable coservice. You can use it only twice." else pcdata "I just created a disposable coservice. You can use it only twice."); br (); a disp_coservice sp [pcdata "Try it!"] ()]])))
The default timeout for sessions in one hour. Sessions will be automatically closed after that amount of time of inactivity from the user. You can change that value for your whole site during initialisation using:
Eliomsessions.set_global_volatile_timeout (Some 7200.)
Here 7200 seconds. None means no timeout.
You can change that value for your whole site after initialisation using:
Eliomsessions.set_global_volatile_timeout ~sp (Some 7200.)
You can change that value for one user only using:
Eliomsessions.set_volatile_session_timeout ~sp (Some 7200.)
Note that there is also a possibility to change the default value for Eliom in the configuration file like this:
<extension module="path_to/eliom.cma">
<timeout value="7200"/>
</extension>
value="infinity" means no timeout.
Warning: that default may be overriden by each site using
Eliomsessions.set_global_volatile_timeout or
Eliomsessions.set_default_volatile_timeout.
If you want your user to be able to set the default in the
configuration file for your site (between <site>
and </site>), you must parse the configuration
(Eliomsessions.get_config () function, see below).
It is also possible to put timeouts on coservices using
the optional parameter ?timeout of functions
new_coservice,
new_coservice', etc.
Note that session coservices cannot survive after the end of the session.
Use this if you don't want your coservice to be available during all the
session duration. For example if your coservice is here to show the
results of a search, you probably want it to be available only for
a short time. The following example shows a coservice with timeout
registered in the session table.
let timeout = new_service ["timeout"] unit () let _ = let page sp () () = let timeoutcoserv = register_new_coservice_for_session ~sp ~fallback:timeout ~get_params:unit ~timeout:5. (fun _ _ _ -> return (html (head (title (pcdata "Coservices with timeouts")) []) (body [p [pcdata "I am a coservice with timeout."; br (); pcdata "Try to reload the page!"; br (); pcdata "I will disappear after 5 seconds of inactivity." ]; ]))) in return (html (head (title (pcdata "Coservices with timeouts")) []) (body [p [pcdata "I just created a coservice with 5 seconds timeout."; br (); a timeoutcoserv sp [pcdata "Try it"] (); ]; ])) in register timeout page
If you want to register coservices in the
public table during a session, (that is, after the initialisation
phase of your module), you must add the optional ~sp
parameter to the register function.
Remember that using register without ~sp
is possible only during initialisation!
We recommend to put a timeout on such coservices, otherwise, they will be available until the end of the server process, and it will not be possible to re-create them when the server is relaunched.
The following example is a translation of the previous one using the public table:
let publiccoduringsess = new_service ["publiccoduringsess"] unit () let _ = let page sp () () = let timeoutcoserv = register_new_coservice ~sp ~fallback:publiccoduringsess ~get_params:unit ~timeout:5. (fun _ _ _ -> return (html (head (title (pcdata "Coservices with timeouts")) []) (body [p [pcdata "I am a public coservice with timeout."; br (); pcdata "I will disappear after 5 seconds of inactivity." ]; ]))) in return (html (head (title (pcdata "Public coservices with timeouts")) []) (body [p [pcdata "I just created a public coservice with 5 seconds timeout."; br (); a timeoutcoserv sp [pcdata "Try it"] (); ]; ])) in register publiccoduringsess page
When an exception is raised during the generation of a page,
or when the page has not been found or has wrong parameters,
an HTTP error 500 or 404 is sent to the client. You may want to
catch these exceptions to print your own error page.
Do this using Eliomservices.set_exn_handler.
Here is the handler used by this tutorial:
let _ = Eliomservices.set_exn_handler (fun sp e -> match e with Eliommod.Eliom_404 -> Eliompredefmod.Xhtml.send ~code:404 ~sp (html (head (title (pcdata "")) []) (body [h1 [pcdata "Eliom tutorial"]; p [pcdata "Page not found"]])) Eliommod.Eliom_Wrong_parameter -> Eliompredefmod.Xhtml.send ~sp (html (head (title (pcdata "")) []) (body [h1 [pcdata "Eliom tutorial"]; p [pcdata "Wrong parameters"]])) e -> fail e)
You can add your own options in the configuration file for your Web site. For example:
<eliom module="path_to/yourmodule.cmo">
<youroptions> ...
</eliom>
Use Eliomsessions.get_config () during the initialization
of your module to get the data between
<eliom> and </eliom>.
Warning: parsing these data is very basic for now.
That feature will be improved in the future.
By default, Eliom is using three cookies :
They correspond to three different sessions (opened only if needed).
Eliomsessions.close_session
closes all three sessions, but you may want to desynchronize
the three sessions by using
Eliomsessions.close_persistent_session (persistent session),
Eliomsessions.close_service_session (session services), or
Eliomsessions.close_data_session (volatile data session).
There is also
Eliomsessions.close_volatile_session for both volatile data session and session services.
The module Eliomsessions also contains functions for setting timeouts or expiration dates for cookies for each kind of session.
If you need more sessions (for example several different data sessions)
for the same site, you can give a name to your sessions by giving
the optional parameter ?session_name to functions like
Eliomsessions.close_data_session,
register_for_session, or
Eliomsessions.get_volatile_session_data.
Note that this tutorial has been implemented using this feature,
even if it has been hidden for the sake of simplicity.
That's how the different examples of sessions in this tutorial are
independant.
This section shows more advanced use of page parameters and corresponding forms.
Eliomparameters.regexp allows to parse 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 = Eliompredefmod.Xhtml.register_new_service ~path:["regexp"] ~get_params:(regexp r "$1" "myparam") (fun _ g () -> return (html (head (title (pcdata "")) []) (body [p [pcdata g]])))
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 = register_new_service ~path:["bool"] ~get_params:(bool "case") (fun _ case () -> return << <html> <head><title></title></head> <body> <p> $pcdata (if case then "checked" else "not checked")$ </p> </body> </html> >>) let create_form_bool casename = <:xmllist< <p>check? $bool_checkbox ~name:casename ()$ <br/> $string_input ~input_type:`Submit ~value:"Click" ()$</p> >> let form_bool = register_new_service ["formbool"] unit (fun sp () () -> let f = get_form bool_params sp create_form_bool in return << <html> <head><title></title></head> <body> $f$ </body> </html> >>)
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 are tried in order of registration! The first matching service will answer.
Other types similar to bool:
Eliomparameters.opt (page taking an optional parameter),
Eliomparameters.sum (either a parameter or another).
See the interface here.
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 Eliomparameters.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 = register_new_service ~path:["set"] ~get_params:(set string "s") (fun _ l () -> let ll = List.map (fun s -> << <strong>$str:s$ </strong> >>) l in 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 = register_new_service ~path:["setform"] ~get_params:unit (fun sp () () -> return (html (head (title (pcdata "")) []) (body [h1 [pcdata "Set Form"]; get_form set sp (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" ()]]) ])))
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 firstly registered service that matches will answer.
Here is an example of a select box.
let select_example_result = register_new_service ~path:["select"] ~get_params:(string "s") (fun sp g () -> return (html (head (title (pcdata "")) []) (body [p [pcdata "You selected: "; strong [pcdata g]]]))) let create_select_form = (fun select_name -> [p [pcdata "Select something: "; Eliompredefmod.Xhtml.string_select ~name:select_name (Eliompredefmod.Xhtml.Option ([] (* attributes *), "Bob" (* value *), None (* Content, if different from value *), false (* not selected *))) (* first line *) [Eliompredefmod.Xhtml.Option ([], "Marc", None, false); (Eliompredefmod.Xhtml.Optgroup ([], "Girls", ([], "Karin", None, false), [([a_disabled `Disabled], "Juliette", None, false); ([], "Alice", None, true); ([], "Germaine", Some (pcdata "Bob's mother"), false)]))] ; Eliompredefmod.Xhtml.string_input ~input_type:`Submit ~value:"Send" ()]]) let select_example = register_new_service ["select"] unit (fun sp () () -> let f = Eliompredefmod.Xhtml.get_form select_example_result sp create_select_form in return (html (head (title (pcdata "")) []) (body [f])))
To do "multiple" select boxes, use functions like
Eliompredefmod.Xhtml.string_multiple_select.
As you can see in the type, the service must be declared with parameters
of type set.
Here is an example of clickable image. You receive the coordinates the user clicked on.
let coord = register_new_service ~path:["coord"] ~get_params:(coordinates "coord") (fun _ c () -> 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 = register_new_service ~path:["imageform"] ~get_params:unit (fun sp () () -> return (html (head (title (pcdata "")) []) (body [h1 [pcdata "Image Form"]; get_form coord sp (fun n -> [p [image_input ~src:(make_uri ~service:(static_dir sp) ~sp ["ocsigen5.png"]) ~name:n ()]]) ])))
You may also send a value with the coordinates:
let coord2 = register_new_service ~path:["coord2"] ~get_params:(int_coordinates "coord") (fun _ (i, c) () -> 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 = register_new_service ~path:["imageform2"] ~get_params:unit (fun sp () () -> return (html (head (title (pcdata "")) []) (body [h1 [pcdata "Image Form"]; get_form coord2 sp (fun n -> [p [int_image_input ~src:(make_uri ~service:(static_dir sp) ~sp ["ocsigen5.png"]) ~name:n ~value:3 ()]]) ])))
list
Another way (than Eliomparameters.set) to do variable length forms
is to use indexed lists (using Eliomparameters.list).
The use of that feature is a bit more complex than set
and still experimental.
Here is an example of service taking an indexed list as parameter:
(* lists *) let coucou_list = register_new_service ~path:["coucou"] ~get_params:(list "a" (string "str")) (fun _ l () -> let ll = List.map (fun s -> << <strong>$str:s$</strong> >>) l in 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 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 2 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 *) f.it (fun stringname v -> <:xmllist< <p>Write the value for $str:v$: $string_input ~input_type:`Text ~name:stringname ()$ </p> >>) ["one";"two";"three";"four"] <:xmllist< <p>$string_input ~input_type:`Submit ~value:"Click" ()$</p>