diff --git a/2.2/manual/application.html b/2.2/manual/application.html deleted file mode 100644 index f5d0da17..00000000 --- a/2.2/manual/application.html +++ /dev/null @@ -1,598 +0,0 @@ -Writing a client/server Eliom application

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 with the 2.2 release of -the Ocsigen bundle.
-

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.

The final code is available for download.

Basics

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 Lwt
-open Eliom_content.Html5.D
-open Eliom_service
-open Eliom_parameter
-open Eliom_registration.Html5
-
-let main_service =
-  register_service ~path:["graff"] ~get_params:unit
-    (fun () () -> return (html (head (title (pcdata "Page title")) [])
-                               (body [h1 [pcdata "Graffiti"]])))

The same, written with fully qualified names (without open): -

let main_service =
-  Eliom_registration.Html5.register_service
-    ~path:["graff"] 
-    ~get_params:Eliom_parameter.unit
-    (fun () () ->
-      Lwt.return
-        (Eliom_content.Html5.D.html
-          (Eliom_content.Html5.D.head (Eliom_content.Html5.D.title
-					 (Eliom_content.Html5.D.pcdata "")) [])
-          (Eliom_content.Html5.D.body
-	     [Eliom_content.Html5.D.h1
-		 [Eliom_content.Html5.D.pcdata "Graffiti"]])))

Copy one of the two pieces of code above in a file graffiti.ml, -then compile it with the following command ; it produce a file called -graffiti.cmo. -

ocamlfind ocamlc -thread -package eliom.server -c graffiti.ml
-

Download the configuration file graffiti.conf, then launch -Ocsigen server, with the following command: -

ocsigenserver -c graffiti.conf
-

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 that, 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"
-    end)

It is now possible to use MyAppl instead of -Eliom_registration.Html5 for registering our main service -(now at URL /): -

let main_service =
-  My_app.register_service ~path:[""]
-    ~get_params:Eliom_parameter.unit
-    (fun () () ->
-      Lwt.return
-        (html
-           (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, -

{client{
-  let _ = Dom_html.window##alert(Js.string "Hello")
-}}

Download the Makefile -and graffiti.conf -files. You may want to adapt the configuration file to set some paths, -especially the directory for static files (see also STATICDIR in -the Makefile). It is not necessary (and even not a good idea, for -security reasons) to put .ml and .cma/.cmo in the -static files directory! -

The default Makefile recognizes files using Eliom's syntax extension by -their extension .eliom (instead of .ml). -Put the new version of our program in a file named graffiti.eliom -and compile it by typing: -

make
-

This will generate a file called graffiti.cma and another one -called graffiti.js. The latter must be placed in the static -file directory. To copy it to its rightful place, type: -

make install
-

And finaly, run Ocsigen server: -

ocsigenserver -c graffiti.conf
-

Your page is now available at URL http://localhost:8080/. It -should open an alert box. If not, check that the static file directory -in the configuration file is correct. -

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 =
-  My_app.register_service ~path:[""] ~get_params:Eliom_parameter.unit
-    (fun () () ->
-      let count = incr count; !count in
-      Eliom_service.onload {{
-        Dom_html.window##alert(Js.string
-	  (Printf.sprintf "You came %i times to this page" %count))
-      }};
-      Lwt.return
-        (html
-           (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 the client execute the event -handler registred with Eliom_service.onload : this client side code access to the -counter using the syntax extension %count, and displays it in a -message box. -

>> -

Collaborative drawing application

Drawing on a canvas

We now want to draw something on the page using an HTML5 canvas. As a -first-step, we define a client-side function called draw that -draw a line between two given points in a canvas and we call this -function once in the onload event handler to draw an orange line. Here is the (full) new version -of the program: -

module My_app =
-  Eliom_registration.App (
-    struct
-      let application_name = "graffiti"
-    end)
-
-{shared{
-  open Eliom_content
-  open Eliom_content.Html5.D
-  let width = 700
-  let height = 400
-}}
-
-{client{
-  open Eliom_content
-  let draw ctx (color, size, (x1, y1), (x2, y2)) =
-    ctx##strokeStyle <- (Js.string color);
-    ctx##lineWidth <- float size;
-    ctx##beginPath();
-    ctx##moveTo(float x1, float y1);
-    ctx##lineTo(float x2, float y2);
-    ctx##stroke()
-}}
-
-let canvas_elt =
-  canvas ~a:[a_width width; a_height height]
-    [pcdata "your browser doesn't support canvas"]
-
-let page =
-  (html
-    (head (title (pcdata "Graffiti")) [])
-    (body [h1 [pcdata "Graffiti"];
-           canvas_elt] ) )
-
-let onload_handler = {{
-  let canvas = Html5.To_dom.of_canvas %canvas_elt in
-  let ctx = canvas##getContext (Dom_html._2d_) in
-  ctx##lineCap <- Js.string "round";
-  draw ctx ("#ffaa33", 12, (10, 10), (200, 100))
-}}
-
-let main_service =
-  My_app.register_service ~path:[""] ~get_params:Eliom_parameter.unit
-    (fun () () ->
-      Eliom_service.onload onload_handler;
-      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 Js_of_ocaml.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. Another idea is to use the expressivity allowed by the -functional part of OCaml to hide the complexity behind a nice -combinator library and dramaticaly reduce the code size. In this -tutorial, we will use the experimental Event_arrows module from the -Js_of_ocaml's library. -

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

let onload_handler = {{
-
-  let canvas = 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
-  in
-
-  let compute_line ev =
-    let oldx = !x and oldy = !y in
-    set_coord ev;
-    ("#ff9933", 5, (oldx, oldy), (!x, !y))
-  in
-
-  let line ev = draw ctx (compute_line ev) in
-
-  let open Event_arrows in
-  ignore (run (mousedowns canvas
-                 (arr (fun ev -> set_coord ev; line ev) >>>
-                  first [mousemoves Dom_html.document (arr line);
-                         mouseup Dom_html.document >>> (arr line)])) ())
-}}

We use two references x and y to record the last mouse -position. The function set_coord updates these 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 implements 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 and coordinates of drawn lines. -

{shared{
-  type messages = (string * 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's bus to broadcast draw orders to each 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 of the onload_handler by: -

let line ev =
-  let v = compute_line ev in
-  let _ = Eliom_bus.write %bus v in
-  draw ctx v in

Finally, to interpret the draw orders read on the bus, we add the -following line in the onload_handler just before the "arrow -event handler". -

let _ = Lwt_stream.iter (draw ctx) (Eliom_bus.stream %bus) in

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 that we used add two widgets provided by the -OClosure widget library. -

For using OClosure, you have to make sure that it is installed. If you are -using the bundle, it should have been configured with the option –enable-oclosure. -Cf. the corresponding bundle documentation. -

To create the widgets, we add the following code in the -onload_handler immediatly after canvas configuration: -

(* Size of the brush *)
-let slider = jsnew Goog.Ui.slider(Js.null) in
-slider##setMinimum(1.);
-slider##setMaximum(80.);
-slider##setValue(10.);
-slider##setMoveToPointEnabled(Js._true);
-slider##render(Js.some Dom_html.document##body);
-(* The color palette: *)
-let pSmall =
-  jsnew Goog.Ui.hsvPalette(Js.null, Js.null,
-                           Js.some (Js.string "goog-hsv-palette-sm"))
-in
-pSmall##render(Js.some Dom_html.document##body);

And to change the size and the color of the brush, we replace the last -line of the function compute_line of the onload_handler -by: -

let color = Js.to_string (pSmall##getColor()) in
-let size = int_of_float (Js.to_float (slider##getValue())) in
-(color, size, (oldx, oldy), (!x, !y))

As last step, we need to add some stylesheets and one JS file in the -headers of our page: -

let page =
-  html
-    (head
-       (title (pcdata "Graffiti"))
-       [ css_link
-	   ~uri:(make_uri (Eliom_service.static_dir ())
-		  ["css";"common.css"]) ();
-	 css_link
-	   ~uri:(make_uri (Eliom_service.static_dir ())
-		  ["css";"hsvpalette.css"]) ();
-	 css_link
-	   ~uri:(make_uri (Eliom_service.static_dir ())
-		  ["css";"slider.css"]) ();
-	 css_link
-	   ~uri:(make_uri (Eliom_service.static_dir ())
-		  ["css";"graffiti.css"]) ();
-	 js_script
-	   ~uri:(make_uri  (Eliom_service.static_dir ())
-		  ["graffiti_oclosure.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: -

the following image should go into static/images: -

Finally, the graffiti_oclosure.js script is generated according to the -graffiti.js with the following command: -

oclosure_req graffiti.js
-

Using the example {{{Makefile}}} all -those static files are generated and installed by the install -rule in the configured static directory (see the STATICDIR -variable). -

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, make sure that it is installed. If you are using the bundle, -it should have been configured with the option –enable-cairo. -

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 rgb_from_string color = (* color is in format "#rrggbb" *)
-  let get_color i =
-    (float_of_string ("0x"^(String.sub color (1+2*i) 2))) /. 255.
-  in
-  try get_color 0, get_color 1, get_color 2 with | _ -> 0.,0.,0.
-
-let draw_server, image_string =
-  let surface =
-    Cairo.image_surface_create Cairo.FORMAT_ARGB32 ~width ~height
-  in
-  let ctx = Cairo.create surface in
-  ((fun ((color : string), size, (x1, y1), (x2, y2)) ->
-
-    (* Set thickness of brush *)
-    Cairo.set_line_width ctx (float size) ;
-    Cairo.set_line_join ctx Cairo.LINE_JOIN_ROUND ;
-    Cairo.set_line_cap ctx Cairo.LINE_CAP_ROUND ;
-    let red, green, blue =  rgb_from_string color 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.close_path ctx ;
-
-    (* Apply the ink *)
-    Cairo.stroke ctx ;
-   ),
-   (fun () ->
-     let b = Buffer.create 10000 in
-     (* Output a PNG in a string *)
-     Cairo_png.surface_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 =
-  Eliom_registration.String.register_service
-    ~path:["image"]
-    ~get_params:Eliom_parameter.unit
-    (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 =
-  Html5.To_dom.of_img
-    (img ~alt:"canvas"
-       ~src:(make_uri ~service:%imageservice ())
-       ())
-in
-img##onload <- Dom_html.handler
-                (fun ev -> ctx##drawImage(img, 0., 0.); Js._false);

This new version of the graffiti.cma module now depends on -cairo. We must ask ocsigenserver to load cairo -before loading graffiti.cma. This is done by adding the -following line in the configuration file. -

<extension findlib-package="cairo" />
-

The first version of the program is now complete.

Download the code.

Exercises

  • Add an OClosure button to make possible to download the current -image and save it to the hard disk (reuse the service -imageservice). -
  • Add an OClosure 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).
diff --git a/2.2/manual/chat.html b/2.2/manual/chat.html deleted file mode 100644 index 5ad5333e..00000000 --- a/2.2/manual/chat.html +++ /dev/null @@ -1,128 +0,0 @@ - Chat: Design Overview

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

Chat: Design Overview

Chat is a chatting module and application, currently for conversations -between two users each. (Extension for multi-user channels is left as an -exercise to the user.) -

First of all, -

Description

When the user is logged in, he sees a list of available users. He can choose -one of them to start a conversation with him. The conversation then becomes -visible to both users as a message prompt and a growing list of messages. The -participating users can enter messages and they are made visible to the other -participants. If a user becomes unavailable for Chat, he is removed from the -list of available users, and all conversations he was participating are removed -from the opponent's UI. -

Central Data Types

The following data are crucial to the functionality of Chat. -

Conversation

A conversation (type Shared.Conversation.t) is shared between all its -client participants. It comprises of two values: Firstly, it contains a bus for -transferring conversation messages (c.f. type Shared.Conversation.message) -between the participants. Those are displayed as they come on every client. -Secondly, it contains the set of participating users. This is used when the -conversation is teared down: An event for removing the UI element for the -conversation is sent to every participant. -

Client Process

When a user enters a web page which contains Chat, it is rendered in HTML and -a new client process is created in doing so. Every client process holds a -channel, where the server can send messages to add or to remove conversations -(c.f. type Shared.event). -

User Info

On the server side, the channel for those events is stored for each user along -with a list of conversations he participates in. This is done with a -Eliom-reference of scope session_group, such that it is shared among all -sessions of a user (and likewise all client processes). -

But, when one user requests to start (or end) a conversation with any another -user, it is also necessary to access the event channel of any user. -As it is only stored in an Eliom-reference specific to the user, it is -impossible to access. In favour of this, the user info is moreover -stored in a weak hash table (c.f. module Weak_info) which associates the -user info to each user in a globally accessible way. This association is weak -in its value, the user info, such that it is garbage-collected as soon as the -Eliom-reference storing it is cleared, i.e. when the last session of the -respective user is closed. -

Available Users

The client's UI contains a list of users which are currently available for -chatting. This is achieved as follows. -

If it is the user's first client process he is added to the set of available -users, which is available in an react signal Chat.users_signal on a set of -user (i.e. User_set.t React.S.t). -As long as a user is available with at least one client he is kept in the value -of the signal. This signal is send to the client when initializing Chat. -Changes in its value are then directly reflected in the list of available users -in the client's UI (c.f. Client.change_users). -

To observe when a user is not available through a given client process anymore, -Chat awaits for every client process that it is inactive for a given -time. Eliom_comet.Channels.wait_timeout is used for for detecting a client -process to be abandoned. -

When it is observed that a user isn't avaible through any client process -anymore, he is removed from the Chat.users_signal, and all conversation he -is participating are teared down (c.f. Chat.remove_client_process). -

Starting a conversation

When the user selects one of the available users for a conversation, one of two -things may happen (c.f. Client.create_or_focus_conversation): -

Firstly, if there is already a conversation between exactly those two users, -the message prompt is focused. -Secondly, if there is no conversation between those users yet, a new one is created -by calling the server's Chat.create_dialog_service (dialog is a conversatio -of two) with the selected user as argument. -

This service establishes the conversation between the users: It creates the -conversation on the server side (i.e. the set of participants and the bus for -messages) and sends an event to append the conversation to each participant. -Each client is streaming such events (c.f. Client.dispatch_event) and -creates the respective UI-element for the conversation with event handlers for -sending and receiving messages in the conversation. -

Sending a message in a conversationg

Sending messages within an conversation is achieved without any server-side -code; the messages are sent "directly" to all all participants of the -conversation through its Eliom-bus. Two things make this work. -

Firstly, an event listener on the message prompt of the conversation -(c.f. Client.handle_enter_pressed) awaits that the enter-key is hit in -the message prompt of a conversation. It then creates a message and sends it -to the conversation's event bus. -

Secondly, every participant of a conversation is streaming the messages which -are sent over the conversation bus. -A stream iterator on that bus then displays those messages as they arrive -(c.f. Client.dispatch_message). -

Have fun. Be communicative! -

diff --git a/2.2/manual/files/tutorial.tar.gz b/2.2/manual/files/tutorial.tar.gz deleted file mode 100644 index 67ef2014..00000000 Binary files a/2.2/manual/files/tutorial.tar.gz and /dev/null differ diff --git a/2.2/manual/files/tutorial/chapter1/no_oclosure/.depend b/2.2/manual/files/tutorial/chapter1/no_oclosure/.depend deleted file mode 100644 index 1aa3f8e8..00000000 --- a/2.2/manual/files/tutorial/chapter1/no_oclosure/.depend +++ /dev/null @@ -1,6 +0,0 @@ -_server/graffiti.cmo: -_server/graffiti.cmx: -_client/graffiti.cmo: -_client/graffiti.cmx: -_client/graffiti.cmo: graffiti.type_mli -_client/graffiti.cmx: graffiti.type_mli diff --git a/2.2/manual/files/tutorial/chapter1/no_oclosure/Makefile b/2.2/manual/files/tutorial/chapter1/no_oclosure/Makefile deleted file mode 100644 index fd3fb21c..00000000 --- a/2.2/manual/files/tutorial/chapter1/no_oclosure/Makefile +++ /dev/null @@ -1,127 +0,0 @@ - -## Sample Makefile for eliom application. - -APP_NAME := graffiti - -## Packages required to build the server part of the application - -SERVER_PACKAGES := cairo - -## Packages to be linked in the client part - -CLIENT_PACKAGES := - -## Source files for the server part - -SERVER_FILES := ${wildcard *.eliom} - -## Source files for the client part - -CLIENT_FILES := ${wildcard *.eliom} - -## Required binaries - -ELIOMC := eliomc -ELIOMOPT := eliomopt -ELIOMDEP := eliomdep -JS_OF_ELIOM := js_of_eliom - -## Where to put intermediate object files. -## - ELIOM_{SERVER,CLIENT}_DIR must be distinct -## - ELIOM_CLIENT_DIR mustn't be the local dir. -## - ELIOM_SERVER_DIR could be ".", but you need to -## remove it from the "clean" rules... - -export ELIOM_SERVER_DIR := _server -export ELIOM_CLIENT_DIR := _client -export ELIOM_TYPE_DIR := . - -##################################### - -all: byte opt -byte:: ${APP_NAME}.cma ${APP_NAME}.js -opt:: ${APP_NAME}.cmxs ${APP_NAME}.js - -#### Server side compilation ####### - -SERVER_INC := ${addprefix -package ,${SERVER_PACKAGES}} - -SERVER_OBJS := $(patsubst %.eliom,${ELIOM_SERVER_DIR}/%.cmo, ${SERVER_FILES}) -SERVER_OBJS := $(patsubst %.ml,${ELIOM_SERVER_DIR}/%.cmo, ${SERVER_OBJS}) - -${APP_NAME}.cma: ${SERVER_OBJS} - ${ELIOMC} -a -o $@ $^ -${APP_NAME}.cmxa: ${SERVER_OBJS:.cmo=.cmx} - ${ELIOMOPT} -a -o $@ $^ - -${ELIOM_TYPE_DIR}/%.type_mli: %.eliom - ${ELIOMC} -infer ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmi: %.mli - ${ELIOMC} -c ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmo: %.ml - ${ELIOMC} -c ${SERVER_INC} $< -${ELIOM_SERVER_DIR}/%.cmo: %.eliom - ${ELIOMC} -c -noinfer ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmx: %.ml - ${ELIOMOPT} -c ${SERVER_INC} $< -${ELIOM_SERVER_DIR}/%.cmx: %.eliom - ${ELIOMOPT} -c -noinfer ${SERVER_INC} $< - -%.cmxs: %.cmxa - $(ELIOMOPT) -shared -linkall -o $@ $< - -##### Client side compilation #### - -CLIENT_LIBS := ${addprefix -package ,${CLIENT_PACKAGES}} -CLIENT_INC := ${addprefix -package ,${CLIENT_PACKAGES}} - -CLIENT_OBJS := $(patsubst %.eliom,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_FILES}) -CLIENT_OBJS := $(patsubst %.ml,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) - -${APP_NAME}.js: ${CLIENT_OBJS} - ${JS_OF_ELIOM} -o $@ ${CLIENT_LIBS} $^ - -${ELIOM_CLIENT_DIR}/%.cmi: %.mli - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< - -${ELIOM_CLIENT_DIR}/%.cmo: %.eliom - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< -${ELIOM_CLIENT_DIR}/%.cmo: %.ml - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< - -############ - -## Clean up - -clean: - -rm -f *.cm[ioax] *.cmxa *.cmxs *.o *.a *.annot - -rm -f *.type_mli - -rm -f ${APP_NAME}.js - -rm -rf ${ELIOM_CLIENT_DIR} ${ELIOM_SERVER_DIR} - -distclean: clean.local - -rm -f *~ \#* .\#* - -## Dependencies - -depend: .depend -.depend: ${SERVER_FILES} ${CLIENT_FILES} - $(ELIOMDEP) -server ${SERVER_INC} ${SERVER_FILES} > .depend - $(ELIOMDEP) -client ${CLIENT_INC} ${CLIENT_FILES} >> .depend - -## Warning: Dependencies towards *.eliom are not handled by eliomdep yet. - -include .depend - -## installation ######### - -STATICDIR := /tmp/static - -$(STATICDIR): - mkdir -p $@ - -install: all $(STATICDIR) - cp $(APP_NAME).js $(STATICDIR)/$(APP_NAME).js diff --git a/2.2/manual/files/tutorial/chapter1/no_oclosure/graffiti.conf b/2.2/manual/files/tutorial/chapter1/no_oclosure/graffiti.conf deleted file mode 100644 index 5e4ddfd8..00000000 --- a/2.2/manual/files/tutorial/chapter1/no_oclosure/graffiti.conf +++ /dev/null @@ -1,38 +0,0 @@ - - - - - 8080 - - - - /tmp - /tmp - - - /tmp/ocsigen_command - - utf-8 - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2.2/manual/files/tutorial/chapter1/no_oclosure/graffiti.eliom b/2.2/manual/files/tutorial/chapter1/no_oclosure/graffiti.eliom deleted file mode 100644 index 64c8b201..00000000 --- a/2.2/manual/files/tutorial/chapter1/no_oclosure/graffiti.eliom +++ /dev/null @@ -1,123 +0,0 @@ -{shared{ - open Eliom_content - open Eliom_content.Html5.D - let width = 700 - let height = 400 -}} - -module My_appl = - Eliom_registration.App ( - struct - let application_name = "graffiti" - end) - -{client{ - open Event_arrows - let draw ctx (color, size, (x1, y1), (x2, y2)) = - ctx##strokeStyle <- (Js.string color); - ctx##lineWidth <- float size; - ctx##beginPath(); - ctx##moveTo(float x1, float y1); - ctx##lineTo(float x2, float y2); - ctx##stroke() -}} - -{shared{ - type messages = (string * int * (int * int) * (int * int)) deriving (Json) -}} - -let bus = Eliom_bus.create ~name:"graff" Json.t - -let rgb_from_string color = (* color is in format "#rrggbb" *) - let get_color i = (float_of_string ("0x"^(String.sub color (1+2*i) 2))) /. 255. in - try get_color 0, get_color 1, get_color 2 with | _ -> 0.,0.,0. - -let draw_server, image_string = - let surface = Cairo.image_surface_create Cairo.FORMAT_ARGB32 ~width ~height in - let ctx = Cairo.create surface in - ((fun ((color : string), size, (x1, y1), (x2, y2)) -> - - (* Set thickness of brush *) - Cairo.set_line_width ctx (float size) ; - Cairo.set_line_join ctx Cairo.LINE_JOIN_ROUND ; - Cairo.set_line_cap ctx Cairo.LINE_CAP_ROUND ; - let red, green, blue = rgb_from_string color 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.close_path ctx ; - - (* Apply the ink *) - Cairo.stroke ctx ; - ), - (fun () -> - let b = Buffer.create 10000 in - (* Output a PNG in a string *) - Cairo_png.surface_write_to_stream surface (Buffer.add_string b); - Buffer.contents b - )) - -let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) - -let imageservice = - Eliom_registration.Text.register_service - ~path:["image"] - ~get_params:Eliom_parameter.unit - (fun () () -> Lwt.return (image_string (), "image/png")) - -let page = - html - (head (title (pcdata "Graffiti")) []) - (body [h1 [pcdata "Graffiti"]]) - -let onload_handler = {{ - let canvas = Dom_html.createCanvas Dom_html.document in - let ctx = canvas##getContext (Dom_html._2d_) in - canvas##width <- width; canvas##height <- height; - ctx##lineCap <- Js.string "round"; - - Dom.appendChild Dom_html.document##body canvas; - - (* The initial image: *) - let img = - Html5.To_dom.of_img - (img ~alt:"canvas" - ~src:(make_uri ~service:%imageservice ()) - ()) - in - img##onload <- Dom_html.handler - (fun ev -> ctx##drawImage(img, 0., 0.); Js._false); - - 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 in - - let compute_line ev = - let oldx = !x and oldy = !y in - set_coord ev; - ("#ff9933", 5, (oldx, oldy), (!x, !y)) - in - - let line ev = - let v = compute_line ev in - let _ = Eliom_bus.write %bus v in - draw ctx v - in - - let _ = Lwt_stream.iter (draw ctx) (Eliom_bus.stream %bus) in - - ignore (run (mousedowns canvas - (arr (fun ev -> set_coord ev; line ev) - >>> first [mousemoves Dom_html.document (arr line); - mouseup Dom_html.document >>> (arr line)])) ()); -}} - -let main_service = - My_appl.register_service ~path:[""] ~get_params:Eliom_parameter.unit - (fun () () -> - Eliom_service.onload onload_handler; - Lwt.return page) - diff --git a/2.2/manual/files/tutorial/chapter1/start/Makefile b/2.2/manual/files/tutorial/chapter1/start/Makefile deleted file mode 100644 index 4b16f914..00000000 --- a/2.2/manual/files/tutorial/chapter1/start/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -graffiti.cmo: graffiti.ml - ocamlfind ocamlc -thread -package eliom.server -c graffiti.ml diff --git a/2.2/manual/files/tutorial/chapter1/start/graffiti.conf b/2.2/manual/files/tutorial/chapter1/start/graffiti.conf deleted file mode 100644 index 82b016de..00000000 --- a/2.2/manual/files/tutorial/chapter1/start/graffiti.conf +++ /dev/null @@ -1,29 +0,0 @@ - - - - - 8080 - - - - /tmp - /tmp - - - /tmp/ocsigen_command - - - - - - - - - - - - - - - - diff --git a/2.2/manual/files/tutorial/chapter1/start/graffiti.ml b/2.2/manual/files/tutorial/chapter1/start/graffiti.ml deleted file mode 100644 index 3c597168..00000000 --- a/2.2/manual/files/tutorial/chapter1/start/graffiti.ml +++ /dev/null @@ -1,10 +0,0 @@ -open Lwt -open Eliom_parameter -open Eliom_content.Html5.D -open Eliom_registration.Html5 - -let main_service = - register_service ~path:["graff"] ~get_params:unit - (fun () () -> return (html (head (title (pcdata "")) []) - (body [h1 [pcdata "Graffiti"]]))) - diff --git a/2.2/manual/files/tutorial/chapter1/with_oclosure/.depend b/2.2/manual/files/tutorial/chapter1/with_oclosure/.depend deleted file mode 100644 index e77c7be2..00000000 --- a/2.2/manual/files/tutorial/chapter1/with_oclosure/.depend +++ /dev/null @@ -1,2 +0,0 @@ -_server/graffiti.cmo: -_server/graffiti.cmx: diff --git a/2.2/manual/files/tutorial/chapter1/with_oclosure/Makefile b/2.2/manual/files/tutorial/chapter1/with_oclosure/Makefile deleted file mode 100644 index 16770188..00000000 --- a/2.2/manual/files/tutorial/chapter1/with_oclosure/Makefile +++ /dev/null @@ -1,140 +0,0 @@ - -## Sample Makefile for eliom application. - -APP_NAME := graffiti - -## Packages required to build the server part of the application - -SERVER_PACKAGES := cairo - -## Packages to be linked in the client part - -CLIENT_PACKAGES := oclosure - -## Source files for the server part - -SERVER_FILES := ${wildcard *.eliom} - -## Source files for the client part - -CLIENT_FILES := ${wildcard *.eliom} - -## Required binaries - -ELIOMC := eliomc -ELIOMOPT := eliomopt -ELIOMDEP := eliomdep -JS_OF_ELIOM := js_of_eliom -OCLOSURE_REQ := oclosure_req - -## Where to put intermediate object files. -## - ELIOM_{SERVER,CLIENT}_DIR must be distinct -## - ELIOM_CLIENT_DIR mustn't be the local dir. -## - ELIOM_SERVER_DIR could be ".", but you need to -## remove it from the "clean" rules... - -export ELIOM_SERVER_DIR := _server -export ELIOM_CLIENT_DIR := _client -export ELIOM_TYPE_DIR := . - -##################################### - -all: byte opt -byte:: ${APP_NAME}.cma ${APP_NAME}.js ${APP_NAME}_oclosure.js -opt:: ${APP_NAME}.cmxs ${APP_NAME}.js ${APP_NAME}_oclosure.js - -#### Server side compilation ####### - -SERVER_INC := ${addprefix -package ,${SERVER_PACKAGES}} - -SERVER_OBJS := $(patsubst %.eliom,${ELIOM_SERVER_DIR}/%.cmo, ${SERVER_FILES}) -SERVER_OBJS := $(patsubst %.ml,${ELIOM_SERVER_DIR}/%.cmo, ${SERVER_OBJS}) - -${APP_NAME}.cma: ${SERVER_OBJS} - ${ELIOMC} -a -o $@ $^ -${APP_NAME}.cmxa: ${SERVER_OBJS:.cmo=.cmx} - ${ELIOMOPT} -a -o $@ $^ - -${ELIOM_TYPE_DIR}/%.type_mli: %.eliom - ${ELIOMC} -infer ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmi: %.mli - ${ELIOMC} -c ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmo: %.ml - ${ELIOMC} -c ${SERVER_INC} $< -${ELIOM_SERVER_DIR}/%.cmo: %.eliom - ${ELIOMC} -c -noinfer ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmx: %.ml - ${ELIOMOPT} -c ${SERVER_INC} $< -${ELIOM_SERVER_DIR}/%.cmx: %.eliom - ${ELIOMOPT} -c -noinfer ${SERVER_INC} $< - -%.cmxs: %.cmxa - $(ELIOMOPT) -shared -linkall -o $@ $< - -##### Client side compilation #### - -CLIENT_LIBS := ${addprefix -package ,${CLIENT_PACKAGES}} -CLIENT_INC := ${addprefix -package ,${CLIENT_PACKAGES}} - -CLIENT_OBJS := $(patsubst %.eliom,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_FILES}) -CLIENT_OBJS := $(patsubst %.ml,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) - -${APP_NAME}.js: ${CLIENT_OBJS} - ${JS_OF_ELIOM} -o $@ ${CLIENT_LIBS} $^ - -${ELIOM_CLIENT_DIR}/%.cmi: %.mli - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< - -${ELIOM_CLIENT_DIR}/%.cmo: %.eliom - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< -${ELIOM_CLIENT_DIR}/%.cmo: %.ml - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< - -############ - -## Clean up - -clean: - -rm -f *.cm[ioax] *.cmxa *.cmxs *.o *.a *.annot - -rm -f *.type_mli - -rm -f ${APP_NAME}.js - -rm -rf ${ELIOM_CLIENT_DIR} ${ELIOM_SERVER_DIR} - -rm -f $(APP_NAME)_oclosure.js - -distclean: clean.local - -rm -f *~ \#* .\#* - -## Dependencies - -depend: .depend -.depend: ${SERVER_FILES} ${CLIENT_FILES} - $(ELIOMDEP) -server ${SERVER_INC} ${SERVER_FILES} > .depend - $(ELIOMDEP) -client ${CLIENT_INC} ${CLIENT_FILES} >> .depend - -## Warning: Dependencies towards *.eliom are not handled by eliomdep yet. - --include .depend - -## installation ######### - -STATICDIR := /tmp/static - -$(STATICDIR): - mkdir -p $@ - -install: all $(STATICDIR) - cp $(APP_NAME)_oclosure.js $(STATICDIR)/$(APP_NAME)_oclosure.js - cp $(APP_NAME).js $(STATICDIR)/$(APP_NAME).js - cp -r static/* $(STATICDIR) - -## oclosure ############# - -OCAML := ocaml -OCAMLFIND := ocamlfind -OCLOSUREDIR := $(shell ${OCAMLFIND} query oclosure) - -$(APP_NAME)_oclosure.js: $(APP_NAME).js - ${OCLOSURE_REQ} $^ diff --git a/2.2/manual/files/tutorial/chapter1/with_oclosure/graffiti.conf b/2.2/manual/files/tutorial/chapter1/with_oclosure/graffiti.conf deleted file mode 100644 index 5e4ddfd8..00000000 --- a/2.2/manual/files/tutorial/chapter1/with_oclosure/graffiti.conf +++ /dev/null @@ -1,38 +0,0 @@ - - - - - 8080 - - - - /tmp - /tmp - - - /tmp/ocsigen_command - - utf-8 - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2.2/manual/files/tutorial/chapter1/with_oclosure/graffiti.eliom b/2.2/manual/files/tutorial/chapter1/with_oclosure/graffiti.eliom deleted file mode 100644 index 0a3e3f23..00000000 --- a/2.2/manual/files/tutorial/chapter1/with_oclosure/graffiti.eliom +++ /dev/null @@ -1,161 +0,0 @@ -{shared{ - open Eliom_content - open Eliom_content.Html5.D - let width = 700 - let height = 400 -}} - -module My_appl = - Eliom_registration.App ( - struct - let application_name = "graffiti" - end) - -{client{ - let draw ctx (color, size, (x1, y1), (x2, y2)) = - ctx##strokeStyle <- (Js.string color); - ctx##lineWidth <- float size; - ctx##beginPath(); - ctx##moveTo(float x1, float y1); - ctx##lineTo(float x2, float y2); - ctx##stroke() -}} - -{shared{ - type messages = (string * int * (int * int) * (int * int)) deriving (Json) -}} - -let bus = Eliom_bus.create ~name:"graff" Json.t - -let rgb_from_string color = (* color is in format "#rrggbb" *) - let get_color i = (float_of_string ("0x"^(String.sub color (1+2*i) 2))) /. 255. in - try get_color 0, get_color 1, get_color 2 with | _ -> 0.,0.,0. - -let draw_server, image_string = - let surface = Cairo.image_surface_create Cairo.FORMAT_ARGB32 ~width ~height in - let ctx = Cairo.create surface in - ((fun ((color : string), size, (x1, y1), (x2, y2)) -> - - (* Set thickness of brush *) - Cairo.set_line_width ctx (float size) ; - Cairo.set_line_join ctx Cairo.LINE_JOIN_ROUND ; - Cairo.set_line_cap ctx Cairo.LINE_CAP_ROUND ; - let red, green, blue = rgb_from_string color 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.close_path ctx ; - - (* Apply the ink *) - Cairo.stroke ctx ; - ), - (fun () -> - let b = Buffer.create 10000 in - (* Output a PNG in a string *) - Cairo_png.surface_write_to_stream surface (Buffer.add_string b); - Buffer.contents b - )) - -let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) - -let imageservice = - Eliom_registration.Text.register_service - ~path:["image"] - ~get_params:Eliom_parameter.unit - (fun () () -> Lwt.return (image_string (), "image/png")) - -let canvas_elt = - canvas ~a:[a_width width; a_height height] - [pcdata "your browser doesn't support canvas"] - -let page = - html - (head - (title (pcdata "Graffiti")) - [ css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"common.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"hsvpalette.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"slider.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"graffiti.css"]) (); - js_script - ~uri:(make_uri (Eliom_service.static_dir ()) - ["graffiti_oclosure.js"]) (); - ]) - (body [h1 [pcdata "Graffiti"]; canvas_elt]) - -let onload_handler = {{ - - (* Initialize the canvas *) - let canvas = Html5.To_dom.of_canvas %canvas_elt in - let ctx = canvas##getContext (Dom_html._2d_) in - ctx##lineCap <- Js.string "round"; - - (* The initial image: *) - let img = - Html5.To_dom.of_img - (img ~alt:"canvas" - ~src:(make_uri ~service:%imageservice ()) - ()) - in - img##onload <- Dom_html.handler - (fun _ -> ctx##drawImage(img, 0., 0.); Js._false); - - (* Size of the brush *) - let slider = jsnew Goog.Ui.slider(Js.null) in - slider##setOrientation(Goog.Ui.SliderBase.Orientation._VERTICAL); - slider##setMinimum(1.); - slider##setMaximum(80.); - slider##setValue(10.); - slider##setMoveToPointEnabled(Js._true); - slider##render(Js.some Dom_html.document##body); - - (* The color palette: *) - let pSmall = - jsnew Goog.Ui.hsvPalette(Js.null, Js.null, - Js.some (Js.string "goog-hsv-palette-sm")) - in - pSmall##render(Js.some Dom_html.document##body); - - 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 in - - let compute_line ev = - let oldx = !x and oldy = !y in - set_coord ev; - let color = Js.to_string (pSmall##getColor()) in - let size = int_of_float (Js.to_float (slider##getValue())) in - (color, size, (oldx, oldy), (!x, !y)) - in - - let line ev = - let v = compute_line ev in - let _ = Eliom_bus.write %bus v in - draw ctx v - in - - let _ = Lwt_stream.iter (draw ctx) (Eliom_bus.stream %bus) in - - let open Event_arrows in - ignore (run (mousedowns canvas - (arr (fun ev -> set_coord ev; line ev) - >>> first [mousemoves Dom_html.document (arr line); - mouseup Dom_html.document >>> (arr line)])) ()); -}} - -let main_service = - My_appl.register_service ~path:[""] ~get_params:Eliom_parameter.unit - (fun () () -> - Eliom_service.onload onload_handler; - Lwt.return page) - diff --git a/2.2/manual/files/tutorial/chapter2/tuto.conf b/2.2/manual/files/tutorial/chapter2/tuto.conf deleted file mode 100644 index 4b8fe362..00000000 --- a/2.2/manual/files/tutorial/chapter2/tuto.conf +++ /dev/null @@ -1,33 +0,0 @@ - - - - - 8080 - - - - /tmp - /tmp - - - /tmp/ocsigen_command - /etc/ocsigen/mime.types - - utf-8 - - - - - - - - - - - - - - - - - diff --git a/2.2/manual/files/tutorial/chapter2/tuto.ml b/2.2/manual/files/tutorial/chapter2/tuto.ml deleted file mode 100644 index b4e34880..00000000 --- a/2.2/manual/files/tutorial/chapter2/tuto.ml +++ /dev/null @@ -1,166 +0,0 @@ -open Eliom_content.Html5.D -open Eliom_parameter - -(* Services *) -let main_service = Eliom_service.service ~path:[""] ~get_params:unit () - -let user_service = - Eliom_service.service - ~path:["users"] ~get_params:(suffix (string "name")) () - -let connection_service = - Eliom_service.post_service - ~fallback:main_service - ~post_params:(string "name" ** string "password") - () - -let disconnection_service = Eliom_service.post_coservice' ~post_params:unit () - -let new_user_form_service = Eliom_service.service ~path:["create account"] ~get_params:unit () - -let account_confirmation_service = - Eliom_service.post_coservice ~fallback:new_user_form_service ~post_params:(string "name" ** string "password") () - - - - -(* User names and passwords: *) -let users = ref [("Calvin", "123"); ("Hobbes", "456")] - -let user_links () = - ul (List.map (fun (name, _) -> li [a ~service:user_service [pcdata name] name]) !users) - -let check_pwd name pwd = try List.assoc name !users = pwd with Not_found -> false - - - -(* Eliom references *) -let username = Eliom_reference.eref ~scope:Eliom_common.session None - -let wrong_pwd = Eliom_reference.eref ~scope:Eliom_common.request false - - - -(* Page widgets: *) -let disconnect_box () = - post_form disconnection_service - (fun _ -> [fieldset - [string_input - ~input_type:`Submit ~value:"Log out" ()]]) () - -let connection_box () = - lwt u = Eliom_reference.get username in - lwt wp = Eliom_reference.get wrong_pwd in - Lwt.return - (match u with - | Some s -> div [p [pcdata "You are connected as "; pcdata s; ]; - disconnect_box () ] - | None -> - let l = - [post_form ~service:connection_service - (fun (name1, name2) -> - [fieldset - [label ~a:[a_for name1] [pcdata "login: "]; - string_input ~input_type:`Text ~name:name1 (); - br (); - label ~a:[a_for name2] [pcdata "password: "]; - string_input ~input_type:`Password ~name:name2 (); - br (); - string_input ~input_type:`Submit ~value:"Connect" () - ]]) (); - p [a new_user_form_service [pcdata "Create an account"] ()]] - in - if wp - then div ((p [em [pcdata "Wrong user or password"]])::l) - else div l - ) - -let create_account_form () = - post_form ~service:account_confirmation_service - (fun (name1, name2) -> - [fieldset - [label ~a:[a_for name1] [pcdata "login: "]; - string_input ~input_type:`Text ~name:name1 (); - br (); - label ~a:[a_for name2] [pcdata "password: "]; - string_input ~input_type:`Password ~name:name2 (); - br (); - string_input ~input_type:`Submit ~value:"Connect" () - ]]) () - - - - -(* Registration of services *) -let _ = - Eliom_registration.Html5.register - ~service:main_service - (fun () () -> - lwt cf = connection_box () in - Lwt.return - (html (head (title (pcdata "")) []) - (body [h1 [pcdata "Hello"]; - cf; - user_links ()]))); - - Eliom_registration.Any.register - ~service:user_service - (fun name () -> - if List.exists (fun (n, _) -> n = name) !users - then begin - lwt cf = connection_box () in - Eliom_registration.Html5.send - (html (head (title (pcdata name)) []) - (body [h1 [pcdata name]; - cf; - p [a ~service:main_service [pcdata "Home"] ()]])) - end - else - Eliom_registration.Html5.send - ~code:404 - (html (head (title (pcdata "404")) []) - (body [h1 [pcdata "404"]; - p [pcdata "That page does not exist"]])) - ); - - Eliom_registration.Action.register - ~service:connection_service - (fun () (name, password) -> - if check_pwd name password - then Eliom_reference.set username (Some name) - else Eliom_reference.set wrong_pwd true); - - Eliom_registration.Action.register - ~service:disconnection_service - (fun () () -> Eliom_state.discard ~scope:Eliom_common.session ()); - - Eliom_registration.Html5.register - ~service:new_user_form_service - (fun () () -> - Lwt.return - (html (head (title (pcdata "")) []) - (body [h1 [pcdata "Create an account"]; - create_account_form (); - ]))); - - Eliom_registration.Html5.register - ~service:account_confirmation_service - (fun () (name, pwd) -> - let create_account_service = - Eliom_registration.Action.register_coservice - ~fallback:main_service - ~get_params:Eliom_parameter.unit - ~timeout:60. - (fun () () -> - users := (name, pwd)::!users; - Lwt.return ()) - in - Lwt.return - (html (head (title (pcdata "")) []) - (body [h1 [pcdata "Confirm account creation for "; pcdata name]; - p [a ~service:create_account_service [pcdata "Yes"] (); - pcdata " "; - a ~service:main_service [pcdata "No"] ()] - ]))) - - diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti/Makefile b/2.2/manual/files/tutorial/chapter3/multigraffiti/Makefile deleted file mode 100644 index 675ba493..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti/Makefile +++ /dev/null @@ -1,68 +0,0 @@ - -APP_NAME := graffiti -SERVER_PACKAGE := cairo, unix -CLIENT_PACKAGE := - -SERVER_FILES = common.ml server.ml ${wildcard *.eliom} -CLIENT_FILES = common.ml client.ml ${wildcard *.eliom} - -PORT := 8080 - -OCLOSURE := YES - -### - -all: local byte opt conf - -LIBDIR := local/var/www/lib -JSDIR := local/var/www/static - -include Makefile.common - -distclean:: - -rm -rf css/closure - -rm -rf local - -rm -f graffiti.conf - -#### - -DIRS = local/var/lib/ocsidbm local/var/run local/var/log \ - local/var/www/static local/var/www/lib local/etc \ - local/var/www/static/graffiti_saved - -local: ${DIRS} local/var/www/static/css local/var/www/static/images css/closure - -local/var/www/static/css: - ln -fs $(shell pwd)/css local/var/www/static/css - -local/var/www/static/images: - ln -fs $(shell pwd)/images local/var/www/static/images - -css/closure: - ln -fs $(shell ocamlfind query oclosure)/closure/goog/css/ css/closure - -${DIRS}: - mkdir -p $@ - -conf: graffiti.conf - -graffiti.conf: graffiti.conf.in - sed -e "s|%%SRC%%|$(shell pwd)|" \ - -e "s|%%LIBDIR%%|${LIBDIR}|" \ - -e "s|%%JSDIR%%|${JSDIR}|" \ - -e "s|%%PORT%%|${PORT}|" \ - $< > $@ - -run.local: graffiti.conf - ocsigenserver -c graffiti.conf - -run.opt.local: graffiti.conf - ocsigenserver.opt -c graffiti.conf - -#### - -install:: - install -d -m 775 ${INSTALL_USER} ${INSTALL_DIR}/static/css - install -m 664 ${INSTALL_USER} css/*.css ${INSTALL_DIR}/static/css - cd $(shell ocamlfind query oclosure)/closure/goog/css/ && \ - find -type f -exec install -D -m 664 {} ${INSTALL_DIR}/static/css/closure/{} \; diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti/Makefile.common b/2.2/manual/files/tutorial/chapter3/multigraffiti/Makefile.common deleted file mode 100644 index 1d8ddd63..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti/Makefile.common +++ /dev/null @@ -1,161 +0,0 @@ -## Sample Makefile for eliom application. - -APP_NAME ?= $(error Please define the APP_NAME variable) - -## Package required to build the server part of the application - -SERVER_PACKAGE ?= - -## Package to be linked in the client part - -CLIENT_PACKAGE ?= - -## Source files for the server part - -SERVER_FILES ?= ${wildcard *.eliom} - -## Source files for the client part - -CLIENT_FILES ?= ${wildcard *.eliom} - -## Needed binaries - -ELIOMC ?= eliomc -ELIOMOPT ?= eliomopt -ELIOMDEP ?= eliomdep -JS_OF_ELIOM ?= js_of_eliom -OCLOSURE_REQ ?= oclosure_req - -## Where to put intermediate object files. -## - ELIOM_{SERVER,CLIENT}_DIR must be distinct -## - ELIOM_CLIENT_DIR mustn't be the local dir. -## - ELIOM_SERVER_DIR could be ".", but you need to -## remove it from the "clean" rules... - -ELIOM_SERVER_DIR ?= _server -ELIOM_CLIENT_DIR ?= _client -ELIOM_TYPE_DIR ?= ${ELIOM_SERVER_DIR} - -export ELIOM_SERVER_DIR -export ELIOM_CLIENT_DIR -export ELIOM_TYPE_DIR - -#### - -LIBDIR ?= . - -byte:: ${LIBDIR}/${APP_NAME}.cma -opt:: ${LIBDIR}/${APP_NAME}.cmxs - -JSDIR ?= . - -ifneq (${CLIENT_FILES},) -byte:: ${JSDIR}/${APP_NAME}.js -opt:: ${JSDIR}/${APP_NAME}.js - -OCLOSURE ?= NO - -ifeq ($(OCLOSURE),YES) -CLIENT_PACKAGE += oclosure -byte:: ${JSDIR}/${APP_NAME}_oclosure.js -opt:: ${JSDIR}/${APP_NAME}_oclosure.js -endif -endif - -#### Server side compilation ####### - -SERVER_INC := ${addprefix -package ,${SERVER_PACKAGE}} - -SERVER_OBJS := $(patsubst %.eliom,${ELIOM_SERVER_DIR}/%.cmo, ${SERVER_FILES}) -SERVER_OBJS := $(patsubst %.ml,${ELIOM_SERVER_DIR}/%.cmo, ${SERVER_OBJS}) - -${LIBDIR}/${APP_NAME}.cma: ${SERVER_OBJS} - ${ELIOMC} ${ELIOMCFLAGS} -a -o $@ $^ -${LIBDIR}/${APP_NAME}.cmxa: ${SERVER_OBJS:.cmo=.cmx} - ${ELIOMOPT} ${ELIOMOPTFLAGS} -a -o $@ $^ - -${ELIOM_TYPE_DIR}/%.type_mli: %.eliom - ${ELIOMC} ${ELIOMCFLAGS} -infer ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmi: %.mli - ${ELIOMC} ${ELIOMCFLAGS} -c ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmo: %.ml - ${ELIOMC} ${ELIOMCFLAGS} -c ${SERVER_INC} $< -${ELIOM_SERVER_DIR}/%.cmo: %.eliom - ${ELIOMC} ${ELIOMCFLAGS} -c -noinfer ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmx: %.ml - ${ELIOMOPT} ${ELIOMOPTFLAGS} -c ${SERVER_INC} $< -${ELIOM_SERVER_DIR}/%.cmx: %.eliom - ${ELIOMOPT} ${ELIOMOPTFLAGS} -c -noinfer ${SERVER_INC} $< - -%.cmxs: %.cmxa - $(ELIOMOPT) ${ELIOMOPTFLAGS} -shared -linkall -o $@ $< - -##### Client side compilation #### - -CLIENT_LIBS := ${addprefix -package ,${CLIENT_PACKAGE}} -CLIENT_INC := ${addprefix -package ,${CLIENT_PACKAGE}} - -CLIENT_OBJS := $(patsubst %.eliom,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_FILES}) -CLIENT_OBJS := $(patsubst %.ml,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) - -${JSDIR}/${APP_NAME}.js: ${CLIENT_OBJS} - ${JS_OF_ELIOM} -jsopt -pretty -jsopt -noinline -o $@ ${CLIENT_LIBS} $^ - -${ELIOM_CLIENT_DIR}/%.cmi: %.mli - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< - -${ELIOM_CLIENT_DIR}/%.cmo: %.eliom - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< -${ELIOM_CLIENT_DIR}/%.cmo: %.ml - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< - -### O'Closure compilation ### - -${JSDIR}/$(APP_NAME)_oclosure.js: ${JSDIR}/$(APP_NAME).js - ${OCLOSURE_REQ} $^ - -############ - -## Clean up - -clean:: - -rm -f *.cm[ioax] *.cmxa *.cmxs *.o *.a *.annot - -rm -f *.type_mli - -rm -f .depend - -rm -f ${JSDIR}/${APP_NAME}.js ${JSDIR}/${APP_NAME}_oclosure.js - -rm -rf ${ELIOM_CLIENT_DIR} ${ELIOM_SERVER_DIR} - -distclean:: clean - -find \( -name \*~ -or -name \#\* -or -name .\#\* \) -delete - - -## Dependencies - -depend: .depend -.depend:: ${SERVER_FILES} ${CLIENT_FILES} - $(ELIOMDEP) -server ${SERVER_INC} ${SERVER_FILES} > .depend - $(ELIOMDEP) -client ${CLIENT_INC} ${CLIENT_FILES} >> .depend - -## Warning: Dependencies towards *.eliom are not handled by eliomdep yet. - --include .depend - - -#### - -INSTALL_USER ?= -o www-data -g www-data -INSTALL_DIR ?= local/var/www/${APP_NAME} - -install:: - install -d -m 775 ${INSTALL_USER} ${INSTALL_DIR}/lib - install -m 664 ${INSTALL_USER} ${LIBDIR}/${APP_NAME}.cma ${LIBDIR}/${APP_NAME}.cmxs ${INSTALL_DIR}/lib/ - install -d -m 775 ${INSTALL_USER} ${INSTALL_DIR}/static -ifneq (${CLIENT_FILES},) - install -m 664 ${INSTALL_USER} ${JSDIR}/${APP_NAME}.js ${INSTALL_DIR}/static/ -ifeq ($(OCLOSURE),YES) - install -m 664 ${INSTALL_USER} ${JSDIR}/${APP_NAME}_oclosure.js ${INSTALL_DIR}/static/ -endif -endif diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti/client.ml b/2.2/manual/files/tutorial/chapter3/multigraffiti/client.ml deleted file mode 100644 index bb58dc9e..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti/client.ml +++ /dev/null @@ -1,77 +0,0 @@ -open Eliom_content -open Common -open Event_arrows - -let draw ctx (color, size, (x1, y1), (x2, y2)) = - ctx##strokeStyle <- (Js.string color); - ctx##lineWidth <- float size; - ctx##beginPath(); - ctx##moveTo(float x1, float y1); - ctx##lineTo(float x2, float y2); - ctx##stroke() - -(* type containing all informations we need to stop interaction - inside the page *) -type drawing_canceller = - { drawing_thread : unit Lwt.t; - (* the thread reading messages from the bus *) - drawing_arrow : Event_arrows.canceller; - (* the arrow handling mouse events *) - } - -let stop_drawing { drawing_thread; drawing_arrow } = - Lwt.cancel drawing_thread; - (* cancelling this thread also close the bus *) - Event_arrows.cancel drawing_arrow - -let launch_client_canvas bus image_elt canvas_elt = - let canvas = Html5.To_dom.of_canvas canvas_elt in - let ctx = canvas##getContext (Dom_html._2d_) in - ctx##lineCap <- Js.string "round"; - - let img = Html5.To_dom.of_img image_elt in - let copy_image () = ctx##drawImage(img, 0., 0.) in - if Js.to_bool (img##complete) - then copy_image () - else img##onload <- Dom_html.handler - (fun ev -> copy_image (); Js._false); - - (* Size of the brush *) - let slider = jsnew Goog.Ui.slider(Js.null) in - slider##setMinimum(1.); - slider##setMaximum(80.); - slider##setValue(10.); - slider##setMoveToPointEnabled(Js._true); - slider##render(Js.some Dom_html.document##body); - - (* The color palette: *) - let pSmall = - jsnew Goog.Ui.hsvPalette(Js.null, Js.null, - Js.some (Js.string "goog-hsv-palette-sm")) - in - pSmall##render(Js.some Dom_html.document##body); - - 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 in - let compute_line ev = - let oldx = !x and oldy = !y in - set_coord ev; - let color = Js.to_string (pSmall##getColor()) in - let size = int_of_float (Js.to_float (slider##getValue())) in - (color, size, (oldx, oldy), (!x, !y)) - in - let line ev = - let v = compute_line ev in - let _ = Eliom_bus.write bus v in - draw ctx v - in - let t = Lwt_stream.iter (draw ctx) (Eliom_bus.stream bus) in - let drawing_arrow = - run (mousedowns canvas - (arr (fun ev -> set_coord ev; line ev) >>> - first [mousemoves Dom_html.document (arr line); - mouseup Dom_html.document >>> (arr line)])) () in - { drawing_thread = t; - drawing_arrow = drawing_arrow } diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti/common.ml b/2.2/manual/files/tutorial/chapter3/multigraffiti/common.ml deleted file mode 100644 index 8d256e01..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti/common.ml +++ /dev/null @@ -1,4 +0,0 @@ -type messages = (string * int * (int * int) * (int * int)) deriving (Json) - -let width = 700 -let height = 400 diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti/css/common.css b/2.2/manual/files/tutorial/chapter3/multigraffiti/css/common.css deleted file mode 100644 index 02d7073c..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti/css/common.css +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2009 The Closure Library Authors. All Rights Reserved. - * - * Use of this source code is governed by an Apache 2.0 License. - * See the COPYING file for details. - */ - -/* - * Cross-browser implementation of the "display: inline-block" CSS property. - * See http://www.w3.org/TR/CSS21/visuren.html#propdef-display for details. - * Tested on IE 6 & 7, FF 1.5 & 2.0, Safari 2 & 3, Webkit, and Opera 9. - * - * @author attila@google.com (Attila Bodis) - */ - -/* - * Default rule; only Safari, Webkit, and Opera handle it without hacks. - */ -.goog-inline-block { - position: relative; - display: -moz-inline-box; /* Ignored by FF3 and later. */ - display: inline-block; -} - -/* - * Pre-IE7 IE hack. On IE, "display: inline-block" only gives the element - * layout, but doesn't give it inline behavior. Subsequently setting display - * to inline does the trick. - */ -* html .goog-inline-block { - display: inline; -} - -/* - * IE7-only hack. On IE, "display: inline-block" only gives the element - * layout, but doesn't give it inline behavior. Subsequently setting display - * to inline does the trick. - */ -*:first-child+html .goog-inline-block { - display: inline; -} diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti/css/graffiti.css b/2.2/manual/files/tutorial/chapter3/multigraffiti/css/graffiti.css deleted file mode 100644 index 5768a851..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti/css/graffiti.css +++ /dev/null @@ -1,21 +0,0 @@ -body { - width: 750px; -} - -canvas { - border: solid 1px #aaaaaa; -} - -div.goog-slider-vertical, -DIV.goog-slider-vertical { - position: absolute; - top: 100px; - left: 740px; -} - -div.goog-hsv-palette-sm, -DIV.goog-hsv-palette-sm { - position: absolute; - top: 100px; - left: 780px; -} diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti/css/hsvpalette.css b/2.2/manual/files/tutorial/chapter3/multigraffiti/css/hsvpalette.css deleted file mode 100644 index d3ae7f67..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti/css/hsvpalette.css +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2008 The Closure Library Authors. All Rights Reserved. - * - * Use of this source code is governed by an Apache 2.0 License. - * See the COPYING file for details. - */ - -/* - * All Rights Reserved. - * - * Styles for the HSV color palette. - * - * @author smcbride@google.com (Sean McBride) - * @author arv@google.com (Erik Arvidsson) - * @author manucornet@google.com (Manu Cornet) - */ - -.goog-hsv-palette, -.goog-hsv-palette-sm { - position: relative; - border: 1px solid #999; - border-color: #ccc #999 #999 #ccc; - width: 400px; - height: 276px; -} - -.goog-hsv-palette-sm { - width: 182px; - height: 185px; -} - -.goog-hsv-palette label span, -.goog-hsv-palette-sm label span { - display: none; -} - -.goog-hsv-palette-hs-backdrop, -.goog-hsv-palette-sm-hs-backdrop, -.goog-hsv-palette-hs-image, -.goog-hsv-palette-sm-hs-image { - position: absolute; - top: 10px; - left: 10px; - width: 256px; - height: 256px; - border: 1px solid #999; -} - -.goog-hsv-palette-sm-hs-backdrop, -.goog-hsv-palette-sm-hs-image { - top: 45px; - width: 128px; - height: 128px; -} - -.goog-hsv-palette-hs-backdrop, -.goog-hsv-palette-sm-hs-backdrop { - background-color: #000; -} - -.goog-hsv-palette-hs-image, -.goog-hsv-palette-v-image, -.goog-hsv-palette-hs-handle, -.goog-hsv-palette-v-handle { - background-image: url(../images/hsv-sprite.png); -} - -.goog-hsv-palette-noalpha .goog-hsv-palette-hs-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-v-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-hs-handle, -.goog-hsv-palette-noalpha .goog-hsv-palette-v-handle { - background-image: url(../images/hsv-sprite.gif); -} - -.goog-hsv-palette-sm-hs-image, -.goog-hsv-palette-sm-v-image, -.goog-hsv-palette-sm-hs-handle, -.goog-hsv-palette-sm-v-handle { - background-image: url(../images/hsv-sprite-sm.png); -} - -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-hs-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-v-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-hs-handle, -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-v-handle { - background-image: url(../images/hsv-sprite-sm.gif); -} - -.goog-hsv-palette-hs-image, -.goog-hsv-palette-sm-hs-image { - background-position: 0 0; -} - -.goog-hsv-palette-hs-handle, -.goog-hsv-palette-sm-hs-handle { - position: absolute; - left: 5px; - top: 5px; - width: 11px; - height: 11px; - overflow: hidden; - background-position: 0 -256px; -} - -.goog-hsv-palette-sm-hs-handle { - top: 40px; - background-position: 0 -128px; -} - -.goog-hsv-palette-v-image, -.goog-hsv-palette-sm-v-image { - position: absolute; - top: 10px; - left: 286px; - width: 19px; - height: 256px; - border: 1px solid #999; - background-color: #fff; - background-position: -256px 0; -} - -.goog-hsv-palette-sm-v-image { - top: 45px; - left: 155px; - width: 9px; - height: 128px; - background-position: -128px 0; -} - -.goog-hsv-palette-v-handle, -.goog-hsv-palette-sm-v-handle { - position: absolute; - top: 5px; - left: 279px; - width: 35px; - height: 11px; - background-position: -11px -256px; - overflow: hidden; -} - -.goog-hsv-palette-sm-v-handle { - top: 40px; - left: 148px; - width: 25px; - background-position: -11px -128px; -} - -.goog-hsv-palette-swatch, -.goog-hsv-palette-sm-swatch { - position: absolute; - top: 10px; - right: 10px; - width: 65px; - height: 65px; - border: 1px solid #999; - background-color: #fff; -} - -.goog-hsv-palette-sm-swatch { - top: 10px; - right: auto; - left: 10px; - width: 30px; - height: 22px; -} - -.goog-hsv-palette-input, -.goog-hsv-palette-sm-input { - position: absolute; - top: 85px; - right: 10px; - width: 65px; -} - -.goog-hsv-palette-sm-input { - top: 10px; - right: auto; - left: 50px; -} diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti/css/slider.css b/2.2/manual/files/tutorial/chapter3/multigraffiti/css/slider.css deleted file mode 100644 index ddd07482..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti/css/slider.css +++ /dev/null @@ -1,45 +0,0 @@ -.goog-slider-vertical, -.goog-slider-horizontal { -background-color: ThreeDFace; -position: relative; -overflow: hidden; -} - -.goog-slider-vertical { -height: 300px; -width: 20px; -} - -.goog-slider-horizontal { -height: 20px; -width: 400px; -} - -.goog-slider-thumb { -position: absolute; -background-color: ThreeDShadow; -overflow: hidden; -} - -.goog-slider-vertical .goog-slider-thumb { -left: 0; -height: 20px; -width: 100%; -} - -.goog-slider-horizontal .goog-slider-thumb { -top: 0; -width: 20px; -height: 100%; -} -#s-h { -margin-bottom: 2em; -} -strong { -display: block; -margin-bottom: 3px; -} -#out1, #out2 { -color: #999; -margin-left: 1em; -} diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti/graffiti.conf.in b/2.2/manual/files/tutorial/chapter3/multigraffiti/graffiti.conf.in deleted file mode 100644 index 89f88db3..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti/graffiti.conf.in +++ /dev/null @@ -1,35 +0,0 @@ - - - - - %%PORT%% - - %%SRC%%/local/var/log - %%SRC%%/local/var/run - %%SRC%%/local/var/run/ocsigenserver_command - - utf-8 - - - - - - - - - - - - - - - - - - %%SRC%%/%%JSDIR%% - - - - - - diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti/graffiti.eliom b/2.2/manual/files/tutorial/chapter3/multigraffiti/graffiti.eliom deleted file mode 100644 index ae01b330..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti/graffiti.eliom +++ /dev/null @@ -1,37 +0,0 @@ -{shared{ - open Eliom_content.Html5.D - open Common -}} -{client{ - open Client -}} -open Server - -let start_drawing name image canvas = - let bus = get_bus name in - Eliom_service.onload - {{ - let canceller = launch_client_canvas %bus %image %canvas in - Eliom_client.on_unload (fun () -> stop_drawing canceller) - }} - -let counter = ref 0 - -let () = Connected.register ~service:multigraffiti_service - !% (fun name () username -> - (* Some browsers won't reload the image, so we force - them by changing the url each time. *) - incr counter; - let image = - img ~alt:name - ~src:(make_uri - ~service:imageservice (name,!counter)) () in - let canvas = - canvas ~a:[ a_width width; a_height height ] - [pcdata "your browser doesn't support canvas"; br (); image] in - start_drawing name image canvas; - make_page - [h1 [pcdata name]; - disconnect_box (); - choose_drawing_form (); - canvas;]) diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti/images/hsv-sprite-sm.png b/2.2/manual/files/tutorial/chapter3/multigraffiti/images/hsv-sprite-sm.png deleted file mode 100644 index 470f8e0c..00000000 Binary files a/2.2/manual/files/tutorial/chapter3/multigraffiti/images/hsv-sprite-sm.png and /dev/null differ diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti/server.ml b/2.2/manual/files/tutorial/chapter3/multigraffiti/server.ml deleted file mode 100644 index b425c48e..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti/server.ml +++ /dev/null @@ -1,205 +0,0 @@ -open Eliom_content.Html5.D -open Common -open Lwt - -module My_app = - Eliom_registration.App ( - struct - let application_name = "graffiti" - end) - -let rgb_from_string color = (* color is in format "#rrggbb" *) - let get_color i = - (float_of_string ("0x"^(String.sub color (1+2*i) 2))) /. 255. - in - try get_color 0, get_color 1, get_color 2 with | _ -> 0.,0.,0. - -let launch_server_canvas () = - let bus = Eliom_bus.create Json.t in - - let draw_server, image_string = - let surface = Cairo.image_surface_create - Cairo.FORMAT_ARGB32 ~width ~height in - let ctx = Cairo.create surface in - ((fun ((color : string), size, (x1, y1), (x2, y2)) -> - - (* Set thickness of brush *) - Cairo.set_line_width ctx (float size) ; - Cairo.set_line_join ctx Cairo.LINE_JOIN_ROUND ; - Cairo.set_line_cap ctx Cairo.LINE_CAP_ROUND ; - let red, green, blue = rgb_from_string color 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.close_path ctx ; - - (* Apply the ink *) - Cairo.stroke ctx ; - ), - (fun () -> - let b = Buffer.create 10000 in - (* Output a PNG in a string *) - Cairo_png.surface_write_to_stream surface (Buffer.add_string b); - Buffer.contents b - )) - in - let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) in - bus,image_string - -let graffiti_info = Hashtbl.create 0 - -let imageservice = - Eliom_registration.Text.register_service - ~path:["image"] - ~headers:Http_headers.dyn_headers - ~get_params:(let open Eliom_parameter in string "name" ** int "q") - (* we add an int parameter for the browser not to cache the image: - at least for chrome, there is no way to force the browser to - reload the image without leaving the application *) - (fun (name,_) () -> - try_lwt - let _ ,image_string = Hashtbl.find graffiti_info name in - Lwt.return (image_string (), "image/png") - with - | Not_found -> raise_lwt Eliom_common.Eliom_404) - -let get_bus (name:string) = - (* create a new bus and image_string function only if it did not exists *) - try - fst (Hashtbl.find graffiti_info name) - with - | Not_found -> - let bus,image_string = launch_server_canvas () in - Hashtbl.add graffiti_info name (bus,image_string); - bus - -let main_service = Eliom_service.service ~path:[""] - ~get_params:(Eliom_parameter.unit) () -let multigraffiti_service = Eliom_service.service ~path:[""] - ~get_params:(Eliom_parameter.suffix (Eliom_parameter.string "name")) () - -let choose_drawing_form () = - get_form ~service:multigraffiti_service - (fun (name) -> - [fieldset - [label ~a:[a_for name] - [pcdata "drawing name: "]; - string_input ~input_type:`Text ~name (); - br (); - string_input ~input_type:`Submit ~value:"Go" () - ]]) - -let oclosure_script = - Eliom_content.Html5.Id.create_global_elt - (js_script - ~uri:(make_uri (Eliom_service.static_dir ()) - ["graffiti_oclosure.js"]) ()) - -let make_page content = - Lwt.return - (html - (head - (title (pcdata "Graffiti")) - [ css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"common.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"hsvpalette.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"slider.css"]) (); - oclosure_script; - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"graffiti.css"]) (); - ]) - (body content)) - -let connection_service = - Eliom_service.post_coservice' ~post_params: - (let open Eliom_parameter in (string "name" ** string "password")) () -let disconnection_service = Eliom_service.post_coservice' - ~post_params:Eliom_parameter.unit () -let create_account_service = - Eliom_service.post_coservice ~fallback:main_service ~post_params: - (let open Eliom_parameter in (string "name" ** string "password")) () - -let username = Eliom_reference.eref ~scope:Eliom_common.session None - -let users = ref ["user","password";"test","test"] - -let check_pwd name pwd = - try Lwt.return (List.assoc name !users = pwd) with - | Not_found -> Lwt.return false - -let () = Eliom_registration.Action.register - ~service:create_account_service - (fun () (name, pwd) -> - users := (name, pwd)::!users; - Lwt.return ()) - -let () = Eliom_registration.Action.register - ~service:connection_service - (fun () (name, password) -> - match_lwt check_pwd name password with - | true -> Eliom_reference.set username (Some name) - | false -> Lwt.return ()) - -let () = - Eliom_registration.Action.register - ~service:disconnection_service - (fun () () -> Eliom_state.discard ~scope:Eliom_common.session ()) - -let disconnect_box () = - post_form disconnection_service - (fun _ -> [fieldset - [string_input - ~input_type:`Submit ~value:"Log out" ()]]) () - -let login_name_form service button_text = - post_form ~service - (fun (name1, name2) -> - [fieldset - [label ~a:[a_for name1] [pcdata "login: "]; - string_input ~input_type:`Text ~name:name1 (); - br (); - label ~a:[a_for name2] [pcdata "password: "]; - string_input - ~input_type:`Password - ~name:name2 (); - br (); - string_input - ~input_type:`Submit - ~value:button_text () - ]]) () - -let default_content () = - make_page - [h1 [pcdata "Welcome to Multigraffiti"]; - h2 [pcdata "log in"]; - login_name_form connection_service "Connect"; - h2 [pcdata "create account"]; - login_name_form create_account_service "Create account";] - -module Connected_translate = -struct - type page = string -> My_app.page Lwt.t - let translate page = - Eliom_reference.get username >>= - function - | None -> default_content () - | Some username -> page username -end - -module Connected = - Eliom_registration.Customize ( My_app ) ( Connected_translate ) - -let ( !% ) f = fun a b -> return (fun c -> f a b c) - -let () = Connected.register ~service:main_service - !% (fun () () username -> - make_page - [h1 [pcdata ("Welcome to Multigraffiti " ^ username)]; - choose_drawing_form ()]) diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti_audio/graffiti.eliom b/2.2/manual/files/tutorial/chapter3/multigraffiti_audio/graffiti.eliom deleted file mode 100644 index 1625144c..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti_audio/graffiti.eliom +++ /dev/null @@ -1,44 +0,0 @@ -{shared{ - open Eliom_content.Html5.D - open Common -}} -{client{ - open Client -}} -open Server - -let start_drawing name image canvas = - let bus = get_bus name in - Eliom_service.onload - {{ - let canceller = launch_client_canvas %bus %image %canvas in - Eliom_client.on_unload (fun () -> stop_drawing canceller) - }} - -let counter = ref 0 - -let player = - Eliom_content.Html5.Id.create_global_elt - (audio - ~srcs:(make_uri (Eliom_service.static_dir ()) - ["music.ogg"],[]) - ~a:[a_autoplay (`Autoplay);a_controls (`Controls)] - [pcdata "Your browser does not support audio element" ]) - -let () = Connected.register ~service:multigraffiti_service - !% ( fun name () username -> - (* Some browsers won't reload the image, so we force - them by changing the url each time. *) - incr counter; - let image = img ~alt:name ~src:(make_uri - ~service:imageservice (name,!counter)) () in - let canvas = canvas ~a:[ a_width width; a_height height ] - [pcdata "your browser doesn't support canvas"; br (); image] in - start_drawing name image canvas; - make_page - [h1 [pcdata name]; - disconnect_box (); - choose_drawing_form (); - canvas; - player]) - diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti_feed/Makefile b/2.2/manual/files/tutorial/chapter3/multigraffiti_feed/Makefile deleted file mode 100644 index 8aeb4758..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti_feed/Makefile +++ /dev/null @@ -1,68 +0,0 @@ - -APP_NAME := graffiti -SERVER_PACKAGE := cairo, unix -CLIENT_PACKAGE := - -SERVER_FILES = common.ml server.ml feed.ml ${wildcard *.eliom} -CLIENT_FILES = common.ml client.ml ${wildcard *.eliom} - -PORT := 8080 - -OCLOSURE := YES - -### - -all: local byte opt conf - -LIBDIR := local/var/www/lib -JSDIR := local/var/www/static - -include Makefile.common - -distclean:: - -rm -rf css/closure - -rm -rf local - -rm -f graffiti.conf - -#### - -DIRS = local/var/lib/ocsidbm local/var/run local/var/log \ - local/var/www/static local/var/www/lib local/etc \ - local/var/www/static/graffiti_saved - -local: ${DIRS} local/var/www/static/css local/var/www/static/images css/closure - -local/var/www/static/css: - ln -fs $(shell pwd)/css local/var/www/static/css - -local/var/www/static/images: - ln -fs $(shell pwd)/images local/var/www/static/images - -css/closure: - ln -fs $(shell ocamlfind query oclosure)/closure/goog/css/ css/closure - -${DIRS}: - mkdir -p $@ - -conf: graffiti.conf - -graffiti.conf: graffiti.conf.in - sed -e "s|%%SRC%%|$(shell pwd)|" \ - -e "s|%%LIBDIR%%|${LIBDIR}|" \ - -e "s|%%JSDIR%%|${JSDIR}|" \ - -e "s|%%PORT%%|${PORT}|" \ - $< > $@ - -run.local: graffiti.conf - ocsigenserver -c graffiti.conf - -run.opt.local: graffiti.conf - ocsigenserver.opt -c graffiti.conf - -#### - -install:: - install -d -m 775 ${INSTALL_USER} ${INSTALL_DIR}/static/css - install -m 664 ${INSTALL_USER} css/*.css ${INSTALL_DIR}/static/css - cd $(shell ocamlfind query oclosure)/closure/goog/css/ && \ - find -type f -exec install -D -m 664 {} ${INSTALL_DIR}/static/css/closure/{} \; diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti_feed/feed.ml b/2.2/manual/files/tutorial/chapter3/multigraffiti_feed/feed.ml deleted file mode 100644 index 7328dcbb..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti_feed/feed.ml +++ /dev/null @@ -1,101 +0,0 @@ -open Eliom_content -open Html5.D -open Server - -let static_dir = - match Eliom_config.get_config () with - | [Simplexmlparser.Element ("staticdir", [], [Simplexmlparser.PCData dir])] -> - dir - | [] -> - raise (Ocsigen_extensions.Error_in_config_file - ("staticdir must be configured")) - | _ -> - raise (Ocsigen_extensions.Error_in_config_file - ("Unexpected content inside graffiti config")) - -let image_dir name = - let dir = static_dir ^ "/graffiti_saved/" ^ (Url.encode name) in - (try_lwt Lwt_unix.mkdir dir 0o777 with - | _ -> debug "could not create the directory %s" dir; Lwt.return ()) >|= - (fun () -> dir) - -let make_filename name number = - image_dir name >|= ( fun dir -> (dir ^ "/" ^ (string_of_int number) ^ ".png") ) - -let save image name number = - lwt file_name = make_filename name number in - lwt out_chan = Lwt_io.open_file ~mode:Lwt_io.output file_name in - Lwt_io.write out_chan image - -let image_info_table = Ocsipersist.open_table "image_info_table" - -let save_image username = - let now = CalendarLib.Calendar.now () in - lwt number,_,list = - try_lwt Ocsipersist.find image_info_table username with - | Not_found -> Lwt.return (0,now,[]) - | e -> Lwt.fail e - in - lwt () = Ocsipersist.add image_info_table - username (number+1,now,(number,now)::list) in - let (_,image_string) = Hashtbl.find graffiti_info username in - save (image_string ()) username number - -let save_image_box name = - let save_image_service = - Eliom_registration.Action.register_post_coservice' - ~post_params:Eliom_parameter.unit - (fun () () -> save_image name) - in - post_form save_image_service - (fun _ -> - [p [string_input - ~input_type:`Submit ~value:"save" ()]]) () - -let feed_service = Eliom_service.service ~path:["feed"] - ~get_params:(Eliom_parameter.string "name") () - -let local_filename name number = - ["graffiti_saved"; Url.encode name ; (string_of_int number) ^ ".png"] - -let rec entries name list = function - | 0 -> [] - | len -> - match list with - | [] -> [] - | (n,saved)::q -> - let title = Atom_feed.plain ("graffiti " ^ name ^ " " ^ (string_of_int n)) in - let uri = - Xhtml.F.make_uri ~absolute:true ~service:(Eliom_service.static_dir ()) - (local_filename name n) - in - let entry = - Atom_feed.entry ~title ~id:uri ~updated:saved - [Atom_feed.xhtmlC [ Xhtml.F.img ~src:uri ~alt:"image" ()]] in - entry::(entries name q (len - 1)) - -let feed name () = - let id = Xhtml.F.make_uri ~absolute:true ~service:feed_service name in - let title = Atom_feed.plain ("nice drawings of " ^ name) in - try_lwt - Ocsipersist.find image_info_table name >|= - (fun (number,updated,list) -> Atom_feed.feed ~id ~updated ~title (entries name list 10)) - with - | Not_found -> - let now = CalendarLib.Calendar.now () in - Lwt.return (Atom_feed.feed ~id ~updated:now ~title []) - | e -> Lwt.fail e - -let feed name () = - let id = Xhtml.F.make_uri ~absolute:true ~service:feed_service name in - let title = Atom_feed.plain ("nice drawings of " ^ name) in - Lwt.catch - (fun () -> Ocsipersist.find image_info_table name >|= - (fun (number,updated,list) -> Atom_feed.feed ~id ~updated ~title (entries name list 10))) - ( function Not_found -> - let now = CalendarLib.Calendar.now () in - Lwt.return (Atom_feed.feed ~id ~updated:now ~title []) - | e -> Lwt.fail e ) - -let () = Eliom_atom.Reg.register - ~service:feed_service feed diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti_feed/graffiti.eliom b/2.2/manual/files/tutorial/chapter3/multigraffiti_feed/graffiti.eliom deleted file mode 100644 index f2db62ce..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti_feed/graffiti.eliom +++ /dev/null @@ -1,43 +0,0 @@ -{shared{ - open Eliom_content.Html5.D - open Common -}} -{client{ - open Client -}} -open Server -open Feed - -let start_drawing name image canvas = - let bus = get_bus name in - Eliom_service.onload - {{ - let canceller = launch_client_canvas %bus %image %canvas in - Eliom_client.on_unload (fun () -> stop_drawing canceller) - }} - -let counter = ref 0 - -let () = Connected.register ~service:multigraffiti_service - !% ( fun name () username -> - (* Some browsers won't reload the image, so we force - them by changing the url each time. *) - incr counter; - let image = - img ~alt:name - ~src:(make_uri - ~service:imageservice (name,!counter)) () in - let canvas = - canvas ~a:[ a_width width; a_height height ] - [pcdata "your browser doesn't support canvas"; br (); image] in - start_drawing name image canvas; - make_page - [h1 [pcdata name]; - disconnect_box (); - choose_drawing_form (); - a feed_service [pcdata "atom feed"] name; - div ( if name = username - then [save_image_box name] - else [pcdata "no saving"] ); - canvas;]) - diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti_macaque/Makefile b/2.2/manual/files/tutorial/chapter3/multigraffiti_macaque/Makefile deleted file mode 100644 index 7030ddca..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti_macaque/Makefile +++ /dev/null @@ -1,68 +0,0 @@ - -APP_NAME := graffiti -SERVER_PACKAGE := cairo, unix, macaque.syntax -CLIENT_PACKAGE := - -SERVER_FILES = common.ml server.ml ${wildcard *.eliom} -CLIENT_FILES = common.ml client.ml ${wildcard *.eliom} - -PORT := 8080 - -OCLOSURE := YES - -### - -all: local byte opt conf - -LIBDIR := local/var/www/lib -JSDIR := local/var/www/static - -include Makefile.common - -distclean:: - -rm css/closure - -rm -r local - -rm graffiti.conf - -#### - -DIRS = local/var/lib/ocsidbm local/var/run local/var/log \ - local/var/www/static local/var/www/lib local/etc \ - local/var/www/static/graffiti_saved - -local: ${DIRS} local/var/www/static/css local/var/www/static/images css/closure - -local/var/www/static/css: - ln -fs $(shell pwd)/css local/var/www/static/css - -local/var/www/static/images: - ln -fs $(shell pwd)/images local/var/www/static/images - -css/closure: - ln -fs $(shell ocamlfind query oclosure)/closure/goog/css/ css/closure - -${DIRS}: - mkdir -p $@ - -conf: graffiti.conf - -graffiti.conf: graffiti.conf.in - sed -e "s|%%SRC%%|$(shell pwd)|" \ - -e "s|%%LIBDIR%%|${LIBDIR}|" \ - -e "s|%%JSDIR%%|${JSDIR}|" \ - -e "s|%%PORT%%|${PORT}|" \ - $< > $@ - -run.local: graffiti.conf - ocsigenserver -c graffiti.conf - -run.opt.local: graffiti.conf - ocsigenserver.opt -c graffiti.conf - -#### - -install:: - install -d -m 775 ${INSTALL_USER} ${INSTALL_DIR}/static/css - install -m 664 ${INSTALL_USER} css/*.css ${INSTALL_DIR}/static/css - cd $(shell ocamlfind query oclosure)/closure/goog/css/ && \ - find -type f -exec install -D -m 664 {} ${INSTALL_DIR}/static/css/closure/{} \; diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti_macaque/create_table.sql b/2.2/manual/files/tutorial/chapter3/multigraffiti_macaque/create_table.sql deleted file mode 100644 index 1c9bebad..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti_macaque/create_table.sql +++ /dev/null @@ -1,5 +0,0 @@ - -CREATE TABLE users ( - login text NOT NULL, - password text NOT NULL -); diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti_macaque/graffiti.conf.in b/2.2/manual/files/tutorial/chapter3/multigraffiti_macaque/graffiti.conf.in deleted file mode 100644 index 85afbc9b..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti_macaque/graffiti.conf.in +++ /dev/null @@ -1,36 +0,0 @@ - - - - - %%PORT%% - - %%SRC%%/local/var/log - %%SRC%%/local/var/run - %%SRC%%/local/var/run/ocsigenserver_command - - utf-8 - - - - - - - - - - - - - - - - - - - %%SRC%%/%%JSDIR%% - - - - - - diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti_macaque/server.ml b/2.2/manual/files/tutorial/chapter3/multigraffiti_macaque/server.ml deleted file mode 100644 index 9c79ff35..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti_macaque/server.ml +++ /dev/null @@ -1,237 +0,0 @@ -open Eliom_content -open Html5.D -open Common -open Lwt - -module My_app = - Eliom_registration.App (struct - let application_name = "graffiti" - end) - -let rgb_from_string color = (* color is in format "#rrggbb" *) - let get_color i = - (float_of_string ("0x"^(String.sub color (1+2*i) 2))) /. 255. - in - try get_color 0, get_color 1, get_color 2 with | _ -> 0.,0.,0. - -let launch_server_canvas () = - let bus = Eliom_bus.create Json.t in - - let draw_server, image_string = - let surface = Cairo.image_surface_create - Cairo.FORMAT_ARGB32 ~width ~height in - let ctx = Cairo.create surface in - ((fun ((color : string), size, (x1, y1), (x2, y2)) -> - - (* Set thickness of brush *) - Cairo.set_line_width ctx (float size) ; - Cairo.set_line_join ctx Cairo.LINE_JOIN_ROUND ; - Cairo.set_line_cap ctx Cairo.LINE_CAP_ROUND ; - let red, green, blue = rgb_from_string color 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.close_path ctx ; - - (* Apply the ink *) - Cairo.stroke ctx ; - ), - (fun () -> - let b = Buffer.create 10000 in - (* Output a PNG in a string *) - Cairo_png.surface_write_to_stream surface (Buffer.add_string b); - Buffer.contents b - )) - in - let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) in - bus,image_string - -let graffiti_info = Hashtbl.create 0 - -let imageservice = - Eliom_registration.Text.register_service - ~path:["image"] - ~headers:Http_headers.dyn_headers - ~get_params:(let open Eliom_parameter in string "name" ** int "q") - (* we add another parameter for the browser not to cache: at least - for chrome, there is no way to force the browser to reload the - image without leaving the application *) - (fun (name,_) () -> - try_lwt - let _ ,image_string = Hashtbl.find graffiti_info name in - Lwt.return (image_string (), "image/png") - with - | Not_found -> raise_lwt Eliom_common.Eliom_404) - -let get_bus (name:string) = - (* create a new bus and image_string function only if it did not exists *) - try - fst (Hashtbl.find graffiti_info name) - with - | Not_found -> - let bus,image_string = launch_server_canvas () in - Hashtbl.add graffiti_info name (bus,image_string); - bus - -let main_service = Eliom_service.service ~path:[""] - ~get_params:(Eliom_parameter.unit) () -let multigraffiti_service = Eliom_service.service ~path:[""] - ~get_params:(Eliom_parameter.suffix (Eliom_parameter.string "name")) () - -let choose_drawing_form () = - get_form ~service:multigraffiti_service - (fun (name) -> - [fieldset - [label ~a:[a_for name] [pcdata "drawing name: "]; - string_input ~input_type:`Text ~name (); - br (); - string_input ~input_type:`Submit ~value:"Go" () - ]]) - -let connection_service = - Eliom_service.post_coservice' - ~post_params:(let open Eliom_parameter in (string "name" ** string "password")) - () -let disconnection_service = Eliom_service.post_coservice' ~post_params:Eliom_parameter.unit () -let create_account_service = - Eliom_service.post_coservice ~fallback:main_service ~post_params:(let open Eliom_parameter in (string "name" ** string "password")) () - -let username = Eliom_reference.eref ~scope:Eliom_common.session None - -module Lwt_thread = struct - include Lwt - include Lwt_chan -end -module Lwt_PGOCaml = PGOCaml_generic.Make(Lwt_thread) -module Lwt_Query = Query.Make_with_Db(Lwt_thread)(Lwt_PGOCaml) - -let get_db : unit -> unit Lwt_PGOCaml.t Lwt.t = - let db_handler = ref None in - fun () -> - match !db_handler with - | Some h -> Lwt.return h - | None -> Lwt_PGOCaml.connect ~database:"testbase" () - -let table = <:table< users ( - login text NOT NULL, - password text NOT NULL -) >> - -let find name = - (get_db () >>= fun dbh -> - Lwt_Query.view dbh - <:view< {password = user_.password} | - user_ in $table$; - user_.login = $string:name$; >>) - -let insert name pwd = - get_db () >>= fun dbh -> - Lwt_Query.query dbh - <:insert< $table$ := { login = $string:name$; password = $string:pwd$; } >> - -let check_pwd name pwd = - (get_db () >>= fun dbh -> - Lwt_Query.view dbh - <:view< {password = user_.password} | - user_ in $table$; - user_.login = $string:name$; - user_.password = $string:pwd$ >>) - >|= (function [] -> false | _ -> true) - -let () = Eliom_registration.Action.register - ~service:create_account_service - (fun () (name, pwd) -> - find name >>= - (function - | [] -> insert name pwd - | _ -> Lwt.return ())) - -let () = Eliom_registration.Action.register - ~service:connection_service - (fun () (name, password) -> - check_pwd name password >>= - (function - | true -> Eliom_reference.set username (Some name) - | false -> Lwt.return ())) - -let () = - Eliom_registration.Action.register - ~service:disconnection_service - (fun () () -> Eliom_state.discard ~scope:Eliom_common.session ()) - -let disconnect_box () = - post_form disconnection_service - (fun _ -> [fieldset - [string_input - ~input_type:`Submit ~value:"Log out" ()]]) () - -let login_name_form service button_text = - post_form ~service - (fun (name1, name2) -> - [fieldset - [label ~a:[a_for name1] [pcdata "login: "]; - string_input ~input_type:`Text ~name:name1 (); - br (); - label ~a:[a_for name2] [pcdata "password: "]; - string_input ~input_type:`Password ~name:name2 (); - br (); - string_input ~input_type:`Submit ~value:button_text () - ]]) () - -let oclosure_script = - Html5.Id.create_global_elt - (js_script - ~uri:(make_uri (Eliom_service.static_dir ()) - ["graffiti_oclosure.js"]) ()) - -let make_page content = - Lwt.return - (html - (head - (title (pcdata "Graffiti")) - [ css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"common.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"hsvpalette.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"slider.css"]) (); - oclosure_script; - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"graffiti.css"]) (); - ]) - (body content)) - -let default_content () = - make_page - [h1 [pcdata "Welcome to Multigraffiti"]; - h2 [pcdata "log in"]; - login_name_form connection_service "Connect"; - h2 [pcdata "create account"]; - login_name_form create_account_service "Create account";] - -module Connected_translate = -struct - type page = string -> My_app.page Lwt.t - let translate page = - Eliom_reference.get username >>= - function - | None -> default_content () - | Some username -> page username -end - -module Connected = - Eliom_registration.Customize ( My_app ) ( Connected_translate ) - -let ( !% ) f = fun a b -> return (fun c -> f a b c) - -let () = Connected.register ~service:main_service - !% (fun () () username -> - make_page - [h1 [pcdata ("Welcome to Multigraffiti " ^ username)]; - choose_drawing_form ()]) - diff --git a/2.2/manual/files/tutorial/chapter3/multigraffiti_ocsipersist/server.ml b/2.2/manual/files/tutorial/chapter3/multigraffiti_ocsipersist/server.ml deleted file mode 100644 index 9b3e4406..00000000 --- a/2.2/manual/files/tutorial/chapter3/multigraffiti_ocsipersist/server.ml +++ /dev/null @@ -1,208 +0,0 @@ -open Eliom_content.Html5.D -open Common -open Lwt - -module My_app = - Eliom_registration.App ( - struct - let application_name = "graffiti" - end) - -let rgb_from_string color = (* color is in format "#rrggbb" *) - let get_color i = - (float_of_string ("0x"^(String.sub color (1+2*i) 2))) /. 255. - in - try get_color 0, get_color 1, get_color 2 with | _ -> 0.,0.,0. - -let launch_server_canvas () = - let bus = Eliom_bus.create Json.t in - - let draw_server, image_string = - let surface = Cairo.image_surface_create - Cairo.FORMAT_ARGB32 ~width ~height in - let ctx = Cairo.create surface in - ((fun ((color : string), size, (x1, y1), (x2, y2)) -> - - (* Set thickness of brush *) - Cairo.set_line_width ctx (float size) ; - Cairo.set_line_join ctx Cairo.LINE_JOIN_ROUND ; - Cairo.set_line_cap ctx Cairo.LINE_CAP_ROUND ; - let red, green, blue = rgb_from_string color 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.close_path ctx ; - - (* Apply the ink *) - Cairo.stroke ctx ; - ), - (fun () -> - let b = Buffer.create 10000 in - (* Output a PNG in a string *) - Cairo_png.surface_write_to_stream surface (Buffer.add_string b); - Buffer.contents b - )) - in - let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) in - bus,image_string - -let graffiti_info = Hashtbl.create 0 - -let imageservice = - Eliom_registration.Text.register_service - ~path:["image"] - ~headers:Http_headers.dyn_headers - ~get_params:(let open Eliom_parameter in string "name" ** int "q") - (* we add an int parameter for the browser not to cache the image: - at least for chrome, there is no way to force the browser to - reload the image without leaving the application *) - (fun (name,_) () -> - try_lwt - let _ ,image_string = Hashtbl.find graffiti_info name in - Lwt.return (image_string (), "image/png") - with - | Not_found -> raise_lwt Eliom_common.Eliom_404) - -let get_bus (name:string) = - (* create a new bus and image_string function only if it did not exists *) - try - fst (Hashtbl.find graffiti_info name) - with - | Not_found -> - let bus,image_string = launch_server_canvas () in - Hashtbl.add graffiti_info name (bus,image_string); - bus - -let main_service = Eliom_service.service ~path:[""] - ~get_params:(Eliom_parameter.unit) () -let multigraffiti_service = Eliom_service.service ~path:[""] - ~get_params:(Eliom_parameter.suffix (Eliom_parameter.string "name")) () - -let choose_drawing_form () = - get_form ~service:multigraffiti_service - (fun (name) -> - [fieldset - [label ~a:[a_for name] - [pcdata "drawing name: "]; - string_input ~input_type:`Text ~name (); - br (); - string_input - ~input_type:`Submit - ~value:"Go" () - ]]) - -let oclosure_script = - Eliom_content.Html5.Id.create_global_elt - (js_script - ~uri:(make_uri (Eliom_service.static_dir ()) - ["graffiti_oclosure.js"]) ()) - -let make_page content = - Lwt.return - (html - (head - (title (pcdata "Graffiti")) - [ css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"common.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"hsvpalette.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"slider.css"]) (); - oclosure_script; - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"graffiti.css"]) (); - ]) - (body content)) - -let connection_service = - Eliom_service.post_coservice' ~post_params: - (let open Eliom_parameter in (string "name" ** string "password")) () -let disconnection_service = Eliom_service.post_coservice' - ~post_params:Eliom_parameter.unit () -let create_account_service = - Eliom_service.post_coservice ~fallback:main_service ~post_params: - (let open Eliom_parameter in (string "name" ** string "password")) () - -let username = Eliom_reference.eref ~scope:Eliom_common.session None - -let user_table = Ocsipersist.open_table "user_table" - -let check_pwd name pwd = - try_lwt - lwt saved_password = Ocsipersist.find user_table name in - Lwt.return (pwd = saved_password) - with - Not_found -> Lwt.return false - -let () = Eliom_registration.Action.register - ~service:create_account_service - (fun () (name, pwd) -> Ocsipersist.add user_table name pwd) - -let () = Eliom_registration.Action.register - ~service:connection_service - (fun () (name, password) -> - match_lwt check_pwd name password with - | true -> Eliom_reference.set username (Some name) - | false -> Lwt.return ()) - -let () = - Eliom_registration.Action.register - ~service:disconnection_service - (fun () () -> Eliom_state.discard ~scope:Eliom_common.session ()) - -let disconnect_box () = - post_form disconnection_service - (fun _ -> [fieldset - [string_input - ~input_type:`Submit ~value:"Log out" ()]]) () - -let login_name_form service button_text = - post_form ~service - (fun (name1, name2) -> - [fieldset - [label ~a:[a_for name1] [pcdata "login: "]; - string_input ~input_type:`Text ~name:name1 (); - br (); - label ~a:[a_for name2] [pcdata "password: "]; - string_input - ~input_type:`Password - ~name:name2 (); - br (); - string_input - ~input_type:`Submit - ~value:button_text () - ]]) () - -let default_content () = - make_page - [h1 [pcdata "Welcome to Multigraffiti"]; - h2 [pcdata "log in"]; - login_name_form connection_service "Connect"; - h2 [pcdata "create account"]; - login_name_form create_account_service "Create account";] - -module Connected_translate = -struct - type page = string -> My_app.page Lwt.t - let translate page = - Eliom_reference.get username >>= - function - | None -> default_content () - | Some username -> page username -end - -module Connected = - Eliom_registration.Customize ( My_app ) ( Connected_translate ) - -let ( !% ) f = fun a b -> return (fun c -> f a b c) - -let () = Connected.register ~service:main_service - !% (fun () () username -> - make_page - [h1 [pcdata ("Welcome to Multigraffiti " ^ username)]; - choose_drawing_form ()]) diff --git a/2.2/manual/files/tutorial/static/css/common.css b/2.2/manual/files/tutorial/static/css/common.css deleted file mode 100644 index 02d7073c..00000000 --- a/2.2/manual/files/tutorial/static/css/common.css +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2009 The Closure Library Authors. All Rights Reserved. - * - * Use of this source code is governed by an Apache 2.0 License. - * See the COPYING file for details. - */ - -/* - * Cross-browser implementation of the "display: inline-block" CSS property. - * See http://www.w3.org/TR/CSS21/visuren.html#propdef-display for details. - * Tested on IE 6 & 7, FF 1.5 & 2.0, Safari 2 & 3, Webkit, and Opera 9. - * - * @author attila@google.com (Attila Bodis) - */ - -/* - * Default rule; only Safari, Webkit, and Opera handle it without hacks. - */ -.goog-inline-block { - position: relative; - display: -moz-inline-box; /* Ignored by FF3 and later. */ - display: inline-block; -} - -/* - * Pre-IE7 IE hack. On IE, "display: inline-block" only gives the element - * layout, but doesn't give it inline behavior. Subsequently setting display - * to inline does the trick. - */ -* html .goog-inline-block { - display: inline; -} - -/* - * IE7-only hack. On IE, "display: inline-block" only gives the element - * layout, but doesn't give it inline behavior. Subsequently setting display - * to inline does the trick. - */ -*:first-child+html .goog-inline-block { - display: inline; -} diff --git a/2.2/manual/files/tutorial/static/css/graffiti.css b/2.2/manual/files/tutorial/static/css/graffiti.css deleted file mode 100644 index 5768a851..00000000 --- a/2.2/manual/files/tutorial/static/css/graffiti.css +++ /dev/null @@ -1,21 +0,0 @@ -body { - width: 750px; -} - -canvas { - border: solid 1px #aaaaaa; -} - -div.goog-slider-vertical, -DIV.goog-slider-vertical { - position: absolute; - top: 100px; - left: 740px; -} - -div.goog-hsv-palette-sm, -DIV.goog-hsv-palette-sm { - position: absolute; - top: 100px; - left: 780px; -} diff --git a/2.2/manual/files/tutorial/static/css/hsvpalette.css b/2.2/manual/files/tutorial/static/css/hsvpalette.css deleted file mode 100644 index d3ae7f67..00000000 --- a/2.2/manual/files/tutorial/static/css/hsvpalette.css +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2008 The Closure Library Authors. All Rights Reserved. - * - * Use of this source code is governed by an Apache 2.0 License. - * See the COPYING file for details. - */ - -/* - * All Rights Reserved. - * - * Styles for the HSV color palette. - * - * @author smcbride@google.com (Sean McBride) - * @author arv@google.com (Erik Arvidsson) - * @author manucornet@google.com (Manu Cornet) - */ - -.goog-hsv-palette, -.goog-hsv-palette-sm { - position: relative; - border: 1px solid #999; - border-color: #ccc #999 #999 #ccc; - width: 400px; - height: 276px; -} - -.goog-hsv-palette-sm { - width: 182px; - height: 185px; -} - -.goog-hsv-palette label span, -.goog-hsv-palette-sm label span { - display: none; -} - -.goog-hsv-palette-hs-backdrop, -.goog-hsv-palette-sm-hs-backdrop, -.goog-hsv-palette-hs-image, -.goog-hsv-palette-sm-hs-image { - position: absolute; - top: 10px; - left: 10px; - width: 256px; - height: 256px; - border: 1px solid #999; -} - -.goog-hsv-palette-sm-hs-backdrop, -.goog-hsv-palette-sm-hs-image { - top: 45px; - width: 128px; - height: 128px; -} - -.goog-hsv-palette-hs-backdrop, -.goog-hsv-palette-sm-hs-backdrop { - background-color: #000; -} - -.goog-hsv-palette-hs-image, -.goog-hsv-palette-v-image, -.goog-hsv-palette-hs-handle, -.goog-hsv-palette-v-handle { - background-image: url(../images/hsv-sprite.png); -} - -.goog-hsv-palette-noalpha .goog-hsv-palette-hs-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-v-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-hs-handle, -.goog-hsv-palette-noalpha .goog-hsv-palette-v-handle { - background-image: url(../images/hsv-sprite.gif); -} - -.goog-hsv-palette-sm-hs-image, -.goog-hsv-palette-sm-v-image, -.goog-hsv-palette-sm-hs-handle, -.goog-hsv-palette-sm-v-handle { - background-image: url(../images/hsv-sprite-sm.png); -} - -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-hs-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-v-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-hs-handle, -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-v-handle { - background-image: url(../images/hsv-sprite-sm.gif); -} - -.goog-hsv-palette-hs-image, -.goog-hsv-palette-sm-hs-image { - background-position: 0 0; -} - -.goog-hsv-palette-hs-handle, -.goog-hsv-palette-sm-hs-handle { - position: absolute; - left: 5px; - top: 5px; - width: 11px; - height: 11px; - overflow: hidden; - background-position: 0 -256px; -} - -.goog-hsv-palette-sm-hs-handle { - top: 40px; - background-position: 0 -128px; -} - -.goog-hsv-palette-v-image, -.goog-hsv-palette-sm-v-image { - position: absolute; - top: 10px; - left: 286px; - width: 19px; - height: 256px; - border: 1px solid #999; - background-color: #fff; - background-position: -256px 0; -} - -.goog-hsv-palette-sm-v-image { - top: 45px; - left: 155px; - width: 9px; - height: 128px; - background-position: -128px 0; -} - -.goog-hsv-palette-v-handle, -.goog-hsv-palette-sm-v-handle { - position: absolute; - top: 5px; - left: 279px; - width: 35px; - height: 11px; - background-position: -11px -256px; - overflow: hidden; -} - -.goog-hsv-palette-sm-v-handle { - top: 40px; - left: 148px; - width: 25px; - background-position: -11px -128px; -} - -.goog-hsv-palette-swatch, -.goog-hsv-palette-sm-swatch { - position: absolute; - top: 10px; - right: 10px; - width: 65px; - height: 65px; - border: 1px solid #999; - background-color: #fff; -} - -.goog-hsv-palette-sm-swatch { - top: 10px; - right: auto; - left: 10px; - width: 30px; - height: 22px; -} - -.goog-hsv-palette-input, -.goog-hsv-palette-sm-input { - position: absolute; - top: 85px; - right: 10px; - width: 65px; -} - -.goog-hsv-palette-sm-input { - top: 10px; - right: auto; - left: 50px; -} diff --git a/2.2/manual/files/tutorial/static/css/slider.css b/2.2/manual/files/tutorial/static/css/slider.css deleted file mode 100644 index ddd07482..00000000 --- a/2.2/manual/files/tutorial/static/css/slider.css +++ /dev/null @@ -1,45 +0,0 @@ -.goog-slider-vertical, -.goog-slider-horizontal { -background-color: ThreeDFace; -position: relative; -overflow: hidden; -} - -.goog-slider-vertical { -height: 300px; -width: 20px; -} - -.goog-slider-horizontal { -height: 20px; -width: 400px; -} - -.goog-slider-thumb { -position: absolute; -background-color: ThreeDShadow; -overflow: hidden; -} - -.goog-slider-vertical .goog-slider-thumb { -left: 0; -height: 20px; -width: 100%; -} - -.goog-slider-horizontal .goog-slider-thumb { -top: 0; -width: 20px; -height: 100%; -} -#s-h { -margin-bottom: 2em; -} -strong { -display: block; -margin-bottom: 3px; -} -#out1, #out2 { -color: #999; -margin-left: 1em; -} diff --git a/2.2/manual/files/tutorial/static/images/hsv-sprite-sm.png b/2.2/manual/files/tutorial/static/images/hsv-sprite-sm.png deleted file mode 100644 index 470f8e0c..00000000 Binary files a/2.2/manual/files/tutorial/static/images/hsv-sprite-sm.png and /dev/null differ diff --git a/2.2/manual/interaction.html b/2.2/manual/interaction.html deleted file mode 100644 index 702988e4..00000000 --- a/2.2/manual/interaction.html +++ /dev/null @@ -1,791 +0,0 @@ -Implementing Web Interaction Using Eliom

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

Implementing Web Interaction Using Eliom

The code of this tutorial has been tested with the 2.2 release of -the Ocsigen bundle.
-

This chapter is a tutorial explaining how to create a small Web site -with several pages, users, sessions, etc. Then, in next chapter, -we will incorporate the features of this site with the program from the -previous chapter, to show that we can -mix this kind of interaction with client-side programs. -

We assume you have read at least the first section of the previous -chapter, which explained how to create a service and constuct valid -HTML pages. -

We will create a simple Web site with one main page and a page for -each user (assuming we have several users already created). Then we -will add a login/connection form. We will also add a registration -form, in order to learn how to create dynamically new services, -and why it is very useful. -

The full code of the program can be -downloaded. -

Services

The main page

Let's start again from scratch with the following site. -

open Eliom_content.Html5.D
-
-let main_service =
-  Eliom_registration.Html5.register_service
-    ~path:[""] ~get_params:Eliom_parameter.unit
-    (fun () () ->
-      Lwt.return
-        (html (head (title (pcdata "")) [])
-                       (body [h1 [pcdata "Hello"]])))

Note that we are using Eliom_registration.Html5, as we are not building a -client side program for now. -

Put this example in a file (tuto.ml), compile it like so: -

eliomc -c tuto.ml
-

Finally, modify your configuration file as explained in the previous -chapter to load the file tuto.cmo. Then launch Ocsigen to run -the site. -

Adding a page for each user

We will now create a page for each user. To do this, we will create a -new service, taking the user name as parameter: -

let user_service =
-  Eliom_registration.Html5.register_service
-    ~path:[""] ~get_params:(Eliom_parameter.string "name")
-    (fun name () ->
-      Lwt.return
-        (html (head (title (pcdata name)) [])
-                       (body [h1 [pcdata name]])))

Add these lines to the same file, compile, start the server and verify -that everything is working by trying, for example: -http://localhost:8080/?name=toto. -

For our program, we would prefer to take one part of the URL path as the parameter -describing the name of the user. -I change the definition of my service this way: -

let user_service =
-  Eliom_registration.Html5.register_service
-    ~path:["users"]
-    ~get_params:
-       (Eliom_parameter.suffix (Eliom_parameter.string "name"))
-    (fun name () -> ... )

The user pages are now available at URLs http://localhost:8080/users/username. -

Links

We now want to add a link on each user page to go back to the main -page. -

Change the handler of user_service into: -

(fun name () -> Lwt.return
-  (html
-    (head (title (pcdata name)) [])
-    (body [h1 [pcdata name];
-           p [a ~service:main_service [pcdata "Home"] ()]])))

Links towards services with parameters

In our example above, the last parameter is () because the -service does not expect any parameter. If the service expects, for -example, a pair (int * string), you must provide a matching -value as last parameter. OCaml checks at compile time that the type -of the parameters in a link corresponds to the type expected by the -service! Also note that the parameter names are generated -automatically from the service, making it impossible to erroneously -create bad links. -

To show an example of a link with parameters, we will display the list -of user pages on the main page. Here is the new version of the full -program: -

open Eliom_content.Html5.D
-open Eliom_parameter
-
-let main_service =
-  Eliom_service.service ~path:[""] ~get_params:unit ()
-
-let user_service =
-  Eliom_service.service
-    ~path:["users"] ~get_params:(suffix (string "name")) ()
-
-(* User names and passwords: *)
-let users = ref [("Calvin", "123"); ("Hobbes", "456")]
-
-let user_links () =
-  ul (List.map (fun (name, _) -> 
-                  li [a
-                        ~service:user_service [pcdata name] name])
-               !users)
-
-let _ =
-  Eliom_registration.Html5.register
-    ~service:main_service
-    (fun () () ->
-      Lwt.return
-        (html (head (title (pcdata "")) [])
-              (body [h1 [pcdata "Hello"];
-                     user_links ()])));
-
-  Eliom_registration.Html5.register
-    ~service:user_service
-    (fun name () ->
-      Lwt.return
-        (html (head (title (pcdata name)) [])
-              (body [h1 [pcdata name];
-                     p [a ~service:main_service [pcdata "Home"] ()]])));

Sessions

Connection service

Now I want to add a connection form. First, I will create a service -for checking the name and password. Since I don't want the username -and password to be shown in the URL, I will use hidden parameters -(or POST parameters). Thus, I'll need to create a new service taking -these parameters: -

let connection_service =
-  Eliom_service.post_service
-    ~fallback:main_service
-    ~post_params:(string "name" ** string "password")
-    ()

Now you can register a handler for the new service: -

Eliom_registration.Html5.register
-    ~service:connection_service
-    (fun () (name, password) ->
-      let message =
-        if check_pwd name password
-        then "Hello "^name
-        else "Wrong name or password"
-      in
-      Lwt.return
-        (html (head (title (pcdata "")) [])
-              (body [h1 [pcdata message];
-                     user_links ()])));

where check_pwd is defined by: -

let check_pwd name pwd =
-  try List.assoc name !users = pwd with Not_found -> false

Connection form

For now, I will add the connection form only on the main page of the -site. -

Let's create a function for generating the form: -

let connection_box () =
-  post_form ~service:connection_service
-    (fun (name1, name2) ->
-      [fieldset
-         [label ~a:[a_for name1] [pcdata "login: "];
-          string_input ~input_type:`Text
-                                          ~name:name1 ();
-          br ();
-          label ~a:[a_for name2] [pcdata "password: "];
-          string_input ~input_type:`Password
-                                          ~name:name2 ();
-          br ();
-          string_input ~input_type:`Submit
-                                          ~value:"Connect" ()
-         ]]) ()

Now, add a call to this function in the handler of the main service -(for example just before the user links). -

Opening a session

Now we want to remember that the user is successfully connected. To do -that we will set a reference when the user successfully connects, and -we will restrict the scope of this reference to the session (that is, -to the browser). -

Define your Eliom reference with a default value: -

let username = Eliom_reference.eref ~scope:Eliom_common.session None

Here is the new connection_box function: -

let connection_box () =
-  lwt u = Eliom_reference.get username in
-  Lwt.return
-    (match u with
-      | Some s -> p [pcdata "You are connected as "; pcdata s]
-      | None ->
-        post_form ~service:connection_service
-          (fun (name1, name2) ->
-            [fieldset
-	       [label ~a:[a_for name1] [pcdata "login: "];
-                string_input ~input_type:`Text
-                                                ~name:name1 ();
-                br ();
-                label ~a:[a_for name2] [pcdata "password: "];
-                string_input ~input_type:`Password
-                                                ~name:name2 ();
-                br ();
-                string_input ~input_type:`Submit
-                                                ~value:"Connect" ()
-               ]]) ())

... and replace the registration of the main service and the connection service by: -

Eliom_registration.Html5.register
-    ~service:main_service
-    (fun () () ->
-      lwt cf = connection_box () in
-      Lwt.return
-        (html (head (title (pcdata "")) [])
-              (body [h1 [pcdata "Hello"];
-                     cf;
-                     user_links ()])));
-
-  Eliom_registration.Html5.register
-    ~service:connection_service
-    (fun () (name, password) ->
-      lwt message =
-        if check_pwd name password
-        then begin
-          Eliom_reference.set username (Some name) >>=
-            (fun _ -> Lwt.return ("Hello "^name))
-        end else
-	  Lwt.return "Wrong name or password" in
-      Lwt.return
-        (html (head (title (pcdata "")) [])
-              (body [h1 [pcdata message];
-                     user_links ()])));

Display the usual page after connection

As you can see, our connection service is displaying a welcome page -which is different from the main page in connected mode. We would -rather display the same page. One solution is to call the same -handler after registering session data. -

A cleaner solution is to use an action, that is: a service which will -just perform a side effect. Replace the registration -of the connection service by: -

Eliom_registration.Action.register
-    ~service:connection_service
-    (fun () (name, password) ->
-      if check_pwd name password
-      then Eliom_reference.set username (Some name)
-      else Lwt.return ());

Now the main page is displayed after connection. -

Putting a connection form on each page

Transform the connection service into a non-attached coservice: -

let connection_service =
-  Eliom_service.post_coservice'
-    ~post_params:(string "name" ** string "password")
-    ()

Now you can add the connection box on user pages. -

Eliom_registration.Html5.register
-    ~service:user_service
-    (fun name () ->
-      connection_box () >>= fun cf ->
-      Lwt.return
-        (html (head (title (pcdata name)) [])
-              (body [h1 [pcdata name];
-                     cf;
-                     p [a
-                          ~service:main_service [pcdata "Home"] ()]])));

Disconnection

To create a logout/disconnection form, we create another non-attached -coservice using POST method, and register another action. We call the -function Eliom_state.discard with scope Eliom_common.session to remove all session -data. -

let disconnection_service =
-  Eliom_service.post_coservice' ~post_params:unit ()
-
-let disconnect_box () =
-  post_form disconnection_service
-    (fun _ -> [p [string_input
-                    ~input_type:`Submit ~value:"Log out" ()]]) ()
-
-let _ =
-  Eliom_registration.Action.register
-    ~service:disconnection_service
-    (fun () () -> Eliom_state.discard ~scope:Eliom_common.session ())

Then add this form in the connection box: -

let connection_box () =
-  lwt u = Eliom_reference.get username in
-  Lwt.return
-    (match u with
-      | Some s -> div [p [pcdata "You are connected as "; pcdata s; ];
-                       disconnect_box () ]
-      | None -> ...

Registration of users

Basic registration form

We will now add a registration form to the application. We create a -new regular service, attached to the path /registration, that -displays a registration form, and an action that will add the user to -the "database": -

let new_user_form_service =
-  Eliom_service.service ~path:["registration"] ~get_params:unit ()
-
-let create_account_service = 
-  Eliom_service.post_coservice
-    ~fallback:main_service
-    ~post_params:(string "name" ** string "password") ()
-
-let create_account_form () =
-  post_form ~service:create_account_service
-    (fun (name1, name2) ->
-      [fieldset
-         [label ~a:[a_for name1] [pcdata "login: "];
-          string_input ~input_type:`Text ~name:name1 ();
-          br ();
-          label ~a:[a_for name2] [pcdata "password: "];
-          string_input ~input_type:`Password ~name:name2 ();
-          br ();
-          string_input ~input_type:`Submit ~value:"Connect" ()
-         ]]) ()
-
-let _ =
-  Eliom_registration.Html5.register
-    ~service:new_user_form_service
-    (fun () () ->
-      Lwt.return
-        (html (head (title (pcdata "")) [])
-              (body [h1 [pcdata "Create an account"];
-                     create_account_form ();
-                    ])));
-
-  Eliom_registration.Action.register
-    ~service:create_account_service
-    (fun () (name, pwd) ->
-      users := (name, pwd)::!users;
-      Lwt.return ())

Then add the link to this service in the connection box: -

let connection_box () =
-  lwt u = Eliom_reference.get username in
-  Lwt.return
-    (match u with
-      | Some s -> div [p [pcdata "You are connected as "; pcdata s; ];
-                       disconnect_box () ]
-      | None ->
-        div [post_form ~service:connection_service
-                (fun (name1, name2) ->
-...
-                ) ();
-             p [a new_user_form_service
-                  [pcdata "Create an account"] ()]]
-            )

Registration form with confirmation

Now we want to add a confirmation page before actually creating the -account. We replace the service create_account_service by a -new POST attached coservice called account_confirmation_service: -

let account_confirmation_service =
-  Eliom_service.post_coservice
-    ~fallback:new_user_form_service
-    ~post_params:(string "name" ** string "password")
-    ()

and we make the account creation form point at this new service. -

We register an HTML handler on this service, with the confirmation -page. As a side effect, this page will create the actual account -creation service: -

Eliom_registration.Html5.register
-  ~service:account_confirmation_service
-  (fun () (name, pwd) ->
-    let create_account_service =
-      Eliom_registration.Action.register_coservice
-        ~fallback:main_service
-        ~get_params:Eliom_parameter.unit
-        ~timeout:60.
-        (fun () () ->
-          users := (name, pwd)::!users;
-          Lwt.return ())
-    in
-    Lwt.return
-      (html
-        (head (title (pcdata "")) [])
-          (body
-            [h1 [pcdata "Confirm account creation for "; pcdata name];
-             p [a ~service:create_account_service [pcdata "Yes"] ();
-                pcdata " ";
-                a ~service:main_service [pcdata "No"] ()]
-            ])));

Also remove the registration of the create_account_service -service and modify the user creation form to make it points towards -account_confirmation_service. -

A few enhancements

Displaying a "wrong password" message

In the current version, our Web site fails silently when the password -is wrong. Let's improve this behavior by displaying an error message. -To do that, we need to pass information to the service occurring after -the action. We record this information in an Eliom reference with -scope Eliom_common.request. -

Define an Eliom reference: -

let wrong_pwd = Eliom_reference.eref ~scope:Eliom_common.request false

Modify the connection box this way: -

let connection_box () =
-  lwt u = Eliom_reference.get username in
-  lwt wp = Eliom_reference.get wrong_pwd in
-  Lwt.return
-    (match u with
-      | Some s -> div [p [pcdata "You are connected as "; pcdata s; ];
-                       disconnect_box () ]
-      | None ->
-        let l =
-          [post_form ~service:connection_service
-            (fun (name1, name2) ->
-              [fieldset
-	         [label ~a:[a_for name1] [pcdata "login: "];
-                  string_input ~input_type:`Text ~name:name1 ();
-                  br ();
-                  label ~a:[a_for name2] [pcdata "password: "];
-                  string_input ~input_type:`Password ~name:name2 ();
-                  br ();
-                  string_input ~input_type:`Submit ~value:"Connect" ()
-                 ]]) ();
-             p [a new_user_form_service
-                  [pcdata "Create an account"] ()]]
-        in
-        if wp
-        then div ((p [em [pcdata "Wrong user or password"]])::l)
-        else div l
-    )

... and modify the connection_service handler: -

Eliom_registration.Action.register
-    ~service:connection_service
-    (fun () (name, password) ->
-      if check_pwd name password
-      then Eliom_reference.set username (Some name)
-      else Eliom_reference.set wrong_pwd true);

Sending 404 errors for non-existing users

Our service user_service responds to any request parameter, even -if the user does not exist in the database. We want to check that the -user is in the database before displaying the page, and send a 404 -error if the user is not. To do that, we will replace the module - My_app by Eliom_registration.Any to register the service user_service: -

Eliom_registration.Any.register
-  ~service:user_service
-  (fun name () ->
-    if List.exists (fun (n, _) -> n = name) !users
-    then begin
-      lwt cf = connection_box () in
-      Eliom_registration.Html5.send
-        (html (head (title (pcdata name)) [])
-              (body [h1 [pcdata name];
-                     cf;
-                     p [a ~service:main_service [pcdata "Home"] ()]]))
-    end else
-      Eliom_registration.Html5.send
-        ~code:404
-        (html (head (title (pcdata "404")) [])
-              (body [h1 [pcdata "404"];
-                     p [pcdata "That page does not exist"]]))
-    );

> -

Using customized output module to simplify getting user data

When you want to assume that you have informations available in -sessions, for instance when a site is mainly available to connected -users, it becomes tedious to check everywhere that a reference is not -None. We can build a version of a registration module to simplify that -using Eliom_registration.Customize. -

We first need a translation module which checks for session -informations and fall back to a default page if they are not -available. -

module Connected_translate =
-struct
-  type page = string -> Eliom_registration.Html5.page Lwt.t
-  let translate page =
-    lwt username = Eliom_reference.get username in
-    match username with
-    | None ->
-      let connection_box =
-        post_form ~service:connection_service
-          (fun (name1, name2) ->
-    	    [fieldset
-	      [label ~a:[a_for name1] [pcdata "login: "];
-               string_input ~input_type:`Text ~name:name1 ();
-               br ();
-               label ~a:[a_for name2] [pcdata "password: "];
-               string_input ~input_type:`Password ~name:name2 ();
-               br ();
-               string_input ~input_type:`Submit ~value:"Connect" ()
-	     ]]) ()
-      in
-      Lwt.return
-        (html 
-	  (head (title (pcdata "")) [])
-          (body [h1 [pcdata "Hello"];
-	         connection_box;]))
-    | Some username -> page username
-end

The translate function takes a function page and apply it -with the current username if available and falls back to a default -login page if not. -

We can now make our own registration module. -

module Connected =
-  Eliom_registration.Customize
-    (Eliom_registration.Html5) (Connected_translate)
-
-let _ = Connected.register_service 
-  ~path:[""] ~get_params:unit
-  (fun () () ->
-    Lwt.return
-     (fun username ->
-        Lwt.return (html (head (title (pcdata "")) [])
-                   (body [h1 [pcdata ("Welcome " ^ username) ]; ]))))

The type of Connected.register_service forces us to have the -heavy notation: Lwt.return (fun username -> Lwt.return...). We -can make it lighter using -

let ( !% ) f = fun a b ->  Lwt.return (fun c -> f a b c)
-
-let _ = Connected.register_service 
-  ~path:[""] ~get_params:unit
-  !% (fun () () username ->
-     Lwt.return (html (head  (title (pcdata "")) [])
-	      (body [h1 [pcdata ("Welcome " ^ username) ]; ])))
diff --git a/2.2/manual/intro.html b/2.2/manual/intro.html deleted file mode 100644 index 3851d7d7..00000000 --- a/2.2/manual/intro.html +++ /dev/null @@ -1,195 +0,0 @@ -Introduction

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

The code of this tutorial has been tested with the 2.2 release of -the Ocsigen bundle.
-

Introduction

This tutorial is an overview of the main features of the Ocsigen framework. -It explains how to program Web sites and client-server Web applications in -OCaml using Eliom, Lwt, Js_of_ocaml and the other -software of the Ocsigen project. -Read it to understand the basics, -then refer to each project's manual for more detail. -

Warning: This programming guide assumes you know the OCaml -language, and that you have a working installation of Ocsigen -server, Eliom, Lwt, Js_of_ocaml, (and O'Closure for -some examples). In particular, you need to have the interfaces (cmi) -installed for all these packages. They are sometimes in separate -packages ("-dev" packages in Debian/Ubuntu). We recommend to use -the packages from your distribution if they are available. If not, try -to install all OCaml related packages with the bundle we provide, or using -Godi. If it does not work, please report -the problem to the package maintainers. We recommend to use the source -packages only if you want to test unreleased features from development -versions. -

The first section of this introduction lists our main -software projects. The second section is an overview of the tutorial. -In the following chapters, we will show how to build -step by step a small client/server Web application. -

-

Ocsigen software projects

Most projects are independent and can be used for non Eliom-releated -applications, for example: -

  • the cooperative threading library Lwt, -
  • the TyXML library for generating typed XML, -
  • the database query library Macaque, -
  • the Js_of_ocaml compiler (from Ocaml bytecode to Javascript), -
  • the O'Closure library, a binding for the Google closure Javascript widget -library to be used with Js_of_ocaml, -
  • the Ocsigen server Web server. -

Ocsigen server

Ocsigen server is a full featured Web server written in modular way. -It implements most features of the HTTP protocol, and has a very powerful -extension mechanism that make very easy to plug your own OCaml modules -for generating pages. Many extensions are already written: -

Staticmod -
is an extension to serve static files. -
Eliom -
is an extension to create reliable client/server Web applications -or Web sites in OCaml using advanced high level concepts. -
Extendconfiguration -
allows for more options in the configuration file. -
Accesscontrol -
allows to restrict the access to the sites from the config file (to request coming from a subnet, containing some headers, etc.). -
Authbasic -
allows to restrict the access to the sites from the config file using Basic HTTP Authentication. -
CGImod -
is an extension to serve CGI scripts. It may also be used to serve PHP through CGI. -
Deflatemod -
is an extension used to compress data before sending to the client. -
Redirectmod -
is an extension to set redirections towards other Web sites from the configuration file. -
Revproxy -
is a reverse proxy for Ocsigen server. -It allows to ask another server to handle the request. -
Rewritemod -
is a module to make changes in incoming requests before sending them to other extensions. -
Outputfilter -
allows to rewrite some parts of the output before sending it to the client. -
Userconf -
allows users to have their own configuration files. -
Comet -
makes server to client communications possible. -

Ocsigen server has a sophisticated configuration file mechanism allowing -complex configurations of sites. -

Eliom

-Eliom is an extension for Ocsigen server -that allows the creation of reliable client/server Web applications (or traditional dynamic Websites) in OCaml. -It implements many new concepts, and as a result, its approach to application development is very different -from all other Web programming tools. -

Expressiveness

Eliom enables the creation of complex Web sites in very few lines of code, by providing high level programming primitives for many aspects of Web programming: communication between the client and the server, implementation of complex Web interaction, sessions, etc. -

Static typing

Eliom also improves the stability of your application by making extensive use of OCaml's static typing. The compiler checks many things for you, helping you to remove many bugs. For example, the validity of pages (e.g. well-formed HTML) is checked at compile time! Moreover, Eliom also statically checks, for example, that your Web site does not contain broken links or that the parameters in a link or a form correspond to the service it leads to (and many other things). -

A client/server application in one piece of code!

But one of the most significant features of Eliom is that it allows to program both sides of a Web application as a single program. The client side parts are automatically extracted and compiled into Javascript to be executed in the browser. The communication between the server is straightforward (in both directions!), as you can use server side values in the client side code. -

Note that client side features can be mixed with traditional Web interaction -(links, forms, bookmarks, back button ...) -

Security

Eliom helps you to make your Web application secure by automatically addressing several common security issues (code injection, session fixation ...) and by providing features to implement very secure behaviours (avoiding for example "cross-site request forgery").
-

Lwt

Lwt is a cooperative thread library for OCaml. -It provides an alternative to the more usual preemptive threads approach -for programming concurrent applications, that avoids most problems of concurrent -data access and deadlocks. -It is used by Ocsigen server and Eliom and has now become a standard way to -implement concurrent applications in OCaml. -All your Web sites must be written in Lwt-compatible way! -

Js_of_ocaml

Js_of_ocaml is a compiler of OCaml bytecode to Javascript. It makes it possible to run Ocaml programs in a Web browser. Its key features are the following: -

  • The whole language, and most of the standard library are supported. -
  • The compiler is easy to install: it only depends on Findlib and Lwt. -
  • The generated code is independant of Eliom and the Ocsigen server. You can use it with any Web server. -
  • You can use a standard installation of OCaml to compile your programs. In particular, you do not have to recompile a library to use it with Js_of_ocaml. You just have to link your program with a specific library to interface with the browser APIs. -

Macaque

Macaque is a library for safe and flexible database queries -using comprehensions. -

O'Closure

O'Closure is a binding for the Google Closure Javascript widget library. -It makes possible to write dynamic user interfaces for Web pages in OCaml -using Js_of_ocaml. -

Overview of the tutorial

The goal of this tutorial is to give an overview of the -features the Ocsigen framework brings to build Web applications. It -will allow you to understand the philosophy of Eliom (the Web -programming framework in OCaml), and are also a good starting point to -understand other libraries of the Ocsigen project: Lwt, Macaque, etc. -

Theses chapters do not aim at exhaustiveness but many references -towards other chapters are made all along the text, so that you can -learn the concepts in detail if you want to. -

We assume the reader has a good knowledge of the Objective Caml -language.
-

Next chapter is a tutorial for writing a small client/server -collaborative drawing application. You will learn -how to: -

  • Create new services -
  • Output valid HTML -
  • Send OCaml code to be executed on the client -
  • Call Javascript methods from OCaml -
  • Catch mouse events -
  • Communicate with the server, in both directions -
  • Use the O'Closure widget library -
  • Create services with non-HTML output -

The following chapter is devoted to server side programming. We will -create a new Web site with several pages and user connection. In this -chapter, you will learn how to: -

  • Create a link towards another service -
  • Create forms -
  • Register session data or session services -
  • Create services performing actions (with no output) -
  • Dynamically register new services (continuation based Web programming) -

The last chapter is an overview of miscellaneous features. We will -mix the application from the two previous chapters -to produce a multi-user collaborative drawing -application. In this chapter, you will learn how to: -

  • Integrate a typical Web interaction (links, forms, …) with a client side program. -
  • Use Macaque for safe database interaction -
  • Add sounds or videos to your application -
  • Change pages without stopping the client side program -
  • Connect with external accounts (openID) -
  • Add an Atom stream -

If you are not interested in client-side programming you can safely -skip most of the first chapter (although we recommend you read at least -the first section, which explains the basics). Conversely, if you are -not interested in standard Web interactions, with sessions links, and -forms, you can skip the second chapter. -

Keep in mind that one of the key feature of Eliom is that it allows -one to mix commonplace Web interactions (URLs, forms, links, -bookmarks, back button) with dynamic client side features. In -particular, the client-side program does not stop when the user -clicks a link, sends a form, or presses the back button–yet the user -still can save bookmarks on pages! This opens up a wide field of new -possibilities, and should facilitate the emergence of new kinds of Web -applications.

diff --git a/2.2/manual/misc.html b/2.2/manual/misc.html deleted file mode 100644 index 8082e439..00000000 --- a/2.2/manual/misc.html +++ /dev/null @@ -1,684 +0,0 @@ -Miscellaneous features

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

Miscellaneous features

The code of this tutorial has been tested with the 2.2 release of -the Ocsigen bundle.
-

Multi-user collaborative drawing application

We now want to take back our collaborative drawing application and -turn it to a multi user one. Each user will have his own drawing, -where everyone can draw, and the owner will have the ability to save -the drawing he wants and create an Atom feed containing all saved drawings. -

Download the full code of the examples -or browse it -online. -

Split application into multiple files and using several canvas

In this first section, we will build a multi-canvas drawing application. -Each drawing has its own URL. Everyone can create a new drawing by going -to the corresponding URL. -

We need to refactor some parts. In particular, we need to be able to handle -separately different drawings. To do this, we will turn all global -variable, like the bus, in local ones. -

When an application grows, it start being useful to split it in -multiple files. For sake of example, we will split graffiti into 4 -files. -

  • common.ml which will be part of both client and server, -containing shared types and declarations -
  • client.ml client only part of the application -
  • server.ml server only part of the application -
  • graffiti.eliom the only part where we need to include both client and server code -

common.ml

It contains what was in {shared{ .. }} -

type messages = (string * int * (int * int) * (int * int)) deriving (Json)
-
-let width = 700
-let height = 400

client.ml

It is almost the same code as what was enclosed in {client{ ... }} -with the difference that what was previously in - Eliom_service.onload {{ }} is now in the function launch_client_canvas. -

open Eliom_content
-open Common
-open Event_arrows
-
-let draw ctx (color, size, (x1, y1), (x2, y2)) =
-  ctx##strokeStyle <- (Js.string color);
-  ctx##lineWidth <- float size;
-  ctx##beginPath();
-  ctx##moveTo(float x1, float y1);
-  ctx##lineTo(float x2, float y2);
-  ctx##stroke()
-
-(* type containing all informations we need to stop interaction
-   inside the page *)
-type drawing_canceller =
-    { drawing_thread : unit Lwt.t;
-      (* the thread reading messages from the bus *)
-      drawing_arrow : Event_arrows.canceller;
-      (* the arrow handling mouse events *)
-    }
-
-let stop_drawing { drawing_thread; drawing_arrow } =
-  Lwt.cancel drawing_thread;
-  (* cancelling this thread also close the bus *)
-  Event_arrows.cancel drawing_arrow

Notice that the client does not keep data from pages it left. -Lwt.cancel t stops the thread t. In this case it also closes the -bus on which t is listening. For more informations see the - Lwt programming guide and -Eliom_bus. -

let launch_client_canvas bus image_elt canvas_elt =
-  let canvas = Html5.To_dom.of_canvas canvas_elt in
-  let ctx = canvas##getContext (Dom_html._2d_) in
-  ctx##lineCap <- Js.string "round";
-
-  let img = Html5.To_dom.of_img image_elt in
-  let copy_image () = ctx##drawImage(img, 0., 0.) in
-  if Js.to_bool (img##complete)
-  then copy_image ()
-  else img##onload <- Dom_html.handler
-    (fun ev -> copy_image (); Js._false);
-
-  (* Size of the brush *)
-  let slider = jsnew Goog.Ui.slider(Js.null) in
-  slider##setMinimum(1.);
-  slider##setMaximum(80.);
-  slider##setValue(10.);
-  slider##setMoveToPointEnabled(Js._true);
-  slider##render(Js.some Dom_html.document##body);
-
-  (* The color palette: *)
-  let pSmall =
-    jsnew Goog.Ui.hsvPalette(Js.null, Js.null,
-                             Js.some (Js.string "goog-hsv-palette-sm"))
-  in
-  pSmall##render(Js.some Dom_html.document##body);
-
-  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 in
-  let compute_line ev =
-    let oldx = !x and oldy = !y in
-    set_coord ev;
-    let color = Js.to_string (pSmall##getColor()) in
-    let size = int_of_float (Js.to_float (slider##getValue())) in
-    (color, size, (oldx, oldy), (!x, !y))
-  in
-  let line ev =
-    let v = compute_line ev in
-    let _ = Eliom_bus.write bus v in
-    draw ctx v
-  in
-  let t = Lwt_stream.iter (draw ctx) (Eliom_bus.stream bus) in
-  let drawing_arrow =
-    run (mousedowns canvas
-           (arr (fun ev -> set_coord ev; line ev) >>>
-              first [mousemoves Dom_html.document (arr line);
-                     mouseup Dom_html.document >>> (arr line)])) () in
-  { drawing_thread = t;
-    drawing_arrow = drawing_arrow }

server.ml

It contains almost all the server parts of the code. -

open Eliom_content
-open Eliom_content.Html5.D
-open Common
-open Lwt
-
-module My_app =
-  Eliom_registration.App (
-    struct
-      let application_name = "graffiti"
-    end)
-
-let rgb_from_string color = (* color is in format "#rrggbb" *)
-  let get_color i =
-    (float_of_string ("0x"^(String.sub color (1+2*i) 2))) /. 255.
-  in
-  try get_color 0, get_color 1, get_color 2 with | _ -> 0.,0.,0.

The main difference is that the bus is now local. -

let launch_server_canvas () =
-  let bus = Eliom_bus.create Json.t<messages> in
-
-  let draw_server, image_string =
-    let surface = Cairo.image_surface_create
-      Cairo.FORMAT_ARGB32 ~width ~height in
-    let ctx = Cairo.create surface in
-    ((fun ((color : string), size, (x1, y1), (x2, y2)) ->
-
-      (* Set thickness of brush *)
-      Cairo.set_line_width ctx (float size) ;
-      Cairo.set_line_join ctx Cairo.LINE_JOIN_ROUND ;
-      Cairo.set_line_cap ctx Cairo.LINE_CAP_ROUND ;
-      let red, green, blue =  rgb_from_string color 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.close_path ctx ;
-
-      (* Apply the ink *)
-      Cairo.stroke ctx ;
-     ),
-     (fun () ->
-       let b = Buffer.create 10000 in
-       (* Output a PNG in a string *)
-       Cairo_png.surface_write_to_stream surface (Buffer.add_string b);
-       Buffer.contents b
-     ))
-  in
-  let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) in
-  bus,image_string
-
-let graffiti_info = Hashtbl.create 0
-
-let imageservice =
-  Eliom_registration.Text.register_service
-    ~path:["image"]
-    ~headers:Http_headers.dyn_headers
-    ~get_params:(let open Eliom_parameter in string "name" ** int "q")
-    (* we add an int parameter for the browser not to cache the image:
-       at least for chrome, there is no way to force the browser to
-       reload the image without leaving the application *)
-    (fun (name,_) () ->
-      try_lwt
-        let _ ,image_string = Hashtbl.find graffiti_info name in
-	Lwt.return (image_string (), "image/png")
-      with
-	| Not_found -> raise_lwt Eliom_common.Eliom_404)
-
-let get_bus (name:string) =
-  (* create a new bus and image_string function only if it did not exists *)
-  try
-    fst (Hashtbl.find graffiti_info name)
-  with
-    | Not_found ->
-      let bus,image_string = launch_server_canvas () in
-      Hashtbl.add graffiti_info name (bus,image_string);
-      bus

The main page now contains only a -form to choose to which drawing you want to go. -The drawing will be sent by the multigraffiti_service service, -registered in graffiti.eliom. -

let main_service = Eliom_service.service ~path:[""]
-  ~get_params:(Eliom_parameter.unit) ()
-let multigraffiti_service = Eliom_service.service ~path:[""]
-  ~get_params:(Eliom_parameter.suffix (Eliom_parameter.string "name")) ()
-
-let choose_drawing_form () =
-  get_form ~service:multigraffiti_service
-    (fun (name) ->
-      [fieldset
-          [label ~a:[a_for name]
-             [pcdata "drawing name: "];
-           string_input ~input_type:`Text ~name ();
-           br ();
-           string_input ~input_type:`Submit ~value:"Go" ()
-          ]])
-    
-let oclosure_script =
-  Html5.Id.create_global_elt
-    (js_script
-       ~uri:(make_uri  (Eliom_service.static_dir ())
-               ["graffiti_oclosure.js"]) ())
-
-let make_page content =
-  Lwt.return
-    (html
-       (head
-	  (title (pcdata "Graffiti"))
-       [ css_link
-           ~uri:(make_uri (Eliom_service.static_dir ())
-                  ["css";"common.css"]) ();
-         css_link
-           ~uri:(make_uri (Eliom_service.static_dir ())
-                  ["css";"hsvpalette.css"]) ();
-         css_link
-           ~uri:(make_uri (Eliom_service.static_dir ())
-                  ["css";"slider.css"]) ();
-         oclosure_script;
-         css_link
-           ~uri:(make_uri (Eliom_service.static_dir ())
-                  ["css";"graffiti.css"]) ();
-       ])
-       (body content))
-
-let () = My_app.register ~service:main_service
-  (fun () () ->
-    make_page [h1 [pcdata "Welcome to Multigraffiti"];
-	       choose_drawing_form ()])

graffiti.eliom

Here is the code that mixes client and server parts. -

We first open the corresponding modules for each parts of the -application. -

{shared{
-  open Eliom_content.Html5.D
-  open Common
-}}
-{client{
-  open Client
-}}
-open Server

And then we define the function adding the onload handler. -

let start_drawing name image canvas =
-  let bus = get_bus name in
-  Eliom_service.onload
-    {{
-      let canceller = launch_client_canvas %bus %image %canvas in
-      Eliom_client.on_unload (fun () -> stop_drawing canceller)
-    }}

The function registered by Eliom_service.on_unload will be called when the page change -inside the application. -

And we finally register the service sending a drawing: -

let counter = ref 0
-
-let () = My_app.register ~service:multigraffiti_service
-  (fun name () ->
-    (* Some browsers won't reload the image, so we force
-       them by changing the url each time. *)
-    incr counter;
-    let image =
-      img ~alt:name
-        ~src:(make_uri
-		~service:imageservice (name,!counter)) () in
-    let canvas =
-      canvas ~a:[ a_width width; a_height height ]
-        [pcdata "your browser doesn't support canvas"; br (); image] in
-    start_drawing name image canvas;
-    make_page
-      [h1 [pcdata name];
-       choose_drawing_form ();
-       canvas;])

At this point, you can run your application on the server provided that you installed the css and images directories in the main directory of your application, build it using this {{{Makefile}}} along with this {{{Makefile.common}}} and configured it using {{{graffiti.conf.in}}} as groundwork of configuration file. -

Mixing client/server application with traditional Web interaction

We now want to restrict the site to connected users. -

From the previous chapter, we copy the code handling users to server.ml: -

let connection_service =
-  Eliom_service.post_coservice' ~post_params:
-    (let open Eliom_parameter in (string "name" ** string "password")) ()
-let disconnection_service = Eliom_service.post_coservice'
-  ~post_params:Eliom_parameter.unit ()
-let create_account_service =
-  Eliom_service.post_coservice ~fallback:main_service ~post_params:
-  (let open Eliom_parameter in (string "name" ** string "password")) ()
-
-let username = Eliom_reference.eref ~scope:Eliom_common.session None
-
-let users = ref ["user","password";"test","test"]
-
-let check_pwd name pwd =
- try Lwt.return (List.assoc name !users = pwd) with
-   | Not_found -> Lwt.return false
-
-let () = Eliom_registration.Action.register
-  ~service:create_account_service
-  (fun () (name, pwd) ->
-    users := (name, pwd)::!users;
-    Lwt.return ())
-
-let () = Eliom_registration.Action.register
-  ~service:connection_service
-  (fun () (name, password) ->
-    match_lwt check_pwd name password with
-      | true -> Eliom_reference.set username (Some name)
-      | false -> Lwt.return ())
-
-let () =
-  Eliom_registration.Action.register
-    ~service:disconnection_service
-    (fun () () -> Eliom_state.discard ~scope:Eliom_common.session ())
-
-let disconnect_box () =
-  post_form disconnection_service
-    (fun _ -> [fieldset
-                 [string_input ~input_type:`Submit ~value:"Log out" ()]]) ()
-
-let login_name_form service button_text =
-  post_form ~service
-    (fun (name1, name2) ->
-      [fieldset
-         [label ~a:[a_for name1] [pcdata "login: "];
-          string_input ~input_type:`Text ~name:name1 ();
-          br ();
-          label ~a:[a_for name2] [pcdata "password: "];
-          string_input ~input_type:`Password ~name:name2 ();
-          br ();
-          string_input ~input_type:`Submit ~value:button_text ()
-         ]]) ()

We make a customized registration module such that disconnected users -(those for which the username reference is not set), are automaticaly -shown a connection box. That way the other pages can assume that the -username is always available. -

let default_content () =
-  make_page
-    [h1 [pcdata "Welcome to Multigraffiti"];
-     h2 [pcdata "log in"];
-     login_name_form connection_service "Connect";
-     h2 [pcdata "create account"];
-     login_name_form create_account_service "Create account";]
-
-module Connected_translate =
-struct
-  type page = string -> My_app.page Lwt.t
-  let translate page =
-    Eliom_reference.get username >>=
-      function
-	| None -> default_content ()
-	| Some username -> page username
-end
-
-module Connected =
-  Eliom_registration.Customize ( My_app ) ( Connected_translate )
-
-let ( !% ) f = fun a b -> return (fun c -> f a b c)

We replace the previous main_service registration : -

let () = My_app.register ~service:main_service
-  (fun () () ->
-    make_page [h1 [pcdata "Welcome to Multigraffiti"];
-	       choose_drawing_form ()])

by : -

let () = Connected.register ~service:main_service
-  !% (fun () () username ->
-    make_page
-      [h1 [pcdata ("Welcome to Multigraffiti " ^ username)];
-       choose_drawing_form ()])

to use that, in graffiti.eliom we just replace -

let () = My_app.register ~service:multigraffiti_service
-  (fun name () -> ... )

by -

let () = Connected.register ~service:multigraffiti_service
-  !% (fun name () username -> ...

and add a call to disconnect_box -

[h1 [pcdata name];
-       disconnect_box ();
-       choose_drawing_form ();
-       canvas;])

Type safe database requests using Macaque

In this section we will replace our dumb user handling code (check_pwd, connection_service and create_account_service) by one using a real database backend. -

We will implement our database access function using -the Macaque library, that allows easy manipulation of Postgresql -database fully compatible with Lwt. (For more information see - Macaque manual). -

We will store the login and the password of users in a Postgresql -database. For more information on how to set up and run it, see -Postgresql -manual. -

When the base is up and running, we create the base by running is a shell: -

$ createdb testbase

Then we create the users table, by executing this sql script: -

CREATE TABLE users (
-       login text NOT NULL,
-       password text NOT NULL
-);

We save it under create_table.sql and run -

$ psql -d testbase -f create_table.sql

Macaque can use any thread library that provides a monadic -interface. The default one provides simple blocking access to the -database. It isn't good for us because an access to the base by one -user will prevent the server from handling anything else until the -request is finished. We need a version of Macaque specialised for -Lwt. It is obtained by -

module Lwt_thread = struct
-  include Lwt
-  include Lwt_chan
-end
-module Lwt_PGOCaml = PGOCaml_generic.Make(Lwt_thread)
-module Lwt_Query = Query.Make_with_Db(Lwt_thread)(Lwt_PGOCaml)

We can now open the database with our newly created Lwt_PGOCaml.connect. -

let get_db : unit -> unit Lwt_PGOCaml.t Lwt.t =
-  let db_handler = ref None in
-  fun () ->
-    match !db_handler with
-      | Some h -> Lwt.return h
-      | None -> Lwt_PGOCaml.connect ~database:"testbase" ()

Then we declare the table on which we will work and the different -requests we do on it. (We no longer need the users array reference) -

let table = <:table< users (
-  login text NOT NULL,
-  password text NOT NULL
-) >>
-
-let find name =
-  (get_db () >>= fun dbh ->
-   Lwt_Query.view dbh
-   <:view< {password = user_.password} |
-            user_ in $table$;
-            user_.login = $string:name$; >>)
-
-let insert name pwd =
-  get_db () >>= fun dbh ->
-  Lwt_Query.query dbh
-  <:insert< $table$ :=
-    { login = $string:name$; password = $string:pwd$; } >>

Finally, we modify the handling code : -

let check_pwd name pwd =
-  (get_db () >>= fun dbh ->
-   Lwt_Query.view dbh
-   <:view< {password = user_.password} |
-            user_ in $table$;
-            user_.login = $string:name$;
-	    user_.password = $string:pwd$ >>)
-  >|= (function [] -> false | _ -> true)
-
-let () = Eliom_registration.Action.register
-  ~service:create_account_service
-  (fun () (name, pwd) ->
-    find name >>=
-      (function
-	| [] -> insert name pwd
-	| _ -> Lwt.return ()) )
-
-let () = Eliom_registration.Action.register
-  ~service:connection_service
-  (fun () (name, password) ->
-    check_pwd name password >>=
-      (function
-	| true -> Eliom_reference.set username (Some name)
-	| false -> Lwt.return ()))

We need to reference macaque in the makefile : -

SERVER_PACKAGE := cairo, unix, macaque.syntax
-

and in graffiti.conf.in : -

<extension findlib-package="macaque"/>
-

Lightweight database using Ocsipersist

For maintaining the list of user and password, we do not need the full -power of a SQL database, a key/value table is sufficient. Ocsigen has -such a simple table mechanism directly integrated: Ocsipersist. -

We first create a table holding ocaml values: -

let user_table = Ocsipersist.open_table "user_table"

Then we can easily replace the user management code: -

let check_pwd name pwd =
-  try_lwt
-    lwt saved_password = Ocsipersist.find user_table name in
-    Lwt.return (pwd = saved_password)
-  with
-    Not_found -> Lwt.return false
-
-let () = Eliom_registration.Action.register
-  ~service:create_account_service
-  (fun () (name, pwd) -> Ocsipersist.add user_table name pwd)
-
-let () = Eliom_registration.Action.register
-  ~service:connection_service
-  (fun () (name, password) ->
-    match_lwt check_pwd name password with
-      | true -> Eliom_reference.set username (Some name)
-      | false -> Lwt.return ())

Saving favorite pictures

We will now add a button to the application to save the current -image. The images will be saved to the filesystem using the module -Lwt_io. We will then make an Atom feed -with the saved images using Atom_feed. -

We save the images in the directory containing the static contents -under the directory graffiti_saved/username. The username -directory is created if needed. If it already exists mkdir fails -and we do nothing. -

We will add this code in a new file: -

feed.ml

open Eliom_content
-open Eliom_content.Html5.D
-open Server
-
-let static_dir = "/tmp/static/"
-
-let image_dir name =
-  let dir = static_dir ^ "/graffiti_saved/" ^ (Url.encode name) in
-  (try_lwt Lwt_unix.mkdir dir 511 with
-    | _ -> debug "could not create the directory %s" dir; Lwt.return ())
-  >|= (fun () -> dir)
-
-let make_filename name number =
-  image_dir name >|= fun dir ->
-  dir ^ "/" ^ (string_of_int number) ^ ".png"
-
-let save image name number =
-  lwt file_name = make_filename name number in
-  lwt out_chan = Lwt_io.open_file ~mode:Lwt_io.output file_name in
-  Lwt_io.write out_chan image

We number images and associate to each image the time of creation. It -is stocked in an Ocsipersist -table. -

let image_info_table = Ocsipersist.open_table "image_info_table"

For each user, we stock a value of type
- int * CalendarLib.Calendar.t * ((int * CalendarLib.Calendar.t) list). -The first integer is the name under which will be saved the image, the first time is the last update for that user and the list contains the names and times of old images. We need those times to timestamp the entries of the feed. -

let save_image username =
-  let now = CalendarLib.Calendar.now () in
-  lwt number,_,list =
-    try_lwt Ocsipersist.find image_info_table username with
-      | Not_found -> Lwt.return (0,now,[])
-      | e -> Lwt.fail e
-  in
-  lwt () = Ocsipersist.add image_info_table
-    username (number+1,now,(number,now)::list) in
-  let (_,image_string) = Hashtbl.find graffiti_info username in
-  save (image_string ()) username number
-
-let save_image_box name =
-  let save_image_service =
-    Eliom_registration.Action.register_post_coservice'
-      ~post_params:Eliom_parameter.unit
-      (fun () () -> save_image name)
-  in
-  post_form save_image_service
-    (fun _ ->
-      [p [string_input ~input_type:`Submit ~value:"save" ()]]) ()

We find the url of the images with Eliom_service.static_dir. -It is a service taking file path as -parameter, serving the content of the static directory. We use Eliom_uri.make_string_uri to get the -url as a string. -

let feed_service = Eliom_service.service ~path:["feed"]
-  ~get_params:(Eliom_parameter.string "name") ()
-
-let local_filename name number =
-  ["graffiti_saved"; Url.encode name ; (string_of_int number) ^ ".png"]
-
-let rec entries name list = function
-  | 0 -> []
-  | len ->
-    match list with
-      | [] -> []
-      | (n,saved)::q ->
-	let title = Atom_feed.plain
-	  ("graffiti " ^ name ^ " " ^ (string_of_int n)) in
-	let uri =
-	  Eliom_content.Xhtml.F.make_uri ~absolute:true
-	    ~service:(Eliom_service.static_dir ())
-	    (local_filename name n)
-	in
-	let entry =
-	  Atom_feed.entry ~title ~id:uri ~updated:saved
-            [Atom_feed.xhtmlC [ Xhtml.F.img ~src:uri ~alt:"image" ()]] in
-	entry::(entries name q (len - 1))
-
-let feed name () =
-  let id = Eliom_content.Xhtml.F.make_uri
-    ~absolute:true
-    ~service:feed_service name in
-  let title = Atom_feed.plain ("nice drawings of " ^ name) in
-  Lwt.catch
-    (fun () -> Ocsipersist.find image_info_table name >|=
-	(fun (number,updated,list) ->
-	  Atom_feed.feed ~id ~updated ~title (entries name list 10)))
-    ( function Not_found ->
-      let now = CalendarLib.Calendar.now () in
-      Lwt.return (Atom_feed.feed ~id ~updated:now ~title [])
-      | e -> Lwt.fail e )
-
-let () = Eliom_atom.Reg.register ~service:feed_service feed

In graffiti.eliom, we add a link to the feed and a save button -that appears only if the user owns the page. -

open Server
-open Feed
-
-...
-
-
-  disconnect_box ();
-  choose_drawing_form ();
-  a feed_service [pcdata "atom feed"] name;
-    div (if name = username
-      then [save_image_box name]
-      else [pcdata "no saving"]);
-    canvas;])

Before running, make sure feed is targeted in the Makefile : -

SERVER_FILES = common.ml server.ml feed.ml ${wildcard *.eliom}
-

and edit -let static_dir = "/tmp/static/" to adapt to your configuration. -

Custom configuration options

It is not convenient to have to edit the code to change some -configurations, like the location where are saved the favorite -images. Fortunately Ocsigen provides a mechanism to extend its -configuration file. -

let static_dir =
-  match Eliom_config.get_config () with
-    | [Simplexmlparser.Element
-     ("staticdir", [], [Simplexmlparser.PCData dir])] ->
-        dir
-    | [] ->
-      raise (Ocsigen_extensions.Error_in_config_file
-               ("staticdir must be configured"))
-    | _ ->
-      raise (Ocsigen_extensions.Error_in_config_file
-               ("Unexpected content inside graffiti config"))

This will add a mandatory child to the eliom tag of graffiti in the -configuration file: -

<eliom module="path/to/graffiti.cma">
-  <staticdir>/tmp/static</staticdir>
-</eliom>
-

Connection with external accounts

Listening music

We will add an audio player to the page that will stay when page -changes. This emphasises the fact that browsing inside an application -does not stop the client side code: the music keeps playing when the -content of the page and the url change. -

graffiti.eliom

We first create the player node at toplevel. -

let player =
-  Eliom_content.Html5.Id.create_global_elt
-    (audio
-       ~srcs:(make_uri (Eliom_service.static_dir ())
-                  ["music.ogg"],
-              [])
-       ~a:[a_autoplay (`Autoplay);a_controls (`Controls)]
-       [pcdata "Your browser does not support audio element" ])

And we insert the player in the page: -

...
-       canvas;
-       player])

And that's all ! Since the player node is declared unique, no new -player is created when the page changed: this is exact same node. -

To run this example, you will need to add an ogg file in the static directory. If you can't find one, there is one here: -http://www.gnu.org/music/free-software-song.html.

diff --git a/7.1/manual/application.html b/7.1/manual/application.html deleted file mode 100644 index 66a480e7..00000000 --- a/7.1/manual/application.html +++ /dev/null @@ -1,692 +0,0 @@ -Writing a client/server Eliom application

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

Writing a client/server Eliom application

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. -

This tutorial is a good starting point if you want a step-by-step -introduction to Eliom programming. -

The final eliom code is available -for download.

Basics

If not already done, install Eliom first: -

opam install ocsipersist-sqlite eliom ocsigen-ppx-rpc
-

To get started, we recommend using Eliom distillery, a program which -creates scaffolds for Eliom projects. The following command creates a -very simple project called graffiti in the directory -graffiti: -

$ eliom-distillery -name graffiti -template client-server.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%server Eliom_content.Html.D (* provides functions to create HTML nodes *)
-
-let%server main_service =
-  Eliom_service.create
-    ~path:(Eliom_service.Path ["graff"])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    ()
-
-let%server () =
-  Eliom_registration.Html.register ~service:main_service
-    (fun () () ->
-      Lwt.return
-        (html
-           (head (title (txt "Page title")) [])
-           (body [h1 [txt "Graffiti"]])))

Annotations %server tells the compiler that the code is going to be executed -on the server (see later). -

Replace the content of file graffiti.eliom by the above lines and run: -

$ make test.byte

This will compile your application and run ocsigenserver. -

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.Html.create, as -all we wanted to do was return HTML. But we actually want a service -that corresponds to a full application with client and server -parts. To do so, we need to create our own registration module by -using the functor Eliom_registration.App: -

module Graffiti_app =
-  Eliom_registration.App (struct
-      let application_name = "graffiti"
-      let global_data_path = None
-    end)

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

let%server main_service =
-  Eliom_service.create
-    ~path:(Eliom_service.Path [""])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    ()
-
-let%server () =
-  Graffiti_app.register ~service:main_service
-    (fun () () ->
-      Lwt.return
-        (html
-           (head (title (txt "Graffiti")) [])
-           (body [h1 [txt "Graffiti"]]) ) )

We can now add some OCaml code to be executed by the browser. For this -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%client _ = Eliom_lib.alert "Hello!"

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

Accessing server side variables on client side code

The client side process is not strictly separated from the server -side. We can access some server variables from the client code. For -instance: -

open%client Js_of_ocaml
let%server count = ref 0
-
-let%server main_service =
-  Eliom_service.create
-    ~path:(Eliom_service.Path [""])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    ()
-
-let%server () =
-  Graffiti_app.register ~service:main_service
-    (fun () () ->
-       let c = incr count; !count in
-       let text = Printf.sprintf "You came %i times to this page" in
-       ignore [%client
-         (Dom_html.window##alert
-            (Js.string @@ Printf.sprintf "You came %i times to this page" ~%c)
-          : unit)
-       ];
-       Lwt.return
-         (html
-            (head (title (txt "Graffiti")) [])
-            (body [h1 [txt @@ text c]])))

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 initializes the value inside [%client ... ] , -and thus triggers an alert window. More specifically, the variable -c, in the scope of the client value on the server is made -available to 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 HTML 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 -line for now. -

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

open%server Eliom_content
-open%server Eliom_content.Html.D
open%client Js_of_ocaml
module%server Graffiti_app =
-  Eliom_registration.App (
-    struct
-      let application_name = "graffiti"
-      let global_data_path = None
-    end)
let%server width  = 700
-let%server height = 400
let%client 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##beginPath;
-  ctx##(moveTo (float x1) (float y1));
-  ctx##(lineTo (float x2) (float y2));
-  ctx##stroke
let%server canvas_elt =
-  Html.D.canvas ~a:[Html.D.a_width width; Html.D.a_height height]
-    [Html.D.txt "your browser doesn't support canvas"]
-
-let%server page () =
-  html
-     (head (title (txt "Graffiti")) [])
-     (body [h1 [txt "Graffiti"];
-            canvas_elt])
let%client init_client () =
-  let canvas = Eliom_content.Html.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%server main_service =
-  Eliom_service.create
-    ~path:(Eliom_service.Path ["graff"])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    ()
-
-let%server () =
-  Graffiti_app.register
-    ~service:main_service
-    (fun () () ->
-       (* Cf. section "Client side side-effects on the server" *)
-       let _ = [%client (init_client () : unit) ] 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 typical JavaScript code in OCaml; for -example by using the function Js_of_ocaml.Dom_events.listen that is the Js_of_ocaml's equivalent of -addEventListener. However, this solution is at least as verbose -as the JavaScript equivalent, hence not satisfactory. Js_of_ocaml's -library provides a much easier way to do that with the help of Lwt. -

To use this, add the following line on top of your file: -

open%client Js_of_ocaml_lwt

Then, replace the init_client of the previous example by the -following piece of code, then compile and draw! -

let%client init_client () =
-
-  let canvas = Eliom_content.Html.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
-  in
-
-  let compute_line ev =
-    let oldx = !x and oldy = !y in
-    set_coord ev;
-    ((0, 0, 0), 5, (oldx, oldy), (!x, !y))
-  in
-
-  let line ev = draw ctx (compute_line ev); Lwt.return () in
-
-  Lwt.async (fun () ->
-    let open Lwt_js_events in
-    mousedowns canvas
-      (fun ev _ ->
-         set_coord ev;
-         let%lwt () = line ev in
-         Lwt.pick
-           [mousemoves Dom_html.document (fun x _ -> line x);
-            let%lwt ev = mouseup Dom_html.document in line ev]))

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 implement the event-handling loop. They -can be read as follows: 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 that terminates: -

  • 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%shared 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 a JSON plugin for -ppx_deriving, which you -need to install. 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 basic types, or other types annotated with -[@@deriving json]. -

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%server bus = Eliom_bus.create [%json: messages]

To write draw commands 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 : (messages, messages) Eliom_bus.t)))

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 -Ocsigen Toolkit. -

To install Ocsigen Toolkit, do: -

opam install ocsigen-toolkit
-

Add package ocsigen-toolkit.server to the libraries section of your dune file, and ocsigen-toolkit.client to the libraries section of your client/dune file. -

In Makefile.options, created by Eliom's distillery, add -ocsigen-toolkit.server to the SERVER_PACKAGES: -

SERVER_PACKAGES := ... ocsigen-toolkit.server

To create the widget, we replace page by : -

let%server page () =
-  let colorpicker, cp_sig =
-    Ot_color_picker.make ~a:[Html.D.a_class ["colorpicker"]] ()
-  in
-  ( Html.D.html
-      (Html.D.head
-         (Html.D.title (Html.D.txt "Graffiti")) [])
-      (Html.D.body [h1 [txt "Graffiti"]
-                   ; canvas_elt
-                   ; colorpicker])
-  , cp_sig )

Replace the registration of main_service by: -

let%server () =
-  Graffiti_app.register ~service:main_service
-    (fun () () ->
-       (* Cf. section "Client side side-effects on the server" *)
-      let page, cp_sig = page () in
-       let _ = [%client (init_client ~cp_sig:~%cp_sig () : unit) ] in
-       Lwt.return page)

We subsequently add a simple HTML slider to change the size of the -brush. Near the canvas_elt definition, simply add the following -code: -

let%server slider =
-  Eliom_content.Html.D.Form.input
-    ~a:
-      [ Html.D.a_id "slider"
-      ; Html.D.a_class ["slider"]
-      ; Html.D.a_input_min (`Number 1)
-      ; Html.D.a_input_max (`Number 80)
-      ; Html.D.a_value "22" ]
-    ~input_type:`Range Html.D.Form.int

Form.int is a typing information telling that this input takes -an integer value. This kind of input can only be associated to -services taking an integer as parameter. -

We then add the slider to the page body, between the canvas and -the colorpicker. -

To change the size and the color of the brush, we add parameter -~cp_sig to init_client and modify -function compute_line: -

let%client init_client ~cp_sig () =
-...
-  let compute_line ev =
-    let oldx = !x and oldy = !y in
-    set_coord ev;
-    let size_slider = Eliom_content.Html.To_dom.of_input ~%slider in
-    let size = int_of_string (Js.to_string size_slider##.value) in
-    let h, s, v = Eliom_shared.React.S.value cp_sig in
-    let r, g, b = Ot_color_picker.hsv_to_rgb h s v in
-    let rgb = int_of_float r, int_of_float g, int_of_float b in
-    (rgb, size, (oldx, oldy), (!x, !y))
-  in
-...

Finally, we need to add a stylesheet in the headers of our page -with function Eliom_tools.D.css_link: -

let%server page () =
-  let colorpicker, cp_sig =
-    Ot_color_picker.make ~a:[Html.D.a_class ["colorpicker"]] ()
-  in
-  ( html
-      (head
-         (title (Html.D.txt "Graffiti"))
-         [ css_link
-             ~uri:
-               (Html.D.make_uri
-                  ~service:(Eliom_service.static_dir ())
-                  ["css"; "graffiti.css"])
-             ()
-         ; css_link
-             ~uri:
-               (make_uri
-                  ~service:(Eliom_service.static_dir ())
-                  ["css"; "ot_color_picker.css"])
-             () ])
-      (body [canvas_elt; slider; colorpicker])
-  , cp_sig )

You need to install the corresponding stylesheets and images into your -project. The stylesheet files should go to the directory -static/css. -Download file graffiti.css from here. -Copy file ot_color_picker.css from directory -~/.opam/<version>/share/ocsigen-toolkit/css into static/css. -

You can then 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 (it is -available as cairo2 via OPAM). Second, add it to the -libraries section in your dune file. -

Second, add it to the SERVER_PACKAGES in your Makefile.options: -

SERVER_PACKAGES := ... cairo2

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%server draw_server, image_string =
-  let rgb_ints_to_floats (r, g, b) =
-    float r /. 255., float g /. 255., float b /. 255.
-  in
-  (* needed by cairo *)
-  let surface = Cairo.Image.create Cairo.Image.ARGB32 ~w:width ~h:height in
-  let ctx = Cairo.create surface in
-  ( (fun (rgb, size, (x1, y1), (x2, y2)) ->
-      (* Set thickness of brush *)
-      let r, g, b = rgb_ints_to_floats rgb in
-      Cairo.set_line_width ctx (float size);
-      Cairo.set_line_join ctx Cairo.JOIN_ROUND;
-      Cairo.set_line_cap ctx Cairo.ROUND;
-      Cairo.set_source_rgb ctx r g b;
-      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)
-  , 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%server _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus)

We also define a service that sends the picture: -

let%server imageservice =
-  Eliom_service.create
-    ~path:(Eliom_service.Path ["image"])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    ()
-
-let%server () =
-  Eliom_registration.String.register ~service: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 after the creation of the canvas context -in init_client: -

(* The initial image: *)
-let img = Eliom_content.Html.To_dom.of_img
-    (img ~alt:"canvas"
-       ~src:(make_uri ~service:~%imageservice ())
-       ())
-in
-img##.onload := Dom_html.handler (fun _ev ->
-  ctx##drawImage img 0. 0.; Js._false);

As we are now using Eliom_content.Html.D in both client and server sections, -we need to open it in a shared section: -

open%shared Eliom_content.Html.D

Finally, we can add a new canvas where we would draw a visualisation of the -current size of the brush. The complete code of this application -can be found here. -

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. -

Exercises

  • Add a button that allows download the current -image, and saving 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).

If you want to continue learning client-server programming with Eliom -and build your first application, we suggest to read -the tutorial about Ocsigen Start.

diff --git a/7.1/manual/basics-server.html b/7.1/manual/basics-server.html deleted file mode 100644 index 593e5d66..00000000 --- a/7.1/manual/basics-server.html +++ /dev/null @@ -1,442 +0,0 @@ -Server-side website programming guide

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

Server-side website programming guide

While Eliom is well known for its unique client-server programming -model, it is also perfectly suited to programming more traditional -websites. This page describes how you can generate Web pages in OCaml, -and handle links, forms, page parameters, sessions, etc. You will see -that you can get very quickly a working Web site without having to learn -innovative concepts and this might be enough for your needs. -

You will then learn how Eliom is simplifying the programming of very -common behaviours by introducing innovative concepts like scoped -sessions or continuation-based Web programming. -

Programming with Eliom will make your website ready for future -evolutions by allowing you to introduce progressively client-side -features like event handlers, fully in OCaml. You will even be able to -turn your website into a distributed client-server Web app, -and even a mobile app if needed in the future, without having to rewrite -anything. -

Table of contents

OCaml

This programming guide assumes you know the OCaml language. -Many resources and books are available online.

Lwt

Lwt is a concurrent programming library for OCaml, initially written -by Jérôme Vouillon in 2001 for the -Unison file synchronizer. -It provides an alternative to the more usual preemptive threads approach -for programming concurrent applications, that avoids most problems of concurrent -data access and deadlocks. -It is used by Ocsigen Server and Eliom and has now become one of the -standard ways to implement concurrent applications in OCaml. -All your Web sites must be written in Lwt-compatible way! -

How it works

Instead of calling blocking functions, like Unix.sleep -or Unix.read, that could block the entire program, replace them -by their cooperative counterparts (Lwt_unix.sleep, -Lwt_unix.read, etc.). Instead of taking time to execute, -they always return immediately a promise of the result, -of type 'a Lwt.t. This type is abstract, and the only way -to use the result is to bind a function to the promise. -Lwt.bind p f means: "when promise p is completed, give its result -to function f". -

Syntax let%lwt x = p in e is equivalent to Lwt.bind p (fun x -> e) -and makes it very natural to sequentialize computations without blocking the rest -of the program.

To learn Lwt, read this short tutorial, or its user manual.

Eliom: Services

The following code shows how to create a service that answers -for requests at URL http://.../aaa/bbb, by invoking an -Ocaml function f of type: -

f : (string * string) list -> unit -> string Lwt.t

Function f generates HTML as a string, taking as first argument the list -of URL parameters (GET parameters), and as second argument the list of POST -parameters (here none). -

let f _ () =
-  Lwt.return "<html><head><title>Hello world</title></head><body>Welcome</body></html>"
-
-let myservice =
-  Eliom_service.create
-    ~path:(Eliom_service.Path ["aaa"; "bbb"])
-    ~meth:(Eliom_service.Get Eliom_parameter.any)
-    ()
-
-let () =
-  Eliom_registration.Html_text.register
-    ~service:myservice
-    f

Eliom_service.Get Eliom_parameter.any means that the service uses the GET HTTP method -and takes any GET parameter. The first parameter of function f is an association list of GET parameters. -

Module Eliom_registration.Html_text is used to register a service sending -HTML as strings. But we recommend to used typed-HTML instead (see below).

Compiling

Eliom provides an helper program called eliom-distillery to create your projects easily, with the right dune configuration and all default directories you usually need for a Web application (static files, server logs, database, etc.). We will show how to use eliom-distillery in another section below. -

In this section, we will show how to compile and run a server-side only Web site by creating your project manually. -

opam install ocsipersist-sqlite eliom
-dune init proj --kind=lib mysite
-cd mysite
-

Add (libraries eliom.server) into file lib/dune, in the library section. -

Create your .ml files in directory lib. -For example, copy the definition and registration of service myservice above. -

Compile: -

dune build
-

Create a configuration file mysite.conf with this content on your project root directory: -

<ocsigen>
-  <server>
-    <port>8080</port>
-
-    <logdir>local/var/log/mysite</logdir>
-    <datadir>local/var/data/mysite</datadir>
-    <charset>utf-8</charset>
-
-    <commandpipe>local/var/run/mysite-cmd</commandpipe>
-    <extension findlib-package="ocsigenserver.ext.staticmod"/>
-    <extension findlib-package="ocsipersist.sqlite"/>
-    <extension findlib-package="eliom.server">
-      <ignoredgetparams regexp="utm_[a-z]*|[a-z]*clid|li_fat_id"/>
-    </extension>
-    <host hostfilter="*">
-      <static dir="static" />
-      <static dir="local/var/www/mysite/eliom" />
-      <eliommodule module="_build/default/lib/mysite.cma" />
-      <eliom/>
-    </host>
-  </server>
-</ocsigen>
-

Create the missing directories: -

mkdir -p local/var/log/mysite
-mkdir -p local/var/data/mysite
-mkdir -p local/var/run
-

Launch the application: -

ocsigenserver -c mysite.conf
-

Open URL http://localhost:8080/aaa/bbb with your browser.

TyXML: typing HTML

TyXML statically checks that your OCaml functions will never -generate wrong HTML. For example a program that could generate a paragraph -containing another paragraph will be rejected at compile time. -

Example of use: -

let open Eliom_content.Html.F in
-html
-  (head (title (txt "Ex")) [])
-  (body [h1 ~a:[a_id "toto"; a_class ["blah"; "blih"]]
-           [txt "Hallo!"]])

How it works

TyXML builds the page as an OCaml data-structure using a construction function -for each HTML tag. These functions take as parameters and return nodes -of type 'a elt where 'a is a polymorphic variant type added in the -module signature to constrain usage (phantom type).

Example of typing error

p [p [txt "Aïe"]]
-   ^^^^^^^^^^^^^
-Error: This expression has type
-         ([> Html_types.p ] as 'a) Eliom_content.Html.F.elt =
-           'a Eliom_content.Html.elt
-       but an expression was expected of type
-         ([< Html_types.p_content_fun ] as 'b) Eliom_content.Html.F.elt =
-           'b Eliom_content.Html.elt
-       Type 'a = [> `P ] is not compatible with type
-         'b =
-           [< `A of Html_types.phrasing_without_interactive
-            | `Abbr
-            | `Audio of Html_types.phrasing_without_media
-            ...
-            | `Output
-            | `PCDATA
-            | `Progress
-            | `Q
-            ...
-            | `Wbr ]
-       The second variant type does not allow tag(s) `P

Read more about TyXML in this short tutorial or in its user manual.

Eliom: Service returning typed HTML

To use typed HTML, just replace module Eliom_registration.Html_text -by Eliom_registration.Html: -

let f _ () =
-  Lwt.return
-    Eliom_content.Html.F.(html (head (title (txt "")) [])
-                               (body [h1 [txt "Hello"]]))
-
-let myservice =
-  Eliom_service.create
-    ~path:(Eliom_service.Path ["aaa"; "bbb"])
-    ~meth:(Eliom_service.Get Eliom_parameter.any)
-    ()
-
-let () =
-  Eliom_registration.Html.register
-    ~service:myservice
-    f

Outputs

Services can return a typed HTML page as in the example above, but also -any other kind of result. To choose the return type, use the register function -from the corresponding submodule of -Eliom_registration: -

HtmlServices returning typed HTML pages
Html_textServices returning untyped HTML pages as strings
AppApply this functor to generate registration functions for services belonging to an Eliom client/server application. These services also return typed HTML pages, but Eliom will automatically add the client-side program as a JS file, and all the data needed (values of all injections, etc.)
FlowServices returning portions of HTML pages.
ActionServices performing actions (server side effects) with or without reloading the page (e.g. login, logout, payment, modification of user information...)
FilesServe files from the server hard drive
OcamlServices returning OCaml values to be sent to a client side OCaml program (this kind of services is used as low level interface for server functions – see below)
StringServices returning any OCaml string (array of byte)
RedirectionServices returning an HTTP redirection to another service
AnyTo be used to make the service chose what it sends. Call function send from the corresponding module to choose the output.
CustomizeApply this functor to define your own registration module

Eliom: Typing page parameters

Instead of taking GET parameters as an untyped string * string -association list, you can ask Eliom to decode and check parameter types -automatically. -

For example, the following code defines a service at URL /foo, -that will use GET HTTP method, and take one parameter of type string, -named s, and one of type int, named i. -

let myservice =
-  Eliom_service.create
-    ~path:(Eliom_service.Path ["foo"])
-    ~meth:(Eliom_service.Get (Eliom_parameter.(string "s" ** int "i")))
-    ()

Then register an OCaml function as handler on this service: -

let () =
-  Eliom_registration.Html.register ~service:myservice
-    (fun (s, i) () ->
-      Lwt.return
-         Eliom_content.Html.F.(html (head (title (txt "")) [])
-                                    (body [h1 [txt (s^string_of_int i)]])))

The handler takes as first parameter the GET page parameters, typed according to -the parameter specification given while creating the service. -The second parameter is for POST parameters (see below). -

Recompile you program, and to to URL http://localhost:8080/foo?s=hello&i=22 -to see the result. -

Parameters

Module Eliom_parameter -is used to describe the type of service parameters. -

Examples: -

Eliom_parameter.(int "i" ** (string "s" ** bool "b"))
-   (* /path?i=42&s=toto&b=on *)
-
-Eliom_parameter.(int "i" ** opt (string "s"))
-   (* An integer named i, and an optional string named s *)
-
-Eliom_parameter.(int "i" ** any)
-   (* An integer named i, and any other parameters, as an association list
-      of type (string * string) list *)
-
-Eliom_parameter.(set string "s")
-   (* /path?s=toto&s=titi&s=bobo *)
-
-Eliom_parameter.(list "l" (int "i"))
-   (* /path?l[0]=11&l[1]=2&l[2]=42 *)
-
-Eliom_parameter.(suffix (int "year" ** int "month"))
-   (* /path/2012/09 *)
-
-Eliom_parameter.(suffix_prod (int "year" ** int "month") (int "a"))
-   (* /path/2012/09?a=4 *)

Eliom: POST services

To define a service with POST parameters, just change the ~meth parameter. -For example the following example takes the same GET parameters as the service -above, plus one POST parameter of type string, named "mypostparam". -

~meth:(Eliom_service.Post (Eliom_parameter.((string "s" ** int "i"),
-                                            (string "mypostparam"))))

Eliom: Other kinds of services

The detailed explanation of services can be found in Eliom's manual. Here is a summary: -

Pathless services

Pathless services are not identified by the path in the URL, -but by a name given as parameter, regardless of the path. -Use this to make a functionality available from all pages -(for example: log-in or log-out actions, add something in a shopping basket ...). -The name can be specified manually -using the ~name optional parameter, otherwise, a random name is -generated automatically. -This is also used to implement server functions (see below). -If you are programming a client-server Eliom app, you will often prefer -remote procedure calls (`let%rpc`). -

let pathless_service =
-  Eliom_service.create
-    ~name:"pathless_example"
-    ~path:Eliom_service.No_path
-    ~meth:(Eliom_service.Get (Eliom_parameter.(int "i")))
-    ()

More information -in the manual. -

Attached services

It is also possible to create services identified by both a path and a -special parameter, using functions -Eliom_service.create_attached_get or -Eliom_service.create_attached_post. -They take a regular service (with a path) as parameter (~fallback). -

It is also possible to attach an existing pathless service to the URL of -another service, with function -Eliom_service.attach. -This allows for example to create a link towards a pathless service, -but on another path. -

External services

Use Eliom_service.extern to create -links or forms towards external Web sites as if they were Eliom services. -

Predefined services

Use service (Eliom_service.static_dir ()) -to create links towards static files (see example below for images). -

Use service Eliom_service.reload_action and its variants to create links or forms towards the current URL (reload the page). From a client section, you can also call Os_lib.reload to reload the page and restart the client-side program. -

Full documentation about services, a tutorial about traditional service based Web programming, API documentation of modules Eliom_service and Eliom_registration. -

This example shows how to insert an image using static_dir: -

img
-  ~alt:"blip"
-  ~src:(Eliom_content.Html.F.make_uri
-         (Eliom_service.static_dir ())
-         ["dir" ; "image.jpg"])
-  ()

Forms and links

Function Eliom_content.Html.F.a creates typed links to services with their parameters. -For example, if home_service expects no parameter -and other_service expects a string and an optional int: -

Eliom_content.Html.F.a ~service:home_service [txt "Home"] ()
-Eliom_content.Html.F.a ~service:other_service [txt "Other"] ("hello", Some 4)

Module Eliom_content.Html.F -defines the form's elements with the usual typed interface from TyXML. -Use this for example if you have a client side program and -want to manipulate the form's content from client side functions -(for example do a server function call with the form's elements' content). -

In contrast, -module Eliom_content.Html.F.Form defines a typed interface -for form elements. Use this for links (see above), or if you program traditional -server-side Web interaction (with or without client-side program). This will statically check that your forms -match the services. Example: -

let open Eliom_content.Html.F in
-Form.post_form
- ~service:connection_service
-   (fun (name, password) ->
-     [fieldset
-       [label ~a:[a_for name] [txt "Name: "];
-        Form.input ~input_type:`Text ~name:name Form.int;
-        br ();
-        Form.input
-          ~a:[a_placeholder "Password"]
-          ~input_type:`Password
-          ~name:password
-          Form.string;
-        br ();
-        Form.input ~input_type:`Submit ~value:"Connect" Form.string
-      ]]) ()

As you can see, function -Eliom_content.Html.F.Form.post_form -is used to create a form sending parameters using the POST HTTP method -(and similarly, get_form for GET method). -It takes the service as first parameter, and a function that will generate the form. -This function takes the names of the GET or POST parameters as arguments. -

Form elements (like inputs) are also built from using the -Eliom_content.Html.F.Form module. -They take the names as parameters, and a last parameter (like Form.int or Form.string) to match the expected type.

Sessions

Session data is saved on server side in Eliom references. -

The following Eliom reference will count the number of visits of a user on a page: -

let%server count_ref =
-  Eliom_reference.eref
-    ~scope:Eliom_common.default_session_scope
-    0 (* default value for everyone *)

And somewhere in your service handler, increment the counter: -

let%lwt count = Eliom_reference.get count_ref in
-Eliom_reference.set count_ref (count + 1);
-Lwt.return ()

With function Eliom_reference.eref_from_fun, -you can create Eliom references without initial value. The initial value is computed for the session -the first time you use it. -

An Eliom reference can be persistant (value saved on hard drive) or volatile (in memory). -

Scopes

Sessions are relative to a browser, and implemented using browser cookies. -But Eliom allows to create Eliom references with other scopes than session: -

global_scopeGlobal value for all the Web server
site_scopeGlobal value for the Eliom app in that subsite of the Web site
default_group_scopeValue for a group of sessions. For example Ocsigen Start defines a group of session for each user, making it possible to save server side data for all sessions of a user.
default_session_scopeThe usual session data, based on browser cookies
default_process_scopeServer side data for a given client-side process (a tab of the browser or a mobile app). This is available only with a client-server Eliom app.

Applications based on Ocsigen Start use these scopes for user management. -Session or client process data are discarded when a user logs in or out. -But Ocsigen Start also defines scopes -Os_session.user_indep_session_scope -and -Os_session.user_indep_process_scope -which remain even if a user logs in or out. -

When session group is not set (for example the user is not connected), -you can still use the group session scope: in that case, the group contains only -one session.

Browser events

By default, event handlers on HTML elements are given as OCaml functions, -but it works only if you have a client-server Eliom program. -If not, you want to give a javascript expression (as a string) instead. -To so that, use attributes functions from module Raw. For example -Eliom_content.Html.F.Raw.a_onclick -instead of -Eliom_content.Html.F.a_onclick. -

Example: -

Eliom_content.Html.F.(button ~a:[Raw.onclick "alert(\"beep\");"] [txt "click"])

Database access

You can use your favourite database library with Ocsigen. -Ocsigen Start's template uses -PG'OCaml -(typed queries for Postgresql using a PPX syntax extension). -

Here is an example, taken from Ocsigen Start's demo: -

let get () =
-  full_transaction_block (fun dbh ->
-    [%pgsql dbh "SELECT lastname FROM ocsigen_start.users"])

Continuation-based Web programming

Eliom allows the dynamic creation of temporary services. -This is equivalent to a programming pattern known as "continuation-based -Web programming". -While present in very few Web frameworks, -it is a very powerful feature that can save you a lot of time -when programming a server-side website. -A typical use case is when you have a series of pages, -each of which depending on the entries made on the previous pages, -for example a multi-step train ticket booking. -

Implementing that without continuation-based Web programming is tedious: -you must store the data previously sent by the user and find a way to -get it for each step of the interaction. It is not possible to save it as -session data, as you want for example to be able to have several different -interactions in different tabs of your browser, or to press the back button of -your browser to go back in the past. This is known as -"the back button problem". -

With Eliom, you just need to create new temporary services especially for -one user, that will depend on previous interaction with them. -The form data will be recorded in the closure of the service handler -function. -In our example, you can implement a first page with a form -(departure, destination, date, first name, last name ...). -The form sends this data to another service. -This second service will display a list of train tickets, each with a link -to buy the ticket. Each of these links corresponds to a service that has been -specifically created for this user and train, which will display the payment -page for this ticket. -

To implement temporary services, we usually use pathless or attached -services (see above). To avoid a memory leak, you can make them temporary using -optional parameters of the service creation function (?max_use or ?timeout).

Internationalisation

Ocsigen i18n -is an internationalisation library for your OCaml programs. -

Create a .tsv file with, on each line, a key and the text in several languages: -

welcome_message Welcome everybody!        Bienvenue à tous !      Benvenuti a tutti !
-

and Ocsigen i18n will automatically generate functions like this one: -

let%shared welcome_message ?(lang = get_language ()) () () =
-  match lang with
-  | En -> [txt "Welcome everybody!"]
-  | Fr -> [txt "Bienvenue à tous !"]
-  | It -> [txt "Benvenuti a tutti !"]

Ocsigen i18n also defines a syntax extension to use these functions: -

Eliom_content.Html.F.h1 [%i18n welcome_message]

Ocsigen i18n offers many other features: -

  • Text can be inserted as a TyXML node (as in the example above) or as a string (ex: [%i18n S.welcome_message]), -
  • Text can be parametrizable, or contain holes (ex: [%i18n welcome ~capitalize:true ~name:"William"]) -
  • .tsv file can be split into several modules -

Have a look at the -README file -to see the full documentation, -and see examples in -Ocsigen Start's template.

Ocsigen Server

Ocsigen Server is a full featured Web server. -

It is now based on Cohttp. -

It has a powerful -extension mechanism that makes it easy to plug your own OCaml modules -for generating pages. Many extensions are already written: -

Staticmod -
to serve static files. -
Eliom -
to create reliable client/server Web applications -or Web sites in OCaml using advanced high level concepts. -
Extendconfiguration -
allows for more options in the configuration file. -
Accesscontrol -
restricts access to the sites from the config file (to requests coming from a subnet, containing some headers, etc.). -
Authbasic -
restricts access to the sites from the config file using Basic HTTP Authentication. -
CGImod -
serves CGI scripts. It may also be used to serve PHP through CGI. -
Deflatemod -
used to compress data before sending it to the client. -
Redirectmod -
sets redirections towards other Web sites from the configuration file. -
Revproxy -
a reverse proxy for Ocsigen Server. -It allows to ask another server to handle the request. -
Rewritemod -
changes incoming requests before sending them to other extensions. -
Outputfilter -
rewrites some parts of the output before sending it to the client. -
Userconf -
allows users to have their own configuration files. -
Comet -
facilitates server to client communications. -

Ocsigen Server has a sophisticated configuration file mechanism allowing -complex configurations of sites.

diff --git a/7.1/manual/basics.html b/7.1/manual/basics.html deleted file mode 100644 index cb6d0585..00000000 --- a/7.1/manual/basics.html +++ /dev/null @@ -1,695 +0,0 @@ -Client-server application programming guide

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

Client-server application programming guide

This page describes the main concepts you need to master to become fully operational -with Ocsigen. Use it as your training plan or as a cheatcheet while programming. -

Depending on your needs, you may not need to learn all this. Ocsigen is -very flexible and can be used both for Web site programing (see -this page) or more complex client-server Web apps and -their mobile counterparts. -

In parallel to the reading of that page, -we recommend to generate your first Ocsigen Start app to see -running examples of all these concepts (see that page). -

Table of contents

OCaml

This programming guide assumes you know the OCaml language. -Many resources and books are available online.

Lwt

Lwt is a concurrent programming library for OCaml, initially written -by Jérôme Vouillon in 2001 for the -Unison file synchronizer. -It provides an alternative to the more usual preemptive threads approach -for programming concurrent applications, that avoids most problems of concurrent -data access and deadlocks. -It is used by Ocsigen Server and Eliom and has now become one of the -standard ways to implement concurrent applications in OCaml. -All your Web sites must be written in Lwt-compatible way! -

How it works

Instead of calling blocking functions, like Unix.sleep -or Unix.read, that could block the entire program, replace them -by their cooperative counterparts (Lwt_unix.sleep, -Lwt_unix.read, etc.). Instead of taking time to execute, -they always return immediately a promise of the result, -of type 'a Lwt.t. This type is abstract, and the only way -to use the result is to bind a function to the promise. -Lwt.bind p f means: "when promise p is completed, give its result -to function f". -

Syntax let%lwt x = p in e is equivalent to Lwt.bind p (fun x -> e) -and makes it very natural to sequentialize computations without blocking the rest -of the program.

To learn Lwt, read this short tutorial, or its user manual.

TyXML: typing HTML

TyXML statically checks that your OCaml functions will never -generate wrong HTML. For example a program that could generate a paragraph -containing another paragraph will be rejected at compile time. -

Example of use: -

let open Eliom_content.Html.D in
-html
-  (head (title (txt "Ex")) [])
-  (body [h1 ~a:[a_id "toto"; a_class ["blah"; "blih"]]
-           [txt "Hallo!"]])

How it works

TyXML builds the page as an OCaml data-structure using a construction function -for each HTML tag. These functions take as parameters and return nodes -of type 'a elt where 'a is a polymorphic variant type added in the -module signature to constrain usage (phantom type).

Example of typing error

p [p [txt "Aïe"]]
-   ^^^^^^^^^^^^^
-Error: This expression has type
-         ([> Html_types.p ] as 'a) Eliom_content.Html.F.elt =
-           'a Eliom_content.Html.elt
-       but an expression was expected of type
-         ([< Html_types.p_content_fun ] as 'b) Eliom_content.Html.F.elt =
-           'b Eliom_content.Html.elt
-       Type 'a = [> `P ] is not compatible with type
-         'b =
-           [< `A of Html_types.phrasing_without_interactive
-            | `Abbr
-            | `Audio of Html_types.phrasing_without_media
-            ...
-            | `Output
-            | `PCDATA
-            | `Progress
-            | `Q
-            ...
-            | `Wbr ]
-       The second variant type does not allow tag(s) `P

Read more about TyXML in this short tutorial or in its user manual.

Eliom: Services

Pages are generated by services. -Eliom provides a very simple (yet extremely powerful) service creation and identification mechanism. -

To create a service, call Eliom_service.create. -For example, the following code defines a service at URL /foo, -that will use GET HTTP method, and take one parameter of type string, -named myparam, and one of type int, named i. -

let myservice =
-  Eliom_service.create
-    ~path:(Eliom_service.Path ["foo"])
-    ~meth:(Eliom_service.Get (Eliom_parameter.(string "myparam" ** int "i")))
-    ()

Then register an OCaml function as handler on this service: -

let () =
-  Eliom_registration.Html.register ~service:myservice
-    (fun (myparam, _i) () ->
-      Lwt.return
-         Eliom_content.Html.F.(html (head (title (txt "")) [])
-                                    (body [h1 [txt myparam]])))

The handler takes as first parameter the GET page parameters, typed according to -the parameter specification given while creating the service. -The second parameter is for POST parameters (see below). -

Outputs

Services can return a typed HTML page as in the example above, but also -any other kind of result. To choose the return type, use the register function -from the corresponding submodule of -Eliom_registration: -

HtmlServices returning typed HTML pages
Html_textServices returning untyped HTML pages as strings
AppApply this functor to generate registration functions for services belonging to an Eliom client/server application. These services also return typed HTML pages, but Eliom will automatically add the client-side program as a JS file, and all the data needed (values of all injections, etc.)
FlowServices returning portions of HTML pages.
ActionServices performing actions (server side effects) with or without reloading the page (e.g. login, logout, payment, modification of user information...)
FilesServe files from the server hard drive
OcamlServices returning OCaml values to be sent to a client side OCaml program (this kind of services is used as low level interface for server functions – see below)
StringServices returning any OCaml string (array of byte)
RedirectionServices returning an HTTP redirection to another service
AnyTo be used to make the service chose what it sends. Call function send from the corresponding module to choose the output.
CustomizeApply this functor to define your own registration module

Parameters

Module Eliom_parameter -is used to describe the type of service parameters. -

Examples: -

Eliom_parameter.(int "i" ** (string "s" ** bool "b"))
-   (* /path?i=42&s=toto&b=on *)
-
-Eliom_parameter.(int "i" ** opt (string "s"))
-   (* An integer named i, and an optional string named s *)
-
-Eliom_parameter.(int "i" ** any)
-   (* An integer named i, and any other parameters, as an association list
-      of type (string * string) list *)
-
-Eliom_parameter.(set string "s")
-   (* /path?s=toto&s=titi&s=bobo *)
-
-Eliom_parameter.(list "l" (int "i"))
-   (* /path?l[0]=11&l[1]=2&l[2]=42 *)
-
-Eliom_parameter.(suffix (int "year" ** int "month"))
-   (* /path/2012/09 *)
-
-Eliom_parameter.(suffix_prod (int "year" ** int "month") (int "a"))
-   (* /path/2012/09?a=4 *)

POST services

To define a service with POST parameters, just change the ~meth parameter. -For example the following example takes the same GET parameters as the service -above, plus one POST parameter of type string, named "mypostparam". -

~meth:(Eliom_service.Post (Eliom_parameter.((string "myparam" ** int "i"),
-                                            (string "mypostparam"))))

Pathless services

Pathless services are not identified by the path in the URL, -but by a name given as parameter. This name can be specified manually -using the ~name optional parameter, otherwise, a random name is -generated automatically. -This is used to implement server functions (see below). -If you are programming a client-server Eliom app, you will probably prefer server functions. -If you are using traditional service based Web programming, -use this to make a functionality available from all pages -(for example: log-in or log-out actions, add something in a shopping basket ...). -

let pathless_service =
-  Eliom_service.create
-    ~name:"pathless_example"
-    ~path:Eliom_service.No_path
-    ~meth:(Eliom_service.Get (Eliom_parameter.(int "i")))
-    ()

More information -in the manual. -

External services

Use Eliom_service.extern to create -links or forms towards external Web sites as if they were Eliom services. -

Predefined services

Use service (Eliom_service.static_dir ()) -to create links towards static files (see example below for images). -

Use service Eliom_service.reload_action and its variants to create links or forms towards the current URL (reload the page). From a client section, you can also call Os_lib.reload to reload the page and restart the client-side program. -

Full documentation about services, a tutorial about traditional service based Web programming, API documentation of modules Eliom_service and Eliom_registration. -

This example shows how to insert an image using static_dir: -

img
-  ~alt:"blip"
-  ~src:(Eliom_content.Html.F.make_uri
-         (Eliom_service.static_dir ())
-         ["dir" ; "image.jpg"])
-  ()

Compiling

Eliom provides an helper program called eliom-distillery to create your projects easily, with the right dune configuration and all default directories you usually need for a Web application (static files, server logs, database, etc.). We will show how to use eliom-distillery in another section below. -

In this section, we will show how to compile and run a server-side only Web site by creating your project manually. -

opam install ocsipersist-sqlite eliom
-dune init proj --kind=lib mysite
-cd mysite
-

Add (libraries eliom.server) into file lib/dune, in the library section. -

Create your .ml files in directory lib. -For example, copy the definition and registration of service myservice above. -

Compile: -

dune build
-

Create a configuration file mysite.conf with this content on your project root directory: -

<ocsigen>
-  <server>
-    <port>8080</port>
-
-    <logdir>local/var/log/mysite</logdir>
-    <datadir>local/var/data/mysite</datadir>
-    <charset>utf-8</charset>
-
-    <commandpipe>local/var/run/mysite-cmd</commandpipe>
-    <extension findlib-package="ocsigenserver.ext.staticmod"/>
-    <extension findlib-package="ocsipersist.sqlite"/>
-    <extension findlib-package="eliom.server">
-      <ignoredgetparams regexp="utm_[a-z]*|[a-z]*clid|li_fat_id"/>
-    </extension>
-    <host hostfilter="*">
-      <static dir="static" />
-      <static dir="local/var/www/mysite/eliom" />
-      <eliommodule module="_build/default/lib/mysite.cma" />
-      <eliom/>
-    </host>
-  </server>
-</ocsigen>
-

Create the missing directories: -

mkdir -p local/var/log/mysite
-mkdir -p local/var/data/mysite
-mkdir -p local/var/run
-

Launch the application: -

ocsigenserver -c mysite.conf
-

Open URL http://localhost:8080/foo?myparam=Hello&i=27 with your browser.

Forms and links

Functions Eliom_content.Html.F.a and -D.a -create typed links to services with their parameters. -For example, if home_service expects no parameter -and other_service expects a string and an optional int: -

Eliom_content.Html.D.a ~service:home_service [txt "Home"] ()
-Eliom_content.Html.D.a ~service:other_service [txt "Other"] ("hello", Some 4)

Modules Eliom_content.Html.F and -D -define the form's elements with the usual typed interface from TyXML. -Use this for example if you have a client side program and -want to manipulate the form's content from client side functions -(for example do a server function call with the form's elements' content). -

In contrast, -modules Eliom_content.Html.F.Form and -D.Form -define a typed interface -for form elements. Use this for links (see above), or if you program traditional -server-side Web interaction (with or without client-side program). This will statically check that your forms -match the services. See more information in the -server-side programming manual.

Js_of_ocaml

Js_of_ocaml is a compiler of OCaml bytecode to JavaScript, -allowing to run Ocaml programs in a Web browser. -Its key features are the following: -

  • The whole language, and most of the standard library are supported. -
  • You can use a standard installation of OCaml to compile your programs. In particular, you do not have to recompile a library to use it with Js_of_ocaml. -
  • It comes with a library to interface with the browser API. -

Interaction with Javascript can be done: -

  • either with untyped function calls (module Js.Unsafe), -
  • or you can generate an interface just by writing an annotated .mli -using Gen_js_api, -
  • or you use a syntax extension to generate typed calls, using a -.mli file describing Javascript objects -with OCaml class types as phantom types. -

The latter is used for the default Javascript library. Here is how it works: -

obj##.maccess a JS property (has type u when obj : <m : u prop> Js.t)
obj##.m := echange a JS property (when obj : <m : u prop> Js.t and e:u)
obj##m e_1 ... e_ncall a JS method (has type u when obj : <m : t_1 -> ... -> t_n -> u meth; ..> Js.t and e_i : t_i)
new%js constr e1 ... encreate a JS object (has type u Js.t when constr : (t_1 -> ... -> t_n -> u Js.t) Js.constr and e_i : t_i)

Module Js_of_ocaml.Js -defines the base JS types and conversion functions from/to OCaml types. -Example: Js.Opt to take into account nullable values, Js.Optdef for undefined values, -or functions like Js.to_string and Js.string for consersions to and from -OCaml strings. -

Use modules Js_of_ocaml.Dom -and Js_of_ocaml.Dom_html -to interact with the DOM, or more specifically with HTML. -

You can test Js_of_ocaml online in this -Toplevel running in the browser -

Examples

# Dom_html.document;;
-- : Js_of_ocaml.Dom_html.document Js_of_ocaml__.Js.t = <abstr>
-# Dom_html.document##.body;;
-- : Js_of_ocaml.Dom_html.bodyElement Js_of_ocaml__.Js.t = <abstr>
-# Dom_html.document##.qkjhkjqhkjqsd;;
-Line 1, characters 0-17:
-Error: This expression has type
-         < activeElement : Js_of_ocaml.Dom_html.element Js_of_ocaml__.Js.t
-                           Js_of_ocaml__.Js.opt
-                           Js_of_ocaml__.Js.readonly_prop;
-...
-...
-           write : Js_of_ocaml__.Js.js_string Js_of_ocaml__.Js.t ->
-                   unit Js_of_ocaml__.Js.meth >
-       It has no method qkjhkjqhkjqsd
-# Dom_html.window;;
-- : Js_of_ocaml.Dom_html.window Js_of_ocaml__.Js.t = <abstr>
-# Dom_html.window##alert (Js.string "Salut");;
-- : unit = ()
-# Dom_html.document##.body##querySelector (Js.string "h3");;
-- : Js_of_ocaml.Dom_html.element Js_of_ocaml__.Js.t Js_of_ocaml__.Js.opt =
-<abstr>
-# let fsth3 = Dom_html.document##.body##querySelector (Js.string "h3");;
-  (* Get the first h3 element in the page *)
-val fsth3 :
-  Js_of_ocaml.Dom_html.element Js_of_ocaml__.Js.t Js_of_ocaml__.Js.opt =
-  <abstr>
-# Js.Opt.iter fsth3 (fun e -> e##.style##.color := Js.string "#ff0000");;
-  (* Change its color *)
-- : unit = ()
-# Dom.appendChild Dom_html.document##.body (Eliom_content.Html.(To_dom.of_p (F.p [F.txt "Salut"])));;
-  (* Append a paragraph generated with TyXML to the page *)
-- : unit = ()
-# Firebug.console##log (Js.string "toto");;
-- : unit = ()
-# Firebug.console##log [1;2];;
-- : unit = ()
-# Firebug.console##log (Dom_html.document##.body);;
-- : unit = ()

HTML: Functional and DOM semantics

DOM nodes and TyXML/Eliom_content nodes

Functions Eliom_content.Html.To_dom.of_element, Eliom_content.Html.To_dom.of_div, etc. -help to convert TyXML/Eliom_content nodes into the DOM/js_of_ocaml counterparts. -

Module Eliom_content.Html.Manip -allows direct manipulation of TyXML nodes without conversion (only for D nodes) -(see for example -Eliom_content.Html.Manip.appendChild, -Eliom_content.Html.Manip.removeSelf -Eliom_content.Html.Manip.Class.add). -

F or D

Eliom uses TyXML to create several kinds of nodes: -

Module Eliom_content.Html.F -will create functional values representing -your nodes. On client side, calling Eliom_content.Html.To_dom.of_element -on these nodes will create a new DOM node. -

Module Eliom_content.Html.D -will automatically insert an id in the -attributes of the node, to label a precise instance of the node in the DOM. -On client side, calling Eliom_content.Html.To_dom.of_element -on these nodes will return the actual version of the nodes that are currently -in the page. -

In a client server Eliom app, -you probably always want to use Eliom_content.Html.D -each time you want to bind events on -an element (and more generally if you need to inject this element using ~%). -

Read more about Eliom_content.Html (D or F?) in -this manual page.

Eliom: client-server apps

Eliom can transform OCaml into a multi-tier language, allowing one to -implement (both the server and client parts of) a distributed application -entirely in OCaml, as a single program. -This greatly simplifies communication between server and client. -

Pages can be generated either on the server or the client. -The first HTTP request usually returns a server-side generated HTML page -(thus indexable by search engines), but subsequent page generations can be done -by the client for better performance. -In a mobile app, all pages are usually generated on the client. -

One of the key features of Eliom is that it allows -one to mix traditional Web interactions (URLs, forms, links, -bookmarks, back button) with dynamic client side features. In -particular, the client-side program does not stop when the user -clicks on a link, sends a form, or presses the back button–yet the user -can still save bookmarks on pages! This opens up a wide field of new -possibilities. -

Sections

Eliom statically generates two programs from the same set of OCaml files: -one compiled to native code (or bytecode) to be executed on the server, -the other one compiled to Javascript with Js_of_ocaml to be executed -in the browser. -

PPX annotations allow to split the code into these two programs: -

let%shared ... = ... Code to be included in both the client and server apps
let%client ... = ... Code to be included in client app only
let%server ... = ... Code to be included in server app only

Same for module%shared, open%shared, type%shared etc. -

Client-server build system -

Client values

Fragments of client code can be included in server (or shared) sections. -

Example: -

button ~a:[a_onclick [%client fun ev -> ... ]] [ ... ]

The syntax is [%client (<value> : <type>)]. -Type annotation is almost always required. -

These client fragments can be manipulated as server side OCaml values: -

let%server x : int Eliom_client_value.t = [%client 1 + 3 ]

If such section is reached while generating a page on server side, -the client-side code will be executed when the page is displayed. -

If such section is reached while generating a page on client side, -the client-side code will be executed immediately -

If such section is reached during module initialization on the server -(global client section), it will be executed on client side everytime -a new client side program is launched. -

The tutorial Client-Server Widgets -shows how client values can be manipulated -on server side. -

Injections

Server side values can be injected in client code by prefixing -them with ~% as in this example: -

let%server ... =
-  ...
-  let x = ... in
-  [%client[ ... ~%x ... ]]
-  ...

The value will automatically be sent with the page by Eliom. -

It is possible to combine injections and client-values: -

let%server x : int Eliom_client_value.t = [%client 1 + 3 ]
let%client c : int = 3 + ~%x

Calling server side functions from the client

Eliom makes it possible to call server side OCaml functions from your -client-side program. You must export these functions explicitely, -and declare the type of their parameters. Example: -

let%rpc g (i : int) : string Lwt.t =
-  Lwt.return (string_of_int (i + Random.int 1000))

Warning: type annotations are mandatory here -so that the ppx can automatically inject the right conversion functions. -These functions are generated automatically by -Deriving, as long as it knows -a deriver for each subtype. To create a deriver for your own types -just append [@@deriving json] after your type declaration. Example: -

type%shared t = A | B
-[@@deriving json]

More documentation about the JSON deriver. -

How it works

The following picture shows two examples of requests: -

  • First, the browser asks for a new page, and the server generates the page -
  • then the user clicks on a link in the page, and the page is generated by the client-side program (because the service is registered on both sides). In this example, while generating the page, the client does a RPC to the server. -

In both cases (first request or RPC), the server returns the expected value, but also the value of injections and an order for the client-side program to execute the client-values met during te server-side computation. -

Example of requests -

Tip 1: You can avoid waiting for the RPC to return by using a spinner from Ocsigen Toolkit (see module Ot_spinner). Thus, the client-side generated page will be displayed without delay. -

Tip 2: To delay the execution of a client fragment after the page is actually displayed, you might want to use function -Ot_nodeready.nodeready from Ocsigen Toolkit. -

Regardless of the construction used and their combination, there is -only one communication from server to client, when the Web page is -sent. This is due to the fact that client values are not executed -immediately when encountered inside server code. The intuitive -semantic is the following: client code is not executed when -encountered, instead it is registered for later execution, once the -Web page has been sent to the client. Then all the client code is -executed in the order it was encountered on the server, with the value -of injections that was also sent with the page. -

For each client-values, the client-side PPX will create a function in -the client-side program. The parameters of this function are all the -injections it contains. -

The server-side PPX will replace the client-value by some code -that will insert in the currently generated page some instruction to -ask the client-side program to call the corresponding functions. -Their arguments (injections) are serialized at the same time by the -server-side program and also inserted in the generated page.

Example

This section shows a typical example of client-server code: call a function -when user clicks on a page element. Take time to analyse this example, -as most of your code will probably be very similar. -

open%client Js_of_ocaml
-open%client Js_of_ocaml_lwt
-open%client Eliom_content.Html
open%shared Eliom_content.Html.F
let%server theservice =
-  Eliom_service.create
-    ~path:["ex"]
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    ()
let%client theservice = ~%theservice
let%shared () =
-  My_app.register ~service:theservice (fun () () ->
-      let aa = string_of_int (Random.int 1000) in
-      let _ = [%client (print_endline ~%aa : unit)] in (* print in browser console *)
-      let b = Eliom_content.Html.D.button [txt aa] in
-      let _ =
-        (* binding clicks on button (see browser events below) *)
-        [%client
-          (Lwt.async (fun () ->
-               Lwt_js_events.clicks (To_dom.of_element ~%b) (fun _ _ ->
-                   Dom_html.window##alert (Js.string (~%aa));
-                   Lwt.return_unit))
-            : unit)]
-      in
-      Lwt.return (html (head (title (txt "Example")) [])
-                       (body [h1 [txt aa]; b])))

Service handlers and service registration -are usually written in shared sections to enable -page generation on both sides. -

  • Examples of client sections, injections or server functions can be found in -the demo included in -Ocsigen-Start's app template. -
  • This page is a step by step introduction to client-server programming with Eliom for beginners. -
  • This one is a quick introduction for more experienced OCaml developers. -
  • Comprehensive documentation on client-server programming can be found in -Eliom's user manual.

Compiling a client-server app

Compiling a client-server app requires a dedicated build system, -which will separate server-side and client-side code, compile each side, -and check types. -

Eliom provides two build-systems: -

  • One based on dune -
  • One with make -

To get the dune-based build-system, create a project with Ocsigen-Start's template. Install Ocsigen-Start: -

opam install ocsigen-start
-

Then: -

eliom-distillery -template os.pgocaml -name myapp
-

It contains by default some code examples that you can remove or adapt to your own needs. -Have a look at the README file. -

If you don't want to use Ocsigen-Start, you can use the basic template: -

eliom-distillery -template client-server.basic -name myapp
-

Have a look at the README file.

Sessions

Session data is saved on server side in Eliom references. -

The following Eliom reference will count the number of visits of a user on a page: -

let%server count_ref =
-  Eliom_reference.eref
-    ~scope:Eliom_common.default_session_scope
-    0 (* default value for everyone *)

And somewhere in your service handler, increment the counter: -

let%lwt count = Eliom_reference.get count_ref in
-Eliom_reference.set count_ref (count + 1);
-Lwt.return ()

With function Eliom_reference.eref_from_fun, -you can create Eliom references without initial value. The initial value is computed for the session -the first time you use it. -

An Eliom reference can be persistant (value saved on hard drive) or volatile (in memory). -

Scopes

Sessions are relative to a browser, and implemented using browser cookies. -But Eliom allows to create Eliom references with other scopes than session: -

global_scopeGlobal value for all the Web server
site_scopeGlobal value for the Eliom app in that subsite of the Web site
default_group_scopeValue for a group of sessions. For example Ocsigen Start defines a group of session for each user, making it possible to save server side data for all sessions of a user.
default_session_scopeThe usual session data, based on browser cookies
default_process_scopeServer side data for a given client-side process (a tab of the browser or a mobile app).

Applications based on Ocsigen Start use these scopes for user management. -Session or client process data are discarded when a user logs in or out. -But Ocsigen Start also defines scopes -Os_session.user_indep_session_scope -and -Os_session.user_indep_process_scope -which remain even if a user logs in or out. -

When session group is not set (for example the user is not connected), -you can still use the group session scope: in that case, the group contains only -one session.

Browser events

Attributes like a_onclick in module Eliom_content.Html.D or F -take a client side function as parameter: -

button ~a:[a_onclick [%client fun ev -> ... ]] [ ... ]

Module Lwt_js_events of Js_of_ocaml defines a way to bind browser events -using Lwt promises. -

For example, -the following code will wait for a click on element d before continuing: -

let%lwt ev = Lwt_js_events.click (Eliom_content.Html.To_dom.of_element ~%d) in
-...

Functions like Lwt_js_events.clicks or Lwt_js_events.mousedowns -(ending with "s") -will call the function given as second parameter -for each click or mousedown events -on their first parameter. -

For example, the following code (inspired from this tutorial) -will wait for all mousedown events on the canvas, -then for each mousemove event on the document, -it will call function f, until mouseup is triggered. -(See Lwt.pick) -

let open Lwt_js_events in
-Lwt.async (mousedowns
-            (Eliom_content.Html.To_dom.of_element ~%canvas)
-            (fun ev _ ->
-               Lwt.pick [mousemoves Dom_html.document f;
-                         mouseup Dom_html.document]))

Ocsigen Toolkit

Ocsigen Toolkit defines several widgets that can be generated either -on server or clide sides. Have look at -Ocsigen Start's demo app -(or the corresponding Android app) -to see them in action: carousel, drawer menu, date or time picker, color picker, -pull to refresh feature for mobile apps, etc. -

For example module -Ot_spinner -implements a widgets that you can use to display a spinner (or fake elements) -when some parts of your page take time to appear. -It can be used in shared sections and gives you precise control -of the delays and the feeling of responsiveness of your app.

Ocsigen Start

Ocsigen-start is a library and a template of Eliom application, -with many common features like -user registration, login box, notification system, etc. -

It also provides a demo of many features presented in this page. -A live version is accessible online. -Read this page to create your first Ocsigen Start -app and take time to study the code of each example. -

User management features are fully usable in production and -will save you from implementing account creation, activation links -or password recovery yourself. -Module Os_current_user -gives you information about current user -from anywhere in your program.

Database access

You can use your favourite database library with Ocsigen. -Ocsigen Start's template uses -PG'OCaml -(typed queries for Postgresql using a PPX syntax extension). -

Here is an example, taken from Ocsigen Start's demo: -

let get () =
-  full_transaction_block (fun dbh ->
-    [%pgsql dbh "SELECT lastname FROM ocsigen_start.users"])

Server to client communication

Modules -Eliom_notif -and -Os_notif -define the simplest interface to enable server to client communication -(the second one being aware of Ocsigen Start users). -

How it works

Say you want to receive the messages for one or more chat rooms. -First, define your notification module: -

module%server Notif = Os_notif.Make_Simple (struct
-  type key = int64 (* the chat room ids *)
-  type notification = string (* the type of messages *)
-end)

If you want to be notified when there is a new message in a chat room, call function -Notif.listen (server side) on the chat room id. -

If you want to send a message in a chat room, call function -Notif.notify (server side) with the chat room id and the message as parameters. -

On client side, ~%(Notif.client_ev ()) is a React event of type -(key, notif) React.E.t. Use it to receive the messages.

Have a look at a running example in Ocsigen Start's demo -(source). -

Eliom has other communication modules: -

  • Eliom_bus defines a communication bus, -that you can use to share information with other client processes -(see an example here). -
  • Eliom_react defines client-server React events. -
  • Eliom_comet is lower level interface for -server to client communication.

Internationalisation

Ocsigen i18n -is an internationalisation library for your OCaml programs. -

Create a .tsv file with, on each line, a key and the text in several languages: -

welcome_message Welcome everybody!        Bienvenue à tous !      Benvenuti a tutti !
-

and Ocsigen i18n will automatically generate functions like this one: -

let%shared welcome_message ?(lang = get_language ()) () () =
-  match lang with
-  | En -> [txt "Welcome everybody!"]
-  | Fr -> [txt "Bienvenue à tous !"]
-  | It -> [txt "Benvenuti a tutti !"]

Ocsigen i18n also defines a syntax extension to use these functions: -

Eliom_content.Html.F.h1 [%i18n welcome_message]

Ocsigen i18n offers many other features: -

  • Text can be inserted as a TyXML node (as in the example above) or as a string (ex: [%i18n S.welcome_message]), -
  • Text can be parametrizable, or contain holes (ex: [%i18n welcome ~capitalize:true ~name:"William"]) -
  • .tsv file can be split into several modules -

Have a look at the -README file -to see the full documentation, -and see examples in -Ocsigen Start's template.

Reactive programming

Eliom allows to insert reactive nodes in pages, that is, nodes which are automatically -updated when the values on which they depend change. -

This is based on the React library by Daniel Bünzli, -which implements Functional Reactive Programming. -

Functional Reactive Programming principle

Function React.S.create creates a signal and a function to change its value: -

let%client mysignal, set_mysignal = React.S.create 0

Functions like React.S.map or React.S.l2 create new signals -from one (resp. two) input signals. -They are updated automatically when their input signals change. -For example, we can can define the same value as a string signal and as TyXML node signal: -

let%client s_string = React.S.map string_of_int mysignal
-let%client s_p = React.S.map (fun v -> p [txt v]) s_string

Reactive nodes

Insert a (client side) reactive node in a page using function -Eliom_content.Html.R.node: -

let%client f () =
-  let open Eliom_content.Html in
-  F.div [ R.node s_p ]

Reactive node content

Module -Eliom_content.Html.R -also defines all TyXML nodes, -which take reactive content as parameter. -

For example Eliom_content.Html.R.txt takes -a value of type string React.S.t as parameter (string signal). -

Instead of taking a list signal as parameter, functions like -div or p of module Eliom_content.Html.R -take a parameter of type -Eliom_content.Html.F.elt ReactiveData.Rlist.t. -This enables incremental update of the content -(usually, appending an element at the end of the list, -without having to redraw the whole list). -See an example with ReactiveData in this tutorial. -

Node attributes can also be reactive. For example if s has type string list React.S.t, -you can write Eliom_content.Html.F.div ~a:[R.a_class s] []. -

Client-server reactive programming

Reactive nodes can be created in server or shared sections. -To do that, use module -Eliom_shared.React -instead of the usual React module. -On client side, this module behaves like React. -But when executed on server side, it will generate non reactive values, -that will automatically become reactive on the client. -

Full example: -

let%server theservice =
-  Eliom_service.create
-    ~path:["ex"]
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    ()
let%client theservice = ~%theservice
open%client Js_of_ocaml_lwt
open%shared Eliom_content.Html.F
-
-let%shared () =
-  My_app.register ~service:theservice (fun () () ->
-      let monsignal, set_signal = Eliom_shared.React.S.create 0 in
-      let s_string =
-        Eliom_shared.React.S.map [%shared string_of_int] monsignal
-      in
-      let _ =
-        [%client
-          (* A thread that will change the signal value every second: *)
-          (let rec aux () =
-             let%lwt () = Lwt_js.sleep 1.0 in
-             ~%set_signal (Random.int 100);
-             aux ()
-           in
-           Lwt.async aux
-          : unit)]
-      in
-      Lwt.return
-        (html
-           (head (title (txt s)) [])
-           (body [h1 [txt s];
-                  p [Eliom_content.Html.R.txt s_string]])))

Function Eliom_shared.React.S.map (and Eliom_shared.React.S.l2, etc) -takes a shared function as parameter (syntax [%shared f]). -This can be seen as a couple containing both the server a client side implementation -of the function.

Mobile apps

Applications can run on any Web browser or mobile device (iOS, Android, ...), -thus eliminating the need for one custom version per platform. -

Your CSS must be fully responsive to adapt to all screen sizes. -

Ocsigen Start's template comes with a Makefile which will automatically -download the required NPM modules for Cordova and build your Android or iOS -apps. -Read the README file -to learn how to do that. -

Download -Ocsigen Start's demo app -from Google Play store to see an example. -Be Sport mobile apps are also generated like this (available in -Google Play Store -and Apple app store).

Ocsigen Server

Ocsigen Server is a full featured Web server. -

It is now based on Cohttp. -

It has a powerful -extension mechanism that makes it easy to plug your own OCaml modules -for generating pages. Many extensions are already written: -

Staticmod -
to serve static files. -
Eliom -
to create reliable client/server Web applications -or Web sites in OCaml using advanced high level concepts. -
Extendconfiguration -
allows for more options in the configuration file. -
Accesscontrol -
restricts access to the sites from the config file (to requests coming from a subnet, containing some headers, etc.). -
Authbasic -
restricts access to the sites from the config file using Basic HTTP Authentication. -
CGImod -
serves CGI scripts. It may also be used to serve PHP through CGI. -
Deflatemod -
used to compress data before sending it to the client. -
Redirectmod -
sets redirections towards other Web sites from the configuration file. -
Revproxy -
a reverse proxy for Ocsigen Server. -It allows to ask another server to handle the request. -
Rewritemod -
changes incoming requests before sending them to other extensions. -
Outputfilter -
rewrites some parts of the output before sending it to the client. -
Userconf -
allows users to have their own configuration files. -
Comet -
facilitates server to client communications. -

Ocsigen Server has a sophisticated configuration file mechanism allowing -complex configurations of sites.

diff --git a/7.1/manual/chat.html b/7.1/manual/chat.html deleted file mode 100644 index 00c2da05..00000000 --- a/7.1/manual/chat.html +++ /dev/null @@ -1,123 +0,0 @@ - Chat: Design Overview

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

Chat: Design Overview

Chat is a chatting module and application, currently for conversations -between two users each. (Extension for multi-user channels is left as an -exercise to the user.) -

You can find the code here. -

Description

When the user is logged in, he sees a list of available users. He can choose -one of them to start a conversation with him. The conversation then becomes -visible to both users as a message prompt and a growing list of messages. The -participating users can enter messages and they are made visible to the other -participants. If a user becomes unavailable for Chat, he is removed from the -list of available users, and all conversations he was participating are removed -from the opponent's UI. -

Central Data Types

The following data are crucial to the functionality of Chat. -

Conversation

A conversation (type Shared.Conversation.t) is shared between all its -client participants. It comprises of two values: Firstly, it contains a bus for -transferring conversation messages (c.f. type Shared.Conversation.message) -between the participants. Those are displayed as they come on every client. -Secondly, it contains the set of participating users. This is used when the -conversation is teared down: An event for removing the UI element for the -conversation is sent to every participant. -

Client Process

When a user enters a web page which contains Chat, it is rendered in HTML and -a new client process is created in doing so. Every client process holds a -channel, where the server can send messages to add or to remove conversations -(c.f. type Shared.event). -

User Info

On the server side, the channel for those events is stored for each user along -with a list of conversations he participates in. This is done with a -Eliom-reference of scope session_group, such that it is shared among all -sessions of a user (and likewise all client processes). -

But, when one user requests to start (or end) a conversation with any another -user, it is also necessary to access the event channel of any user. -As it is only stored in an Eliom-reference specific to the user, it is -impossible to access. In favour of this, the user info is moreover -stored in a weak hash table (c.f. module Weak_info) which associates the -user info to each user in a globally accessible way. This association is weak -in its value, the user info, such that it is garbage-collected as soon as the -Eliom-reference storing it is cleared, i.e. when the last session of the -respective user is closed. -

Available Users

The client's UI contains a list of users which are currently available for -chatting. This is achieved as follows. -

If it is the user's first client process he is added to the set of available -users, which is available in an react signal Chat.users_signal on a set of -user (i.e. User_set.t React.S.t). -As long as a user is available with at least one client he is kept in the value -of the signal. This signal is send to the client when initializing Chat. -Changes in its value are then directly reflected in the list of available users -in the client's UI (c.f. Client.change_users). -

To observe when a user is not available through a given client process anymore, -Chat awaits for every client process that it is inactive for a given -time. Eliom_comet.Channels.wait_timeout is used for for detecting a client -process to be abandoned. -

When it is observed that a user isn't avaible through any client process -anymore, he is removed from the Chat.users_signal, and all conversation he -is participating are teared down (c.f. Chat.remove_client_process). -

Starting a conversation

When the user selects one of the available users for a conversation, one of two -things may happen (c.f. Client.create_or_focus_conversation): -

Firstly, if there is already a conversation between exactly those two users, -the message prompt is focused. -Secondly, if there is no conversation between those users yet, a new one is created -by calling the server's Chat.create_dialog_service (dialog is a conversatio -of two) with the selected user as argument. -

This service establishes the conversation between the users: It creates the -conversation on the server side (i.e. the set of participants and the bus for -messages) and sends an event to append the conversation to each participant. -Each client is streaming such events (c.f. Client.dispatch_event) and -creates the respective UI-element for the conversation with event handlers for -sending and receiving messages in the conversation. -

Sending a message in a conversationg

Sending messages within an conversation is achieved without any server-side -code; the messages are sent "directly" to all all participants of the -conversation through its Eliom-bus. Two things make this work. -

Firstly, an event listener on the message prompt of the conversation -(c.f. Client.handle_enter_pressed) awaits that the enter-key is hit in -the message prompt of a conversation. It then creates a message and sends it -to the conversation's event bus. -

Secondly, every participant of a conversation is streaming the messages which -are sent over the conversation bus. -A stream iterator on that bus then displays those messages as they arrive -(c.f. Client.dispatch_message). -

Have fun. Be communicative!

diff --git a/7.1/manual/custom-conf.html b/7.1/manual/custom-conf.html deleted file mode 100644 index 69657bb4..00000000 --- a/7.1/manual/custom-conf.html +++ /dev/null @@ -1,113 +0,0 @@ -Custom configuration options

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

Custom configuration options

It is not convenient to have to edit the code to change some -configurations, like the location where are saved the favorite -images in the Graffiti tutorial -(see: Saving favorite pictures). -Fortunately Ocsigen provides a mechanism to extend its -configuration file. -

Basic interface

let static_dir =
-  match Eliom_config.get_config () with
-    | [Simplexmlparser.Element
-     ("staticdir", [], [Simplexmlparser.PCData dir])] ->
-        dir
-    | [] ->
-      raise (Ocsigen_extensions.Error_in_config_file
-               ("staticdir must be configured"))
-    | _ ->
-      raise (Ocsigen_extensions.Error_in_config_file
-               ("Unexpected content inside config"))

This will add a mandatory child to the eliom tag in the -configuration file: -

<eliom module="path/to/your/module.cma">
-  <staticdir>/tmp/static</staticdir>
-</eliom>
-

New interface

From Eliom 4.0 it is much easier to define configuration file extension. -Have a look at module Eliom_config. -For instance, here is how you can add an element "<ldap>" -in the configuration file to store a list of LDAP servers your application -interacts with. -

(** An LDAP server is characterized by an host and a port. *)
-type ldap_configuration = {
-  mutable host : string;
-  mutable port : int;
-  }
-
-
-(** We store a list of LDAP servers to interact with. *)
-let ldap_servers = ref []
-
-(** The user-defined extension of the configuration file. *)
-let ldap_configuration = Ocsigen_extensions.Configuration.(
-  (** Default configuration *)
-  let config () = {
-    host = "";
-    port = 339;
-  }
-  in
-  let init () =
-    ldap_servers := config () :: !ldap_servers
-  in
-  let current () =
-    List.hd !ldap_servers
-  in
-
-  (** Parsing functions. *)
-  let req_attr name = attribute ~name ~obligatory:true
-  and opt_attr name = attribute ~name ~obligatory:false
-  in
-  let name = "LDAP"
-  and obligatory = false
-  and attributes = [
-    req_attr "host" (fun h -> (current ()).host <- h);
-    opt_attr "port" (fun p -> (current ()).port <- int_of_string p);
-    );
-  ]
-  in
-  element ~init ~name ~obligatory ~attributes ()
-)
-
-let _ = Eliom_config.parse_config [ldap_configuration]
diff --git a/7.1/manual/files/2014-epita-video.png b/7.1/manual/files/2014-epita-video.png deleted file mode 100644 index 253afdcf..00000000 Binary files a/7.1/manual/files/2014-epita-video.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/.directory b/7.1/manual/files/screenshots/.directory deleted file mode 100644 index 79c63a86..00000000 --- a/7.1/manual/files/screenshots/.directory +++ /dev/null @@ -1,4 +0,0 @@ -[Dolphin] -PreviewsShown=true -Timestamp=2016,12,12,17,5,19 -Version=3 diff --git a/7.1/manual/files/screenshots/start-mobile-1.png b/7.1/manual/files/screenshots/start-mobile-1.png deleted file mode 100644 index 65ba09d4..00000000 Binary files a/7.1/manual/files/screenshots/start-mobile-1.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/start-mobile-2.png b/7.1/manual/files/screenshots/start-mobile-2.png deleted file mode 100644 index 6b95975e..00000000 Binary files a/7.1/manual/files/screenshots/start-mobile-2.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/start-mobile-3.png b/7.1/manual/files/screenshots/start-mobile-3.png deleted file mode 100644 index eb02e5f9..00000000 Binary files a/7.1/manual/files/screenshots/start-mobile-3.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/start-mobile-4.png b/7.1/manual/files/screenshots/start-mobile-4.png deleted file mode 100644 index a1f5f898..00000000 Binary files a/7.1/manual/files/screenshots/start-mobile-4.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/start-mobile-5.png b/7.1/manual/files/screenshots/start-mobile-5.png deleted file mode 100644 index 92ebfb12..00000000 Binary files a/7.1/manual/files/screenshots/start-mobile-5.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/start-mobile-6.png b/7.1/manual/files/screenshots/start-mobile-6.png deleted file mode 100644 index 4286da90..00000000 Binary files a/7.1/manual/files/screenshots/start-mobile-6.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/start-mobile-7.png b/7.1/manual/files/screenshots/start-mobile-7.png deleted file mode 100644 index d034efb2..00000000 Binary files a/7.1/manual/files/screenshots/start-mobile-7.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/start-mobile-8.png b/7.1/manual/files/screenshots/start-mobile-8.png deleted file mode 100644 index 1161054a..00000000 Binary files a/7.1/manual/files/screenshots/start-mobile-8.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/start1.png b/7.1/manual/files/screenshots/start1.png deleted file mode 100644 index 185e08bb..00000000 Binary files a/7.1/manual/files/screenshots/start1.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/start2.png b/7.1/manual/files/screenshots/start2.png deleted file mode 100644 index 1ce3676a..00000000 Binary files a/7.1/manual/files/screenshots/start2.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/start3.png b/7.1/manual/files/screenshots/start3.png deleted file mode 100644 index 62ac5ce5..00000000 Binary files a/7.1/manual/files/screenshots/start3.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/start4.png b/7.1/manual/files/screenshots/start4.png deleted file mode 100644 index e0133871..00000000 Binary files a/7.1/manual/files/screenshots/start4.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/start5.png b/7.1/manual/files/screenshots/start5.png deleted file mode 100644 index f4a6c2b6..00000000 Binary files a/7.1/manual/files/screenshots/start5.png and /dev/null differ diff --git a/7.1/manual/files/screenshots/start6.png b/7.1/manual/files/screenshots/start6.png deleted file mode 100644 index b961511d..00000000 Binary files a/7.1/manual/files/screenshots/start6.png and /dev/null differ diff --git a/7.1/manual/files/tutorial/chapter1/README b/7.1/manual/files/tutorial/chapter1/README deleted file mode 100644 index 37d111c1..00000000 --- a/7.1/manual/files/tutorial/chapter1/README +++ /dev/null @@ -1 +0,0 @@ -This code isn't maintened anymore and might not work. Please go to https://github.com/ocsigen/graffiti/simple for the latest working versions. diff --git a/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/Makefile b/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/Makefile deleted file mode 100644 index 70b892db..00000000 --- a/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/Makefile +++ /dev/null @@ -1,242 +0,0 @@ - -##---------------------------------------------------------------------- -## DISCLAIMER -## -## This file contains the rules to make an Eliom project. The project is -## configured through the variables in the file Makefile.options. -##---------------------------------------------------------------------- - -include Makefile.options - -##---------------------------------------------------------------------- -## Internals - -## Required binaries -ELIOMC := eliomc -ELIOMOPT := eliomopt -JS_OF_ELIOM := js_of_eliom -ELIOMDEP := eliomdep -OCSIGENSERVER := ocsigenserver -OCSIGENSERVER.OPT := ocsigenserver.opt - -## Where to put intermediate object files. -## - ELIOM_{SERVER,CLIENT}_DIR must be distinct -## - ELIOM_CLIENT_DIR must not be the local dir. -## - ELIOM_SERVER_DIR could be ".", but you need to -## remove it from the "clean" rules... -export ELIOM_SERVER_DIR := _server -export ELIOM_CLIENT_DIR := _client -export ELIOM_TYPE_DIR := _server -DEPSDIR := _deps - -ifeq ($(DEBUG),yes) - GENERATE_DEBUG ?= -g - RUN_DEBUG ?= "-v" - DEBUG_JS ?= -jsopt -pretty -jsopt -noinline -jsopt -debuginfo -endif - -##---------------------------------------------------------------------- -## General - -.PHONY: all byte opt -all: byte opt -byte opt:: $(TEST_PREFIX)$(ELIOMSTATICDIR)/${PROJECT_NAME}.js -byte opt:: $(TEST_PREFIX)$(ETCDIR)/$(PROJECT_NAME).conf -byte opt:: $(TEST_PREFIX)$(ETCDIR)/$(PROJECT_NAME)-test.conf -byte:: $(TEST_PREFIX)$(LIBDIR)/${PROJECT_NAME}.cma -opt:: $(TEST_PREFIX)$(LIBDIR)/${PROJECT_NAME}.cmxs - -DIST_DIRS = $(ETCDIR) $(DATADIR) $(LIBDIR) $(LOGDIR) $(STATICDIR) $(ELIOMSTATICDIR) $(shell dirname $(CMDPIPE)) - -##---------------------------------------------------------------------- -## Testing - -DIST_FILES = $(ELIOMSTATICDIR)/$(PROJECT_NAME).js $(LIBDIR)/$(PROJECT_NAME).cma - -.PHONY: test.byte test.opt -test.byte: $(addprefix $(TEST_PREFIX),$(ETCDIR)/$(PROJECT_NAME)-test.conf $(DIST_DIRS) $(DIST_FILES)) - $(OCSIGENSERVER) $(RUN_DEBUG) -c $< -test.opt: $(addprefix $(TEST_PREFIX),$(ETCDIR)/$(PROJECT_NAME)-test.conf $(DIST_DIRS) $(patsubst %.cma,%.cmxs, $(DIST_FILES))) - $(OCSIGENSERVER.OPT) $(RUN_DEBUG) -c $< - -$(addprefix $(TEST_PREFIX), $(DIST_DIRS)): - mkdir -p $@ - -##---------------------------------------------------------------------- -## Installing & Running - -.PHONY: install install.byte install.byte install.opt install.static install.etc install.lib install.lib.byte install.lib.opt run.byte run.opt -install: install.byte install.opt -install.byte: install.lib.byte install.etc install.static | $(addprefix $(PREFIX),$(DATADIR) $(LOGDIR) $(shell dirname $(CMDPIPE))) -install.opt: install.lib.opt install.etc install.static | $(addprefix $(PREFIX),$(DATADIR) $(LOGDIR) $(shell dirname $(CMDPIPE))) -install.lib: install.lib.byte install.lib.opt -install.lib.byte: $(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cma | $(PREFIX)$(LIBDIR) - install $< $(PREFIX)$(LIBDIR) -install.lib.opt: $(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cmxs | $(PREFIX)$(LIBDIR) - install $< $(PREFIX)$(LIBDIR) -install.static: $(TEST_PREFIX)$(ELIOMSTATICDIR)/$(PROJECT_NAME).js | $(PREFIX)$(STATICDIR) $(PREFIX)$(ELIOMSTATICDIR) - cp -r $(LOCAL_STATIC)/* $(PREFIX)$(STATICDIR) - [ -z $(WWWUSER) ] || chown -R $(WWWUSER) $(PREFIX)$(STATICDIR) - install $(addprefix -o ,$(WWWUSER)) $< $(PREFIX)$(ELIOMSTATICDIR) -install.etc: $(TEST_PREFIX)$(ETCDIR)/$(PROJECT_NAME).conf | $(PREFIX)$(ETCDIR) - install $< $(PREFIX)$(ETCDIR)/$(PROJECT_NAME).conf - -.PHONY: -print-install-files: - @echo $(PREFIX)$(LIBDIR) - @echo $(PREFIX)$(STATICDIR) - @echo $(PREFIX)$(ELIOMSTATICDIR) - @echo $(PREFIX)$(ETCDIR) - -$(addprefix $(PREFIX),$(ETCDIR) $(LIBDIR)): - install -d $@ -$(addprefix $(PREFIX),$(DATADIR) $(LOGDIR) $(STATICDIR) $(ELIOMSTATICDIR) $(shell dirname $(CMDPIPE))): - install $(addprefix -o ,$(WWWUSER)) -d $@ - -run.byte: - $(OCSIGENSERVER) $(RUN_DEBUG) -c ${PREFIX}${ETCDIR}/${PROJECT_NAME}.conf -run.opt: - $(OCSIGENSERVER.OPT) $(RUN_DEBUG) -c ${PREFIX}${ETCDIR}/${PROJECT_NAME}.conf - -##---------------------------------------------------------------------- -## Aux - -# Use `eliomdep -sort' only in OCaml>4 -ifeq ($(shell ocamlc -version|cut -c1),4) -eliomdep=$(shell $(ELIOMDEP) $(1) -sort $(2) $(filter %.eliom %.ml,$(3)))) -else -eliomdep=$(3) -endif -objs=$(patsubst %.ml,$(1)/%.$(2),$(patsubst %.eliom,$(1)/%.$(2),$(filter %.eliom %.ml,$(3)))) -depsort=$(call objs,$(1),$(2),$(call eliomdep,$(3),$(4),$(5))) - -##---------------------------------------------------------------------- -## Config files - -FINDLIB_PACKAGES=$(patsubst %,\,$(SERVER_PACKAGES)) -EDIT_WARNING=DON\'T EDIT THIS FILE! It is generated from $(PROJECT_NAME).conf.in, edit that one, or the variables in Makefile.options -SED_ARGS := -e "/^ *%%%/d" -SED_ARGS += -e "s|%%PROJECT_NAME%%|$(PROJECT_NAME)|g" -SED_ARGS += -e "s|%%DATABASE_NAME%%|$(DATABASE_NAME)|g" -SED_ARGS += -e "s|%%DATABASE_USER%%|$(DATABASE_USER)|g" -SED_ARGS += -e "s|%%CMDPIPE%%|%%PREFIX%%$(CMDPIPE)|g" -SED_ARGS += -e "s|%%LOGDIR%%|%%PREFIX%%$(LOGDIR)|g" -SED_ARGS += -e "s|%%DATADIR%%|%%PREFIX%%$(DATADIR)|g" -SED_ARGS += -e "s|%%LIBDIR%%|%%PREFIX%%$(LIBDIR)|g" -SED_ARGS += -e "s|%%WARNING%%|$(EDIT_WARNING)|g" -SED_ARGS += -e "s|%%PACKAGES%%|$(FINDLIB_PACKAGES)|g" -SED_ARGS += -e "s|%%ELIOMSTATICDIR%%|%%PREFIX%%$(ELIOMSTATICDIR)|g" -ifeq ($(DEBUG),yes) - SED_ARGS += -e "s|%%DEBUGMODE%%|\|g" -else - SED_ARGS += -e "s|%%DEBUGMODE%%||g" -endif - -LOCAL_SED_ARGS := -e "s|%%PORT%%|$(TEST_PORT)|g" -LOCAL_SED_ARGS += -e "s|%%STATICDIR%%|$(LOCAL_STATIC)|g" -LOCAL_SED_ARGS += -e "s|%%USERGROUP%%||g" -GLOBAL_SED_ARGS := -e "s|%%PORT%%|$(PORT)|g" -GLOBAL_SED_ARGS += -e "s|%%STATICDIR%%|%%PREFIX%%$(STATICDIR)|g" -ifeq ($(WWWUSER)$(WWWGROUP),) - GLOBAL_SED_ARGS += -e "s|%%USERGROUP%%||g" -else - GLOBAL_SED_ARGS += -e "s|%%USERGROUP%%|$(WWWUSER)$(WWWGROUP)|g" -endif - -$(TEST_PREFIX)${ETCDIR}/${PROJECT_NAME}.conf: ${PROJECT_NAME}.conf.in Makefile.options | $(TEST_PREFIX)$(ETCDIR) - sed $(SED_ARGS) $(GLOBAL_SED_ARGS) $< | sed -e "s|%%PREFIX%%|$(PREFIX)|g" > $@ -$(TEST_PREFIX)${ETCDIR}/${PROJECT_NAME}-test.conf: ${PROJECT_NAME}.conf.in Makefile.options | $(TEST_PREFIX)$(ETCDIR) - sed $(SED_ARGS) $(LOCAL_SED_ARGS) $< | sed -e "s|%%PREFIX%%|$(TEST_PREFIX)|g" > $@ - -##---------------------------------------------------------------------- -## Server side compilation - -SERVER_INC := ${addprefix -package ,${SERVER_PACKAGES}} - -${ELIOM_TYPE_DIR}/%.type_mli: %.eliom - ${ELIOMC} -infer ${SERVER_INC} $< - -$(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cma: $(call objs,$(ELIOM_SERVER_DIR),cmo,$(SERVER_FILES)) | $(TEST_PREFIX)$(LIBDIR) - ${ELIOMC} -a -o $@ $(GENERATE_DEBUG) \ - $(call depsort,$(ELIOM_SERVER_DIR),cmo,-server,$(SERVER_INC),$(SERVER_FILES)) - -$(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cmxa: $(call objs,$(ELIOM_SERVER_DIR),cmx,$(SERVER_FILES)) | $(TEST_PREFIX)$(LIBDIR) - ${ELIOMOPT} -a -o $@ $(GENERATE_DEBUG) \ - $(call depsort,$(ELIOM_SERVER_DIR),cmx,-server,$(SERVER_INC),$(SERVER_FILES)) - -%.cmxs: %.cmxa - $(ELIOMOPT) -shared -linkall -o $@ $(GENERATE_DEBUG) $< - -${ELIOM_SERVER_DIR}/%.cmi: %.mli - ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< - -${ELIOM_SERVER_DIR}/%.cmi: %.eliomi - ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< - -${ELIOM_SERVER_DIR}/%.cmo: %.ml - ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< -${ELIOM_SERVER_DIR}/%.cmo: %.eliom - ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< - -${ELIOM_SERVER_DIR}/%.cmx: %.ml - ${ELIOMOPT} -c ${SERVER_INC} $(GENERATE_DEBUG) $< -${ELIOM_SERVER_DIR}/%.cmx: %.eliom - ${ELIOMOPT} -c ${SERVER_INC} $(GENERATE_DEBUG) $< - - -##---------------------------------------------------------------------- -## Client side compilation - -CLIENT_LIBS := ${addprefix -package ,${CLIENT_PACKAGES}} -CLIENT_INC := ${addprefix -package ,${CLIENT_PACKAGES}} - -CLIENT_OBJS := $(filter %.eliom %.ml, $(CLIENT_FILES)) -CLIENT_OBJS := $(patsubst %.eliom,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) -CLIENT_OBJS := $(patsubst %.ml,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) - -$(TEST_PREFIX)$(ELIOMSTATICDIR)/$(PROJECT_NAME).js: $(call objs,$(ELIOM_CLIENT_DIR),cmo,$(CLIENT_FILES)) | $(TEST_PREFIX)$(ELIOMSTATICDIR) - ${JS_OF_ELIOM} -o $@ $(GENERATE_DEBUG) $(CLIENT_INC) $(DEBUG_JS) \ - $(call depsort,$(ELIOM_CLIENT_DIR),cmo,-client,$(CLIENT_INC),$(CLIENT_FILES)) - -$(TEST_PREFIX)$(ELIOMSTATICDIR)/$(PROJECT_NAME)_oclosure.js: $(TEST_PREFIX)$(ELIOMSTATICDIR)/$(PROJECT_NAME).js - oclosure_req $< - -${ELIOM_CLIENT_DIR}/%.cmi: %.mli - ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< - -${ELIOM_CLIENT_DIR}/%.cmo: %.eliom - ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< -${ELIOM_CLIENT_DIR}/%.cmo: %.ml - ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< - -${ELIOM_CLIENT_DIR}/%.cmi: %.eliomi - ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< - -##---------------------------------------------------------------------- -## Dependencies - -include .depend - -.depend: $(patsubst %,$(DEPSDIR)/%.server,$(SERVER_FILES)) $(patsubst %,$(DEPSDIR)/%.client,$(CLIENT_FILES)) - cat $^ > $@ - -$(DEPSDIR)/%.server: % | $(DEPSDIR) - $(ELIOMDEP) -server $(SERVER_INC) $< > $@ - -$(DEPSDIR)/%.client: % | $(DEPSDIR) - $(ELIOMDEP) -client $(CLIENT_INC) $< > $@ - -$(DEPSDIR): - mkdir $@ - -##---------------------------------------------------------------------- -## Clean up - -clean: - -rm -f *.cm[ioax] *.cmxa *.cmxs *.o *.a *.annot - -rm -f *.type_mli - -rm -f ${PROJECT_NAME}.js - -rm -rf ${ELIOM_CLIENT_DIR} ${ELIOM_SERVER_DIR} - -distclean: clean - -rm -rf $(TEST_PREFIX) $(DEPSDIR) .depend diff --git a/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/Makefile.options b/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/Makefile.options deleted file mode 100644 index a001a7ba..00000000 --- a/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/Makefile.options +++ /dev/null @@ -1,57 +0,0 @@ - -##---------------------------------------------------------------------- -## SETTINGS FOR THE ELIOM PROJECT graffiti -##---------------------------------------------------------------------- - -PROJECT_NAME := graffiti - -## Do not forget `make depend' after adding a file! -## Source files for the server -SERVER_FILES := $(wildcard *.eliomi *.eliom) -## Source files for the client -CLIENT_FILES := $(wildcard *.eliomi *.eliom) - -## OCamlfind packages for the server -SERVER_PACKAGES := deriving.syntax cairo -## OCamlfind packages for the client -CLIENT_PACKAGES := deriving.syntax ojwidgets - -## Directory with files to be statically served -LOCAL_STATIC = static - -## Debug application (yes/no): Debugging info in compilation, -## JavaScript, ocsigenserver -DEBUG := no - -## User to run server with (make run.*) -WWWUSER := www-data -WWWGROUP := www-data - -## Port for running the server (make run.*) -PORT := 80 - -## Port for testing (make test.*) -TEST_PORT := 8080 - -## Root of installation (must end with /) -PREFIX := / - -## Local folder for make test.* (must end with /) -TEST_PREFIX := local/ - -## The installation tree (relative to $(PREFIX) when installing/runnin -## or $(TEST_PREFIX) when testing). -# Configuration file $(PROJECT_NAME).conf -ETCDIR := etc/${PROJECT_NAME} -# Project's library $(PROJECT_NAME).cma (cmxs) -LIBDIR := var/lib/${PROJECT_NAME} -# Command pipe, eg. $ echo restart > $(INSTALL_PREFIX)$(CMDPIPE) -CMDPIPE := var/run/${PROJECT_NAME}-cmd -# Ocsigenserver's logging files -LOGDIR := var/log/${PROJECT_NAME} -# Ocsigenserver's persistent data files -DATADIR := var/data/${PROJECT_NAME} -# Copy of $(LOCAL_STATIC) -STATICDIR := var/www/${PROJECT_NAME}/static -# Project's JavaScript file -ELIOMSTATICDIR := var/www/${PROJECT_NAME}/eliom diff --git a/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/README b/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/README deleted file mode 100644 index d2f190e5..00000000 --- a/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/README +++ /dev/null @@ -1,59 +0,0 @@ - -This project is (initially) generated by eliom-distillery as the basic -project "graffiti". - -It contains the following files: - - - graffiti.eliom - This is your initial source file. - All Eliom files (*.eliom, *.eliomi) in this directory are - automatically considered. To add a .ml/.mli file to your project, - add it to the variable SERVER_FILES or CLIENT_FILES. - - - static/ - The content of this folder is statically served. It contains - initially a basic CSS file for your project. - - - Makefile.options - Configure your project here! - - - graffiti.conf.in - This file is a template for the configuration file for - ocsigenserver. You will rarely have to edit itself - it takes its - variables from the Makefile.options. This way, the installation - rules and the configuration files are synchronized with respect to - the different folders. - - - Makefile - This contains all rules necessary to build, test, and run your - Eliom application. You better don't touch it ;) See below for the - relevant targets. - - - README - Not completely desribable here. - - -Here's some help on how to work with this basic distillery project: - - - Compile it - $ make all (or byte/opt) - - - Test your application by running ocsigenserver locally - $ make test.byte (or test.opt) - - - Install it - $ sudo make install (or install.byte/install.opt) - - - Run the server on the installation - $ sudo make run.byte (or run.opt) - If the user to run ocsigenserver is yourself, you don't need the - `sudo'. If Ocsigen isn't installed in a global position, however, - you may need to re-export some environment variables to make this - work: - $ sudo PATH=$PATH OCAMLPATH=$OCAMLPATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH make run.byte/run.opt - - - If you need a findlib package in your project, add it to the - variables SERVER_PACKAGES and/or CLIENT_PACKAGES. - - - The directory static/ will be statically served. Put your CSS and - additional JavaScript files here! diff --git a/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/graffiti.conf.in b/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/graffiti.conf.in deleted file mode 100644 index 82244ac9..00000000 --- a/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/graffiti.conf.in +++ /dev/null @@ -1,28 +0,0 @@ -%%% This is the template for your configuration file. The %%VALUES%% below are -%%% taken from the Makefile to generate the actual configuration files. -%%% This comment will disappear. - - - - %%PORT%% - %%% Only set for running, not for testing - %%USERGROUP%% - %%LOGDIR%% - %%DATADIR%% - utf-8 - %%% Only set when debugging - %%DEBUGMODE%% - %%CMDPIPE%% - - - - %%% This will include the packages defined as SERVER_PACKAGES in your Makefile: - %%PACKAGES%% - - - - - - - - diff --git a/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/graffiti.eliom b/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/graffiti.eliom deleted file mode 100644 index 54d2e3ff..00000000 --- a/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/graffiti.eliom +++ /dev/null @@ -1,146 +0,0 @@ -{shared{ - open Eliom_lib.Lwt_ops - open Eliom_content.Html5.D - - let width = 700 - let height = 400 - - type messages = ((int * int * int) * int * (int * int) * (int * int)) - deriving (Json) -}} - -module My_app = - Eliom_registration.App ( - struct - let application_name = "graffiti" - end) - -let bus = Eliom_bus.create Json.t - -let canvas_elt = - canvas ~a:[a_width width; a_height height] - [pcdata "your browser doesn't support canvas"] - -let slider = int_input ~a:[a_id "slider"] ~input_type:`Range () - -let page = - html - (Eliom_tools.F.head ~title:"Graffiti" - ~css:[["css";"graffiti.css"]] - ~js:[] ()) - (body [h1 [pcdata "Graffiti"]; canvas_elt; div [slider]]) - - -let draw_server, image_string = - let rgb_floats_from_ints (r, g, b) = - float r /. 255., float g /. 255., float b /. 255. in - let surface = - Cairo.image_surface_create Cairo.FORMAT_ARGB32 ~width ~height - 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.LINE_JOIN_ROUND ; - Cairo.set_line_cap ctx Cairo.LINE_CAP_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.close_path ctx ; - - (* Apply the ink *) - Cairo.stroke ctx ; - ), - (fun () -> - let b = Buffer.create 10000 in - (* Output a PNG in a string *) - Cairo_png.surface_write_to_stream surface (Buffer.add_string b); - Buffer.contents b - )) - -let imageservice = - Eliom_registration.String.register_service - ~path:["image"] - ~get_params:Eliom_parameter.unit - (fun () () -> Lwt.return (image_string (), "image/png")) - -{client{ - - 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##beginPath(); - ctx##moveTo(float x1, float y1); - ctx##lineTo(float x2, float y2); - ctx##stroke() - - 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"; - - (* The initial image: *) - let img = - Eliom_content.Html5.To_dom.of_img - (img ~alt:"canvas" - ~src:(make_uri ~service:%imageservice ()) - ()) - in - img##onload <- Dom_html.handler - (fun ev -> ctx##drawImage(img, 0., 0.); Js._false); - - (* 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; - - 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 - in - - let compute_line ev = - let oldx = !x and oldy = !y in - set_coord ev; - 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)) - in - - let line ev = - let v = compute_line ev in - let _ = Eliom_bus.write %bus v in - draw ctx v; - Lwt.return () in - - Lwt.async - (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])); - - Lwt.async (fun () -> Lwt_stream.iter (draw ctx) (Eliom_bus.stream %bus)); - - -}} - -let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) - -let main_service = - My_app.register_service ~path:[""] ~get_params:Eliom_parameter.unit - (fun () () -> - (* Cf. the box "Client side side-effects on the server" *) - ignore {unit{ init_client () }}; - Lwt.return page) diff --git a/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/update-symlinks.sh b/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/update-symlinks.sh deleted file mode 100755 index f49ae5cf..00000000 --- a/7.1/manual/files/tutorial/chapter1/distillery-no-oclosure/update-symlinks.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -ln -s ../../static . - diff --git a/7.1/manual/files/tutorial/chapter1/distillery-oclosure/Makefile b/7.1/manual/files/tutorial/chapter1/distillery-oclosure/Makefile deleted file mode 100644 index 70b892db..00000000 --- a/7.1/manual/files/tutorial/chapter1/distillery-oclosure/Makefile +++ /dev/null @@ -1,242 +0,0 @@ - -##---------------------------------------------------------------------- -## DISCLAIMER -## -## This file contains the rules to make an Eliom project. The project is -## configured through the variables in the file Makefile.options. -##---------------------------------------------------------------------- - -include Makefile.options - -##---------------------------------------------------------------------- -## Internals - -## Required binaries -ELIOMC := eliomc -ELIOMOPT := eliomopt -JS_OF_ELIOM := js_of_eliom -ELIOMDEP := eliomdep -OCSIGENSERVER := ocsigenserver -OCSIGENSERVER.OPT := ocsigenserver.opt - -## Where to put intermediate object files. -## - ELIOM_{SERVER,CLIENT}_DIR must be distinct -## - ELIOM_CLIENT_DIR must not be the local dir. -## - ELIOM_SERVER_DIR could be ".", but you need to -## remove it from the "clean" rules... -export ELIOM_SERVER_DIR := _server -export ELIOM_CLIENT_DIR := _client -export ELIOM_TYPE_DIR := _server -DEPSDIR := _deps - -ifeq ($(DEBUG),yes) - GENERATE_DEBUG ?= -g - RUN_DEBUG ?= "-v" - DEBUG_JS ?= -jsopt -pretty -jsopt -noinline -jsopt -debuginfo -endif - -##---------------------------------------------------------------------- -## General - -.PHONY: all byte opt -all: byte opt -byte opt:: $(TEST_PREFIX)$(ELIOMSTATICDIR)/${PROJECT_NAME}.js -byte opt:: $(TEST_PREFIX)$(ETCDIR)/$(PROJECT_NAME).conf -byte opt:: $(TEST_PREFIX)$(ETCDIR)/$(PROJECT_NAME)-test.conf -byte:: $(TEST_PREFIX)$(LIBDIR)/${PROJECT_NAME}.cma -opt:: $(TEST_PREFIX)$(LIBDIR)/${PROJECT_NAME}.cmxs - -DIST_DIRS = $(ETCDIR) $(DATADIR) $(LIBDIR) $(LOGDIR) $(STATICDIR) $(ELIOMSTATICDIR) $(shell dirname $(CMDPIPE)) - -##---------------------------------------------------------------------- -## Testing - -DIST_FILES = $(ELIOMSTATICDIR)/$(PROJECT_NAME).js $(LIBDIR)/$(PROJECT_NAME).cma - -.PHONY: test.byte test.opt -test.byte: $(addprefix $(TEST_PREFIX),$(ETCDIR)/$(PROJECT_NAME)-test.conf $(DIST_DIRS) $(DIST_FILES)) - $(OCSIGENSERVER) $(RUN_DEBUG) -c $< -test.opt: $(addprefix $(TEST_PREFIX),$(ETCDIR)/$(PROJECT_NAME)-test.conf $(DIST_DIRS) $(patsubst %.cma,%.cmxs, $(DIST_FILES))) - $(OCSIGENSERVER.OPT) $(RUN_DEBUG) -c $< - -$(addprefix $(TEST_PREFIX), $(DIST_DIRS)): - mkdir -p $@ - -##---------------------------------------------------------------------- -## Installing & Running - -.PHONY: install install.byte install.byte install.opt install.static install.etc install.lib install.lib.byte install.lib.opt run.byte run.opt -install: install.byte install.opt -install.byte: install.lib.byte install.etc install.static | $(addprefix $(PREFIX),$(DATADIR) $(LOGDIR) $(shell dirname $(CMDPIPE))) -install.opt: install.lib.opt install.etc install.static | $(addprefix $(PREFIX),$(DATADIR) $(LOGDIR) $(shell dirname $(CMDPIPE))) -install.lib: install.lib.byte install.lib.opt -install.lib.byte: $(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cma | $(PREFIX)$(LIBDIR) - install $< $(PREFIX)$(LIBDIR) -install.lib.opt: $(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cmxs | $(PREFIX)$(LIBDIR) - install $< $(PREFIX)$(LIBDIR) -install.static: $(TEST_PREFIX)$(ELIOMSTATICDIR)/$(PROJECT_NAME).js | $(PREFIX)$(STATICDIR) $(PREFIX)$(ELIOMSTATICDIR) - cp -r $(LOCAL_STATIC)/* $(PREFIX)$(STATICDIR) - [ -z $(WWWUSER) ] || chown -R $(WWWUSER) $(PREFIX)$(STATICDIR) - install $(addprefix -o ,$(WWWUSER)) $< $(PREFIX)$(ELIOMSTATICDIR) -install.etc: $(TEST_PREFIX)$(ETCDIR)/$(PROJECT_NAME).conf | $(PREFIX)$(ETCDIR) - install $< $(PREFIX)$(ETCDIR)/$(PROJECT_NAME).conf - -.PHONY: -print-install-files: - @echo $(PREFIX)$(LIBDIR) - @echo $(PREFIX)$(STATICDIR) - @echo $(PREFIX)$(ELIOMSTATICDIR) - @echo $(PREFIX)$(ETCDIR) - -$(addprefix $(PREFIX),$(ETCDIR) $(LIBDIR)): - install -d $@ -$(addprefix $(PREFIX),$(DATADIR) $(LOGDIR) $(STATICDIR) $(ELIOMSTATICDIR) $(shell dirname $(CMDPIPE))): - install $(addprefix -o ,$(WWWUSER)) -d $@ - -run.byte: - $(OCSIGENSERVER) $(RUN_DEBUG) -c ${PREFIX}${ETCDIR}/${PROJECT_NAME}.conf -run.opt: - $(OCSIGENSERVER.OPT) $(RUN_DEBUG) -c ${PREFIX}${ETCDIR}/${PROJECT_NAME}.conf - -##---------------------------------------------------------------------- -## Aux - -# Use `eliomdep -sort' only in OCaml>4 -ifeq ($(shell ocamlc -version|cut -c1),4) -eliomdep=$(shell $(ELIOMDEP) $(1) -sort $(2) $(filter %.eliom %.ml,$(3)))) -else -eliomdep=$(3) -endif -objs=$(patsubst %.ml,$(1)/%.$(2),$(patsubst %.eliom,$(1)/%.$(2),$(filter %.eliom %.ml,$(3)))) -depsort=$(call objs,$(1),$(2),$(call eliomdep,$(3),$(4),$(5))) - -##---------------------------------------------------------------------- -## Config files - -FINDLIB_PACKAGES=$(patsubst %,\,$(SERVER_PACKAGES)) -EDIT_WARNING=DON\'T EDIT THIS FILE! It is generated from $(PROJECT_NAME).conf.in, edit that one, or the variables in Makefile.options -SED_ARGS := -e "/^ *%%%/d" -SED_ARGS += -e "s|%%PROJECT_NAME%%|$(PROJECT_NAME)|g" -SED_ARGS += -e "s|%%DATABASE_NAME%%|$(DATABASE_NAME)|g" -SED_ARGS += -e "s|%%DATABASE_USER%%|$(DATABASE_USER)|g" -SED_ARGS += -e "s|%%CMDPIPE%%|%%PREFIX%%$(CMDPIPE)|g" -SED_ARGS += -e "s|%%LOGDIR%%|%%PREFIX%%$(LOGDIR)|g" -SED_ARGS += -e "s|%%DATADIR%%|%%PREFIX%%$(DATADIR)|g" -SED_ARGS += -e "s|%%LIBDIR%%|%%PREFIX%%$(LIBDIR)|g" -SED_ARGS += -e "s|%%WARNING%%|$(EDIT_WARNING)|g" -SED_ARGS += -e "s|%%PACKAGES%%|$(FINDLIB_PACKAGES)|g" -SED_ARGS += -e "s|%%ELIOMSTATICDIR%%|%%PREFIX%%$(ELIOMSTATICDIR)|g" -ifeq ($(DEBUG),yes) - SED_ARGS += -e "s|%%DEBUGMODE%%|\|g" -else - SED_ARGS += -e "s|%%DEBUGMODE%%||g" -endif - -LOCAL_SED_ARGS := -e "s|%%PORT%%|$(TEST_PORT)|g" -LOCAL_SED_ARGS += -e "s|%%STATICDIR%%|$(LOCAL_STATIC)|g" -LOCAL_SED_ARGS += -e "s|%%USERGROUP%%||g" -GLOBAL_SED_ARGS := -e "s|%%PORT%%|$(PORT)|g" -GLOBAL_SED_ARGS += -e "s|%%STATICDIR%%|%%PREFIX%%$(STATICDIR)|g" -ifeq ($(WWWUSER)$(WWWGROUP),) - GLOBAL_SED_ARGS += -e "s|%%USERGROUP%%||g" -else - GLOBAL_SED_ARGS += -e "s|%%USERGROUP%%|$(WWWUSER)$(WWWGROUP)|g" -endif - -$(TEST_PREFIX)${ETCDIR}/${PROJECT_NAME}.conf: ${PROJECT_NAME}.conf.in Makefile.options | $(TEST_PREFIX)$(ETCDIR) - sed $(SED_ARGS) $(GLOBAL_SED_ARGS) $< | sed -e "s|%%PREFIX%%|$(PREFIX)|g" > $@ -$(TEST_PREFIX)${ETCDIR}/${PROJECT_NAME}-test.conf: ${PROJECT_NAME}.conf.in Makefile.options | $(TEST_PREFIX)$(ETCDIR) - sed $(SED_ARGS) $(LOCAL_SED_ARGS) $< | sed -e "s|%%PREFIX%%|$(TEST_PREFIX)|g" > $@ - -##---------------------------------------------------------------------- -## Server side compilation - -SERVER_INC := ${addprefix -package ,${SERVER_PACKAGES}} - -${ELIOM_TYPE_DIR}/%.type_mli: %.eliom - ${ELIOMC} -infer ${SERVER_INC} $< - -$(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cma: $(call objs,$(ELIOM_SERVER_DIR),cmo,$(SERVER_FILES)) | $(TEST_PREFIX)$(LIBDIR) - ${ELIOMC} -a -o $@ $(GENERATE_DEBUG) \ - $(call depsort,$(ELIOM_SERVER_DIR),cmo,-server,$(SERVER_INC),$(SERVER_FILES)) - -$(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cmxa: $(call objs,$(ELIOM_SERVER_DIR),cmx,$(SERVER_FILES)) | $(TEST_PREFIX)$(LIBDIR) - ${ELIOMOPT} -a -o $@ $(GENERATE_DEBUG) \ - $(call depsort,$(ELIOM_SERVER_DIR),cmx,-server,$(SERVER_INC),$(SERVER_FILES)) - -%.cmxs: %.cmxa - $(ELIOMOPT) -shared -linkall -o $@ $(GENERATE_DEBUG) $< - -${ELIOM_SERVER_DIR}/%.cmi: %.mli - ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< - -${ELIOM_SERVER_DIR}/%.cmi: %.eliomi - ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< - -${ELIOM_SERVER_DIR}/%.cmo: %.ml - ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< -${ELIOM_SERVER_DIR}/%.cmo: %.eliom - ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< - -${ELIOM_SERVER_DIR}/%.cmx: %.ml - ${ELIOMOPT} -c ${SERVER_INC} $(GENERATE_DEBUG) $< -${ELIOM_SERVER_DIR}/%.cmx: %.eliom - ${ELIOMOPT} -c ${SERVER_INC} $(GENERATE_DEBUG) $< - - -##---------------------------------------------------------------------- -## Client side compilation - -CLIENT_LIBS := ${addprefix -package ,${CLIENT_PACKAGES}} -CLIENT_INC := ${addprefix -package ,${CLIENT_PACKAGES}} - -CLIENT_OBJS := $(filter %.eliom %.ml, $(CLIENT_FILES)) -CLIENT_OBJS := $(patsubst %.eliom,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) -CLIENT_OBJS := $(patsubst %.ml,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) - -$(TEST_PREFIX)$(ELIOMSTATICDIR)/$(PROJECT_NAME).js: $(call objs,$(ELIOM_CLIENT_DIR),cmo,$(CLIENT_FILES)) | $(TEST_PREFIX)$(ELIOMSTATICDIR) - ${JS_OF_ELIOM} -o $@ $(GENERATE_DEBUG) $(CLIENT_INC) $(DEBUG_JS) \ - $(call depsort,$(ELIOM_CLIENT_DIR),cmo,-client,$(CLIENT_INC),$(CLIENT_FILES)) - -$(TEST_PREFIX)$(ELIOMSTATICDIR)/$(PROJECT_NAME)_oclosure.js: $(TEST_PREFIX)$(ELIOMSTATICDIR)/$(PROJECT_NAME).js - oclosure_req $< - -${ELIOM_CLIENT_DIR}/%.cmi: %.mli - ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< - -${ELIOM_CLIENT_DIR}/%.cmo: %.eliom - ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< -${ELIOM_CLIENT_DIR}/%.cmo: %.ml - ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< - -${ELIOM_CLIENT_DIR}/%.cmi: %.eliomi - ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< - -##---------------------------------------------------------------------- -## Dependencies - -include .depend - -.depend: $(patsubst %,$(DEPSDIR)/%.server,$(SERVER_FILES)) $(patsubst %,$(DEPSDIR)/%.client,$(CLIENT_FILES)) - cat $^ > $@ - -$(DEPSDIR)/%.server: % | $(DEPSDIR) - $(ELIOMDEP) -server $(SERVER_INC) $< > $@ - -$(DEPSDIR)/%.client: % | $(DEPSDIR) - $(ELIOMDEP) -client $(CLIENT_INC) $< > $@ - -$(DEPSDIR): - mkdir $@ - -##---------------------------------------------------------------------- -## Clean up - -clean: - -rm -f *.cm[ioax] *.cmxa *.cmxs *.o *.a *.annot - -rm -f *.type_mli - -rm -f ${PROJECT_NAME}.js - -rm -rf ${ELIOM_CLIENT_DIR} ${ELIOM_SERVER_DIR} - -distclean: clean - -rm -rf $(TEST_PREFIX) $(DEPSDIR) .depend diff --git a/7.1/manual/files/tutorial/chapter1/distillery-oclosure/Makefile.options b/7.1/manual/files/tutorial/chapter1/distillery-oclosure/Makefile.options deleted file mode 100644 index 53ea7f5f..00000000 --- a/7.1/manual/files/tutorial/chapter1/distillery-oclosure/Makefile.options +++ /dev/null @@ -1,57 +0,0 @@ - -##---------------------------------------------------------------------- -## SETTINGS FOR THE ELIOM PROJECT graffiti -##---------------------------------------------------------------------- - -PROJECT_NAME := graffiti - -## Do not forget `make depend' after adding a file! -## Source files for the server -SERVER_FILES := $(wildcard *.eliomi *.eliom) -## Source files for the client -CLIENT_FILES := $(wildcard *.eliomi *.eliom) - -## OCamlfind packages for the server -SERVER_PACKAGES := cairo -## OCamlfind packages for the client -CLIENT_PACKAGES := oclosure - -## Directory with files to be statically served -LOCAL_STATIC = static - -## Debug application (yes/no): Debugging info in compilation, -## JavaScript, ocsigenserver -DEBUG := no - -## User to run server with (make run.*) -WWWUSER := www-data -WWWGROUP := www-data - -## Port for running the server (make run.*) -PORT := 80 - -## Port for testing (make test.*) -TEST_PORT := 8080 - -## Root of installation (must end with /) -PREFIX := / - -## Local folder for make test.* (must end with /) -TEST_PREFIX := local/ - -## The installation tree (relative to $(PREFIX) when installing/runnin -## or $(TEST_PREFIX) when testing). -# Configuration file $(PROJECT_NAME).conf -ETCDIR := etc/${PROJECT_NAME} -# Project's library $(PROJECT_NAME).cma (cmxs) -LIBDIR := var/lib/${PROJECT_NAME} -# Command pipe, eg. $ echo restart > $(INSTALL_PREFIX)$(CMDPIPE) -CMDPIPE := var/run/${PROJECT_NAME}-cmd -# Ocsigenserver's logging files -LOGDIR := var/log/${PROJECT_NAME} -# Ocsigenserver's persistent data files -DATADIR := var/data/${PROJECT_NAME} -# Copy of $(LOCAL_STATIC) -STATICDIR := var/www/${PROJECT_NAME}/static -# Project's JavaScript file -ELIOMSTATICDIR := var/www/${PROJECT_NAME}/eliom diff --git a/7.1/manual/files/tutorial/chapter1/distillery-oclosure/README b/7.1/manual/files/tutorial/chapter1/distillery-oclosure/README deleted file mode 100644 index d2f190e5..00000000 --- a/7.1/manual/files/tutorial/chapter1/distillery-oclosure/README +++ /dev/null @@ -1,59 +0,0 @@ - -This project is (initially) generated by eliom-distillery as the basic -project "graffiti". - -It contains the following files: - - - graffiti.eliom - This is your initial source file. - All Eliom files (*.eliom, *.eliomi) in this directory are - automatically considered. To add a .ml/.mli file to your project, - add it to the variable SERVER_FILES or CLIENT_FILES. - - - static/ - The content of this folder is statically served. It contains - initially a basic CSS file for your project. - - - Makefile.options - Configure your project here! - - - graffiti.conf.in - This file is a template for the configuration file for - ocsigenserver. You will rarely have to edit itself - it takes its - variables from the Makefile.options. This way, the installation - rules and the configuration files are synchronized with respect to - the different folders. - - - Makefile - This contains all rules necessary to build, test, and run your - Eliom application. You better don't touch it ;) See below for the - relevant targets. - - - README - Not completely desribable here. - - -Here's some help on how to work with this basic distillery project: - - - Compile it - $ make all (or byte/opt) - - - Test your application by running ocsigenserver locally - $ make test.byte (or test.opt) - - - Install it - $ sudo make install (or install.byte/install.opt) - - - Run the server on the installation - $ sudo make run.byte (or run.opt) - If the user to run ocsigenserver is yourself, you don't need the - `sudo'. If Ocsigen isn't installed in a global position, however, - you may need to re-export some environment variables to make this - work: - $ sudo PATH=$PATH OCAMLPATH=$OCAMLPATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH make run.byte/run.opt - - - If you need a findlib package in your project, add it to the - variables SERVER_PACKAGES and/or CLIENT_PACKAGES. - - - The directory static/ will be statically served. Put your CSS and - additional JavaScript files here! diff --git a/7.1/manual/files/tutorial/chapter1/distillery-oclosure/graffiti.conf.in b/7.1/manual/files/tutorial/chapter1/distillery-oclosure/graffiti.conf.in deleted file mode 100644 index 82244ac9..00000000 --- a/7.1/manual/files/tutorial/chapter1/distillery-oclosure/graffiti.conf.in +++ /dev/null @@ -1,28 +0,0 @@ -%%% This is the template for your configuration file. The %%VALUES%% below are -%%% taken from the Makefile to generate the actual configuration files. -%%% This comment will disappear. - - - - %%PORT%% - %%% Only set for running, not for testing - %%USERGROUP%% - %%LOGDIR%% - %%DATADIR%% - utf-8 - %%% Only set when debugging - %%DEBUGMODE%% - %%CMDPIPE%% - - - - %%% This will include the packages defined as SERVER_PACKAGES in your Makefile: - %%PACKAGES%% - - - - - - - - diff --git a/7.1/manual/files/tutorial/chapter1/distillery-oclosure/graffiti.eliom b/7.1/manual/files/tutorial/chapter1/distillery-oclosure/graffiti.eliom deleted file mode 100644 index fa2bc8cc..00000000 --- a/7.1/manual/files/tutorial/chapter1/distillery-oclosure/graffiti.eliom +++ /dev/null @@ -1,163 +0,0 @@ - - -{shared{ - open Eliom_lib.Lwt_ops - open Eliom_content.Html5.D - - let width = 700 - let height = 400 - - type messages = (string * int * (int * int) * (int * int)) - deriving (Json) -}} - -module My_app = - Eliom_registration.App ( - struct - let application_name = "graffiti" - end) - -let bus = Eliom_bus.create Json.t - -let canvas_elt = - canvas ~a:[a_width width; a_height height] - [pcdata "your browser doesn't support canvas"] - -let page = - html - (Eliom_tools.F.head ~title:"Graffiti" - ~css:[ - ["css";"common.css"]; - ["css";"hsvpalette.css"]; - ["css";"slider.css"]; - ["css";"graffiti.css"]; - ["graffiti_oclosure.js"]; - ] - ~js:[ ["graffiti_oclosure.js"] ] ()) - (body [h1 [pcdata "Graffiti"]; canvas_elt]) - - -let rgb_from_string color = (* color is in format "#rrggbb" *) - let get_color i = - (float_of_string ("0x"^(String.sub color (1+2*i) 2))) /. 255. - in - try get_color 0, get_color 1, get_color 2 with | _ -> 0.,0.,0. - -let draw_server, image_string = - let surface = - Cairo.image_surface_create Cairo.FORMAT_ARGB32 ~width ~height - in - let ctx = Cairo.create surface in - ((fun ((color : string), size, (x1, y1), (x2, y2)) -> - - (* Set thickness of brush *) - Cairo.set_line_width ctx (float size) ; - Cairo.set_line_join ctx Cairo.LINE_JOIN_ROUND ; - Cairo.set_line_cap ctx Cairo.LINE_CAP_ROUND ; - let red, green, blue = rgb_from_string color 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.close_path ctx ; - - (* Apply the ink *) - Cairo.stroke ctx ; - ), - (fun () -> - let b = Buffer.create 10000 in - (* Output a PNG in a string *) - Cairo_png.surface_write_to_stream surface (Buffer.add_string b); - Buffer.contents b - )) - -let imageservice = - Eliom_registration.String.register_service - ~path:["image"] - ~get_params:Eliom_parameter.unit - (fun () () -> Lwt.return (image_string (), "image/png")) - -{client{ - - let draw ctx (color, size, (x1, y1), (x2, y2)) = - ctx##strokeStyle <- (Js.string color); - ctx##lineWidth <- float size; - ctx##beginPath(); - ctx##moveTo(float x1, float y1); - ctx##lineTo(float x2, float y2); - ctx##stroke() - - 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"; - - (* The initial image: *) - let img = - Eliom_content.Html5.To_dom.of_img - (img ~alt:"canvas" - ~src:(make_uri ~service:%imageservice ()) - ()) - in - img##onload <- Dom_html.handler - (fun ev -> ctx##drawImage(img, 0., 0.); Js._false); - - (* Size of the brush *) - let slider = jsnew Goog.Ui.slider(Js.null) in - slider##setMinimum(1.); - slider##setMaximum(80.); - slider##setValue(10.); - slider##setMoveToPointEnabled(Js._true); - slider##render(Js.some Dom_html.document##body); - (* The color palette: *) - let pSmall = - jsnew Goog.Ui.hsvPalette(Js.null, Js.null, - Js.some (Js.string "goog-hsv-palette-sm")) - in - pSmall##render(Js.some Dom_html.document##body); - - 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 - in - - let compute_line ev = - let oldx = !x and oldy = !y in - set_coord ev; - let color = Js.to_string (pSmall##getColor()) in - let size = int_of_float (Js.to_float (slider##getValue())) in - (color, size, (oldx, oldy), (!x, !y)) - in - - let line ev = - let v = compute_line ev in - let _ = Eliom_bus.write %bus v in - draw ctx v; - Lwt.return () in - - Lwt.async - (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])); - - Lwt.async (fun () -> Lwt_stream.iter (draw ctx) (Eliom_bus.stream %bus)); - - -}} - -let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) - -let main_service = - My_app.register_service ~path:[""] ~get_params:Eliom_parameter.unit - (fun () () -> - (* Cf. the box "Client side side-effects on the server" *) - ignore {unit{ init_client () }}; - Lwt.return page) diff --git a/7.1/manual/files/tutorial/chapter1/distillery-oclosure/update-symlinks.sh b/7.1/manual/files/tutorial/chapter1/distillery-oclosure/update-symlinks.sh deleted file mode 100755 index f49ae5cf..00000000 --- a/7.1/manual/files/tutorial/chapter1/distillery-oclosure/update-symlinks.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -ln -s ../../static . - diff --git a/7.1/manual/files/tutorial/chapter1/no_oclosure/.depend b/7.1/manual/files/tutorial/chapter1/no_oclosure/.depend deleted file mode 100644 index c5555cda..00000000 --- a/7.1/manual/files/tutorial/chapter1/no_oclosure/.depend +++ /dev/null @@ -1,8 +0,0 @@ -_server/graffiti.cmo : -_server/graffiti.cmx : -_server/graffiti.cmo : graffiti.type_mli -_server/graffiti.cmx : graffiti.type_mli -_client/graffiti.cmo : -_client/graffiti.cmx : -_client/graffiti.cmo : graffiti.type_mli -_client/graffiti.cmx : graffiti.type_mli diff --git a/7.1/manual/files/tutorial/chapter1/no_oclosure/Makefile b/7.1/manual/files/tutorial/chapter1/no_oclosure/Makefile deleted file mode 100644 index ada2060e..00000000 --- a/7.1/manual/files/tutorial/chapter1/no_oclosure/Makefile +++ /dev/null @@ -1,133 +0,0 @@ - -## Sample Makefile for eliom application. - -APP_NAME := graffiti - -## Packages required to build the server part of the application - -SERVER_PACKAGES := cairo - -## Packages to be linked in the client part - -CLIENT_PACKAGES := - -## Source files for the server part - -SERVER_FILES := ${wildcard *.eliom} - -## Source files for the client part - -CLIENT_FILES := ${wildcard *.eliom} - -## Required binaries - -ELIOMC := eliomc -ELIOMOPT := eliomopt -ELIOMDEP := eliomdep -JS_OF_ELIOM := js_of_eliom - -## Where to put intermediate object files. -## - ELIOM_{SERVER,CLIENT}_DIR must be distinct -## - ELIOM_CLIENT_DIR mustn't be the local dir. -## - ELIOM_SERVER_DIR could be ".", but you need to -## remove it from the "clean" rules... - -export ELIOM_SERVER_DIR := _server -export ELIOM_CLIENT_DIR := _client -export ELIOM_TYPE_DIR := . - -##################################### - -all: byte opt -byte:: ${APP_NAME}.cma ${APP_NAME}.js -opt:: ${APP_NAME}.cmxs ${APP_NAME}.js - -#### Server side compilation ####### - -SERVER_INC := ${addprefix -package ,${SERVER_PACKAGES}} - -SERVER_OBJS := $(patsubst %.eliom,${ELIOM_SERVER_DIR}/%.cmo, ${SERVER_FILES}) -SERVER_OBJS := $(patsubst %.ml,${ELIOM_SERVER_DIR}/%.cmo, ${SERVER_OBJS}) - -${APP_NAME}.cma: ${SERVER_OBJS} - ${ELIOMC} -a -o $@ $^ -${APP_NAME}.cmxa: ${SERVER_OBJS:.cmo=.cmx} - ${ELIOMOPT} -a -o $@ $^ - -${ELIOM_TYPE_DIR}/%.type_mli: %.eliom - ${ELIOMC} -infer ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmi: %.mli - ${ELIOMC} -c ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmo: %.ml - ${ELIOMC} -c ${SERVER_INC} $< -${ELIOM_SERVER_DIR}/%.cmo: %.eliom - ${ELIOMC} -c ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmx: %.ml - ${ELIOMOPT} -c ${SERVER_INC} $< -${ELIOM_SERVER_DIR}/%.cmx: %.eliom - ${ELIOMOPT} -c ${SERVER_INC} $< - -%.cmxs: %.cmxa - $(ELIOMOPT) -shared -linkall -o $@ $< - -##### Client side compilation #### - -SERVER_INC_FOR_JS_OF_ELIOM := ${addprefix -package ,${SERVER_PACKAGES}} - -CLIENT_LIBS := ${addprefix -package ,${CLIENT_PACKAGES}} -CLIENT_INC := ${addprefix -package ,${CLIENT_PACKAGES}} - -CLIENT_OBJS := $(patsubst %.eliom,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_FILES}) -CLIENT_OBJS := $(patsubst %.ml,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) - -${APP_NAME}.js: ${CLIENT_OBJS} - ${JS_OF_ELIOM} -o $@ ${CLIENT_LIBS} $^ - -${ELIOM_CLIENT_DIR}/%.cmi: %.mli - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< - -${ELIOM_CLIENT_DIR}/%.cmo: %.eliom - ${JS_OF_ELIOM} -c ${SERVER_INC_FOR_JS_OF_ELIOM} ${CLIENT_INC} $< -${ELIOM_CLIENT_DIR}/%.cmo: %.ml - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< - -############ - -## Clean up - -clean: - -rm -f *.cm[ioax] *.cmxa *.cmxs *.o *.a *.annot - -rm -f *.type_mli - -rm -f ${APP_NAME}.js - -rm -rf ${ELIOM_CLIENT_DIR} ${ELIOM_SERVER_DIR} - -distclean: clean.local - -rm -f *~ \#* .\#* - -## Dependencies - -depend: .depend -.depend: ${SERVER_FILES} ${CLIENT_FILES} - $(ELIOMDEP) -server ${SERVER_INC} ${SERVER_FILES} > .depend - $(ELIOMDEP) -client ${CLIENT_INC} ${CLIENT_FILES} >> .depend - -## Warning: Dependencies towards *.eliom are not handled by eliomdep yet. -## Add manually dependencies between cmo and cmx files here, -## for example: -## oneeliomfile.cmo: anothereliomfile.cmo -## oneeliomfile.cmx: anothereliomfile.cmx - --include .depend - -## installation ######### - -STATICDIR := /tmp/static - -$(STATICDIR): - mkdir -p $@ - -install: all $(STATICDIR) - cp $(APP_NAME).js $(STATICDIR)/$(APP_NAME).js diff --git a/7.1/manual/files/tutorial/chapter1/no_oclosure/graffiti.conf b/7.1/manual/files/tutorial/chapter1/no_oclosure/graffiti.conf deleted file mode 100644 index 5e4ddfd8..00000000 --- a/7.1/manual/files/tutorial/chapter1/no_oclosure/graffiti.conf +++ /dev/null @@ -1,38 +0,0 @@ - - - - - 8080 - - - - /tmp - /tmp - - - /tmp/ocsigen_command - - utf-8 - - - - - - - - - - - - - - - - - - - - - - - diff --git a/7.1/manual/files/tutorial/chapter1/no_oclosure/graffiti.eliom b/7.1/manual/files/tutorial/chapter1/no_oclosure/graffiti.eliom deleted file mode 100644 index 967438c7..00000000 --- a/7.1/manual/files/tutorial/chapter1/no_oclosure/graffiti.eliom +++ /dev/null @@ -1,124 +0,0 @@ -{shared{ - open Eliom_content - open Eliom_content.Html5.D - open Eliom_lib.Lwt_ops - let width = 700 - let height = 400 -}} - -module My_appl = - Eliom_registration.App ( - struct - let application_name = "graffiti" - end) - -{client{ - let draw ctx (color, size, (x1, y1), (x2, y2)) = - ctx##strokeStyle <- (Js.string color); - ctx##lineWidth <- float size; - ctx##beginPath(); - ctx##moveTo(float x1, float y1); - ctx##lineTo(float x2, float y2); - ctx##stroke() -}} - -{shared{ - type messages = (string * int * (int * int) * (int * int)) deriving (Json) -}} - -let bus = Eliom_bus.create ~name:"graff" Json.t - -let rgb_from_string color = (* color is in format "#rrggbb" *) - let get_color i = (float_of_string ("0x"^(String.sub color (1+2*i) 2))) /. 255. in - try get_color 0, get_color 1, get_color 2 with | _ -> 0.,0.,0. - -let draw_server, image_string = - let surface = Cairo.image_surface_create Cairo.FORMAT_ARGB32 ~width ~height in - let ctx = Cairo.create surface in - ((fun ((color : string), size, (x1, y1), (x2, y2)) -> - - (* Set thickness of brush *) - Cairo.set_line_width ctx (float size) ; - Cairo.set_line_join ctx Cairo.LINE_JOIN_ROUND ; - Cairo.set_line_cap ctx Cairo.LINE_CAP_ROUND ; - let red, green, blue = rgb_from_string color 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.close_path ctx ; - - (* Apply the ink *) - Cairo.stroke ctx ; - ), - (fun () -> - let b = Buffer.create 10000 in - (* Output a PNG in a string *) - Cairo_png.surface_write_to_stream surface (Buffer.add_string b); - Buffer.contents b - )) - -let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) - -let imageservice = - Eliom_registration.String.register_service - ~path:["image"] - ~get_params:Eliom_parameter.unit - (fun () () -> Lwt.return (image_string (), "image/png")) - -let page = - html - (head (title (pcdata "Graffiti")) []) - (body [h1 [pcdata "Graffiti"]]) - -let init_client () = ignore {unit{ - let canvas = Dom_html.createCanvas Dom_html.document in - let ctx = canvas##getContext (Dom_html._2d_) in - canvas##width <- width; canvas##height <- height; - ctx##lineCap <- Js.string "round"; - - Dom.appendChild Dom_html.document##body canvas; - - (* The initial image: *) - let img = - Html5.To_dom.of_img - (img ~alt:"canvas" - ~src:(make_uri ~service:%imageservice ()) - ()) - in - img##onload <- Dom_html.handler - (fun ev -> ctx##drawImage(img, 0., 0.); Js._false); - - 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 in - - let compute_line ev = - let oldx = !x and oldy = !y in - set_coord ev; - ("#ff9933", 5, (oldx, oldy), (!x, !y)) - in - - let line ev = - let v = compute_line ev in - let _ = Eliom_bus.write %bus v in - draw ctx v; - Lwt.return () - in - - let _ = Lwt_stream.iter (draw ctx) (Eliom_bus.stream %bus) in - - let open Lwt_js_events in - ignore (mousedowns canvas - (fun ev _ -> set_coord ev; line ev >>= fun () -> - Lwt.pick [mousemoves Dom_html.document (fun a _ -> line a); - mouseup Dom_html.document >>= line])); -}} - -let main_service = - My_appl.register_service ~path:[""] ~get_params:Eliom_parameter.unit - (fun () () -> - init_client (); - Lwt.return page) diff --git a/7.1/manual/files/tutorial/chapter1/start/Makefile b/7.1/manual/files/tutorial/chapter1/start/Makefile deleted file mode 100644 index 4b16f914..00000000 --- a/7.1/manual/files/tutorial/chapter1/start/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -graffiti.cmo: graffiti.ml - ocamlfind ocamlc -thread -package eliom.server -c graffiti.ml diff --git a/7.1/manual/files/tutorial/chapter1/start/graffiti.conf b/7.1/manual/files/tutorial/chapter1/start/graffiti.conf deleted file mode 100644 index 82b016de..00000000 --- a/7.1/manual/files/tutorial/chapter1/start/graffiti.conf +++ /dev/null @@ -1,29 +0,0 @@ - - - - - 8080 - - - - /tmp - /tmp - - - /tmp/ocsigen_command - - - - - - - - - - - - - - - - diff --git a/7.1/manual/files/tutorial/chapter1/start/graffiti.ml b/7.1/manual/files/tutorial/chapter1/start/graffiti.ml deleted file mode 100644 index 3c597168..00000000 --- a/7.1/manual/files/tutorial/chapter1/start/graffiti.ml +++ /dev/null @@ -1,10 +0,0 @@ -open Lwt -open Eliom_parameter -open Eliom_content.Html5.D -open Eliom_registration.Html5 - -let main_service = - register_service ~path:["graff"] ~get_params:unit - (fun () () -> return (html (head (title (pcdata "")) []) - (body [h1 [pcdata "Graffiti"]]))) - diff --git a/7.1/manual/files/tutorial/chapter1/with_oclosure/.depend b/7.1/manual/files/tutorial/chapter1/with_oclosure/.depend deleted file mode 100644 index 5736884c..00000000 --- a/7.1/manual/files/tutorial/chapter1/with_oclosure/.depend +++ /dev/null @@ -1,8 +0,0 @@ -_server/graffiti.cmo: _server/graffiti.type_mli -_server/graffiti.cmx: _server/graffiti.type_mli -_server/graffiti.cmo: -_server/graffiti.cmx: -_client/graffiti.cmo: _server/graffiti.type_mli -_client/graffiti.cmx: _server/graffiti.type_mli -_client/graffiti.cmo: -_client/graffiti.cmx: diff --git a/7.1/manual/files/tutorial/chapter1/with_oclosure/Makefile b/7.1/manual/files/tutorial/chapter1/with_oclosure/Makefile deleted file mode 100644 index e9067111..00000000 --- a/7.1/manual/files/tutorial/chapter1/with_oclosure/Makefile +++ /dev/null @@ -1,144 +0,0 @@ - -## Sample Makefile for eliom application. - -APP_NAME := graffiti - -## Packages required to build the server part of the application - -SERVER_PACKAGES := cairo - -## Packages to be linked in the client part - -CLIENT_PACKAGES := oclosure - -## Source files for the server part - -SERVER_FILES := ${wildcard *.eliom} - -## Source files for the client part - -CLIENT_FILES := ${wildcard *.eliom} - -## Required binaries - -ELIOMC := eliomc -ELIOMOPT := eliomopt -ELIOMDEP := eliomdep -JS_OF_ELIOM := js_of_eliom -OCLOSURE_REQ := oclosure_req - -## Where to put intermediate object files. -## - ELIOM_{SERVER,CLIENT}_DIR must be distinct -## - ELIOM_CLIENT_DIR mustn't be the local dir. -## - ELIOM_SERVER_DIR could be ".", but you need to -## remove it from the "clean" rules... - -export ELIOM_SERVER_DIR := _server -export ELIOM_CLIENT_DIR := _client -export ELIOM_TYPE_DIR := ${ELIOM_SERVER_DIR} - -##################################### - -all: byte opt -byte:: ${APP_NAME}.cma ${APP_NAME}.js ${APP_NAME}_oclosure.js -opt:: ${APP_NAME}.cmxs ${APP_NAME}.js ${APP_NAME}_oclosure.js - -#### Server side compilation ####### - -SERVER_INC := ${addprefix -package ,${SERVER_PACKAGES}} - -SERVER_OBJS := $(patsubst %.eliom,${ELIOM_SERVER_DIR}/%.cmo, ${SERVER_FILES}) -SERVER_OBJS := $(patsubst %.ml,${ELIOM_SERVER_DIR}/%.cmo, ${SERVER_OBJS}) - -${APP_NAME}.cma: ${SERVER_OBJS} - ${ELIOMC} -a -o $@ $^ -${APP_NAME}.cmxa: ${SERVER_OBJS:.cmo=.cmx} - ${ELIOMOPT} -a -o $@ $^ - -${ELIOM_TYPE_DIR}/%.type_mli: %.eliom - ${ELIOMC} -infer ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmi: %.mli - ${ELIOMC} -c ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmo: %.ml - ${ELIOMC} -c ${SERVER_INC} $< -${ELIOM_SERVER_DIR}/%.cmo: %.eliom - ${ELIOMC} -c -noinfer ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmx: %.ml - ${ELIOMOPT} -c ${SERVER_INC} $< -${ELIOM_SERVER_DIR}/%.cmx: %.eliom - ${ELIOMOPT} -c -noinfer ${SERVER_INC} $< - -%.cmxs: %.cmxa - $(ELIOMOPT) -shared -linkall -o $@ $< - -##### Client side compilation #### - -CLIENT_LIBS := ${addprefix -package ,${CLIENT_PACKAGES}} -CLIENT_INC := ${addprefix -package ,${CLIENT_PACKAGES}} - -CLIENT_OBJS := $(patsubst %.eliom,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_FILES}) -CLIENT_OBJS := $(patsubst %.ml,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) - -${APP_NAME}.js: ${CLIENT_OBJS} - ${JS_OF_ELIOM} -o $@ ${CLIENT_LIBS} $^ - -${ELIOM_CLIENT_DIR}/%.cmi: %.mli - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< - -${ELIOM_CLIENT_DIR}/%.cmo: %.eliom - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< -${ELIOM_CLIENT_DIR}/%.cmo: %.ml - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< - -############ - -## Clean up - -clean: - -rm -f *.cm[ioax] *.cmxa *.cmxs *.o *.a *.annot - -rm -f *.type_mli - -rm -f ${APP_NAME}.js - -rm -rf ${ELIOM_CLIENT_DIR} ${ELIOM_SERVER_DIR} - -rm -f $(APP_NAME)_oclosure.js - -distclean: clean - -rm -f *~ \#* .\#* - -rm -f .depend - -## Dependencies - -depend: .depend -.depend: ${SERVER_FILES} ${CLIENT_FILES} - $(ELIOMDEP) -server ${SERVER_INC} ${SERVER_FILES} > .depend - $(ELIOMDEP) -client ${CLIENT_INC} ${CLIENT_FILES} >> .depend - -## Warning: Dependencies towards *.eliom are not handled by eliomdep yet. -## Add manually dependencies between cmo and cmx files here, -## for example: -## oneeliomfile.cmo: anothereliomfile.cmo -## oneeliomfile.cmx: anothereliomfile.cmx - -include .depend - -## installation ######### - -STATICDIR := /tmp/static - -$(STATICDIR): - mkdir -p $@ - -install: all $(STATICDIR) - cp $(APP_NAME)_oclosure.js $(STATICDIR)/$(APP_NAME)_oclosure.js - cp $(APP_NAME).js $(STATICDIR)/$(APP_NAME).js - -## oclosure ############# - -OCAML := ocaml -OCAMLFIND := ocamlfind -OCLOSUREDIR := $(shell ${OCAMLFIND} query oclosure) - -$(APP_NAME)_oclosure.js: $(APP_NAME).js - ${OCLOSURE_REQ} $^ diff --git a/7.1/manual/files/tutorial/chapter1/with_oclosure/graffiti.conf b/7.1/manual/files/tutorial/chapter1/with_oclosure/graffiti.conf deleted file mode 100644 index 5e4ddfd8..00000000 --- a/7.1/manual/files/tutorial/chapter1/with_oclosure/graffiti.conf +++ /dev/null @@ -1,38 +0,0 @@ - - - - - 8080 - - - - /tmp - /tmp - - - /tmp/ocsigen_command - - utf-8 - - - - - - - - - - - - - - - - - - - - - - - diff --git a/7.1/manual/files/tutorial/chapter1/with_oclosure/graffiti.eliom b/7.1/manual/files/tutorial/chapter1/with_oclosure/graffiti.eliom deleted file mode 100644 index 442f125b..00000000 --- a/7.1/manual/files/tutorial/chapter1/with_oclosure/graffiti.eliom +++ /dev/null @@ -1,163 +0,0 @@ -{shared{ - open Eliom_content - open Eliom_content.Html5.D - open Eliom_lib.Lwt_ops - let width = 700 - let height = 400 -}} - -module My_appl = - Eliom_registration.App ( - struct - let application_name = "graffiti" - end) - -{client{ - let draw ctx (color, size, (x1, y1), (x2, y2)) = - ctx##strokeStyle <- (Js.string color); - ctx##lineWidth <- float size; - ctx##beginPath(); - ctx##moveTo(float x1, float y1); - ctx##lineTo(float x2, float y2); - ctx##stroke() -}} - -{shared{ - type messages = (string * int * (int * int) * (int * int)) deriving (Json) -}} - -let bus = Eliom_bus.create ~name:"graff" Json.t - -let rgb_from_string color = (* color is in format "#rrggbb" *) - let get_color i = (float_of_string ("0x"^(String.sub color (1+2*i) 2))) /. 255. in - try get_color 0, get_color 1, get_color 2 with | _ -> 0.,0.,0. - -let draw_server, image_string = - let surface = Cairo.image_surface_create Cairo.FORMAT_ARGB32 ~width ~height in - let ctx = Cairo.create surface in - ((fun ((color : string), size, (x1, y1), (x2, y2)) -> - - (* Set thickness of brush *) - Cairo.set_line_width ctx (float size) ; - Cairo.set_line_join ctx Cairo.LINE_JOIN_ROUND ; - Cairo.set_line_cap ctx Cairo.LINE_CAP_ROUND ; - let red, green, blue = rgb_from_string color 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.close_path ctx ; - - (* Apply the ink *) - Cairo.stroke ctx ; - ), - (fun () -> - let b = Buffer.create 10000 in - (* Output a PNG in a string *) - Cairo_png.surface_write_to_stream surface (Buffer.add_string b); - Buffer.contents b - )) - -let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) - -let imageservice = - Eliom_registration.String.register_service - ~path:["image"] - ~get_params:Eliom_parameter.unit - (fun () () -> Lwt.return (image_string (), "image/png")) - -let canvas_elt = - canvas ~a:[a_width width; a_height height] - [pcdata "your browser doesn't support canvas"] - -let page = - html - (head - (title (pcdata "Graffiti")) - [ css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"common.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"hsvpalette.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"slider.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"graffiti.css"]) (); - js_script - ~uri:(make_uri (Eliom_service.static_dir ()) - ["graffiti_oclosure.js"]) (); - ]) - (body [h1 [pcdata "Graffiti"]; canvas_elt]) - -let init_client () = ignore {unit{ - - (* Initialize the canvas *) - let canvas = Html5.To_dom.of_canvas %canvas_elt in - let ctx = canvas##getContext (Dom_html._2d_) in - ctx##lineCap <- Js.string "round"; - - (* The initial image: *) - let img = - Html5.To_dom.of_img - (img ~alt:"canvas" - ~src:(make_uri ~service:%imageservice ()) - ()) - in - img##onload <- Dom_html.handler - (fun _ -> ctx##drawImage(img, 0., 0.); Js._false); - - (* Size of the brush *) - let slider = jsnew Goog.Ui.slider(Js.null) in - slider##setOrientation(Goog.Ui.SliderBase.Orientation._VERTICAL); - slider##setMinimum(1.); - slider##setMaximum(80.); - slider##setValue(10.); - slider##setMoveToPointEnabled(Js._true); - slider##render(Js.some Dom_html.document##body); - - (* The color palette: *) - let pSmall = - jsnew Goog.Ui.hsvPalette(Js.null, Js.null, - Js.some (Js.string "goog-hsv-palette-sm")) - in - pSmall##render(Js.some Dom_html.document##body); - - 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 in - - let compute_line ev = - let oldx = !x and oldy = !y in - set_coord ev; - let color = Js.to_string (pSmall##getColor()) in - let size = int_of_float (Js.to_float (slider##getValue())) in - (color, size, (oldx, oldy), (!x, !y)) - in - - let line ev = - let v = compute_line ev in - let _ = Eliom_bus.write %bus v in - draw ctx v; - Lwt.return () - in - - let _ = Lwt_stream.iter (draw ctx) (Eliom_bus.stream %bus) in - - let open Lwt_js_events in - ignore (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])); -}} - -let main_service = - My_appl.register_service ~path:[""] ~get_params:Eliom_parameter.unit - (fun () () -> - init_client (); - Lwt.return page) - diff --git a/7.1/manual/files/tutorial/chapter2/Makefile b/7.1/manual/files/tutorial/chapter2/Makefile deleted file mode 100644 index 8c47d398..00000000 --- a/7.1/manual/files/tutorial/chapter2/Makefile +++ /dev/null @@ -1,240 +0,0 @@ - -##---------------------------------------------------------------------- -## DISCLAIMER -## -## This file contains the rules to make an Eliom project. The project is -## configured through the variables in the file Makefile.options. -##---------------------------------------------------------------------- - -include Makefile.options - -##---------------------------------------------------------------------- -## Internals - -## Required binaries -ELIOMC := eliomc -ppx -ELIOMOPT := eliomopt -ppx -JS_OF_ELIOM := js_of_eliom -ppx -ELIOMDEP := eliomdep -OCSIGENSERVER := ocsigenserver -OCSIGENSERVER.OPT := ocsigenserver.opt - -## Where to put intermediate object files. -## - ELIOM_{SERVER,CLIENT}_DIR must be distinct -## - ELIOM_CLIENT_DIR must not be the local dir. -## - ELIOM_SERVER_DIR could be ".", but you need to -## remove it from the "clean" rules... -export ELIOM_SERVER_DIR := _server -export ELIOM_CLIENT_DIR := _client -export ELIOM_TYPE_DIR := _server -DEPSDIR := _deps - -ifeq ($(DEBUG),yes) - GENERATE_DEBUG ?= -g - RUN_DEBUG ?= "-v" - DEBUG_JS ?= -jsopt -pretty -jsopt -noinline -jsopt -debuginfo -endif - -##---------------------------------------------------------------------- -## General - -.PHONY: all byte opt -all: byte opt -byte opt:: $(TEST_PREFIX)$(ELIOMSTATICDIR)/${PROJECT_NAME}.js -byte opt:: $(TEST_PREFIX)$(ETCDIR)/$(PROJECT_NAME).conf -byte opt:: $(TEST_PREFIX)$(ETCDIR)/$(PROJECT_NAME)-test.conf -byte:: $(TEST_PREFIX)$(LIBDIR)/${PROJECT_NAME}.cma -opt:: $(TEST_PREFIX)$(LIBDIR)/${PROJECT_NAME}.cmxs - -DIST_DIRS = $(ETCDIR) $(DATADIR) $(LIBDIR) $(LOGDIR) $(STATICDIR) $(ELIOMSTATICDIR) $(shell dirname $(CMDPIPE)) - -##---------------------------------------------------------------------- -## Testing - -DIST_FILES = $(ELIOMSTATICDIR)/$(PROJECT_NAME).js $(LIBDIR)/$(PROJECT_NAME).cma - -.PHONY: test.byte test.opt -test.byte: $(addprefix $(TEST_PREFIX),$(ETCDIR)/$(PROJECT_NAME)-test.conf $(DIST_DIRS) $(DIST_FILES)) - $(OCSIGENSERVER) $(RUN_DEBUG) -c $< -test.opt: $(addprefix $(TEST_PREFIX),$(ETCDIR)/$(PROJECT_NAME)-test.conf $(DIST_DIRS) $(patsubst %.cma,%.cmxs, $(DIST_FILES))) - $(OCSIGENSERVER.OPT) $(RUN_DEBUG) -c $< - -$(addprefix $(TEST_PREFIX), $(DIST_DIRS)): - mkdir -p $@ - -##---------------------------------------------------------------------- -## Installing & Running - -.PHONY: install install.byte install.byte install.opt install.static install.etc install.lib install.lib.byte install.lib.opt run.byte run.opt -install: install.byte install.opt -install.byte: install.lib.byte install.etc install.static | $(addprefix $(PREFIX),$(DATADIR) $(LOGDIR) $(shell dirname $(CMDPIPE))) -install.opt: install.lib.opt install.etc install.static | $(addprefix $(PREFIX),$(DATADIR) $(LOGDIR) $(shell dirname $(CMDPIPE))) -install.lib: install.lib.byte install.lib.opt -install.lib.byte: $(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cma | $(PREFIX)$(LIBDIR) - install $< $(PREFIX)$(LIBDIR) -install.lib.opt: $(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cmxs | $(PREFIX)$(LIBDIR) - install $< $(PREFIX)$(LIBDIR) -install.static: $(TEST_PREFIX)$(ELIOMSTATICDIR)/$(PROJECT_NAME).js | $(PREFIX)$(STATICDIR) $(PREFIX)$(ELIOMSTATICDIR) - cp -r $(LOCAL_STATIC)/* $(PREFIX)$(STATICDIR) - [ -z $(WWWUSER) ] || chown -R $(WWWUSER) $(PREFIX)$(STATICDIR) - install $(addprefix -o ,$(WWWUSER)) $< $(PREFIX)$(ELIOMSTATICDIR) -install.etc: $(TEST_PREFIX)$(ETCDIR)/$(PROJECT_NAME).conf | $(PREFIX)$(ETCDIR) - install $< $(PREFIX)$(ETCDIR)/$(PROJECT_NAME).conf - -.PHONY: -print-install-files: - @echo $(PREFIX)$(LIBDIR) - @echo $(PREFIX)$(STATICDIR) - @echo $(PREFIX)$(ELIOMSTATICDIR) - @echo $(PREFIX)$(ETCDIR) - -$(addprefix $(PREFIX),$(ETCDIR) $(LIBDIR)): - install -d $@ -$(addprefix $(PREFIX),$(DATADIR) $(LOGDIR) $(STATICDIR) $(ELIOMSTATICDIR) $(shell dirname $(CMDPIPE))): - install $(addprefix -o ,$(WWWUSER)) -d $@ - -run.byte: - $(OCSIGENSERVER) $(RUN_DEBUG) -c ${PREFIX}${ETCDIR}/${PROJECT_NAME}.conf -run.opt: - $(OCSIGENSERVER.OPT) $(RUN_DEBUG) -c ${PREFIX}${ETCDIR}/${PROJECT_NAME}.conf - -##---------------------------------------------------------------------- -## Aux - -# Use `eliomdep -sort' only in OCaml>4 -ifeq ($(shell ocamlc -version|cut -c1),4) -eliomdep=$(shell $(ELIOMDEP) $(1) -ppx -sort $(2) $(filter %.eliom %.ml,$(3)))) -else -eliomdep=$(3) -endif -objs=$(patsubst %.ml,$(1)/%.$(2),$(patsubst %.eliom,$(1)/%.$(2),$(filter %.eliom %.ml,$(3)))) -depsort=$(call objs,$(1),$(2),$(call eliomdep,$(3),$(4),$(5))) - -##---------------------------------------------------------------------- -## Config files - -FINDLIB_PACKAGES=$(patsubst %,\,$(SERVER_PACKAGES)) -EDIT_WARNING=DON\'T EDIT THIS FILE! It is generated from $(PROJECT_NAME).conf.in, edit that one, or the variables in Makefile.options -SED_ARGS := -e "/^ *%%%/d" -SED_ARGS += -e "s|%%PROJECT_NAME%%|$(PROJECT_NAME)|g" -SED_ARGS += -e "s|%%DATABASE_NAME%%|$(DATABASE_NAME)|g" -SED_ARGS += -e "s|%%DATABASE_USER%%|$(DATABASE_USER)|g" -SED_ARGS += -e "s|%%CMDPIPE%%|%%PREFIX%%$(CMDPIPE)|g" -SED_ARGS += -e "s|%%LOGDIR%%|%%PREFIX%%$(LOGDIR)|g" -SED_ARGS += -e "s|%%DATADIR%%|%%PREFIX%%$(DATADIR)|g" -SED_ARGS += -e "s|%%PERSISTENT_DATA_BACKEND%%|$(PERSISTENT_DATA_BACKEND)|g" -SED_ARGS += -e "s|%%LIBDIR%%|%%PREFIX%%$(LIBDIR)|g" -SED_ARGS += -e "s|%%WARNING%%|$(EDIT_WARNING)|g" -SED_ARGS += -e "s|%%PACKAGES%%|$(FINDLIB_PACKAGES)|g" -SED_ARGS += -e "s|%%ELIOMSTATICDIR%%|%%PREFIX%%$(ELIOMSTATICDIR)|g" -ifeq ($(DEBUG),yes) - SED_ARGS += -e "s|%%DEBUGMODE%%|\|g" -else - SED_ARGS += -e "s|%%DEBUGMODE%%||g" -endif - -LOCAL_SED_ARGS := -e "s|%%PORT%%|$(TEST_PORT)|g" -LOCAL_SED_ARGS += -e "s|%%STATICDIR%%|$(LOCAL_STATIC)|g" -LOCAL_SED_ARGS += -e "s|%%USERGROUP%%||g" -GLOBAL_SED_ARGS := -e "s|%%PORT%%|$(PORT)|g" -GLOBAL_SED_ARGS += -e "s|%%STATICDIR%%|%%PREFIX%%$(STATICDIR)|g" -ifeq ($(WWWUSER)$(WWWGROUP),) - GLOBAL_SED_ARGS += -e "s|%%USERGROUP%%||g" -else - GLOBAL_SED_ARGS += -e "s|%%USERGROUP%%|$(WWWUSER)$(WWWGROUP)|g" -endif - -$(TEST_PREFIX)${ETCDIR}/${PROJECT_NAME}.conf: ${PROJECT_NAME}.conf.in Makefile.options | $(TEST_PREFIX)$(ETCDIR) - sed $(SED_ARGS) $(GLOBAL_SED_ARGS) $< | sed -e "s|%%PREFIX%%|$(PREFIX)|g" > $@ -$(TEST_PREFIX)${ETCDIR}/${PROJECT_NAME}-test.conf: ${PROJECT_NAME}.conf.in Makefile.options | $(TEST_PREFIX)$(ETCDIR) - sed $(SED_ARGS) $(LOCAL_SED_ARGS) $< | sed -e "s|%%PREFIX%%|$(TEST_PREFIX)|g" > $@ - -##---------------------------------------------------------------------- -## Server side compilation - -SERVER_INC := ${addprefix -package ,${SERVER_PACKAGES}} - -${ELIOM_TYPE_DIR}/%.type_mli: %.eliom - ${ELIOMC} -infer ${SERVER_INC} $< - -$(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cma: $(call objs,$(ELIOM_SERVER_DIR),cmo,$(SERVER_FILES)) | $(TEST_PREFIX)$(LIBDIR) - ${ELIOMC} -a -o $@ $(GENERATE_DEBUG) \ - $(call depsort,$(ELIOM_SERVER_DIR),cmo,-server,$(SERVER_INC),$(SERVER_FILES)) - -$(TEST_PREFIX)$(LIBDIR)/$(PROJECT_NAME).cmxa: $(call objs,$(ELIOM_SERVER_DIR),cmx,$(SERVER_FILES)) | $(TEST_PREFIX)$(LIBDIR) - ${ELIOMOPT} -a -o $@ $(GENERATE_DEBUG) \ - $(call depsort,$(ELIOM_SERVER_DIR),cmx,-server,$(SERVER_INC),$(SERVER_FILES)) - -%.cmxs: %.cmxa - $(ELIOMOPT) -shared -linkall -o $@ $(GENERATE_DEBUG) $< - -${ELIOM_SERVER_DIR}/%.cmi: %.mli - ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< - -${ELIOM_SERVER_DIR}/%.cmi: %.eliomi - ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< - -${ELIOM_SERVER_DIR}/%.cmo: %.ml - ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< -${ELIOM_SERVER_DIR}/%.cmo: %.eliom - ${ELIOMC} -c ${SERVER_INC} $(GENERATE_DEBUG) $< - -${ELIOM_SERVER_DIR}/%.cmx: %.ml - ${ELIOMOPT} -c ${SERVER_INC} $(GENERATE_DEBUG) $< -${ELIOM_SERVER_DIR}/%.cmx: %.eliom - ${ELIOMOPT} -c ${SERVER_INC} $(GENERATE_DEBUG) $< - - -##---------------------------------------------------------------------- -## Client side compilation - -CLIENT_LIBS := ${addprefix -package ,${CLIENT_PACKAGES}} -CLIENT_INC := ${addprefix -package ,${CLIENT_PACKAGES}} - -CLIENT_OBJS := $(filter %.eliom %.ml, $(CLIENT_FILES)) -CLIENT_OBJS := $(patsubst %.eliom,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) -CLIENT_OBJS := $(patsubst %.ml,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) - -$(TEST_PREFIX)$(ELIOMSTATICDIR)/$(PROJECT_NAME).js: $(call objs,$(ELIOM_CLIENT_DIR),cmo,$(CLIENT_FILES)) | $(TEST_PREFIX)$(ELIOMSTATICDIR) - ${JS_OF_ELIOM} -o $@ $(GENERATE_DEBUG) $(CLIENT_INC) $(DEBUG_JS) \ - $(call depsort,$(ELIOM_CLIENT_DIR),cmo,-client,$(CLIENT_INC),$(CLIENT_FILES)) - -${ELIOM_CLIENT_DIR}/%.cmi: %.mli - ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< - -${ELIOM_CLIENT_DIR}/%.cmo: %.eliom - ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< -${ELIOM_CLIENT_DIR}/%.cmo: %.ml - ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< - -${ELIOM_CLIENT_DIR}/%.cmi: %.eliomi - ${JS_OF_ELIOM} -c ${CLIENT_INC} $(GENERATE_DEBUG) $< - -##---------------------------------------------------------------------- -## Dependencies - -include .depend - -.depend: $(patsubst %,$(DEPSDIR)/%.server,$(SERVER_FILES)) $(patsubst %,$(DEPSDIR)/%.client,$(CLIENT_FILES)) - cat $^ > $@ - -$(DEPSDIR)/%.server: % | $(DEPSDIR) - $(ELIOMDEP) -server -ppx $(SERVER_INC) $< > $@ - -$(DEPSDIR)/%.client: % | $(DEPSDIR) - $(ELIOMDEP) -client -ppx $(CLIENT_INC) $< > $@ - -$(DEPSDIR): - mkdir $@ - -##---------------------------------------------------------------------- -## Clean up - -clean: - -rm -f *.cm[ioax] *.cmxa *.cmxs *.o *.a *.annot - -rm -f *.type_mli - -rm -f ${PROJECT_NAME}.js - -rm -rf ${ELIOM_CLIENT_DIR} ${ELIOM_SERVER_DIR} - -distclean: clean - -rm -rf $(TEST_PREFIX) $(DEPSDIR) .depend diff --git a/7.1/manual/files/tutorial/chapter2/Makefile.options b/7.1/manual/files/tutorial/chapter2/Makefile.options deleted file mode 100644 index 845db57b..00000000 --- a/7.1/manual/files/tutorial/chapter2/Makefile.options +++ /dev/null @@ -1,61 +0,0 @@ - -#---------------------------------------------------------------------- -# SETTINGS FOR THE ELIOM PROJECT tuto -#---------------------------------------------------------------------- - -PROJECT_NAME := tuto - -# Source files for the server -SERVER_FILES := $(wildcard *.eliomi *.eliom) -# Source files for the client -CLIENT_FILES := $(wildcard *.eliomi *.eliom) - -# OCamlfind packages for the server -SERVER_PACKAGES := lwt.ppx js_of_ocaml.deriving.ppx -# OCamlfind packages for the client -CLIENT_PACKAGES := lwt.ppx js_of_ocaml.ppx js_of_ocaml.deriving.ppx - -# Directory with files to be statically served -LOCAL_STATIC = static - -# The backend for persistent data. Can be dbm or sqlite. -PERSISTENT_DATA_BACKEND = dbm - -# Debug application (yes/no): Debugging info in compilation, -# JavaScript, ocsigenserver -DEBUG := no - -# User to run server with (make run.*) -WWWUSER := www-data -WWWGROUP := www-data - -# Port for running the server (make run.*) -PORT := 80 - -# Port for testing (make test.*) -TEST_PORT := 8080 - -# Root of installation (must end with /) -PREFIX := /usr/local/ - -# Local folder for make test.* (must end with /) -# Do not add files manually in this directory. -# It is just here to test your installation before installing in / -TEST_PREFIX := local/ - -# The installation tree (relative to $(PREFIX) when -# installing/running or $(TEST_PREFIX) when testing). -# Configuration file $(PROJECT_NAME).conf -ETCDIR := etc/${PROJECT_NAME} -# Project's library $(PROJECT_NAME).cma (cmxs) -LIBDIR := lib/${PROJECT_NAME} -# Command pipe, eg. $ echo reload > $(INSTALL_PREFIX)$(CMDPIPE) -CMDPIPE := var/run/${PROJECT_NAME}-cmd -# Ocsigenserver's logging files -LOGDIR := var/log/${PROJECT_NAME} -# Ocsigenserver's persistent data files -DATADIR := var/data/${PROJECT_NAME} -# Copy of $(LOCAL_STATIC) -STATICDIR := var/www/${PROJECT_NAME}/static -# Project's JavaScript file -ELIOMSTATICDIR := var/www/${PROJECT_NAME}/eliom diff --git a/7.1/manual/files/tutorial/chapter2/static/css/tuto.css b/7.1/manual/files/tutorial/chapter2/static/css/tuto.css deleted file mode 100644 index b71fa50a..00000000 --- a/7.1/manual/files/tutorial/chapter2/static/css/tuto.css +++ /dev/null @@ -1,3 +0,0 @@ -* { - font-family: sans-serif; -} diff --git a/7.1/manual/files/tutorial/chapter2/tuto.conf.in b/7.1/manual/files/tutorial/chapter2/tuto.conf.in deleted file mode 100644 index 13a8a1fd..00000000 --- a/7.1/manual/files/tutorial/chapter2/tuto.conf.in +++ /dev/null @@ -1,28 +0,0 @@ -%%% This is the template for your configuration file. The %%VALUES%% below are -%%% taken from the Makefile to generate the actual configuration files. -%%% This comment will disappear. - - - - %%PORT%% - %%% Only set for running, not for testing - %%USERGROUP%% - %%LOGDIR%% - %%DATADIR%% - utf-8 - %%% Only set when debugging - %%DEBUGMODE%% - %%CMDPIPE%% - - - - %%% This will include the packages defined as SERVER_PACKAGES in your Makefile: - %%PACKAGES%% - - - - - - - - diff --git a/7.1/manual/files/tutorial/chapter2/tuto.eliom b/7.1/manual/files/tutorial/chapter2/tuto.eliom deleted file mode 100644 index 1b2bafab..00000000 --- a/7.1/manual/files/tutorial/chapter2/tuto.eliom +++ /dev/null @@ -1,210 +0,0 @@ - -(* =============================Eliom references============================= *) - -let username = - Eliom_reference.eref ~scope:Eliom_common.default_session_scope None - -let wrong_pwd = - Eliom_reference.eref ~scope:Eliom_common.request_scope false - - -(* =================================Services================================= *) - -let main_service = Eliom_service.create - ~path:(Eliom_service.Path [""]) - ~meth:(Eliom_service.Get Eliom_parameter.unit) - () - -let user_service = Eliom_service.create - ~path:(Eliom_service.Path ["users"]) - ~meth:(Eliom_service.Get Eliom_parameter.(suffix (string "name"))) - () - -let redir_service = Eliom_service.create - ~path:(Eliom_service.Path ["redir"]) - ~meth:(Eliom_service.Get Eliom_parameter.unit) - () - -let connection_service = Eliom_service.create - ~path:(Eliom_service.No_path) - ~meth:(Eliom_service.Post ( - Eliom_parameter.unit, - Eliom_parameter.(string "name" ** string "password"))) - () - -let disconnection_service = - Eliom_service.attach_post - ~fallback:redir_service - ~post_params:Eliom_parameter.unit - () - -let new_user_form_service = Eliom_service.create - ~path:(Eliom_service.Path ["registration"]) - ~meth:(Eliom_service.Get Eliom_parameter.unit) - () - -let account_confirmation_service = - Eliom_service.attach_post - ~fallback:new_user_form_service - ~post_params:Eliom_parameter.(string "name" ** string "password") - () - -(* ===========================Usernames/Passwords============================ *) - -let users = ref [("Calvin", "123"); ("Hobbes", "456")] - -let user_links = Eliom_content.Html.D.( - let link_of_user = fun (name, _) -> - li [a ~service:user_service [pcdata name] name] - in - fun () -> ul (List.map link_of_user !users) -) - -let check_pwd name pwd = - try List.assoc name !users = pwd with Not_found -> false - - -(* =================================Widgets================================== *) - -let account_form = Eliom_content.Html.D.( - Form.post_form ~service:account_confirmation_service - (fun (name1, name2) -> - [fieldset - [label [pcdata "login: "]; - Form.input ~input_type:`Text ~name:name1 Form.string; - br (); - label [pcdata "password: "]; - Form.input ~input_type:`Password ~name:name2 Form.string; - br (); - Form.input ~input_type:`Submit ~value:"Create Account" Form.string - ]]) () -) - -let disconnect_box () = Eliom_content.Html.D.( - Form.post_form disconnection_service - (fun _ -> - [fieldset [Form.input ~input_type:`Submit ~value:"Log out" Form.string]] - ) - () - |> Lwt.return -) - -(* =========================Authentification Handler========================= *) - -let authenticated_handler g f = Eliom_content.Html.D.( - let handle_anonymous _get _post = - let%lwt wp = Eliom_reference.get wrong_pwd in - let connection_box = - let l = [ - Form.post_form ~service:connection_service - (fun (name1, name2) -> - [fieldset - [label [pcdata "login: "]; - Form.input ~input_type:`Text ~name:name1 Form.string; - br (); - label [pcdata "password: "]; - Form.input ~input_type:`Password ~name:name2 Form.string; - br (); - Form.input ~input_type:`Submit ~value:"Connect" Form.string - ]]) (); - p [a new_user_form_service - [pcdata "Create an account"] ()]] - in - div (if wp then p [pcdata "Wrong login or password"]::l else l) - in - g - (html - (head (title (pcdata "")) []) - (body [h1 [pcdata "Please connect"]; - connection_box;])) - in - Eliom_tools.wrap_handler - (fun () -> Eliom_reference.get username) - handle_anonymous (* Called when [username] is [None] *) - f (* Called [username] contains something *) -) -(* ===========================Services Registration========================== *) - -let () = Eliom_content.Html.D.( - - Eliom_registration.Html.register - ~service:main_service - (authenticated_handler Lwt.return (fun name () () -> - let%lwt cf = disconnect_box () in - Lwt.return - (html (head (title (pcdata "")) []) - (body [h1 [pcdata ("Hello " ^ name)]; - cf; - user_links ()])))); - - Eliom_registration.Any.register - ~service:user_service - (authenticated_handler Eliom_registration.Html.send (fun _ name () -> - if List.mem_assoc name !users then - begin - let%lwt cf = disconnect_box () in - Eliom_registration.Html.send - (html (head (title (pcdata name)) []) - (body [h1 [pcdata name]; - cf; - p [a ~service:main_service [pcdata "Home"] ()]])) - end - else - Eliom_registration.Html.send - ~code:404 - (html (head (title (pcdata "404")) []) - (body [h1 [pcdata "404"]; - p [pcdata "That page does not exist"]])) - )); - - Eliom_registration.Action.register - ~service:connection_service - (fun () (name, password) -> - if check_pwd name password - then Eliom_reference.set username (Some name) - else Eliom_reference.set wrong_pwd true); - - Eliom_registration.Redirection.register - ~service:redir_service - (fun () () -> Lwt.return (Eliom_registration.Redirection main_service)); - - Eliom_registration.Action.register - ~service:disconnection_service - (fun () () -> Eliom_state.discard ~scope:Eliom_common.default_session_scope ()); - - Eliom_registration.Html.register - ~service:new_user_form_service - (fun () () -> - Lwt.return - (html (head (title (pcdata "")) []) - (body [h1 [pcdata "Create an account"]; - account_form; - ]))); - - Eliom_registration.Html.register - ~service:account_confirmation_service - (fun () (name, pwd) -> - let create_account_service = - Eliom_service.attach_get - ~fallback:main_service - ~get_params:Eliom_parameter.unit - ~timeout:60. - ~max_use:1 - () - in - let _ = Eliom_registration.Action.register - ~service:create_account_service - (fun () () -> - users := (name, pwd)::!users; - Lwt.return ()) - in - Lwt.return - (html - (head (title (pcdata "")) []) - (body - [h1 [pcdata "Confirm account creation for "; pcdata name]; - p [a ~service:create_account_service [pcdata "Yes"] (); - pcdata " "; - a ~service:main_service [pcdata "No"] ()] - ]))); -) diff --git a/7.1/manual/files/tutorial/chapter3/README b/7.1/manual/files/tutorial/chapter3/README deleted file mode 100644 index 37d111c1..00000000 --- a/7.1/manual/files/tutorial/chapter3/README +++ /dev/null @@ -1 +0,0 @@ -This code isn't maintened anymore and might not work. Please go to https://github.com/ocsigen/graffiti/simple for the latest working versions. diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti/Makefile b/7.1/manual/files/tutorial/chapter3/multigraffiti/Makefile deleted file mode 100644 index 675ba493..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti/Makefile +++ /dev/null @@ -1,68 +0,0 @@ - -APP_NAME := graffiti -SERVER_PACKAGE := cairo, unix -CLIENT_PACKAGE := - -SERVER_FILES = common.ml server.ml ${wildcard *.eliom} -CLIENT_FILES = common.ml client.ml ${wildcard *.eliom} - -PORT := 8080 - -OCLOSURE := YES - -### - -all: local byte opt conf - -LIBDIR := local/var/www/lib -JSDIR := local/var/www/static - -include Makefile.common - -distclean:: - -rm -rf css/closure - -rm -rf local - -rm -f graffiti.conf - -#### - -DIRS = local/var/lib/ocsidbm local/var/run local/var/log \ - local/var/www/static local/var/www/lib local/etc \ - local/var/www/static/graffiti_saved - -local: ${DIRS} local/var/www/static/css local/var/www/static/images css/closure - -local/var/www/static/css: - ln -fs $(shell pwd)/css local/var/www/static/css - -local/var/www/static/images: - ln -fs $(shell pwd)/images local/var/www/static/images - -css/closure: - ln -fs $(shell ocamlfind query oclosure)/closure/goog/css/ css/closure - -${DIRS}: - mkdir -p $@ - -conf: graffiti.conf - -graffiti.conf: graffiti.conf.in - sed -e "s|%%SRC%%|$(shell pwd)|" \ - -e "s|%%LIBDIR%%|${LIBDIR}|" \ - -e "s|%%JSDIR%%|${JSDIR}|" \ - -e "s|%%PORT%%|${PORT}|" \ - $< > $@ - -run.local: graffiti.conf - ocsigenserver -c graffiti.conf - -run.opt.local: graffiti.conf - ocsigenserver.opt -c graffiti.conf - -#### - -install:: - install -d -m 775 ${INSTALL_USER} ${INSTALL_DIR}/static/css - install -m 664 ${INSTALL_USER} css/*.css ${INSTALL_DIR}/static/css - cd $(shell ocamlfind query oclosure)/closure/goog/css/ && \ - find -type f -exec install -D -m 664 {} ${INSTALL_DIR}/static/css/closure/{} \; diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti/Makefile.common b/7.1/manual/files/tutorial/chapter3/multigraffiti/Makefile.common deleted file mode 100644 index 76143752..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti/Makefile.common +++ /dev/null @@ -1,165 +0,0 @@ -## Sample Makefile for eliom application. - -APP_NAME ?= $(error Please define the APP_NAME variable) - -## Package required to build the server part of the application - -SERVER_PACKAGE ?= - -## Package to be linked in the client part - -CLIENT_PACKAGE ?= - -## Source files for the server part - -SERVER_FILES ?= ${wildcard *.eliom} - -## Source files for the client part - -CLIENT_FILES ?= ${wildcard *.eliom} - -## Needed binaries - -ELIOMC ?= eliomc -ELIOMOPT ?= eliomopt -ELIOMDEP ?= eliomdep -JS_OF_ELIOM ?= js_of_eliom -OCLOSURE_REQ ?= oclosure_req - -## Where to put intermediate object files. -## - ELIOM_{SERVER,CLIENT}_DIR must be distinct -## - ELIOM_CLIENT_DIR mustn't be the local dir. -## - ELIOM_SERVER_DIR could be ".", but you need to -## remove it from the "clean" rules... - -ELIOM_SERVER_DIR ?= _server -ELIOM_CLIENT_DIR ?= _client -ELIOM_TYPE_DIR ?= ${ELIOM_SERVER_DIR} - -export ELIOM_SERVER_DIR -export ELIOM_CLIENT_DIR -export ELIOM_TYPE_DIR - -#### - -LIBDIR ?= . - -byte:: ${LIBDIR}/${APP_NAME}.cma -opt:: ${LIBDIR}/${APP_NAME}.cmxs - -JSDIR ?= . - -ifneq (${CLIENT_FILES},) -byte:: ${JSDIR}/${APP_NAME}.js -opt:: ${JSDIR}/${APP_NAME}.js - -OCLOSURE ?= NO - -ifeq ($(OCLOSURE),YES) -CLIENT_PACKAGE += oclosure -byte:: ${JSDIR}/${APP_NAME}_oclosure.js -opt:: ${JSDIR}/${APP_NAME}_oclosure.js -endif -endif - -#### Server side compilation ####### - -SERVER_INC := ${addprefix -package ,${SERVER_PACKAGE}} - -SERVER_OBJS := $(patsubst %.eliom,${ELIOM_SERVER_DIR}/%.cmo, ${SERVER_FILES}) -SERVER_OBJS := $(patsubst %.ml,${ELIOM_SERVER_DIR}/%.cmo, ${SERVER_OBJS}) - -${LIBDIR}/${APP_NAME}.cma: ${SERVER_OBJS} - ${ELIOMC} ${ELIOMCFLAGS} -a -o $@ $^ -${LIBDIR}/${APP_NAME}.cmxa: ${SERVER_OBJS:.cmo=.cmx} - ${ELIOMOPT} ${ELIOMOPTFLAGS} -a -o $@ $^ - -${ELIOM_TYPE_DIR}/%.type_mli: %.eliom - ${ELIOMC} ${ELIOMCFLAGS} -infer ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmi: %.mli - ${ELIOMC} ${ELIOMCFLAGS} -c ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmo: %.ml - ${ELIOMC} ${ELIOMCFLAGS} -c ${SERVER_INC} $< -${ELIOM_SERVER_DIR}/%.cmo: %.eliom - ${ELIOMC} ${ELIOMCFLAGS} -c -noinfer ${SERVER_INC} $< - -${ELIOM_SERVER_DIR}/%.cmx: %.ml - ${ELIOMOPT} ${ELIOMOPTFLAGS} -c ${SERVER_INC} $< -${ELIOM_SERVER_DIR}/%.cmx: %.eliom - ${ELIOMOPT} ${ELIOMOPTFLAGS} -c -noinfer ${SERVER_INC} $< - -%.cmxs: %.cmxa - $(ELIOMOPT) ${ELIOMOPTFLAGS} -shared -linkall -o $@ $< - -##### Client side compilation #### - -CLIENT_LIBS := ${addprefix -package ,${CLIENT_PACKAGE}} -CLIENT_INC := ${addprefix -package ,${CLIENT_PACKAGE}} - -CLIENT_OBJS := $(patsubst %.eliom,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_FILES}) -CLIENT_OBJS := $(patsubst %.ml,${ELIOM_CLIENT_DIR}/%.cmo, ${CLIENT_OBJS}) - -${JSDIR}/${APP_NAME}.js: ${CLIENT_OBJS} - ${JS_OF_ELIOM} -jsopt -pretty -jsopt -noinline -o $@ ${CLIENT_LIBS} $^ - -${ELIOM_CLIENT_DIR}/%.cmi: %.mli - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< - -${ELIOM_CLIENT_DIR}/%.cmo: %.eliom - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< -${ELIOM_CLIENT_DIR}/%.cmo: %.ml - ${JS_OF_ELIOM} -c ${CLIENT_INC} $< - -### O'Closure compilation ### - -${JSDIR}/$(APP_NAME)_oclosure.js: ${JSDIR}/$(APP_NAME).js - ${OCLOSURE_REQ} $^ - -############ - -## Clean up - -clean:: - -rm -f *.cm[ioax] *.cmxa *.cmxs *.o *.a *.annot - -rm -f *.type_mli - -rm -f .depend - -rm -f ${JSDIR}/${APP_NAME}.js ${JSDIR}/${APP_NAME}_oclosure.js - -rm -rf ${ELIOM_CLIENT_DIR} ${ELIOM_SERVER_DIR} - -distclean:: clean - -find \( -name \*~ -or -name \#\* -or -name .\#\* \) -delete - - -## Dependencies - -depend: .depend -.depend:: ${SERVER_FILES} ${CLIENT_FILES} - $(ELIOMDEP) -server ${SERVER_INC} ${SERVER_FILES} > .depend - $(ELIOMDEP) -client ${CLIENT_INC} ${CLIENT_FILES} >> .depend - -## Warning: Dependencies towards *.eliom are not handled by eliomdep yet. -## Add manually dependencies between cmo and cmx files here, -## for example: -## oneeliomfile.cmo: anothereliomfile.cmo -## oneeliomfile.cmx: anothereliomfile.cmx - --include .depend - - -#### - -INSTALL_USER ?= -o www-data -g www-data -INSTALL_DIR ?= local/var/www/${APP_NAME} - -install:: - install -d -m 775 ${INSTALL_USER} ${INSTALL_DIR}/lib - install -m 664 ${INSTALL_USER} ${LIBDIR}/${APP_NAME}.cma ${LIBDIR}/${APP_NAME}.cmxs ${INSTALL_DIR}/lib/ - install -d -m 775 ${INSTALL_USER} ${INSTALL_DIR}/static -ifneq (${CLIENT_FILES},) - install -m 664 ${INSTALL_USER} ${JSDIR}/${APP_NAME}.js ${INSTALL_DIR}/static/ -ifeq ($(OCLOSURE),YES) - install -m 664 ${INSTALL_USER} ${JSDIR}/${APP_NAME}_oclosure.js ${INSTALL_DIR}/static/ -endif -endif diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti/client.ml b/7.1/manual/files/tutorial/chapter3/multigraffiti/client.ml deleted file mode 100644 index 019b1dcb..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti/client.ml +++ /dev/null @@ -1,80 +0,0 @@ -open Eliom_content -open Common -open Eliom_lib.Lwt_ops - -let draw ctx (color, size, (x1, y1), (x2, y2)) = - ctx##strokeStyle <- (Js.string color); - ctx##lineWidth <- float size; - ctx##beginPath(); - ctx##moveTo(float x1, float y1); - ctx##lineTo(float x2, float y2); - ctx##stroke() - -(* type containing all informations we need to stop interaction - inside the page *) -type drawing_canceller = - { drawing_thread : unit Lwt.t; - (* the thread reading messages from the bus *) - drawing_event_thread : unit Lwt.t; - (* the thread handling mouse events *) - } - -let stop_drawing { drawing_thread; drawing_event_thread } = - Lwt.cancel drawing_thread; - (* cancelling this thread also closes the bus *) - Lwt.cancel drawing_event_thread - -let launch_client_canvas bus image_elt canvas_elt = - let canvas = Html5.To_dom.of_canvas canvas_elt in - let ctx = canvas##getContext (Dom_html._2d_) in - ctx##lineCap <- Js.string "round"; - - let img = Html5.To_dom.of_img image_elt in - let copy_image () = ctx##drawImage(img, 0., 0.) in - if Js.to_bool (img##complete) - then copy_image () - else img##onload <- Dom_html.handler - (fun ev -> copy_image (); Js._false); - - (* Size of the brush *) - let slider = jsnew Goog.Ui.slider(Js.null) in - slider##setMinimum(1.); - slider##setMaximum(80.); - slider##setValue(10.); - slider##setMoveToPointEnabled(Js._true); - slider##render(Js.some Dom_html.document##body); - - (* The color palette: *) - let pSmall = - jsnew Goog.Ui.hsvPalette(Js.null, Js.null, - Js.some (Js.string "goog-hsv-palette-sm")) - in - pSmall##render(Js.some Dom_html.document##body); - - 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 in - let compute_line ev = - let oldx = !x and oldy = !y in - set_coord ev; - let color = Js.to_string (pSmall##getColor()) in - let size = int_of_float (Js.to_float (slider##getValue())) in - (color, size, (oldx, oldy), (!x, !y)) - in - let line ev = - let v = compute_line ev in - let _ = Eliom_bus.write bus v in - draw ctx v; - Lwt.return () - in - let t = Lwt_stream.iter (draw ctx) (Eliom_bus.stream bus) in - let drawing_event_thread = - 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]) - in - { drawing_thread = t; - drawing_event_thread = drawing_event_thread } diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti/common.ml b/7.1/manual/files/tutorial/chapter3/multigraffiti/common.ml deleted file mode 100644 index 8d256e01..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti/common.ml +++ /dev/null @@ -1,4 +0,0 @@ -type messages = (string * int * (int * int) * (int * int)) deriving (Json) - -let width = 700 -let height = 400 diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti/css/common.css b/7.1/manual/files/tutorial/chapter3/multigraffiti/css/common.css deleted file mode 100644 index 02d7073c..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti/css/common.css +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2009 The Closure Library Authors. All Rights Reserved. - * - * Use of this source code is governed by an Apache 2.0 License. - * See the COPYING file for details. - */ - -/* - * Cross-browser implementation of the "display: inline-block" CSS property. - * See http://www.w3.org/TR/CSS21/visuren.html#propdef-display for details. - * Tested on IE 6 & 7, FF 1.5 & 2.0, Safari 2 & 3, Webkit, and Opera 9. - * - * @author attila@google.com (Attila Bodis) - */ - -/* - * Default rule; only Safari, Webkit, and Opera handle it without hacks. - */ -.goog-inline-block { - position: relative; - display: -moz-inline-box; /* Ignored by FF3 and later. */ - display: inline-block; -} - -/* - * Pre-IE7 IE hack. On IE, "display: inline-block" only gives the element - * layout, but doesn't give it inline behavior. Subsequently setting display - * to inline does the trick. - */ -* html .goog-inline-block { - display: inline; -} - -/* - * IE7-only hack. On IE, "display: inline-block" only gives the element - * layout, but doesn't give it inline behavior. Subsequently setting display - * to inline does the trick. - */ -*:first-child+html .goog-inline-block { - display: inline; -} diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti/css/graffiti.css b/7.1/manual/files/tutorial/chapter3/multigraffiti/css/graffiti.css deleted file mode 100644 index 5768a851..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti/css/graffiti.css +++ /dev/null @@ -1,21 +0,0 @@ -body { - width: 750px; -} - -canvas { - border: solid 1px #aaaaaa; -} - -div.goog-slider-vertical, -DIV.goog-slider-vertical { - position: absolute; - top: 100px; - left: 740px; -} - -div.goog-hsv-palette-sm, -DIV.goog-hsv-palette-sm { - position: absolute; - top: 100px; - left: 780px; -} diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti/css/hsvpalette.css b/7.1/manual/files/tutorial/chapter3/multigraffiti/css/hsvpalette.css deleted file mode 100644 index d3ae7f67..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti/css/hsvpalette.css +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2008 The Closure Library Authors. All Rights Reserved. - * - * Use of this source code is governed by an Apache 2.0 License. - * See the COPYING file for details. - */ - -/* - * All Rights Reserved. - * - * Styles for the HSV color palette. - * - * @author smcbride@google.com (Sean McBride) - * @author arv@google.com (Erik Arvidsson) - * @author manucornet@google.com (Manu Cornet) - */ - -.goog-hsv-palette, -.goog-hsv-palette-sm { - position: relative; - border: 1px solid #999; - border-color: #ccc #999 #999 #ccc; - width: 400px; - height: 276px; -} - -.goog-hsv-palette-sm { - width: 182px; - height: 185px; -} - -.goog-hsv-palette label span, -.goog-hsv-palette-sm label span { - display: none; -} - -.goog-hsv-palette-hs-backdrop, -.goog-hsv-palette-sm-hs-backdrop, -.goog-hsv-palette-hs-image, -.goog-hsv-palette-sm-hs-image { - position: absolute; - top: 10px; - left: 10px; - width: 256px; - height: 256px; - border: 1px solid #999; -} - -.goog-hsv-palette-sm-hs-backdrop, -.goog-hsv-palette-sm-hs-image { - top: 45px; - width: 128px; - height: 128px; -} - -.goog-hsv-palette-hs-backdrop, -.goog-hsv-palette-sm-hs-backdrop { - background-color: #000; -} - -.goog-hsv-palette-hs-image, -.goog-hsv-palette-v-image, -.goog-hsv-palette-hs-handle, -.goog-hsv-palette-v-handle { - background-image: url(../images/hsv-sprite.png); -} - -.goog-hsv-palette-noalpha .goog-hsv-palette-hs-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-v-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-hs-handle, -.goog-hsv-palette-noalpha .goog-hsv-palette-v-handle { - background-image: url(../images/hsv-sprite.gif); -} - -.goog-hsv-palette-sm-hs-image, -.goog-hsv-palette-sm-v-image, -.goog-hsv-palette-sm-hs-handle, -.goog-hsv-palette-sm-v-handle { - background-image: url(../images/hsv-sprite-sm.png); -} - -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-hs-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-v-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-hs-handle, -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-v-handle { - background-image: url(../images/hsv-sprite-sm.gif); -} - -.goog-hsv-palette-hs-image, -.goog-hsv-palette-sm-hs-image { - background-position: 0 0; -} - -.goog-hsv-palette-hs-handle, -.goog-hsv-palette-sm-hs-handle { - position: absolute; - left: 5px; - top: 5px; - width: 11px; - height: 11px; - overflow: hidden; - background-position: 0 -256px; -} - -.goog-hsv-palette-sm-hs-handle { - top: 40px; - background-position: 0 -128px; -} - -.goog-hsv-palette-v-image, -.goog-hsv-palette-sm-v-image { - position: absolute; - top: 10px; - left: 286px; - width: 19px; - height: 256px; - border: 1px solid #999; - background-color: #fff; - background-position: -256px 0; -} - -.goog-hsv-palette-sm-v-image { - top: 45px; - left: 155px; - width: 9px; - height: 128px; - background-position: -128px 0; -} - -.goog-hsv-palette-v-handle, -.goog-hsv-palette-sm-v-handle { - position: absolute; - top: 5px; - left: 279px; - width: 35px; - height: 11px; - background-position: -11px -256px; - overflow: hidden; -} - -.goog-hsv-palette-sm-v-handle { - top: 40px; - left: 148px; - width: 25px; - background-position: -11px -128px; -} - -.goog-hsv-palette-swatch, -.goog-hsv-palette-sm-swatch { - position: absolute; - top: 10px; - right: 10px; - width: 65px; - height: 65px; - border: 1px solid #999; - background-color: #fff; -} - -.goog-hsv-palette-sm-swatch { - top: 10px; - right: auto; - left: 10px; - width: 30px; - height: 22px; -} - -.goog-hsv-palette-input, -.goog-hsv-palette-sm-input { - position: absolute; - top: 85px; - right: 10px; - width: 65px; -} - -.goog-hsv-palette-sm-input { - top: 10px; - right: auto; - left: 50px; -} diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti/css/slider.css b/7.1/manual/files/tutorial/chapter3/multigraffiti/css/slider.css deleted file mode 100644 index ddd07482..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti/css/slider.css +++ /dev/null @@ -1,45 +0,0 @@ -.goog-slider-vertical, -.goog-slider-horizontal { -background-color: ThreeDFace; -position: relative; -overflow: hidden; -} - -.goog-slider-vertical { -height: 300px; -width: 20px; -} - -.goog-slider-horizontal { -height: 20px; -width: 400px; -} - -.goog-slider-thumb { -position: absolute; -background-color: ThreeDShadow; -overflow: hidden; -} - -.goog-slider-vertical .goog-slider-thumb { -left: 0; -height: 20px; -width: 100%; -} - -.goog-slider-horizontal .goog-slider-thumb { -top: 0; -width: 20px; -height: 100%; -} -#s-h { -margin-bottom: 2em; -} -strong { -display: block; -margin-bottom: 3px; -} -#out1, #out2 { -color: #999; -margin-left: 1em; -} diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti/graffiti.conf.in b/7.1/manual/files/tutorial/chapter3/multigraffiti/graffiti.conf.in deleted file mode 100644 index 89f88db3..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti/graffiti.conf.in +++ /dev/null @@ -1,35 +0,0 @@ - - - - - %%PORT%% - - %%SRC%%/local/var/log - %%SRC%%/local/var/run - %%SRC%%/local/var/run/ocsigenserver_command - - utf-8 - - - - - - - - - - - - - - - - - - %%SRC%%/%%JSDIR%% - - - - - - diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti/graffiti.eliom b/7.1/manual/files/tutorial/chapter3/multigraffiti/graffiti.eliom deleted file mode 100644 index b0de220e..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti/graffiti.eliom +++ /dev/null @@ -1,36 +0,0 @@ -{shared{ - open Eliom_content.Html5.D - open Common -}} -{client{ - open Client -}} -open Server - -let start_drawing name image canvas = - let bus = get_bus name in - ignore {unit{ - let canceller = launch_client_canvas %bus %image %canvas in - Eliom_client.onunload (fun () -> stop_drawing canceller) - }} - -let counter = ref 0 - -let () = Connected.register ~service:multigraffiti_service - !% (fun name () username -> - (* Some browsers won't reload the image, so we force - them by changing the url each time. *) - incr counter; - let image = - img ~alt:name - ~src:(make_uri - ~service:imageservice (name,!counter)) () in - let canvas = - canvas ~a:[ a_width width; a_height height ] - [pcdata "your browser doesn't support canvas"; br (); image] in - start_drawing name image canvas; - make_page - [h1 [pcdata name]; - disconnect_box (); - choose_drawing_form (); - canvas;]) diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti/images/hsv-sprite-sm.png b/7.1/manual/files/tutorial/chapter3/multigraffiti/images/hsv-sprite-sm.png deleted file mode 100644 index 470f8e0c..00000000 Binary files a/7.1/manual/files/tutorial/chapter3/multigraffiti/images/hsv-sprite-sm.png and /dev/null differ diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti/server.ml b/7.1/manual/files/tutorial/chapter3/multigraffiti/server.ml deleted file mode 100644 index 0836ae2c..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti/server.ml +++ /dev/null @@ -1,205 +0,0 @@ -open Eliom_content.Html5.D -open Common -open Lwt - -module My_app = - Eliom_registration.App ( - struct - let application_name = "graffiti" - end) - -let rgb_from_string color = (* color is in format "#rrggbb" *) - let get_color i = - (float_of_string ("0x"^(String.sub color (1+2*i) 2))) /. 255. - in - try get_color 0, get_color 1, get_color 2 with | _ -> 0.,0.,0. - -let launch_server_canvas () = - let bus = Eliom_bus.create Json.t in - - let draw_server, image_string = - let surface = Cairo.image_surface_create - Cairo.FORMAT_ARGB32 ~width ~height in - let ctx = Cairo.create surface in - ((fun ((color : string), size, (x1, y1), (x2, y2)) -> - - (* Set thickness of brush *) - Cairo.set_line_width ctx (float size) ; - Cairo.set_line_join ctx Cairo.LINE_JOIN_ROUND ; - Cairo.set_line_cap ctx Cairo.LINE_CAP_ROUND ; - let red, green, blue = rgb_from_string color 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.close_path ctx ; - - (* Apply the ink *) - Cairo.stroke ctx ; - ), - (fun () -> - let b = Buffer.create 10000 in - (* Output a PNG in a string *) - Cairo_png.surface_write_to_stream surface (Buffer.add_string b); - Buffer.contents b - )) - in - let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) in - bus,image_string - -let graffiti_info = Hashtbl.create 0 - -let imageservice = - Eliom_registration.String.register_service - ~path:["image"] - ~headers:Http_headers.dyn_headers - ~get_params:(let open Eliom_parameter in string "name" ** int "q") - (* we add an int parameter for the browser not to cache the image: - at least for chrome, there is no way to force the browser to - reload the image without leaving the application *) - (fun (name,_) () -> - try_lwt - let _ ,image_string = Hashtbl.find graffiti_info name in - Lwt.return (image_string (), "image/png") - with - | Not_found -> raise_lwt Eliom_common.Eliom_404) - -let get_bus (name:string) = - (* create a new bus and image_string function only if it did not exists *) - try - fst (Hashtbl.find graffiti_info name) - with - | Not_found -> - let bus,image_string = launch_server_canvas () in - Hashtbl.add graffiti_info name (bus,image_string); - bus - -let main_service = Eliom_service.App.service ~path:[""] - ~get_params:(Eliom_parameter.unit) () -let multigraffiti_service = Eliom_service.App.service ~path:[""] - ~get_params:(Eliom_parameter.suffix (Eliom_parameter.string "name")) () - -let choose_drawing_form () = - get_form ~service:multigraffiti_service - (fun (name) -> - [fieldset - [label ~a:[a_for name] - [pcdata "drawing name: "]; - string_input ~input_type:`Text ~name (); - br (); - string_input ~input_type:`Submit ~value:"Go" () - ]]) - -let oclosure_script = - Eliom_content.Html5.Id.create_global_elt - (js_script - ~uri:(make_uri (Eliom_service.static_dir ()) - ["graffiti_oclosure.js"]) ()) - -let make_page content = - Lwt.return - (html - (head - (title (pcdata "Graffiti")) - [ css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"common.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"hsvpalette.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"slider.css"]) (); - oclosure_script; - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"graffiti.css"]) (); - ]) - (body content)) - -let connection_service = - Eliom_service.Http.post_coservice' ~post_params: - (let open Eliom_parameter in (string "name" ** string "password")) () -let disconnection_service = Eliom_service.Http.post_coservice' - ~post_params:Eliom_parameter.unit () -let create_account_service = - Eliom_service.Http.post_coservice ~fallback:main_service ~post_params: - (let open Eliom_parameter in (string "name" ** string "password")) () - -let username = Eliom_reference.eref ~scope:Eliom_common.default_session_scope None - -let users = ref ["user","password";"test","test"] - -let check_pwd name pwd = - try Lwt.return (List.assoc name !users = pwd) with - | Not_found -> Lwt.return false - -let () = Eliom_registration.Action.register - ~service:create_account_service - (fun () (name, pwd) -> - users := (name, pwd)::!users; - Lwt.return ()) - -let () = Eliom_registration.Action.register - ~service:connection_service - (fun () (name, password) -> - match_lwt check_pwd name password with - | true -> Eliom_reference.set username (Some name) - | false -> Lwt.return ()) - -let () = - Eliom_registration.Action.register - ~service:disconnection_service - (fun () () -> Eliom_state.discard ~scope:Eliom_common.default_session_scope ()) - -let disconnect_box () = - post_form disconnection_service - (fun _ -> [fieldset - [string_input - ~input_type:`Submit ~value:"Log out" ()]]) () - -let login_name_form service button_text = - post_form ~service - (fun (name1, name2) -> - [fieldset - [label ~a:[a_for name1] [pcdata "login: "]; - string_input ~input_type:`Text ~name:name1 (); - br (); - label ~a:[a_for name2] [pcdata "password: "]; - string_input - ~input_type:`Password - ~name:name2 (); - br (); - string_input - ~input_type:`Submit - ~value:button_text () - ]]) () - -let default_content () = - make_page - [h1 [pcdata "Welcome to Multigraffiti"]; - h2 [pcdata "log in"]; - login_name_form connection_service "Connect"; - h2 [pcdata "create account"]; - login_name_form create_account_service "Create account";] - -module Connected_translate = -struct - type page = string -> My_app.page Lwt.t - let translate page = - Eliom_reference.get username >>= - function - | None -> default_content () - | Some username -> page username -end - -module Connected = - Eliom_registration.Customize ( My_app ) ( Connected_translate ) - -let ( !% ) f = fun a b -> return (fun c -> f a b c) - -let () = Connected.register ~service:main_service - !% (fun () () username -> - make_page - [h1 [pcdata ("Welcome to Multigraffiti " ^ username)]; - choose_drawing_form ()]) diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti_audio/graffiti.eliom b/7.1/manual/files/tutorial/chapter3/multigraffiti_audio/graffiti.eliom deleted file mode 100644 index 828b3e4f..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti_audio/graffiti.eliom +++ /dev/null @@ -1,43 +0,0 @@ -{shared{ - open Eliom_content.Html5.D - open Common -}} -{client{ - open Client -}} -open Server - -let start_drawing name image canvas = - let bus = get_bus name in - ignore {unit{ - let canceller = launch_client_canvas %bus %image %canvas in - Eliom_client.onunload (fun () -> stop_drawing canceller) - }} - -let counter = ref 0 - -let player = - Eliom_content.Html5.Id.create_global_elt - (audio - ~srcs:(make_uri (Eliom_service.static_dir ()) - ["music.ogg"],[]) - ~a:[a_autoplay (`Autoplay);a_controls (`Controls)] - [pcdata "Your browser does not support audio element" ]) - -let () = Connected.register ~service:multigraffiti_service - !% ( fun name () username -> - (* Some browsers won't reload the image, so we force - them by changing the url each time. *) - incr counter; - let image = img ~alt:name ~src:(make_uri - ~service:imageservice (name,!counter)) () in - let canvas = canvas ~a:[ a_width width; a_height height ] - [pcdata "your browser doesn't support canvas"; br (); image] in - start_drawing name image canvas; - make_page - [h1 [pcdata name]; - disconnect_box (); - choose_drawing_form (); - canvas; - player]) - diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti_feed/Makefile b/7.1/manual/files/tutorial/chapter3/multigraffiti_feed/Makefile deleted file mode 100644 index 8aeb4758..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti_feed/Makefile +++ /dev/null @@ -1,68 +0,0 @@ - -APP_NAME := graffiti -SERVER_PACKAGE := cairo, unix -CLIENT_PACKAGE := - -SERVER_FILES = common.ml server.ml feed.ml ${wildcard *.eliom} -CLIENT_FILES = common.ml client.ml ${wildcard *.eliom} - -PORT := 8080 - -OCLOSURE := YES - -### - -all: local byte opt conf - -LIBDIR := local/var/www/lib -JSDIR := local/var/www/static - -include Makefile.common - -distclean:: - -rm -rf css/closure - -rm -rf local - -rm -f graffiti.conf - -#### - -DIRS = local/var/lib/ocsidbm local/var/run local/var/log \ - local/var/www/static local/var/www/lib local/etc \ - local/var/www/static/graffiti_saved - -local: ${DIRS} local/var/www/static/css local/var/www/static/images css/closure - -local/var/www/static/css: - ln -fs $(shell pwd)/css local/var/www/static/css - -local/var/www/static/images: - ln -fs $(shell pwd)/images local/var/www/static/images - -css/closure: - ln -fs $(shell ocamlfind query oclosure)/closure/goog/css/ css/closure - -${DIRS}: - mkdir -p $@ - -conf: graffiti.conf - -graffiti.conf: graffiti.conf.in - sed -e "s|%%SRC%%|$(shell pwd)|" \ - -e "s|%%LIBDIR%%|${LIBDIR}|" \ - -e "s|%%JSDIR%%|${JSDIR}|" \ - -e "s|%%PORT%%|${PORT}|" \ - $< > $@ - -run.local: graffiti.conf - ocsigenserver -c graffiti.conf - -run.opt.local: graffiti.conf - ocsigenserver.opt -c graffiti.conf - -#### - -install:: - install -d -m 775 ${INSTALL_USER} ${INSTALL_DIR}/static/css - install -m 664 ${INSTALL_USER} css/*.css ${INSTALL_DIR}/static/css - cd $(shell ocamlfind query oclosure)/closure/goog/css/ && \ - find -type f -exec install -D -m 664 {} ${INSTALL_DIR}/static/css/closure/{} \; diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti_feed/feed.ml b/7.1/manual/files/tutorial/chapter3/multigraffiti_feed/feed.ml deleted file mode 100644 index 40df2309..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti_feed/feed.ml +++ /dev/null @@ -1,108 +0,0 @@ -open Eliom_content -open Html5.D -open Server -open Eliom_lib -open Eliom_lib.Lwt_ops - -let static_dir = - match Eliom_config.get_config () with - | [Simplexmlparser.Element ("staticdir", [], [Simplexmlparser.PCData dir])] -> - dir - | [] -> - raise (Ocsigen_extensions.Error_in_config_file - ("staticdir must be configured")) - | _ -> - raise (Ocsigen_extensions.Error_in_config_file - ("Unexpected content inside graffiti config")) - -let image_dir name = - let dir = static_dir ^ "/graffiti_saved/" ^ (Eliom_lib.Url.encode name) in - (try_lwt Lwt_unix.mkdir dir 0o777 with - | _ -> debug "could not create the directory %s" dir; Lwt.return ()) >|= - (fun () -> dir) - -let make_filename name number = - image_dir name >|= ( fun dir -> (dir ^ "/" ^ (string_of_int number) ^ ".png") ) - -let save image name number = - lwt file_name = make_filename name number in - lwt out_chan = Lwt_io.open_file ~mode:Lwt_io.output file_name in - Lwt_io.write out_chan image - -let image_info_table = Ocsipersist.Polymorphic.open_table "image_info_table" - -let save_image username = - let now = CalendarLib.Calendar.now () in - lwt number,_,list = - try_lwt Ocsipersist.Polymorphic.find image_info_table username with - | Not_found -> Lwt.return (0,now,[]) - | e -> Lwt.fail e - in - lwt () = Ocsipersist.Polymorphic.add image_info_table - username (number+1,now,(number,now)::list) in - let (_,image_string) = Hashtbl.find graffiti_info username in - save (image_string ()) username number - -let save_image_box name = - let save_image_service = - Eliom_registration.Action.register_post_coservice' - ~post_params:Eliom_parameter.unit - (fun () () -> save_image name) - in - post_form save_image_service - (fun _ -> - [p [string_input - ~input_type:`Submit ~value:"save" ()]]) () - -let feed_service = Eliom_service.Http.service ~path:["feed"] - ~get_params:(Eliom_parameter.string "name") () - -let local_filename name number = - ["graffiti_saved"; Url.encode name ; (string_of_int number) ^ ".png"] - -let rec entries name list = function - | 0 -> [] - | len -> - match list with - | [] -> [] - | (n,saved)::q -> - let title = Atom_feed.plain ("graffiti " ^ name ^ " " ^ (string_of_int n)) in - let uri = - Xhtml.M.uri_of_string - (Eliom_uri.make_string_uri - ~service:(Eliom_service.static_dir ()) - (local_filename name n)) - in - let entry = - Atom_feed.entry ~title ~id:uri ~updated:saved - [Atom_feed.xhtmlC [ Xhtml.M.img ~src:uri ~alt:"image" ()]] in - entry::(entries name q (len - 1)) - -let feed name () = - let id = Xhtml.M.uri_of_string - (Eliom_uri.make_string_uri - ~service:feed_service name) in - let title = Atom_feed.plain ("nice drawings of " ^ name) in - try_lwt - Ocsipersist.Polymorphic.find image_info_table name >|= - (fun (number,updated,list) -> Atom_feed.feed ~id ~updated ~title (entries name list 10)) - with - | Not_found -> - let now = CalendarLib.Calendar.now () in - Lwt.return (Atom_feed.feed ~id ~updated:now ~title []) - | e -> Lwt.fail e - -let feed name () = - let id = Xhtml.M.uri_of_string - (Eliom_uri.make_string_uri ~service:feed_service name) in - let title = Atom_feed.plain ("nice drawings of " ^ name) in - Lwt.catch - (fun () -> Ocsipersist.Polymorphic.find image_info_table name >|= - (fun (number,updated,list) -> Atom_feed.feed ~id ~updated ~title (entries name list 10))) - ( function Not_found -> - let now = CalendarLib.Calendar.now () in - Lwt.return (Atom_feed.feed ~id ~updated:now ~title []) - | e -> Lwt.fail e ) - -let () = Eliom_atom.Reg.register - ~service:feed_service feed diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti_feed/graffiti.eliom b/7.1/manual/files/tutorial/chapter3/multigraffiti_feed/graffiti.eliom deleted file mode 100644 index 3c2bbc1b..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti_feed/graffiti.eliom +++ /dev/null @@ -1,42 +0,0 @@ -{shared{ - open Eliom_content.Html5.D - open Common -}} -{client{ - open Client -}} -open Server -open Feed - -let start_drawing name image canvas = - let bus = get_bus name in - ignore {unit{ - let canceller = launch_client_canvas %bus %image %canvas in - Eliom_client.onunload (fun () -> stop_drawing canceller) - }} - -let counter = ref 0 - -let () = Connected.register ~service:multigraffiti_service - !% ( fun name () username -> - (* Some browsers won't reload the image, so we force - them by changing the url each time. *) - incr counter; - let image = - img ~alt:name - ~src:(make_uri - ~service:imageservice (name,!counter)) () in - let canvas = - canvas ~a:[ a_width width; a_height height ] - [pcdata "your browser doesn't support canvas"; br (); image] in - start_drawing name image canvas; - make_page - [h1 [pcdata name]; - disconnect_box (); - choose_drawing_form (); - a feed_service [pcdata "atom feed"] name; - div ( if name = username - then [save_image_box name] - else [pcdata "no saving"] ); - canvas;]) - diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti_macaque/Makefile b/7.1/manual/files/tutorial/chapter3/multigraffiti_macaque/Makefile deleted file mode 100644 index 7030ddca..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti_macaque/Makefile +++ /dev/null @@ -1,68 +0,0 @@ - -APP_NAME := graffiti -SERVER_PACKAGE := cairo, unix, macaque.syntax -CLIENT_PACKAGE := - -SERVER_FILES = common.ml server.ml ${wildcard *.eliom} -CLIENT_FILES = common.ml client.ml ${wildcard *.eliom} - -PORT := 8080 - -OCLOSURE := YES - -### - -all: local byte opt conf - -LIBDIR := local/var/www/lib -JSDIR := local/var/www/static - -include Makefile.common - -distclean:: - -rm css/closure - -rm -r local - -rm graffiti.conf - -#### - -DIRS = local/var/lib/ocsidbm local/var/run local/var/log \ - local/var/www/static local/var/www/lib local/etc \ - local/var/www/static/graffiti_saved - -local: ${DIRS} local/var/www/static/css local/var/www/static/images css/closure - -local/var/www/static/css: - ln -fs $(shell pwd)/css local/var/www/static/css - -local/var/www/static/images: - ln -fs $(shell pwd)/images local/var/www/static/images - -css/closure: - ln -fs $(shell ocamlfind query oclosure)/closure/goog/css/ css/closure - -${DIRS}: - mkdir -p $@ - -conf: graffiti.conf - -graffiti.conf: graffiti.conf.in - sed -e "s|%%SRC%%|$(shell pwd)|" \ - -e "s|%%LIBDIR%%|${LIBDIR}|" \ - -e "s|%%JSDIR%%|${JSDIR}|" \ - -e "s|%%PORT%%|${PORT}|" \ - $< > $@ - -run.local: graffiti.conf - ocsigenserver -c graffiti.conf - -run.opt.local: graffiti.conf - ocsigenserver.opt -c graffiti.conf - -#### - -install:: - install -d -m 775 ${INSTALL_USER} ${INSTALL_DIR}/static/css - install -m 664 ${INSTALL_USER} css/*.css ${INSTALL_DIR}/static/css - cd $(shell ocamlfind query oclosure)/closure/goog/css/ && \ - find -type f -exec install -D -m 664 {} ${INSTALL_DIR}/static/css/closure/{} \; diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti_macaque/create_table.sql b/7.1/manual/files/tutorial/chapter3/multigraffiti_macaque/create_table.sql deleted file mode 100644 index 1c9bebad..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti_macaque/create_table.sql +++ /dev/null @@ -1,5 +0,0 @@ - -CREATE TABLE users ( - login text NOT NULL, - password text NOT NULL -); diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti_macaque/graffiti.conf.in b/7.1/manual/files/tutorial/chapter3/multigraffiti_macaque/graffiti.conf.in deleted file mode 100644 index 85afbc9b..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti_macaque/graffiti.conf.in +++ /dev/null @@ -1,36 +0,0 @@ - - - - - %%PORT%% - - %%SRC%%/local/var/log - %%SRC%%/local/var/run - %%SRC%%/local/var/run/ocsigenserver_command - - utf-8 - - - - - - - - - - - - - - - - - - - %%SRC%%/%%JSDIR%% - - - - - - diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti_macaque/server.ml b/7.1/manual/files/tutorial/chapter3/multigraffiti_macaque/server.ml deleted file mode 100644 index be19c2d6..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti_macaque/server.ml +++ /dev/null @@ -1,236 +0,0 @@ -open Eliom_content -open Html5.D -open Common -open Lwt - -module My_app = - Eliom_registration.App (struct - let application_name = "graffiti" - end) - -let rgb_from_string color = (* color is in format "#rrggbb" *) - let get_color i = - (float_of_string ("0x"^(String.sub color (1+2*i) 2))) /. 255. - in - try get_color 0, get_color 1, get_color 2 with | _ -> 0.,0.,0. - -let launch_server_canvas () = - let bus = Eliom_bus.create Json.t in - - let draw_server, image_string = - let surface = Cairo.image_surface_create - Cairo.FORMAT_ARGB32 ~width ~height in - let ctx = Cairo.create surface in - ((fun ((color : string), size, (x1, y1), (x2, y2)) -> - - (* Set thickness of brush *) - Cairo.set_line_width ctx (float size) ; - Cairo.set_line_join ctx Cairo.LINE_JOIN_ROUND ; - Cairo.set_line_cap ctx Cairo.LINE_CAP_ROUND ; - let red, green, blue = rgb_from_string color 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.close_path ctx ; - - (* Apply the ink *) - Cairo.stroke ctx ; - ), - (fun () -> - let b = Buffer.create 10000 in - (* Output a PNG in a string *) - Cairo_png.surface_write_to_stream surface (Buffer.add_string b); - Buffer.contents b - )) - in - let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) in - bus,image_string - -let graffiti_info = Hashtbl.create 0 - -let imageservice = - Eliom_registration.String.register_service - ~path:["image"] - ~headers:Http_headers.dyn_headers - ~get_params:(let open Eliom_parameter in string "name" ** int "q") - (* we add another parameter for the browser not to cache: at least - for chrome, there is no way to force the browser to reload the - image without leaving the application *) - (fun (name,_) () -> - try_lwt - let _ ,image_string = Hashtbl.find graffiti_info name in - Lwt.return (image_string (), "image/png") - with - | Not_found -> raise_lwt Eliom_common.Eliom_404) - -let get_bus (name:string) = - (* create a new bus and image_string function only if it did not exists *) - try - fst (Hashtbl.find graffiti_info name) - with - | Not_found -> - let bus,image_string = launch_server_canvas () in - Hashtbl.add graffiti_info name (bus,image_string); - bus - -let main_service = Eliom_service.App.service ~path:[""] - ~get_params:(Eliom_parameter.unit) () -let multigraffiti_service = Eliom_service.App.service ~path:[""] - ~get_params:(Eliom_parameter.suffix (Eliom_parameter.string "name")) () - -let choose_drawing_form () = - get_form ~service:multigraffiti_service - (fun (name) -> - [fieldset - [label ~a:[a_for name] [pcdata "drawing name: "]; - string_input ~input_type:`Text ~name (); - br (); - string_input ~input_type:`Submit ~value:"Go" () - ]]) - -let connection_service = - Eliom_service.Http.post_coservice' - ~post_params:(let open Eliom_parameter in (string "name" ** string "password")) - () -let disconnection_service = Eliom_service.Http.post_coservice' ~post_params:Eliom_parameter.unit () -let create_account_service = - Eliom_service.Http.post_coservice ~fallback:main_service ~post_params:(let open Eliom_parameter in (string "name" ** string "password")) () - -let username = Eliom_reference.eref ~scope:Eliom_common.default_session_scope None - -module Lwt_thread = struct - include Lwt - include Lwt_chan -end -module Lwt_PGOCaml = PGOCaml_generic.Make(Lwt_thread) -module Lwt_Query = Query.Make_with_Db(Lwt_thread)(Lwt_PGOCaml) - -let get_db : unit -> unit Lwt_PGOCaml.t Lwt.t = - let db_handler = ref None in - fun () -> - match !db_handler with - | Some h -> Lwt.return h - | None -> Lwt_PGOCaml.connect ~database:"testbase" () - -let table = <:table< users ( - login text NOT NULL, - password text NOT NULL -) >> - -let find name = - (get_db () >>= fun dbh -> - Lwt_Query.view dbh - <:view< {password = user_.password} | - user_ in $table$; - user_.login = $string:name$; >>) - -let insert name pwd = - get_db () >>= fun dbh -> - Lwt_Query.query dbh - <:insert< $table$ := { login = $string:name$; password = $string:pwd$; } >> - -let check_pwd name pwd = - (get_db () >>= fun dbh -> - Lwt_Query.view dbh - <:view< {password = user_.password} | - user_ in $table$; - user_.login = $string:name$; - user_.password = $string:pwd$ >>) - >|= (function [] -> false | _ -> true) - -let () = Eliom_registration.Action.register - ~service:create_account_service - (fun () (name, pwd) -> - find name >>= - (function - | [] -> insert name pwd - | _ -> Lwt.return ())) - -let () = Eliom_registration.Action.register - ~service:connection_service - (fun () (name, password) -> - check_pwd name password >>= - (function - | true -> Eliom_reference.set username (Some name) - | false -> Lwt.return ())) - -let () = - Eliom_registration.Action.register - ~service:disconnection_service - (fun () () -> Eliom_state.discard ~scope:Eliom_common.default_session_scope ()) - -let disconnect_box () = - post_form disconnection_service - (fun _ -> [fieldset - [string_input - ~input_type:`Submit ~value:"Log out" ()]]) () - -let login_name_form service button_text = - post_form ~service - (fun (name1, name2) -> - [fieldset - [label ~a:[a_for name1] [pcdata "login: "]; - string_input ~input_type:`Text ~name:name1 (); - br (); - label ~a:[a_for name2] [pcdata "password: "]; - string_input ~input_type:`Password ~name:name2 (); - br (); - string_input ~input_type:`Submit ~value:button_text () - ]]) () - -let oclosure_script = - Html5.Id.create_global_elt - (js_script - ~uri:(make_uri (Eliom_service.static_dir ()) - ["graffiti_oclosure.js"]) ()) - -let make_page content = - Lwt.return - (html - (head - (title (pcdata "Graffiti")) - [ css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"common.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"hsvpalette.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"slider.css"]) (); - oclosure_script; - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"graffiti.css"]) (); - ]) - (body content)) - -let default_content () = - make_page - [h1 [pcdata "Welcome to Multigraffiti"]; - h2 [pcdata "log in"]; - login_name_form connection_service "Connect"; - h2 [pcdata "create account"]; - login_name_form create_account_service "Create account";] - -module Connected_translate = -struct - type page = string -> My_app.page Lwt.t - let translate page = - Eliom_reference.get username >>= - function - | None -> default_content () - | Some username -> page username -end - -module Connected = - Eliom_registration.Customize ( My_app ) ( Connected_translate ) - -let ( !% ) f = fun a b -> return (fun c -> f a b c) - -let () = Connected.register ~service:main_service - !% (fun () () username -> - make_page - [h1 [pcdata ("Welcome to Multigraffiti " ^ username)]; - choose_drawing_form ()]) diff --git a/7.1/manual/files/tutorial/chapter3/multigraffiti_ocsipersist/server.ml b/7.1/manual/files/tutorial/chapter3/multigraffiti_ocsipersist/server.ml deleted file mode 100644 index cce9f0a6..00000000 --- a/7.1/manual/files/tutorial/chapter3/multigraffiti_ocsipersist/server.ml +++ /dev/null @@ -1,208 +0,0 @@ -open Eliom_content.Html5.D -open Common -open Lwt - -module My_app = - Eliom_registration.App ( - struct - let application_name = "graffiti" - end) - -let rgb_from_string color = (* color is in format "#rrggbb" *) - let get_color i = - (float_of_string ("0x"^(String.sub color (1+2*i) 2))) /. 255. - in - try get_color 0, get_color 1, get_color 2 with | _ -> 0.,0.,0. - -let launch_server_canvas () = - let bus = Eliom_bus.create Json.t in - - let draw_server, image_string = - let surface = Cairo.image_surface_create - Cairo.FORMAT_ARGB32 ~width ~height in - let ctx = Cairo.create surface in - ((fun ((color : string), size, (x1, y1), (x2, y2)) -> - - (* Set thickness of brush *) - Cairo.set_line_width ctx (float size) ; - Cairo.set_line_join ctx Cairo.LINE_JOIN_ROUND ; - Cairo.set_line_cap ctx Cairo.LINE_CAP_ROUND ; - let red, green, blue = rgb_from_string color 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.close_path ctx ; - - (* Apply the ink *) - Cairo.stroke ctx ; - ), - (fun () -> - let b = Buffer.create 10000 in - (* Output a PNG in a string *) - Cairo_png.surface_write_to_stream surface (Buffer.add_string b); - Buffer.contents b - )) - in - let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) in - bus,image_string - -let graffiti_info = Hashtbl.create 0 - -let imageservice = - Eliom_registration.String.register_service - ~path:["image"] - ~headers:Http_headers.dyn_headers - ~get_params:(let open Eliom_parameter in string "name" ** int "q") - (* we add an int parameter for the browser not to cache the image: - at least for chrome, there is no way to force the browser to - reload the image without leaving the application *) - (fun (name,_) () -> - try_lwt - let _ ,image_string = Hashtbl.find graffiti_info name in - Lwt.return (image_string (), "image/png") - with - | Not_found -> raise_lwt Eliom_common.Eliom_404) - -let get_bus (name:string) = - (* create a new bus and image_string function only if it did not exists *) - try - fst (Hashtbl.find graffiti_info name) - with - | Not_found -> - let bus,image_string = launch_server_canvas () in - Hashtbl.add graffiti_info name (bus,image_string); - bus - -let main_service = Eliom_service.App.service ~path:[""] - ~get_params:(Eliom_parameter.unit) () -let multigraffiti_service = Eliom_service.App.service ~path:[""] - ~get_params:(Eliom_parameter.suffix (Eliom_parameter.string "name")) () - -let choose_drawing_form () = - get_form ~service:multigraffiti_service - (fun (name) -> - [fieldset - [label ~a:[a_for name] - [pcdata "drawing name: "]; - string_input ~input_type:`Text ~name (); - br (); - string_input - ~input_type:`Submit - ~value:"Go" () - ]]) - -let oclosure_script = - Eliom_content.Html5.Id.create_global_elt - (js_script - ~uri:(make_uri (Eliom_service.static_dir ()) - ["graffiti_oclosure.js"]) ()) - -let make_page content = - Lwt.return - (html - (head - (title (pcdata "Graffiti")) - [ css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"common.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"hsvpalette.css"]) (); - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"slider.css"]) (); - oclosure_script; - css_link - ~uri:(make_uri (Eliom_service.static_dir ()) - ["css";"graffiti.css"]) (); - ]) - (body content)) - -let connection_service = - Eliom_service.Http.post_coservice' ~post_params: - (let open Eliom_parameter in (string "name" ** string "password")) () -let disconnection_service = Eliom_service.Http.post_coservice' - ~post_params:Eliom_parameter.unit () -let create_account_service = - Eliom_service.Http.post_coservice ~fallback:main_service ~post_params: - (let open Eliom_parameter in (string "name" ** string "password")) () - -let username = Eliom_reference.eref ~scope:Eliom_common.default_session_scope None - -let user_table = Ocsipersist.Polymorphic.open_table "user_table" - -let check_pwd name pwd = - try_lwt - lwt saved_password = Ocsipersist.Polymorphic.find user_table name in - Lwt.return (pwd = saved_password) - with - Not_found -> Lwt.return false - -let () = Eliom_registration.Action.register - ~service:create_account_service - (fun () (name, pwd) -> Ocsipersist.Polymorphic.add user_table name pwd) - -let () = Eliom_registration.Action.register - ~service:connection_service - (fun () (name, password) -> - match_lwt check_pwd name password with - | true -> Eliom_reference.set username (Some name) - | false -> Lwt.return ()) - -let () = - Eliom_registration.Action.register - ~service:disconnection_service - (fun () () -> Eliom_state.discard ~scope:Eliom_common.default_session_scope ()) - -let disconnect_box () = - post_form disconnection_service - (fun _ -> [fieldset - [string_input - ~input_type:`Submit ~value:"Log out" ()]]) () - -let login_name_form service button_text = - post_form ~service - (fun (name1, name2) -> - [fieldset - [label ~a:[a_for name1] [pcdata "login: "]; - string_input ~input_type:`Text ~name:name1 (); - br (); - label ~a:[a_for name2] [pcdata "password: "]; - string_input - ~input_type:`Password - ~name:name2 (); - br (); - string_input - ~input_type:`Submit - ~value:button_text () - ]]) () - -let default_content () = - make_page - [h1 [pcdata "Welcome to Multigraffiti"]; - h2 [pcdata "log in"]; - login_name_form connection_service "Connect"; - h2 [pcdata "create account"]; - login_name_form create_account_service "Create account";] - -module Connected_translate = -struct - type page = string -> My_app.page Lwt.t - let translate page = - Eliom_reference.get username >>= - function - | None -> default_content () - | Some username -> page username -end - -module Connected = - Eliom_registration.Customize ( My_app ) ( Connected_translate ) - -let ( !% ) f = fun a b -> return (fun c -> f a b c) - -let () = Connected.register ~service:main_service - !% (fun () () username -> - make_page - [h1 [pcdata ("Welcome to Multigraffiti " ^ username)]; - choose_drawing_form ()]) diff --git a/7.1/manual/files/tutorial/chapter3/reactive_media_player/reactive_media_player.eliom b/7.1/manual/files/tutorial/chapter3/reactive_media_player/reactive_media_player.eliom deleted file mode 100644 index ff7c30e7..00000000 --- a/7.1/manual/files/tutorial/chapter3/reactive_media_player/reactive_media_player.eliom +++ /dev/null @@ -1,107 +0,0 @@ -[%%shared - open Eliom_content - open Html5 - - type action = Play | Pause | Seek of float -] - -let%client media_s, set_media_s = React.S.create Pause - -let%client progress_s, set_progress_s = React.S.create (0., 0.) - -let%client unblock_s, set_unblock_s = React.S.create true - -let progress_bar () = - let progress_value = - [%client - (let f (time, duration) = - if duration = 0. then 0. else time /. duration *. 100. - in - React.S.map f progress_s - : float React.signal) - ] in - let attrs = D.([ - a_input_min 0.; - a_input_max 100.; - a_onmousedown [%client fun _ -> set_unblock_s false]; - a_onmouseup [%client fun _ -> set_unblock_s true]; - C.attr [%client - R.a_value - (React.S.map (Printf.sprintf "%0.f") - (React.S.on unblock_s 0. ~%progress_value))] - ]) - in - let d_input = - D.Form.input ~input_type:`Range ~value:0. ~a:attrs - D.Form.float - in - let _ = [%client - (Lwt.async (fun () -> - let d_input = To_dom.of_input ~%d_input in - Lwt_js_events.inputs d_input (fun _ _ -> - set_media_s (Seek (Js.parseFloat d_input##.value)) ; - Lwt.return () - )) - : unit) - ] in - d_input - -let media_uri = - Html5.D.make_uri - ~service:(Eliom_service.static_dir ()) - ["hb.mp3"] - -let media_tag () = - let media = D.(audio ~src:media_uri [pcdata "alt"]) in - let _ = [%client - (Lwt.async (fun () -> - let media = To_dom.of_audio ~%media in - let media_map = function - | Play -> - media##play - | Pause -> - media##pause - | Seek f -> - media##.currentTime := (f /. 100. *. media##.duration) - in Lwt_react.S.keep (React.S.map media_map media_s) ; - Lwt_js_events.timeupdates media (fun _ _ -> - set_progress_s (media##.currentTime, media##.duration) ; - Lwt.return () - )) - : unit) - ] in - media - -let pause_button () = - D.(Form.button_no_value - ~button_type:`Button - ~a:[a_onclick [%client fun _ -> set_media_s Pause ]] - [pcdata "Pause"]) - -let play_button () = - D.(Form.button_no_value - ~button_type:`Button - ~a:[a_onclick [%client fun _ -> set_media_s Play ]] - [pcdata "Play"]) - -module React_Player_app = - Eliom_registration.App - (struct - let application_name = "react_player" - end) - -let media_service = - Eliom_service.App.service ~path:[] ~get_params:Eliom_parameter.unit () - -let () = - React_Player_app.register - ~service:media_service - (fun name () -> - let body = - D.(body [ - h2 [pcdata "Media"]; - media_tag (); - div [play_button (); pause_button (); progress_bar ()] - ]) - in - Lwt.return (Eliom_tools.D.html ~title:"Media" ~css:[] body)) diff --git a/7.1/manual/files/tutorial/client-server-req.svg b/7.1/manual/files/tutorial/client-server-req.svg deleted file mode 100644 index 1843e430..00000000 --- a/7.1/manual/files/tutorial/client-server-req.svg +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - page generation - let%rpc f (x : int) : int = ... [%client ... ~%b ... ] Lwt.return v - ...[%client ... ~%a ... ]Lwt.return (html (...) (...)) - execution of client fragments[%client ... ~%a ...] - execution of client fragments[%client ... ~%b ...] - rpc - js -html -+ injections ~%a - value v+ injections ~%b - https://... - display page - display page - click on a link - - page generation - client - server - - - - - - - - - - diff --git a/7.1/manual/files/tutorial/client-server.svg b/7.1/manual/files/tutorial/client-server.svg deleted file mode 100644 index 559552e2..00000000 --- a/7.1/manual/files/tutorial/client-server.svg +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - let%server - - - - - - - - let%client - - - - - - - - let%shared - - - - - - - - - let%server - - - - - - - - let%shared - - - - - - - - - let%client - - - - - - - - let%shared - - - - - - - - - - - - - - - ppx - ppx - - client-server type-checking - ocamlopt - js_of_ocaml - .cmxs - .js - .eliom - - diff --git a/7.1/manual/files/tutorial/static/css/common.css b/7.1/manual/files/tutorial/static/css/common.css deleted file mode 100644 index 02d7073c..00000000 --- a/7.1/manual/files/tutorial/static/css/common.css +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2009 The Closure Library Authors. All Rights Reserved. - * - * Use of this source code is governed by an Apache 2.0 License. - * See the COPYING file for details. - */ - -/* - * Cross-browser implementation of the "display: inline-block" CSS property. - * See http://www.w3.org/TR/CSS21/visuren.html#propdef-display for details. - * Tested on IE 6 & 7, FF 1.5 & 2.0, Safari 2 & 3, Webkit, and Opera 9. - * - * @author attila@google.com (Attila Bodis) - */ - -/* - * Default rule; only Safari, Webkit, and Opera handle it without hacks. - */ -.goog-inline-block { - position: relative; - display: -moz-inline-box; /* Ignored by FF3 and later. */ - display: inline-block; -} - -/* - * Pre-IE7 IE hack. On IE, "display: inline-block" only gives the element - * layout, but doesn't give it inline behavior. Subsequently setting display - * to inline does the trick. - */ -* html .goog-inline-block { - display: inline; -} - -/* - * IE7-only hack. On IE, "display: inline-block" only gives the element - * layout, but doesn't give it inline behavior. Subsequently setting display - * to inline does the trick. - */ -*:first-child+html .goog-inline-block { - display: inline; -} diff --git a/7.1/manual/files/tutorial/static/css/graffiti.css b/7.1/manual/files/tutorial/static/css/graffiti.css deleted file mode 100644 index 8287935b..00000000 --- a/7.1/manual/files/tutorial/static/css/graffiti.css +++ /dev/null @@ -1,68 +0,0 @@ -/* Graffiti - * http://www.ocsigen.org/graffiti - * Copyright (C) 2013 Vincent Balat - * Laboratoire PPS - CNRS Université Paris Diderot - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, with linking exception; - * either version 2.1 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -body { - width: 750px; - -webkit-user-select: none; -} - -canvas { - border: solid 1px #aaaaaa; -} - -div.ojw_colorpicker { - position: absolute; - top: 8px; - left: 740px; -} - -input { - width: 400px; -} - -.ot-color-picker.colorpicker { - height: 300px; - width: 400px; -} - -.slider { - -webkit-appearance: none; - height: 20px; - background: #d3d3d3; - outline: none; - opacity: 0.7; - -webkit-transition: .2s; - transition: opacity .2s; -} - -.slider:hover { - opacity: 1; -} - -.slider::-moz-range-thumb { - background: #cbd6f1; - cursor: pointer; -} - - -.undercanvas { - z-index: -1; - position: absolute; -} diff --git a/7.1/manual/files/tutorial/static/css/hsvpalette.css b/7.1/manual/files/tutorial/static/css/hsvpalette.css deleted file mode 100644 index d3ae7f67..00000000 --- a/7.1/manual/files/tutorial/static/css/hsvpalette.css +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2008 The Closure Library Authors. All Rights Reserved. - * - * Use of this source code is governed by an Apache 2.0 License. - * See the COPYING file for details. - */ - -/* - * All Rights Reserved. - * - * Styles for the HSV color palette. - * - * @author smcbride@google.com (Sean McBride) - * @author arv@google.com (Erik Arvidsson) - * @author manucornet@google.com (Manu Cornet) - */ - -.goog-hsv-palette, -.goog-hsv-palette-sm { - position: relative; - border: 1px solid #999; - border-color: #ccc #999 #999 #ccc; - width: 400px; - height: 276px; -} - -.goog-hsv-palette-sm { - width: 182px; - height: 185px; -} - -.goog-hsv-palette label span, -.goog-hsv-palette-sm label span { - display: none; -} - -.goog-hsv-palette-hs-backdrop, -.goog-hsv-palette-sm-hs-backdrop, -.goog-hsv-palette-hs-image, -.goog-hsv-palette-sm-hs-image { - position: absolute; - top: 10px; - left: 10px; - width: 256px; - height: 256px; - border: 1px solid #999; -} - -.goog-hsv-palette-sm-hs-backdrop, -.goog-hsv-palette-sm-hs-image { - top: 45px; - width: 128px; - height: 128px; -} - -.goog-hsv-palette-hs-backdrop, -.goog-hsv-palette-sm-hs-backdrop { - background-color: #000; -} - -.goog-hsv-palette-hs-image, -.goog-hsv-palette-v-image, -.goog-hsv-palette-hs-handle, -.goog-hsv-palette-v-handle { - background-image: url(../images/hsv-sprite.png); -} - -.goog-hsv-palette-noalpha .goog-hsv-palette-hs-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-v-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-hs-handle, -.goog-hsv-palette-noalpha .goog-hsv-palette-v-handle { - background-image: url(../images/hsv-sprite.gif); -} - -.goog-hsv-palette-sm-hs-image, -.goog-hsv-palette-sm-v-image, -.goog-hsv-palette-sm-hs-handle, -.goog-hsv-palette-sm-v-handle { - background-image: url(../images/hsv-sprite-sm.png); -} - -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-hs-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-v-image, -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-hs-handle, -.goog-hsv-palette-noalpha .goog-hsv-palette-sm-v-handle { - background-image: url(../images/hsv-sprite-sm.gif); -} - -.goog-hsv-palette-hs-image, -.goog-hsv-palette-sm-hs-image { - background-position: 0 0; -} - -.goog-hsv-palette-hs-handle, -.goog-hsv-palette-sm-hs-handle { - position: absolute; - left: 5px; - top: 5px; - width: 11px; - height: 11px; - overflow: hidden; - background-position: 0 -256px; -} - -.goog-hsv-palette-sm-hs-handle { - top: 40px; - background-position: 0 -128px; -} - -.goog-hsv-palette-v-image, -.goog-hsv-palette-sm-v-image { - position: absolute; - top: 10px; - left: 286px; - width: 19px; - height: 256px; - border: 1px solid #999; - background-color: #fff; - background-position: -256px 0; -} - -.goog-hsv-palette-sm-v-image { - top: 45px; - left: 155px; - width: 9px; - height: 128px; - background-position: -128px 0; -} - -.goog-hsv-palette-v-handle, -.goog-hsv-palette-sm-v-handle { - position: absolute; - top: 5px; - left: 279px; - width: 35px; - height: 11px; - background-position: -11px -256px; - overflow: hidden; -} - -.goog-hsv-palette-sm-v-handle { - top: 40px; - left: 148px; - width: 25px; - background-position: -11px -128px; -} - -.goog-hsv-palette-swatch, -.goog-hsv-palette-sm-swatch { - position: absolute; - top: 10px; - right: 10px; - width: 65px; - height: 65px; - border: 1px solid #999; - background-color: #fff; -} - -.goog-hsv-palette-sm-swatch { - top: 10px; - right: auto; - left: 10px; - width: 30px; - height: 22px; -} - -.goog-hsv-palette-input, -.goog-hsv-palette-sm-input { - position: absolute; - top: 85px; - right: 10px; - width: 65px; -} - -.goog-hsv-palette-sm-input { - top: 10px; - right: auto; - left: 50px; -} diff --git a/7.1/manual/files/tutorial/static/css/slider.css b/7.1/manual/files/tutorial/static/css/slider.css deleted file mode 100644 index ddd07482..00000000 --- a/7.1/manual/files/tutorial/static/css/slider.css +++ /dev/null @@ -1,45 +0,0 @@ -.goog-slider-vertical, -.goog-slider-horizontal { -background-color: ThreeDFace; -position: relative; -overflow: hidden; -} - -.goog-slider-vertical { -height: 300px; -width: 20px; -} - -.goog-slider-horizontal { -height: 20px; -width: 400px; -} - -.goog-slider-thumb { -position: absolute; -background-color: ThreeDShadow; -overflow: hidden; -} - -.goog-slider-vertical .goog-slider-thumb { -left: 0; -height: 20px; -width: 100%; -} - -.goog-slider-horizontal .goog-slider-thumb { -top: 0; -width: 20px; -height: 100%; -} -#s-h { -margin-bottom: 2em; -} -strong { -display: block; -margin-bottom: 3px; -} -#out1, #out2 { -color: #999; -margin-left: 1em; -} diff --git a/7.1/manual/files/tutorial/static/images/hsv-sprite-sm.png b/7.1/manual/files/tutorial/static/images/hsv-sprite-sm.png deleted file mode 100644 index 470f8e0c..00000000 Binary files a/7.1/manual/files/tutorial/static/images/hsv-sprite-sm.png and /dev/null differ diff --git a/7.1/manual/files/tutorial/tutowidgets/ex-final.png b/7.1/manual/files/tutorial/tutowidgets/ex-final.png deleted file mode 100644 index 0d1d2203..00000000 Binary files a/7.1/manual/files/tutorial/tutowidgets/ex-final.png and /dev/null differ diff --git a/7.1/manual/files/tutorial/tutowidgets/ex.css b/7.1/manual/files/tutorial/tutowidgets/ex.css deleted file mode 100644 index c07bc739..00000000 --- a/7.1/manual/files/tutorial/tutowidgets/ex.css +++ /dev/null @@ -1,30 +0,0 @@ -* { - font-family: sans-serif; -} -div.button { - background-color: #5577ee; - width: 200px; - height: 20px; - padding: 0; - text-align: center; - color: #222436; - font-weight: bold; - cursor: pointer; -} -div.content { - background-color: #55ee99; - width: 180px; - height: 40px; - padding: 10px; - margin-top: 0; - color: #222436; -} -div.mywidget { - display: inline-block; - margin: 0 10px 0 0; - height: 100px; - vertical-align: top; -} -.hidden { - display: none; -} diff --git a/7.1/manual/files/tutorial/tutowidgets/ex1.png b/7.1/manual/files/tutorial/tutowidgets/ex1.png deleted file mode 100644 index f5a581c8..00000000 Binary files a/7.1/manual/files/tutorial/tutowidgets/ex1.png and /dev/null differ diff --git a/7.1/manual/files/tutorial/tutowidgets/ex2.png b/7.1/manual/files/tutorial/tutowidgets/ex2.png deleted file mode 100644 index 931b5f8e..00000000 Binary files a/7.1/manual/files/tutorial/tutowidgets/ex2.png and /dev/null differ diff --git a/7.1/manual/hash-password.html b/7.1/manual/hash-password.html deleted file mode 100644 index 69b4cb6e..00000000 --- a/7.1/manual/hash-password.html +++ /dev/null @@ -1,68 +0,0 @@ -Protecting your passwords

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

Protecting your passwords

For protecting the user passwords or other sensitive data, -we can use -ocaml-safepass. -

We can now write the encrypted password in our database -(using Ocsipersist.table -in this example) as follow: -

let table =
-  Ocsipersist.Polymorphic.open_table "users"
-
-let add_user username password =
-  (* Check if the user is already exist *)
-  Lwt.try_bind
-    (fun () -> Ocsipersist.Polymorphic.find table username)
-    (fun _ -> Lwt.return false)
-    (function
-      | Not_found ->
-        Ocsipersist.Polymorphic.add table username
-          (Bcrypt.hash password, email)
-        >>= fun () ->
-        Lwt.return true
-      | e -> Lwt.fail e
-    )
diff --git a/7.1/manual/how-do-i-create-a-cryptographically-safe-identifier.html b/7.1/manual/how-do-i-create-a-cryptographically-safe-identifier.html deleted file mode 100644 index 1b5d3c7a..00000000 --- a/7.1/manual/how-do-i-create-a-cryptographically-safe-identifier.html +++ /dev/null @@ -1,47 +0,0 @@ -

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

diff --git a/7.1/manual/how-does-a-page-s-source-code-look.html b/7.1/manual/how-does-a-page-s-source-code-look.html deleted file mode 100644 index 9386b535..00000000 --- a/7.1/manual/how-does-a-page-s-source-code-look.html +++ /dev/null @@ -1,83 +0,0 @@ -How does a client-server app source code look like?

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

How does a client-server app source code look like?

Eliom client-server applications

Eliom client-server applications are running in your browser for a certain lifetime and consist of one or several pages/URLs. -An application has its associated js file, which must have the same name (generated automatically by the default makefile and added automatically by Eliom in the page). -

For example, we define an application called example: -

module Example =
-  Eliom_registration.App
-    (struct
-      let application_name = "example"
-     end)

Eliom Services

Pages are generated by services. Here is how to define a service: -

let main =
-  Eliom_service.create
-    ~path:(Eliom_service.Path [])
-    ~meth:(Eliom_service.Get unit)
-    ()

The path is a list of string corresponding to the url. Here, the list is empty because it's the main page. -

If, for example, the list is ["hello";"world"], the page will be accessed using the address: -

http://website.com/hello/world/

More informations about parameters: How to use GET parameters (parameters in the URL)? -

Now that our service has been declared, we associate to it an OCaml function that will generate the page. We call this service registration. If the service belongs to the application, we use the registrations functions from module Example defined above. -

let _ =
-  Example.register
-    ~service:main
-    (fun () () ->
-      Lwt.return ...)

The third parameter is the function that will be called to generate the page. -This function takes two parameters, corresponding respectively to GET and POST parameters. Here both are () because the service does not take any parameter. The function returns an element of type html (using Lwt). -

Page Content

The content of Eliom application pages is made using functions. -The html function takes two parameters: -head and body, which are also functions that takes parameters coresponding to their content. -

(html
-           (head (title (txt "Page Title")) [])
-           (body [p [txt "Hello World!"]])))

Most of the elements functions take a list of other elements as a parameter. -

ast -

Validity of HTML is checked at compile time, which means that a program -that may generate a page that does not respect the recommendations of the -W3C will be rejected at compile time. -

Download full code

Warning: This third party code may be outdated. Please notify the author is something is broken, or do a pull request on github. -

Links

diff --git a/7.1/manual/how-to-add-a-div.html b/7.1/manual/how-to-add-a-div.html deleted file mode 100644 index 28623f38..00000000 --- a/7.1/manual/how-to-add-a-div.html +++ /dev/null @@ -1,57 +0,0 @@ -How to add a div?

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

How to add a div?

div ~a:[a_class ["firstclass"; "secondclass"]] [txt "Hello!"]

Required parameter: list containing other elements -(Details of available elements in type -Html_types.flow5). -

Optional parameter for attributes "a" (How to set and id, classes or other attributes to HTML elements?). -

Download full code

Warning: This third party code may be outdated. Please notify the author is something is broken, or do a pull request on github. -

Links

diff --git a/7.1/manual/how-to-add-a-favicon.html b/7.1/manual/how-to-add-a-favicon.html deleted file mode 100644 index b864f3db..00000000 --- a/7.1/manual/how-to-add-a-favicon.html +++ /dev/null @@ -1,53 +0,0 @@ -How to add a Favicon?

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

How to add a Favicon?

A favicon is a file of type "ico" which contain a picture of size 16x16px. It is the picture that you can ususally see next to the title of the page on a browser. -

favicon for Ocsigen.org -

By default, all browsers look for a file favicon.ico -at the root of the website: -

http://website.com/favicon.ico

Just put the file at the root of the static directory set in the configuration file. -

Links

diff --git a/7.1/manual/how-to-add-a-javascript-script.html b/7.1/manual/how-to-add-a-javascript-script.html deleted file mode 100644 index c958d706..00000000 --- a/7.1/manual/how-to-add-a-javascript-script.html +++ /dev/null @@ -1,61 +0,0 @@ -How to add a Javacript script?

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

How to add a Javacript script?

If you have client-side programs on your website, you can use Eliom's client-server features, that will compile client side parts to JS using Ocsigen Js_of_ocaml, and include automatically the script in the page. But in some cases you may also want to include yourselves external JS scripts. -

Include the script on the html header

Javascript scripts are included in the header using the js_script function (defined in Error a_api: exception Dune__exe__Api.Error("invalid ocaml id \"Eliom_content.Html.D\"")). -

open Eliom_content.Html.D (* for make_uri an js_script *)
-
-js_script
-     ~uri:(make_uri (Eliom_service.static_dir ())
-              ["hello.js"])
-     ()

This function has 2 parameters: the file path and unit. -

The file path is generated using the make_uri function (from Error a_api: exception Dune__exe__Api.Error("invalid ocaml id \"Eliom_content.Html.D\"") module). This function creates the relative URL string using the static directory (which is a service) configured in the configuration file and the given list. -

Insert this piece of code on the list given in parameter to the head function. -

Or you can use: -Eliom_tools.F.head -

Call an external function

Have a look at -this page of Js_of_ocaml's manual -to understand how to call JS function from your OCaml program.

diff --git a/7.1/manual/how-to-add-a-list.html b/7.1/manual/how-to-add-a-list.html deleted file mode 100644 index 3e3b9f38..00000000 --- a/7.1/manual/how-to-add-a-list.html +++ /dev/null @@ -1,81 +0,0 @@ -How to add lists in a page?

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

How to add lists in a page?

Simple list and ordered list

Simple list
ul
-    [li [txt "first item"];
-     li [txt "second item"];
-     li [txt "third item"];
-     li [txt "fourth item"]]
Ordered list
ol
-    [li [txt "first item"];
-     li [txt "second item"]]

Required parameter: list containing li elements (Details of li content). -

Optional parameter for attributes "a" (How to set and id, classes or other attributes to HTML elements?). -

Definition list

dl
-    [((dt [txt "Banana"], []),
-      (dd [txt "An elongated curved fruit"], []));
-     ((dt [txt "Orange"], []),
-      (dd [txt "A globose, reddish-yellow, edible fruit"],
-       [dd [txt "A color between yellow and red"]]));
-     ((dt [txt "Kiwi"], []),
-      (dd [txt "Egg-sized green fruit from China"], []))]

This kind of list contains definitions. -

Required parameter: -A list of pair of: -

  • A pair containing: -
    • The first element of type dt -
    • A list of elements of type dt -
  • Another pair containing: -
    • The first element of type dd -
    • A list of elements of type dd -

Details: -

Optional parameter for attributes "a" (How to set and id, classes or other attributes to HTML elements?). -

Download full code

Warning: This third party code may be outdated. Please notify the author is something is broken, or do a pull request on github. -

Links

diff --git a/7.1/manual/how-to-add-a-select-or-other-form-element.html b/7.1/manual/how-to-add-a-select-or-other-form-element.html deleted file mode 100644 index e7913a00..00000000 --- a/7.1/manual/how-to-add-a-select-or-other-form-element.html +++ /dev/null @@ -1,71 +0,0 @@ -How to add a select (or other form element)?

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

How to add a select (or other form element)?

In forms towards Eliom services:

open Eliom_content
-
-Html.D.Form.select ~name:select_name
-  Html.D.Form.string (* type of the parameter *)
-  (Html.D.Form.Option
-    ([] (* attributes *),
-     "Bob" (* value *),
-     None (* Content, if different from value *),
-     false (* not selected *))) (* first line *)
-    [Html.D.Form.Option ([], "Marc", None, false);
-     (Html.D.Form.Optgroup
-        ([],
-         "Girls",
-         ([], "Karin", None, false),
-         [([a_disabled `Disabled], "Juliette", None, false);
-          ([], "Alice", None, true);
-          ([], "Germaine", Some (txt "Bob's mother"), false)]))]

Basic HTML5 elements without Eliom services

For example if you want to use them with client side event handler. -

open Eliom_content.Html.D
-
-Raw.select [option (txt "hello");
-            option ~a:[a_value "a"; a_selected `Selected] (txt "cool")]

Links

diff --git a/7.1/manual/how-to-add-an-image.html b/7.1/manual/how-to-add-an-image.html deleted file mode 100644 index a362d4d1..00000000 --- a/7.1/manual/how-to-add-an-image.html +++ /dev/null @@ -1,69 +0,0 @@ -How to add an image?

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

How to add an image?

Internal image -

img ~alt:("Ocsigen Logo")
-      ~src:(make_uri
-              ~service:(Eliom_service.static_dir ())
-              ["ocsigen_logo.png"])
-    ()

External image -

img ~alt:("Ocsigen Logo")
-      ~src:(Xml.uri_of_string ("http://website.com/image.png"))
-    ()

The function img has 3 parameters: -

  • alt: A description of the image -
  • src: the URL of the image -
  • unit -

For an internal image, the file path is generated using the make_uri function. This function creates the relative URL string using the static directory configured in the configuration file and the given list. -

For an external image, you must convert the string url into uri using the uri_of_string function. You can also create an external service. -

Download full code

Warning: This third party code may be outdated. Please notify the author is something is broken, or do a pull request on github. -

Links

diff --git a/7.1/manual/how-to-add-css-stylesheet.html b/7.1/manual/how-to-add-css-stylesheet.html deleted file mode 100644 index 17ab8261..00000000 --- a/7.1/manual/how-to-add-css-stylesheet.html +++ /dev/null @@ -1,66 +0,0 @@ -How to add a CSS stylesheet?

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

How to add a CSS stylesheet?

Warning: css_link and make_uri come from Eliom_content.Html.D module. This module is opened for each piece of code -

CSS stylesheet are included in the header using the css_link function. -

css_link
-     ~uri:(make_uri (Eliom_service.static_dir ())
-              ["css";"style.css"])
-     ()

This function has 2 parameters: the file path and unit. -

The file path is generated using the make_uri function. This function creates the relative URL string using the static directory configured in the configuration file and the given list. -

Where?

Insert this piece of code on the list given in parameter to the head function: -

(html
-     (head (title (txt "Page Title"))
-        [css_link ~uri:(make_uri (Eliom_service.static_dir ())
-	        	  ["css";"style.css"]) ()])
-     (body [p [txt "Hello World!"]]))))

Or you can use: -Eliom_tools.F.head -

Download full code

Warning: This third party code may be outdated. Please notify the author is something is broken, or do a pull request on github. -

Links

diff --git a/7.1/manual/how-to-attach-ocaml-values-to-dom-elements.html b/7.1/manual/how-to-attach-ocaml-values-to-dom-elements.html deleted file mode 100644 index c8f2b69f..00000000 --- a/7.1/manual/how-to-attach-ocaml-values-to-dom-elements.html +++ /dev/null @@ -1,57 +0,0 @@ -How to attach OCaml values to DOM elements?

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

How to attach OCaml values to DOM elements?

It is often convenient to attach OCaml values to certain elements of -the page. There are several ways to achieve this. -

  • The first possibility is to use DATA attributes (for example if the -page is generated on server side). -
  • Alternatively, you can add a new property to the element using the -##. syntax. -
    • You can create a new class type with the new property -(that can be either an OCaml value or a JS value), possibly -inheriting from the type of the DOM element, and create an -(unsafe) coercion function for this type; or simply -
    • Create getter and setter functions to add the new property.
diff --git a/7.1/manual/how-to-attach-ocaml-values-to-the-html-nodes-sent-to-the-client.html b/7.1/manual/how-to-attach-ocaml-values-to-the-html-nodes-sent-to-the-client.html deleted file mode 100644 index 7ed345ea..00000000 --- a/7.1/manual/how-to-attach-ocaml-values-to-the-html-nodes-sent-to-the-client.html +++ /dev/null @@ -1,47 +0,0 @@ -

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

diff --git a/7.1/manual/how-to-build-js-object.html b/7.1/manual/how-to-build-js-object.html deleted file mode 100644 index 8a1c3e49..00000000 --- a/7.1/manual/how-to-build-js-object.html +++ /dev/null @@ -1,51 +0,0 @@ -How to build js object?

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

How to build js object?

Use syntax new%js: -

Example: -

let get_timestamp () =
-  let date = new%js Js.date_now in
-  int_of_float (Js.to_float (date##getTime))

More details in documentation: Js object constructor

diff --git a/7.1/manual/how-to-call-a-server-side-function-from-client-side.html b/7.1/manual/how-to-call-a-server-side-function-from-client-side.html deleted file mode 100644 index 64a25be7..00000000 --- a/7.1/manual/how-to-call-a-server-side-function-from-client-side.html +++ /dev/null @@ -1,63 +0,0 @@ -How to call a server-side function from client-side?

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

How to call a server-side function from client-side?

It is possible to call server-side functions in client-side. -For security reasons, these functions must first -be declared explicitely as RPCs (with the type of their -argument). -

let%rpc f (x : int) : string Lwt.t = ...
-...
[%client ... f 4 ... ]

The syntax is provided by opam package ocsigen-ppx-rpc. -

The server-side function (f in the example) needs to return a -Lwt value. -

Server functions are just syntactic sugar for pathless services -returning OCaml values. -

Note that you need to install ppx_deriving, and load our JSON -ppx_deriving plugin in your project. The plugin is available as -the Ocamlfind package js_of_ocaml.deriving.ppx. -

If the function takes a more complex type, this type must have been -declared with ppx_deriving. For example, -

type t = int * string [@@deriving json]

Our infrastructure provides server-side data validation of the data -sent, and prevents malformed data from crashing the server.

diff --git a/7.1/manual/how-to-call-an-ocaml-function-from-js-code.html b/7.1/manual/how-to-call-an-ocaml-function-from-js-code.html deleted file mode 100644 index 6a36a7f4..00000000 --- a/7.1/manual/how-to-call-an-ocaml-function-from-js-code.html +++ /dev/null @@ -1,47 +0,0 @@ -How to call an OCaml function from JS code?

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

How to call an OCaml function from JS code?

Have a look at function Js.wrap_callback

diff --git a/7.1/manual/how-to-compile-my-ocsigen-pages.html b/7.1/manual/how-to-compile-my-ocsigen-pages.html deleted file mode 100644 index a49b5129..00000000 --- a/7.1/manual/how-to-compile-my-ocsigen-pages.html +++ /dev/null @@ -1,68 +0,0 @@ -How to compile my Ocsigen pages?

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

How to compile my Ocsigen pages?

Eliom distillery

Eliom-distillery will help you to build your client-server application -using Eliom. -It comes with several templates ("client-server.basic", "os.pgocaml", -and more to come ...). -

$ eliom-distillery -name <name> -template client-server.basic [-target-directory <dir>]
-

Eliom distillery will also create a default configuration file for Ocsigen -Server. -

More information on Eliom distillery in -Eliom's manual. -

More information on how client-server Eliom project are compiled on -this page. -

If you don't need client-server features, the compilation process is very -simple and without surprise. Compile with ocamlc or ocamlopt -using ocamlfind, with package eliom.server. -You will have to create your configuration file manually. -But you can still use Eliom distillery, which will make easier the inclusion -of client side features, later. -

Compilation details

  • eliomdep helps you handle dependencies of eliom files -
  • eliomc compile server-side eliom files (and ml files too) -
  • js_of_eliom compile client-side eliom files -

Read manuals for mor information about these compilers. -

Links

diff --git a/7.1/manual/how-to-configure-and-launch-the-ocsigen-server.html b/7.1/manual/how-to-configure-and-launch-the-ocsigen-server.html deleted file mode 100644 index bb9b67ff..00000000 --- a/7.1/manual/how-to-configure-and-launch-the-ocsigen-server.html +++ /dev/null @@ -1,53 +0,0 @@ -How to configure and launch the Ocsigen Server?

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

How to configure and launch the Ocsigen Server?

Default configuration file

  • Eliom distillery is generating a configuration file -
  • If you want to create it yourself, you can have a look at the default configuration file provided with the installation of Ocsigen (usually /etc/ocsigenserver/ocsigenserver.conf.sample). -
  • Change the port by the network port number you want your website to work on. The port 80 is the port used by http by default but you need to be the administrator of your server to use it. -
  • Adapt paths. -
  • Change other parameters that suit your needs using the configuration file documentation. -

Your own configuration file

Have a look at the configuration file full documentation. -

Launch the server

ocsigenserver -c your_config_file.conf

Links

diff --git a/7.1/manual/how-to-create-form-wizard-sequence-of-pages-depending-on-data-entered-on-previous-ones.html b/7.1/manual/how-to-create-form-wizard-sequence-of-pages-depending-on-data-entered-on-previous-ones.html deleted file mode 100644 index 78fba490..00000000 --- a/7.1/manual/how-to-create-form-wizard-sequence-of-pages-depending-on-data-entered-on-previous-ones.html +++ /dev/null @@ -1,127 +0,0 @@ -How to create a form wizard (sequence of pages depending on data entered on previous ones)?

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

How to create a form wizard (sequence of pages depending on data entered on previous ones)?

The solution to implement wizard-like forms (that is a sequence of pages -depending on data entered on previous ones) is to create dynamically new -services especially for one user. If you use session data (eliom references) -it won't work if you have several tabs on the same site (or if you use the -back button). -

Just create new anonymous (attached)-coservices that depend on the data sent -by the user. In the following example (taken from Eliom's testsuite), I create -a two step wizard that will add two integers. You can try to enter a first -value, duplicate the tab, and both will continue working correctly. -

To prevent memory leaks, use a timeout for these dynamic coservices. -The scope of these service can be session or global for the site (as coservice -identifiers are cryptographically safe). -

open Eliom_content.Html.F
-
-(* -------------------------------------------------------- *)
-(* We create two main services on the same URL,             *)
-(* one with a GET integer parameter:                        *)
-
-let calc = Eliom_service.create
-    ~path:(Eliom_service.Path ["calc"])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit) ()
-let calc_i =
-  Eliom_service.create
-    ~path:(Eliom_service.Path ["calc"])
-    ~meth:(Eliom_service.Get (Eliom_parameter.int "i"))
-    ()
-
-(* -------------------------------------------------------- *)
-(* The handler for the service without parameter.           *)
-(* It displays a form where you can write an integer value: *)
-
-let calc_handler () () =
-  let create_form intname =
-    [p [txt "Write a number: ";
-        Form.input ~input_type:`Text ~name:intname Form.int;
-        br ();
-        Form.input ~input_type:`Submit ~value:"Send" Form.string]]
-  in
-  let f = Form.get_form calc_i create_form in
-  Lwt.return (html (head (title (txt "")) []) (body [f]))
-
-
-(* -------------------------------------------------------- *)
-(* The handler for the service with parameter.              *)
-(* It creates dynamically and registers a new coservice     *)
-(* with one GET integer parameter.                          *)
-(* This new coservice depends on the first value (i)        *)
-(* entered by the user.                                     *)
-
-let calc_i_handler i () =
-  let create_form is =
-    (fun entier ->
-       [p [txt (is^" + ");
-           Form.input ~input_type:`Text ~name:entier Form.int;
-           br ();
-           Form.input ~input_type:`Submit ~value:"Sum" Form.string]])
-  in
-  let is = string_of_int i in
-  let calc_result =
-    Eliom_registration.Html.attach_get
-      ~scope:Eliom_common.default_session_scope
-      ~fallback:calc
-      ~get_params:(Eliom_parameter.int "j")
-      ~timeout:120.
-      (fun j () ->
-        let js = string_of_int j in
-        let ijs = string_of_int (i+j) in
-        Lwt.return (html
-             (head (title (txt "")) [])
-             (body [p [txt (is^" + "^js^" = "^ijs)]])))
-  in
-  let f = Form.get_form calc_result (create_form is) in
-  Lwt.return (html (head (title (txt "")) []) (body [f]))
-
-
-(* -------------------------------------------------------- *)
-(* Registration of main services:                           *)
-
-let () =
-  Eliom_registration.Html.register calc   calc_handler;
-  Eliom_registration.Html.register calc_i calc_i_handler
diff --git a/7.1/manual/how-to-create-link-to-a-current-page-without-knowing-its-url.html b/7.1/manual/how-to-create-link-to-a-current-page-without-knowing-its-url.html deleted file mode 100644 index 0fbd0847..00000000 --- a/7.1/manual/how-to-create-link-to-a-current-page-without-knowing-its-url.html +++ /dev/null @@ -1,51 +0,0 @@ -How to create a link to the current page (without knowing its URL)?

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

How to create a link to the current page (without knowing its URL)?

Void coservices are here for that: -

a ~service:Eliom_service.reload_action
-  [txt "Click to reload"] ();

More information in -Eliom's manual, and API documentation of -Eliom_service.reload_action.

diff --git a/7.1/manual/how-to-detect-channel-disconnection.html b/7.1/manual/how-to-detect-channel-disconnection.html deleted file mode 100644 index 894cdeb2..00000000 --- a/7.1/manual/how-to-detect-channel-disconnection.html +++ /dev/null @@ -1,87 +0,0 @@ -How to detect channel disconnection

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

How to detect channel disconnection

Question

Is there a way to detect that some Eliom_comet channel became -disconnected? I would like to warn the user if the server becomes -unreachable. -

Answer

If you are using Ocsigen-start, you probably have nothing to do. -Ocsigen-start will monitor the life of sessions and close the process -when needed. -

If you are not using Ocsigen-start, you must catch exceptions -Eliom_comet.Channel_full, -Eliom_comet.Channel_closed (and Eliom_comet.Process_closed -on Eliom < 5). Have a look at module Os_comet in -Ocsigen-start. -

explanation pasted from Ocsigen mailing list, 2014-08-06: -

I assume that you want to know that from client side (It is also -possible to know that on server side). -Since there are quite a lot of different kind of channels, there are -different variations of 'disconnected' for them. You receive this -information through an exception when you try to read from the -Lwt_stream. The different exceptions you have to handle depending on the -case are: -

Eliom_comet.Channel_full -
For global channel. Those are never closed on server side and are not attached to a particular session. The server maintain a limited buffer of previous values to avoid memory leak. If a client reconnects to the network and too many messages have been sent to the channel, since its last update, he will receive that exception. -
Eliom_comet.Process_closed (Eliom < 5) -
For a channel associated to a client process. If the process is explicitely closed on the server, the server is rebooted, it timeouted, ... -
Eliom_comet.Channel_closed -
The channel was not maintained alive on the server (garbage collected). -

Usualy it is just simpler to not distinguish and just catch on all those -exception and consider their meaning to be 'disconnected'. -

To handle that reliably, you must be careful: an Lwt_stream raises an -exception only once. One way to ensure that you can't miss an exception -is to map your channel through Lwt_stream.map_exn and handle the -exceptions at that point. -

Depending on your case the strategies I would recommend would be either: -

  • If you maintain a global state between the client and the server, it -is usualy easier to rebuild it from scratch than to rebuild only the -missing parts: i.e. the braindead/failure proof solution is to reload -the complete contents -(Eliom_client.exit_to ~service.Eliom_service.reload_action () ()). -
  • If you only want to follow a stream of events and missing a few in the -middle isn't a problem, you can just provides a Caml service that sends -a channel, and when your channel disconnects, you just fetch a new one. -(You may have to use Lwt_stream.concat to do the plumbing)
diff --git a/7.1/manual/how-to-detect-on-client-side-that-the-server-side-state-for-the-process-is-closed.html b/7.1/manual/how-to-detect-on-client-side-that-the-server-side-state-for-the-process-is-closed.html deleted file mode 100644 index 670b92bc..00000000 --- a/7.1/manual/how-to-detect-on-client-side-that-the-server-side-state-for-the-process-is-closed.html +++ /dev/null @@ -1,56 +0,0 @@ - How to detect on client side that the server side state for the process is closed?

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

How to detect on client side that the server side state for the process is closed?

If you are using Ocsigen-start, you probably have nothing to do. -Ocsigen-start will monitor the life of sessions and close the process -when needed. -

If you are not using Ocsigen-start, you must catch exceptions -Eliom_comet.Channel_full, -Eliom_comet.Channel_closed (and Eliom_comet.Process_closed -on Eliom < 5). -For example, you can use a channel with a different client-process scope -to warn client processes that the session is closed. -Have a look at module Os_comet in Ocsigen-start.

diff --git a/7.1/manual/how-to-do-links-to-other-pages.html b/7.1/manual/how-to-do-links-to-other-pages.html deleted file mode 100644 index 7daf989f..00000000 --- a/7.1/manual/how-to-do-links-to-other-pages.html +++ /dev/null @@ -1,68 +0,0 @@ -How to do links to other pages?

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

How to do links to other pages?

Examples: -

(* Link to a service without parameter: *)
-Html.D.a ~service:coucou [txt "coucou"] ();
-
-(* Link to a service with parameters of type (int * (int * string)): *)
-Html.D.a ~service:coucou_params
-  [txt "coucou_params"] (42, (22, "ciao"));
-
-(* Link to an external service: *)
-Html.D.a
-  (Eliom_service.extern
-    ~prefix:"http://fr.wikipedia.org"
-    ~path:["wiki";""]
-    ~meth:(Eliom_service.Get Eliom_parameter.(suffix (all_suffix "suff")))
-    ())
-  [txt "OCaml on wikipedia"]
-  ["OCaml"];
-
-(* Low level interface for links: *)
-Html.F.Raw.a
-  ~a:[a_href (Xml.uri_of_string "http://en.wikipedia.org/wiki/OCaml")]
-  [txt "OCaml on wikipedia"]
diff --git a/7.1/manual/how-to-implement-a-notification-system.html b/7.1/manual/how-to-implement-a-notification-system.html deleted file mode 100644 index d23633b2..00000000 --- a/7.1/manual/how-to-implement-a-notification-system.html +++ /dev/null @@ -1,121 +0,0 @@ -How to implement a notification system?

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

How to implement a notification system?

The easiest way for the server to send notifications to the client is -to use module Os_notif from -Ocsigen-start (OS), but it requires to use OS's user management -system. If you are not using OS, we recommend to get inspiration from -the code of this module, to implement your notification system -conveniently and without memory leak. -

With Os_notif

For each type of data on which you want to receive updates, -instanciate functor Os_notif.Make_Simple on server side: -

module My_notif = Os_notif.Make_Simple (struct
-  type key = ... (* The type of the identifier of the data we want listen on. *)
-  type notification = ... (* The type of the notifications that will be sent *)
-end)

To declare that you are listening on one piece of data i, -call My_notif.listen i. -

To notify all the clients listening on data i, -call My_notif.notify i (fun userid_o -> Lwt.return (Some notif)). -userid_o is the id of the user who will be notified, -if he is connected (None otherwise). -This make possible to customize the notifications. -Return None if you don't want him to be notified. -

For more information, have a look at -the tutorial -about client-server reactive applications. -

Without Os_notif (manual implementation)

If you want the user to receive notifications from the server -(for example: "you have a new message"), first create a channel, and -register it in an Eliom reference of scope `Client_process: -

let channel_ref =
-  Eliom_reference.Volatile.eref_from_fun
-    ~scope:Eliom_common.default_process_scope
-    (fun () ->
-       let (s, notify) = Lwt_stream.create () in
-       let c = Eliom_comet.Channel.create s in
-       (c, notify)
-    )

On client side, listen on this channel. To do that, execute this code -(on server side) during the service that will start the client process: -

let (channel, _) = Eliom_reference.Volatile.get channel_ref in
-let _ =
-  [%client
-    (Lwt.async (fun () ->
-       Lwt_stream.iter_s
-       (fun v -> (* do something *))
-       ~%channel)
-    : unit)
-  ]
-in
-...

And call function notify on the channel (from server side) -when you want to notify the client. -

To get back the notify functions for one user, you may want to -iterate on all client process states. -To do that, create a session group for each user -(see How to register session data). -Here we suppose that the session group name is the user_id, as a string. -Then iterate on all sessions from this group, and on all client processes -for each session. -

For example: -

let notify user_id v =
-  (* We get the session group state for this user: *)
-  let state =
-    Eliom_state.Ext.volatile_data_group_state
-      ~scope:Eliom_common.default_group_scope (Int64.to_string user_id)
-  in
-  (* We iterate on all sessions from the group: *)
-  Eliom_state.Ext.iter_volatile_sub_states
-    ~state
-    (fun state ->
-      (* We iterate on all client process states in the session: *)
-      Eliom_state.Ext.iter_volatile_sub_states
-        ~state
-        (fun state ->
-          let (_, notify) = Eliom_reference.Volatile.Ext.get state channel_ref in
-          notify (Some v)))

Warning: If you do not call the iterators during -a request or during the initialisation phase of the Eliom module, -you must provide the extra parameter ?sitedata, -that you can get by calling Eliom_request_info.get_sitedata -during the initialisation phase of the Eliom module. -

Have a look at the implementation of Os_notif for more details.

diff --git a/7.1/manual/how-to-insert-raw-form-elements-not-belonging-to-a-form-towards-a-service.html b/7.1/manual/how-to-insert-raw-form-elements-not-belonging-to-a-form-towards-a-service.html deleted file mode 100644 index 78d9b362..00000000 --- a/7.1/manual/how-to-insert-raw-form-elements-not-belonging-to-a-form-towards-a-service.html +++ /dev/null @@ -1,59 +0,0 @@ -How to insert &quot;raw&quot; form elements (not belonging to a form towards a service)?

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

How to insert "raw" form elements (not belonging to a form towards a service)?

Eliom redefines most forms elements (inputs, textareas, checkboxes, etc.) -to make possible to check the type of the form w.r.t. the type of the service. -

If you don't want that (for example if you want to use it only from a client side program), -you can use "raw form elements" (that is, basic tyxml elements), using -module Eliom_content.Html.D.Raw -(or Eliom_content.Html.F.Raw). -

Example: -

{{{
-open Eliom_content.Html.D
-
-Raw.textarea (txt "blah")
-
-}}}
diff --git a/7.1/manual/how-to-iterate-on-all-sessions-for-one-user-or-all-tabs.html b/7.1/manual/how-to-iterate-on-all-sessions-for-one-user-or-all-tabs.html deleted file mode 100644 index d867b04a..00000000 --- a/7.1/manual/how-to-iterate-on-all-sessions-for-one-user-or-all-tabs.html +++ /dev/null @@ -1,65 +0,0 @@ -How to iterate on all session belonging to the same user, or all tabs?

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

How to iterate on all session belonging to the same user, or all tabs?

You must create a session group for each user, then iterate on all -the sessions from this group, and possibly on all client processes -for each session: -

(* We get the session group state for this user: *)
-  let state =
-    Eliom_state.Ext.volatile_data_group_state
-      ~scope:Eliom_common.default_group_scope (Int64.to_string user_id)
-  in
-
-  (* We iterate on all sessions from the group: *)
-  Eliom_state.Ext.iter_volatile_sub_states
-    ~state
-    (fun state ->
-      (* We iterate on all client process states in the session: *)
-      Eliom_state.Ext.iter_volatile_sub_states
-        ~state
-        (fun state ->
-          let a = Eliom_reference.Volatile.Ext.get state my_ref in
-          ...)))
diff --git a/7.1/manual/how-to-know-whether-the-browser-window-has-the-focus-or-not.html b/7.1/manual/how-to-know-whether-the-browser-window-has-the-focus-or-not.html deleted file mode 100644 index 4d5731cd..00000000 --- a/7.1/manual/how-to-know-whether-the-browser-window-has-the-focus-or-not.html +++ /dev/null @@ -1,55 +0,0 @@ -How to know whether the browser window has the focus or not?

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

How to know whether the browser window has the focus or not?

Example: -

let has_focus = ref true
-
-let _ =
-  let open Lwt_js_events in
-  async (fun () -> focuses (Js.Unsafe.coerce (Dom_html.window))
-    (fun _ _ -> has_focus := true; Lwt.return ()));
-  async (fun () -> blurs (Js.Unsafe.coerce (Dom_html.window))
-    (fun _ _ -> has_focus := false; Lwt.return ()));
diff --git a/7.1/manual/how-to-make-hello-world-in-ocsigen.html b/7.1/manual/how-to-make-hello-world-in-ocsigen.html deleted file mode 100644 index 0a4f1cda..00000000 --- a/7.1/manual/how-to-make-hello-world-in-ocsigen.html +++ /dev/null @@ -1,78 +0,0 @@ -How to make a &quot;hello world&quot; in Ocsigen?

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

How to make a "hello world" in Ocsigen?

Here it is! The famous "Hello World" for a client/server Eliom application: -

open Eliom_content
-open Html.D
-open Eliom_parameter
-
-module Example =
-  Eliom_registration.App
-    (struct
-      let application_name = "example"
-     end)
-
-let main =
-  Eliom_service.create
-    ~path:(Eliom_service.Path [])
-    ~meth:(Eliom_service.Get unit)
-    ()
-
-let _ =
-
-  Example.register
-    ~service:main
-    (fun () () ->
-      Lwt.return
-	(html
-	   (head (title (txt "Hello World of Ocsigen")) [])
-	   (body [h1 [txt "Hello World!"];
-		  p [txt "Welcome to my first Ocsigen website."]])))

Links

diff --git a/7.1/manual/how-to-make-page-a-skeleton.html b/7.1/manual/how-to-make-page-a-skeleton.html deleted file mode 100644 index 0b12654f..00000000 --- a/7.1/manual/how-to-make-page-a-skeleton.html +++ /dev/null @@ -1,61 +0,0 @@ -How to make a page skeleton?

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

How to make a page skeleton?

The same header for all your pages

When your site will grow, you will have several different services for pages which will often contain the same header informations. -

A great solutions to avoid code copy-pasting of these recurrent informations are to make a page skeleton function: -

let skeleton body_content =
-  Lwt.return
-    (html
-       (head (title (txt "Page Title")) [])
-       (body body_content)))

So when you define your pages, you just call this skeleton with the content of the page as an argument: -

Example.register ~service:main
-    (fun () () -> skeleton [p [txt "Hello World!"]])

This method can also be adapted to add elements on all your pages, like a header, a footer and a menu. -

Module Eliom_tools has a predefined function -for this: -

Eliom_tools.D.html
-  ~title:"ex"
-  ~css:[["css"; "ex.css"]]
-  (body [h2 [txt "Welcome to Ocsigen!"]]
diff --git a/7.1/manual/how-to-make-responsive-css.html b/7.1/manual/how-to-make-responsive-css.html deleted file mode 100644 index 23c33f1d..00000000 --- a/7.1/manual/how-to-make-responsive-css.html +++ /dev/null @@ -1,51 +0,0 @@ -How to make reponsive CSS with ocsigen?

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

How to make reponsive CSS with ocsigen?

The best way to do that is to make one general css sheet plus three css sheets, small, medium and large screen using media queries, a feature introduced in CSS3. -

Write theses lines in your css sheets: -

@media only screen and (max-device-width: 480px)
-@media only screen and (min-device-width: 481px) and (max-device-width: 768px)
-@media only screen and (min-device-width: 769px)

You can find more informations about making responsive websites with media queries on W3schools.com

diff --git a/7.1/manual/how-to-make-the-client-side-program-get-an-html-element-from-the-server-and-insert-it-in-the-page.html b/7.1/manual/how-to-make-the-client-side-program-get-an-html-element-from-the-server-and-insert-it-in-the-page.html deleted file mode 100644 index 436a8f25..00000000 --- a/7.1/manual/how-to-make-the-client-side-program-get-an-html-element-from-the-server-and-insert-it-in-the-page.html +++ /dev/null @@ -1,60 +0,0 @@ -How to make the client side program get an HTML element from the server and insert it in the page?

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

How to make the client side program get an HTML element from the server and insert it in the page?

A very convenient way to do that is to use RPCs : -

let%rpc get_mydiv (() : unit) : _ Lwt.t = div [ ... ]
[%client
-  ...
-  let%lwt mydiv = get_mydiv () in
-  Dom.appendChild parent (To_dom.of_element mydiv)
-  ...
-]

Server functions take exactly one parameter. -

RPCs are just syntactic sugar for pathless services -returning OCaml values. -

The type of the function parameter must have been declared with the -JSON module of ppx_deriving (which needs to be manually -installed). This enables server-side data validation. -

example: -

type t = int * string [@@deriving json]
diff --git a/7.1/manual/how-to-register-a-service-that-decides-itself-what-to-send.html b/7.1/manual/how-to-register-a-service-that-decides-itself-what-to-send.html deleted file mode 100644 index 25ffe43a..00000000 --- a/7.1/manual/how-to-register-a-service-that-decides-itself-what-to-send.html +++ /dev/null @@ -1,61 +0,0 @@ -How to register a service that decides itself what to send?

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

How to register a service that decides itself what to send?

Use Eliom_registration.Any. -

In the following example, we send an Html page or a redirection: -

let send_any =
-  Eliom_registration.Any.create
-    ~path:(Eliom_service.Path ["any"])
-    ~meth:(Eliom_service.Get (string "s"))
-   (fun s () ->
-     if s = "toto"
-     then
-       Eliom_registration.Html.send
-         (html
-            (head (title (txt "")) [])
-            (body [p [txt "Hello."]]))
-     else
-       Eliom_registration.Redirection.send other_service)
diff --git a/7.1/manual/how-to-register-session-data.html b/7.1/manual/how-to-register-session-data.html deleted file mode 100644 index 261c37d5..00000000 --- a/7.1/manual/how-to-register-session-data.html +++ /dev/null @@ -1,83 +0,0 @@ -How to register session data?

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

How to register session data?

It is very easy to register session data using Eliom references. -Just create an Eliom reference of scope session and its value -will be different for each session (one session = one browser process). -

But most of the time, what we want is to store data for one user, -not for one browser instance. To do that, we group all the sessions -for one user together, by creating a group of sessions. -The name of the group may be for example the user id. -

To create a session group, open the session by doing something like: -

let open_session login password =
-  let%lwt b = check_password login password in
-  if b
-  then Eliom_state.set_volatile_data_session_group
-      ~scope:Eliom_common.default_session_scope
-      (Int64.to_string (get_userid login))
-  else ...

If you want to store user data, -just create a global Eliom reference of scope group: -

let myref =
-  Eliom_reference.Volatile.eref
-    ~scope:Eliom_common.default_group_scope
-    None

And during a request, set this reference: -

...
-  Eliom_reference.Volatile.set myref (Some "user data")

The value of this reference is different for each user: -

Eliom_reference.Volatile.get myref

It is possible to create persistent Eliom references (see module -Eliom_reference). -

Other scopes

If you want to store server side data for one browser (one session), -use scope Eliom_common.default_session_scope. -

If you want to store server side data for one tab of your browser (one client process), -use scope Eliom_common.default_process_scope (available only if you -have a client-server Eliom application). -

If you want to store server side data during one request, -use scope Eliom_common.request_scope. -

Persistent references

Module -Eliom_reference -also defines an interface for persistent references, that will survive -if you restart the server. -

Links

diff --git a/7.1/manual/how-to-send-a-file-to-server-without-stopping-the-client-process.html b/7.1/manual/how-to-send-a-file-to-server-without-stopping-the-client-process.html deleted file mode 100644 index 244023ff..00000000 --- a/7.1/manual/how-to-send-a-file-to-server-without-stopping-the-client-process.html +++ /dev/null @@ -1,86 +0,0 @@ -How to send a file to the server without stopping the client process?

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

How to send a file to the server without stopping the client process?

This requires Eliom ≥ 3.1. -

Due to security reasons, browsers have limitations on sending files. -But if the file is chosen by the user through an input file element, -there is a way to send it to the server. You can't use the server_function -or let%rpc syntax for this, but you can use -Eliom_client.call_caml_service. -

Example: -

[%%client
-open Js_of_ocaml
-open Js_of_ocaml_lwt
-open Eliom_content.Html
-open Eliom_content.Html.F]
-
-let pic_service =
-  Eliom_service.create_ocaml ~name:"upload_pic" ~path:Eliom_service.No_path
-    ~meth:(Eliom_service.Post (Eliom_parameter.unit, Eliom_parameter.file "f"))
-    ()
-
-let () =
-  Eliom_registration.Ocaml.register ~service:pic_service (fun _ _ ->
-      (* get the file ... *)
-      Lwt.return_unit)
-
-let%client upload_pic_form () =
-  let file = D.Raw.input ~a:[a_input_type `File] () in
-  let submit_elt = D.Raw.input ~a:[a_input_type `Submit; a_value "Send"] () in
-  (let open Lwt_js_events in
-  async (fun () ->
-      clicks (To_dom.of_input submit_elt) (fun _ _ ->
-          Js.Optdef.case
-            (To_dom.of_input file)##.files
-            Lwt.return
-            (fun files ->
-              Js.Opt.case
-                files ## (item 0)
-                Lwt.return
-                (fun file ->
-                  Eliom_client.call_ocaml_service ~service:~%pic_service ()
-                    file)))));
-  [txt "Upload a picture:"; file; submit_elt]
diff --git a/7.1/manual/how-to-send-file-download.html b/7.1/manual/how-to-send-file-download.html deleted file mode 100644 index c0e77e65..00000000 --- a/7.1/manual/how-to-send-file-download.html +++ /dev/null @@ -1,51 +0,0 @@ -How to send a file (download)?

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

How to send a file (download)?

To serve file, you can use Ocsigen Server's module staticmod. -But it is also possible to ask Eliom to send files using module -Eliom_registration.File, -for example if you want Eliom to perform some privacy checks before sending, -or if the answer is not always a file.

diff --git a/7.1/manual/how-to-send-file-upload.html b/7.1/manual/how-to-send-file-upload.html deleted file mode 100644 index 11fcaa2c..00000000 --- a/7.1/manual/how-to-send-file-upload.html +++ /dev/null @@ -1,98 +0,0 @@ -How to send a file (upload)?

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

How to send a file (upload)?

To upload a file, use Eliom_parameter.file as service parameter type. -

Ocsigen server will save the file at a temporary location and keep it -there during the request. Then the file will be removed. You must -link it somewhere else on the disk yourself if you want to keep it. -

Be careful also to set the right option in Ocsigen server's configuration file. -For example: -

<ocsigen>
-  <server>
-    <uploaddir>/var/www/tmp</uploaddir>
-    <maxuploadfilesize>2MB</maxuploadfilesize>
-    <maxrequestbodysize>100MB</maxrequestbodysize>
-  </server>
-</ocsigen>

Example with Eliom_content.Html.D module opened: -

let upload = Eliom_service.create
-    ~path:(Eliom_service.Path ["upload"])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    ()
-
-let upload2 =
-  Eliom_registration.Html.create
-    ~path:(Eliom_service.Path ["upload"])
-    ~meth:(Eliom_service.Post
-             (Eliom_parameter.unit,
-              Eliom_parameter.file "file"))
-    (fun () file ->
-      let newname = "/var/www/upload/thefile" in
-      (try
-        Unix.unlink newname;
-      with _ -> ());
-      let%lwt () =
-        Lwt_unix.link (Eliom_request_info.get_tmp_filename file) newname
-      in
-      Lwt.return
-        (html
-           (head (title (txt "Upload")) [])
-           (body [h1 [txt "ok"]])))
-
-
-let uploadform =
-  Eliom_registration.Html.register ~service:upload
-    (fun () () ->
-      let f =
-        (Form.post_form ~service:upload2
-           (fun file ->
-             [p [Form.file_input ~name:file ();
-                 br ();
-                 Form.input ~input_type:`Submit ~value:"Send" Form.string
-               ]]) ()) in
-      Lwt.return
-        (html
-           (head (title (txt "form")) [])
-           (body [f])))
diff --git a/7.1/manual/how-to-set-and-id-classes-or-other-attributes-to-html-elements.html b/7.1/manual/how-to-set-and-id-classes-or-other-attributes-to-html-elements.html deleted file mode 100644 index a877aa66..00000000 --- a/7.1/manual/how-to-set-and-id-classes-or-other-attributes-to-html-elements.html +++ /dev/null @@ -1,75 +0,0 @@ -How to set and id, classes or other attributes to HTML elements?

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

How to set and id, classes or other attributes to HTML elements?

Mandatory element attributes are given as OCaml named parameters to -constructions function. -

Optional element attributes are added using the optional OCaml parameter "?a" which is more or less available for every HTML5 elements. This parameter is taking a list of attributes compatible with the element. -

div ~a:[a_class ["red";"shadow"];
-        a_id "the_div";
-        a_onclick {{ fun ev -> Dom_html.window##alert(Js.string "hello") }}]
-    [txt "I'm a div with two classes to describe me (red and shadow), ";
-     txt "an id to make me unique and an action when you click me."]

Id

The id attribute specifies a unique id for an HTML element (the value must be unique within the HTML document). -

The id attribute is mostly used to set a style in a style sheet for a particular element, and by JavaScript (via the HTML DOM) to manipulate the element with the specific id. With Eliom client-server apps, you do usually not need to set ids manually for that purpose: just use injections (%v) to refer to a node v created on server side. -

Attribute "id" is created by function a_id, taking the value of the attribute as parameter: -

~a:[a_id "identifier"]

Classes

The class attribute specifies one or more classnames for an element. -

The class attribute is mostly used to point to a class in a style sheet. However, it can also be used by a JavaScript (via the HTML DOM) to make changes to HTML elements with a specified class. -

An element can have several classes. Class attribute is -generated by function a_class, that takes a list of strings. -

~a:[a_class ["blue_button"; "small"]]

Style

The style attribute specifies an inline style for an element. The style attribute will override any style set globally, e.g. styles specified in the header of the page. -

Using this attribute to stylish elements is not a good practice. -You should rather use -an external CSS file. -

Its parameter is a string. -

~a:[a_style "color: pink; padding: 10px;"]

Title

The title attribute specifies extra information about an element. The information is most often shown as a tooltip text when the mouse moves over the element. -

Its parameter is a string. -

~a:[a_title "Lorem ipsum dolor sit amet."]

Events

Some attributes allow you to do something when the user do an action. They are many available actions since HTML5. -

Its argument is an Eliom event handler, that is a client-side function -taking the JS event as parameter: -

~a:[a_onclick {{ fun ev -> Dom_html.window##alert(Js.string "hello") }}]

However, we recommend to use Js_of_ocaml_lwt.Lwt_js_events rather than these elements to set event handlers. -

Links

diff --git a/7.1/manual/how-to-stop-default-behaviour-of-events.html b/7.1/manual/how-to-stop-default-behaviour-of-events.html deleted file mode 100644 index 7023fde9..00000000 --- a/7.1/manual/how-to-stop-default-behaviour-of-events.html +++ /dev/null @@ -1,58 +0,0 @@ -How to stop default behaviour of events?

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

How to stop default behaviour of events?

Example: -

(** Disable Js event with stopping propagation during capture phase **)
-let disable_event event html_elt =
-  Lwt.async (fun () ->
-   Lwt_js_events.seq_loop
-     (Lwt_js_events.make_event event) ~use_capture:true html_elt
-     (fun ev _ -> Dom.preventDefault ev; Lwt.return ())))
-
-
-disable_event Dom_html.Event.dragstart Dom_html.document
  • event is a Dom_html.Event
    -
  • html_elt is the target -

More details in documentation: Dom. preventDefault

diff --git a/7.1/manual/how-to-use-get-parameters-or-parameters-in-the-url.html b/7.1/manual/how-to-use-get-parameters-or-parameters-in-the-url.html deleted file mode 100644 index 349ecbc6..00000000 --- a/7.1/manual/how-to-use-get-parameters-or-parameters-in-the-url.html +++ /dev/null @@ -1,81 +0,0 @@ -How to use GET parameters (parameters in the URL)?

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

How to use GET parameters (parameters in the URL)?

Pages are generated by services. -

Funcrions to create services using GET HTTP method take -two mandatory parameters: ~path and ~get_params. -

path

This argument is a list of string, corresponding to the URL where your page service can be found. -

For example, if you want your page to be accessed via this URL: -

http://website.com/a/b/c

You must provide this path: -

~path:["a";"b";"c"]

This parameter can also be an empty list. It means that the service page will be accessed via this URL: -

http://website.com/

get_params

Eliom chooses the service automatically w.r.t. this path and parameter -given (among other criteria). -It decodes parameters automatically and checks their types. -

Description of parameters (names and types) is done using module -Eliom_parameter. -We recommend to open this module (at the beginning of your file, or locally). -

Syntax for several parameters

  • No parameter -
~get_params:unit
  • One parameter -
~get_params:(int "id")
  • More parameters -
~get_params:(string "name" ** int "age" ** bool "graduate")

Types of parameters

Get these parameters

On service registration, parameters can be retrieved as a parameter of the function: -

Example.register ~service:the_service
-    (fun parameters () -> ...)
  • No parameter -
(fun () () -> ...)
  • One parameter -
(fun a () -> ...)
  • Several parameters -

Operator ** is left associative: -

(fun (a, (b, (c, d)))) () -> ...)

Parameters in the path

Sometimes you may want to take parameters from the path, -for example to improve readability of URLs. E.g. http://blah.com/archive/2014/2/ or http://blah.com/user/toto/. -

To do that, add a parameter of type suffix in get_params: -

~get_params:(suffix (string "username"))

Download full code

Warning: This third party code may be outdated. Please notify the author is something is broken, or do a pull request on github. -

Links

diff --git a/7.1/manual/how-to-write-a-json-service.html b/7.1/manual/how-to-write-a-json-service.html deleted file mode 100644 index 440f2ee0..00000000 --- a/7.1/manual/how-to-write-a-json-service.html +++ /dev/null @@ -1,62 +0,0 @@ -How to write a JSON service?

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

How to write a JSON service?

  • If you want to communicate with a client side Eliom program, you do not need to serialize yourself your data to JSON. Serialization/Deserialization of any OCaml type is done automatically by Eliom. Just use OCaml services (or, simpler: server functions and call the function from your OCaml client side program). -
  • If you want to use your own JSON format, you need to have your own serialisation function to JSON (or for example to use some OCaml library like yojson to generate JSON). In that case, you can register your service using Eliom_registration.String instead of Eliom_registration.Html (see example below). The handler function must return the JSON value as a string, and a the content-type. -
  • It is even possible to define yourself your own registration module, to be used instead of Eliom_registration.String. Thus, you can use an OCaml representation of the JSON value (and you don't call the serializer yourself). Have a look on how modules like Eliom_registration.String are implemented to know how to do that. -
let my_json_service =
-  Eliom_service.create
-    ~path:(Eliom_service.Path ["my_json_service"])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    ()
-
-let _ =
-  Eliom_registration.String.register
-    ~service:my_json_service
-    (fun () () ->
-      Lwt.return ("{text:'something'}", "application/json"))

Comments:
-If you want to get result from a Database, it could already be in JSON format.
-Anyway, you probably do not need to pretty-print JSON yourself.

diff --git a/7.1/manual/how-to-write-forms.html b/7.1/manual/how-to-write-forms.html deleted file mode 100644 index 098ae4a5..00000000 --- a/7.1/manual/how-to-write-forms.html +++ /dev/null @@ -1,135 +0,0 @@ -How to write forms?

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

How to write forms?

To write an HTML form towards an Eliom service

Just as we do for links, we provide form-building functions that call -Eliom services in a safe manner. These functions are provided in the -module -Eliom_content.Html.D.Form (and -Eliom_content.Html.F.Form). -The functions -Eliom_content.Html.D.Form.get_form and -Eliom_content.Html.D.Form.post_form -accept a function (named create_form in our example below) -which is responsible for generating the content from -appropriately-typed parameter names. -

Here a complete example. -

[%%shared
-  open Eliom_parameter
-  open Eliom_content.Html.D
-]
-(* Create the form with some inputs *)
-let%server create_form =
-  (fun (number_name, (number2_name, string_name)) ->
-    [p [txt "Write an int: ";
-        Form.input
-          ~input_type:`Text ~name:number_name
-          Form.int;
-        txt "Write another int: ";
-        Form.input
-          ~input_type:`Text ~name:number2_name
-          Form.int;
-        txt "Write a string: ";
-        Form.input ~input_type:`Text ~name:string_name Form.string;
-        Form.input ~input_type:`Submit ~value:"Click" Form.string]])
-
-let%server form_result_service = Eliom_service.create
-  ~path:(Eliom_service.Path ["form-result"])
-  ~meth:(Eliom_service.Get ((int "number_name") ** ((int "number2_name") ** (string "string_name"))))
-  ()
-
-let%server form_result_handler =
-  (fun (number_name, (number2_name, string_name)) () ->
-     Lwt.return (
-       (html
-          (head (title (txt "")) [])
-          (body [
-             p [
-               txt "First number: ";
-               txt (string_of_int number_name);
-             ];
-             p [
-               txt "Second number: ";
-               txt (string_of_int number2_name);
-             ];
-             p [
-               txt "String: ";
-               txt string_name
-             ]
-           ]))))
-
-let%server form_service = Eliom_service.create
-  ~path:(Eliom_service.Path ["form"])
-  ~meth:(Eliom_service.Get unit)
-  ()
-
-let%server form_handler =
-  (fun () () ->
-     let form = Form.get_form ~service:form_result_service create_form in
-     Lwt.return
-       (html
-         (head (title (txt "Form example")) [])
-         (body [form])))
-
-let%server () =
-  Eliom_registration.Html.register ~service:form_result_service form_result_handler;
-  Eliom_registration.Html.register ~service:form_service form_handler

As shown in the example, -Eliom_content.Html.D.Form -provides functions for generating the various widgets, e.g., -Eliom_content.Html.D.Form.input. -These functions need to be called with a "type" argument (e.g., -Eliom_content.Html.D.Form.int) -that matches the type of the corresponding parameter name. -

POST forms may also take get parameters, as last argument of function -post_form: -

Eliom_content.Html.D.Form.post_form
-  my_service_with_get_and_post
-  (fun my_string ->
-    p [txt "Write a string: ";
-       Form.input ~input_type:`Text ~name:my_string Form.string])
-  222

Raw forms

There is also a raw interface to write basic forms without Eliom, -using standard tyxml constructors. -

Use module Eliom_content.Html.D.Raw. -

Links

diff --git a/7.1/manual/how-to-write-titles-and-paragraphs.html b/7.1/manual/how-to-write-titles-and-paragraphs.html deleted file mode 100644 index 69dc1766..00000000 --- a/7.1/manual/how-to-write-titles-and-paragraphs.html +++ /dev/null @@ -1,59 +0,0 @@ -How to write titles and paragrahs?

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

How to write titles and paragrahs?

Titles -

h3 [txt "Hello world"]

There are 6 types of titles: h1, h2, h3, h4, h5 and h6. h1 is the largest and h6 is the smallest. -

Pagragraph -

p [txt "Some text, blah blah blah"]

Required parameter: list containing other elements (content: Html_types.flow5 elements). -

Optional parameter for attributes "a" (How to set and id, classes or other attributes to HTML elements?). -

Download full code

Warning: This third party code may be outdated. Please notify the author is something is broken, or do a pull request on github. -

Links

diff --git a/7.1/manual/html.html b/7.1/manual/html.html deleted file mode 100644 index 7205f16f..00000000 --- a/7.1/manual/html.html +++ /dev/null @@ -1,141 +0,0 @@ - HTML in 5 minutes

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

HTML in 5 minutes

The Tyxml library makes it possible to type-check HTML pages. -This means that your Ocsigen program will never generate pages -which do not follow the recommendations from the W3C. -For example a program that could generate a page with a paragraph -inside another paragraph will be rejected at compile time. -

Typed HTML

Tags

Tyxml (or modules obtained by applying Tyxml functor, -like Eliom_content.Html.D) defines a contructor function -for each HTML tag, and for each attribute. -

Example: -

html
-     (head (title (txt "Hello")) [])
-     (body [ h1 [txt "Hello"] ;
-             p [txt "Blah"] ])

As tag <html> contains a tag <head> and <body>, -function html takes two parameters: -

val html : [`Head] elt -> [`Body] elt -> [`Html] elt

The only way to create a value of type [`Head] elt is to use -function head, -and the only way to create a value of type [`Body] elt is to use -function body. -

As a <title> tag is mandatory inside <head>, -function head takes a [`Title] elt as first argument -(that can be created using function title), -then the list of other children. -

Function txt is used to include raw text. -

Error messages

If you write an invalid page, for example: -

(html
-   (head (title (txt "")) [txt ""])
-   (body [txt "Hallo"]))

You will get an error message similar to the following, referring to -the end of line 2: -

Error: This expression has type ([> `TXT ] as 'a) Html.elt
-       but an expression was expected of type
-         Html_types.head_content_fun Html.elt
-       Type 'a is not compatible with type Html_types.head_content_fun =
-           [ `Base
-           | `Command
-           | `Link
-           | `Meta
-           | `Noscript of [ `Link | `Meta | `Style ]
-           | `Script
-           | `Style ]
-       The second variant type does not allow tag(s) `TXT
-

where Html_types.head_content_fun -is the type of content allowed inside <head> (<base>, -<command>, <link>, <meta>, etc.). Notice that -`TXT (i.e. raw text) is not included in this polymorphic -variant type, which means that <head> cannot contain raw text. -

Attributes

Attributes are given to functions using optional argument ?a. -

Example: -

div ~a:[a_id "the_id"; a_class ["firstclass"; "secondclass"]]
-  [ txt "blah" ]

OCaml node and DOM nodes

Conversions

Tyxml builds OCaml nodes. If you are using it server side, -they are serialized as an XML text before being sent to the browser. -On client side, if you want to get the DOM node corresponding to -an OCaml node, use functions from module Eliom_content.Html.To_dom, -for example Eliom_content.Html.To_dom.of_element, -or Eliom_content.Html.To_dom.of_input, -Eliom_content.Html.To_dom.of_textarea, etc. -

If the node has been created with Eliom_content.Html.F -(functional nodes), -a new DOM node will be created every time you call such a conversion -function. -

If the node has been created with Eliom_content.Html.D, -the conversion function will return the exact DOM node that exists -in the page (and always the same one). If this "D" node is created -server-side, Eliom will automatically add a identifier in the HTML -page to be able to find it back. -

Example: -

[%%shared
-  open Eliom_content.Html
-]
-
-let%server create_widget () =
-  let d = D.div [ ... ] in
-  ignore [%client
-    Lwt.async (fun () ->
-      Lwt_js_events.clicks (To_dom.of_element ~%d) (fun _ _ -> ... )) ];
-  d

Here, d must be a "D" node, as I want to bind click events on -the actual node (that is already in the page), not create a new one. -

Remember to use "D" nodes if you want to use it in a injection -(~%d). If you don't to bother about "D" or "F" nodes, use "D" -nodes by default (even if it will generate too much identifiers in the page). -But remember that, the DOM semantics implies that "D" nodes cannot be -inserted twice in a page. -

Manip

Module -Eliom_content.Html.Manip -makes it possible to manipulate OCaml nodes without conversions -(add them in a page, replace a node, add as child of another node, -add a class, etc.). -

Reactive nodes

Module -Eliom_content.Html.R -makes it possible to create reactive nodes, that is, nodes that will -be updated automatically when the data on which they depend change. -

It is based on module React, -which implements functional reactive programming for OCaml. -

More documentation

Have a look at Tyxml's manual -and Eliom's manual -for more documentation about HTML manipulation in OCaml.

diff --git a/7.1/manual/interaction.html b/7.1/manual/interaction.html deleted file mode 100644 index 476443fd..00000000 --- a/7.1/manual/interaction.html +++ /dev/null @@ -1,815 +0,0 @@ -Implementing Web Interaction Using Eliom

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

Implementing Web Interaction Using Eliom

The code of this tutorial has been tested with Eliom 6.0.
-

This chapter of the tutorial explains how to create a small web site -with several pages, users, sessions, and other elements of classical -web development. Then, in -another tutorial, we will incorporate the -features of this site into our Graffiti application, to -demonstrate that we can combine this kind of interaction with client-side -programs. -

We assume you have read at least the first section of the previous -chapter, which explained how to create a service and constuct valid -HTML pages. -

We will create a simple web site with one main page and a page for -each user (assuming we have several users already created). Then we -will add a login/connection form. We will also add a registration -form, in order to learn how to create dynamically new services, -and why it is very useful. -

The full code -of the program can be downloaded. -

Services

The main page

Let's start again from scratch with the following site. -

let main_service = Eliom_content.Html.D.(
-  Eliom_registration.Html.create
-    ~path:(Eliom_service.Path [""])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    (fun () () ->
-      Lwt.return
-        (html (head (title (txt "")) [])
-           (body [h1 [txt "Hello"]])))
-)

Note that we are using Eliom_registration.Html, as we are not building a -client side program for now. -

For the same reason, you may want to remove the .eliom-file -generated by the distillery and put the code into an -.ml-file. But don't forget to add it to the server files in the -Makefile.options: -

SERVER_FILES := tuto.ml

Adding a page for each user

We will now create a page for each user. To do this, we will create a -new service, taking the user name as parameter: -

let user_service = Eliom_content.Html.D.(
-  Eliom_registration.Html.create
-    ~path:(Eliom_service.Path [""])
-    ~meth:(Eliom_service.Get (Eliom_parameter.string "name"))
-    (fun name () ->
-      Lwt.return
-        (html (head (title (txt name)) [])
-           (body [h1 [txt name]])))
-)

Add these lines to the same file, compile, start the server and verify -that everything is working by trying, for example: -http://localhost:8080/?name=toto. -

For our program, we would prefer to take one part of the URL path as the parameter -describing the name of the user. -To achieve this we change the definition of the previous service this way: -

let user_service =
-  Eliom_registration.Html.create
-    ~path:(Eliom_service.Path ["users"])
-    ~meth:(Eliom_service.Get (
-      Eliom_parameter.(suffix (string "name"))))
-    (fun name () -> ... )

The user pages are now available at URLs http://localhost:8080/users/username. -

Links

We now want to add a link on each user page to go back to the main -page. -

Change the handler of user_service into: -

(fun name () -> Lwt.return
-  (html
-    (head (title (txt name)) [])
-    (body [h1 [txt name];
-           p [a ~service:main_service [txt "Home"] ()]])))

Links towards services with parameters

In our example above, the last parameter is () because the -service does not expect any parameter. If the service expects, for -example, a pair (int * string), you must provide a matching -value as last parameter. OCaml checks at compile time that the type -of the parameters in a link corresponds to the type expected by the -service. Also parameter names are generated -automatically from the service, making it impossible to erroneously -create bad links. -

To show an example of a link with parameters, we will display the list -of user pages on the main page. Here is the new version of the full -program: -

(* Services *)
-let main_service = Eliom_service.create
-  ~path:(Eliom_service.Path [""])
-  ~meth:(Eliom_service.Get Eliom_parameter.unit)
-  ()
-
-let user_service  = Eliom_service.create
-  ~path:(Eliom_service.Path ["users"])
-  ~meth:(Eliom_service.Get Eliom_parameter.(suffix (string "name")))
-  ()
-
-(* User names and passwords: *)
-let users = ref [("Calvin", "123"); ("Hobbes", "456")]
-
-let user_links = Eliom_content.Html.D.(
-  let link_of_user = fun (name, _) ->
-    li [a ~service:user_service [txt name] name]
-  in
-  fun () -> ul (List.map link_of_user !users)
-)
-
-(* Services Registration *)
-let () = Eliom_content.Html.D.(
-
-  Eliom_registration.Html.register
-    ~service:main_service
-    (fun () () ->
-      Lwt.return
-        (html (head (title (txt "")) [])
-           (body [h1 [txt "Hello"];
-                  user_links ()])));
-
-  Eliom_registration.Html.register
-    ~service:user_service
-    (fun name () ->
-      Lwt.return
-        (html (head (title (txt name)) [])
-           (body [h1 [txt name];
-                  p [a ~service:main_service [txt "Home"] ()]])))
-)

Sessions

Connection service

Now we want to add a connection form. First, we create a service -for checking the name and password. Since we don't want the username -and password to be shown in the URL, we use hidden parameters -(or POST parameters). Thus, we need to create a new service taking -these parameters: -

let connection_service = Eliom_service.create_attached_post
-  ~fallback:main_service
-  ~post_params:Eliom_parameter.(string "name" ** string "password")
-  ()

Now we can register a handler for the new service: -

Eliom_registration.Html.register
-    ~service:connection_service
-    (fun () (name, password) ->
-      let message =
-        if check_pwd name password
-        then "Hello " ^ name
-        else "Wrong name or password"
-      in
-      Lwt.return
-        (html (head (title (txt "")) [])
-           (body [h1 [txt message];
-                  user_links ()])));

where check_pwd is defined by: -

let check_pwd name pwd =
-  try List.assoc name !users = pwd with Not_found -> false

Connection form

For now, we will add the connection form only on the main page of the -site. -

Let's create a function for generating the form: -

let connection_box () = Eliom_content.Html.D.(
-  Form.post_form ~service:connection_service
-    (fun (name1, name2) ->
-      [fieldset
-         [label [txt "login: "];
-          Form.input
-            ~input_type:`Text ~name:name1
-            Form.string;
-          br ();
-          label [txt "password: "];
-          Form.input
-            ~input_type:`Password ~name:name2
-            Form.string;
-          br ();
-          Form.input
-            ~input_type:`Submit ~value:"Connect"
-            Form.string
-         ]]) ()
-)

Now, add a call to this function in the handler of the main service -(for example just before the user links). -

Opening a session

Now we want to remember that the user is successfully connected. To do -that we will set a reference when the user successfully connects, and -we will restrict the scope of this reference to the session (that is, -to the browser). -

Define your Eliom reference with a default value: -

let username =
-  Eliom_reference.eref ~scope:Eliom_common.default_session_scope None

Here is the new connection_box function: -

let connection_box () = Eliom_content.Html.D.(
-  let%lwt u = Eliom_reference.get username in
-  Lwt.return
-    (match u with
-      | Some s -> p [txt "You are connected as "; txt s]
-      | None ->
-        Form.post_form ~service:connection_service
-          (fun (name1, name2) ->
-            [fieldset
-	       [label [txt "login: "];
-                Form.input
-                  ~input_type:`Text ~name:name1
-                  Form.string;
-                br ();
-                label [txt "password: "];
-                Form.input
-                  ~input_type:`Password ~name:name2
-                  Form.string;
-                br ();
-                Form.input
-                  ~input_type:`Submit ~value:"Connect"
-                  Form.string
-               ]]) ())
-)

... and replace the registration of the main service and the connection service by: -

Eliom_registration.Html.register
-    ~service:main_service
-    (fun () () ->
-      let%lwt cf = connection_box () in
-      Lwt.return
-        (html (head (title (txt "")) [])
-              (body [h1 [txt "Hello"];
-                     cf;
-                     user_links ()])));
-
-  Eliom_registration.Html.register
-    ~service:connection_service
-    (fun () (name, password) ->
-      let%lwt message =
-        if check_pwd name password then
-	  begin
-            let%lwt () = Eliom_reference.set username (Some name) in
-            Lwt.return ("Hello "^name)
-	  end
-	else
-	  Lwt.return "Wrong name or password"
-      in
-      Lwt.return
-        (html (head (title (txt "")) [])
-           (body [h1 [txt message];
-                  user_links ()])));

Display the usual page after connection

As you can see, our connection service is displaying a welcome page -which is different from the main page in connected mode. We would -rather display the same page. One solution is to call the same -handler after registering session data. -

A cleaner solution is to use an action, that is: a service which will -just perform a side effect. Replace the registration -of the connection service by: -

Eliom_registration.Action.register
-    ~service:connection_service
-    (fun () (name, password) ->
-      if check_pwd name password
-      then Eliom_reference.set username (Some name)
-      else Lwt.return ());

Now the main page is displayed after connection. -

Putting a connection form on each page

We transform the connection service into a non_attached coservice, -to do so we replace the path we specified earlier for the ~path parameter: -

let connection_service = Eliom_service.create
-  ~path:(Eliom_service.No_path)
-  ~meth:(Eliom_service.Post (
-    Eliom_parameter.unit,
-    Eliom_parameter.(string "name" ** string "password")))
-  ()

Now you can add the connection box on user pages. -

Eliom_registration.Html.register
-    ~service:user_service
-    (fun name () ->
-      let%lwt cf = connection_box () in
-      Lwt.return
-        (html (head (title (txt name)) [])
-              (body [h1 [txt name];
-		     cf;
-                     p [a ~service:main_service [txt "Home"] ()]])));

Disconnection

To create a logout/disconnection form, we create another non-attached -coservice using POST method, and register another action. We call the -function Eliom_state.discard with scope Eliom_common.default_session_scope to remove all session -data. -

let disconnection_service = Eliom_service.create
-  ~path:(Eliom_service.No_path)
-  ~meth:(Eliom_service.Post (Eliom_parameter.unit, Eliom_parameter.unit))
-  ()
-
-let disconnect_box () = Eliom_content.Html.D.(
-  Form.post_form disconnection_service
-    (fun _ ->
-      [fieldset [Form.input ~input_type:`Submit ~value:"Log out" Form.string]]
-    )
-    ()
-)
-
-let () =
-  Eliom_registration.Action.register
-    ~service:disconnection_service
-    (fun () () -> Eliom_state.discard ~scope:Eliom_common.default_session_scope ())

Then add this form in the connection box: -

let connection_box () =
-  let%lwt u = Eliom_reference.get username in
-  Lwt.return
-    (match u with
-      | Some s -> div [p [txt "You are connected as "; txt s; ];
-                       disconnect_box () ]
-      | None -> ...

Registration of users

Basic registration form

We now add a registration form to the application. We create a -new regular service, attached to the path /registration, that -displays a registration form, and an action that will add the user to -the "database": -

let new_user_form_service = Eliom_service.create
-  ~path:(Eliom_service.Path ["registration"])
-  ~meth:(Eliom_service.Get Eliom_parameter.unit)
-  ()
-
-let create_account_service = Eliom_service.create_attached_post
-  ~fallback:main_service
-  ~post_params:(Eliom_parameter.(string "name" ** string "password"))
-  ()
-
-let account_form () = Eliom_content.Html.D.(
-  Form.post_form ~service:create_account_service
-    (fun (name1, name2) ->
-      [fieldset
-         [label [txt "login: "];
-          Form.input ~input_type:`Text ~name:name1 Form.string;
-          br ();
-          label [txt "password: "];
-          Form.input ~input_type:`Password ~name:name2 Form.string;
-          br ();
-          Form.input ~input_type:`Submit ~value:"Connect" Form.string
-         ]]) ()
-)
-
-let _ = Eliom_content.Html.D.(
-  Eliom_registration.Html.register
-    ~service:new_user_form_service
-    (fun () () ->
-      Lwt.return
-        (html (head (title (txt "")) [])
-              (body [h1 [txt "Create an account"];
-                     account_form ();
-                    ])));
-
-  Eliom_registration.Action.register
-    ~service:create_account_service
-    (fun () (name, pwd) ->
-      users := (name, pwd)::!users;
-      Lwt.return ())
-)

Then add the link to this service in the connection box: -

let connection_box () = Eliom_content.Html.D.(
-  let%lwt u = Eliom_reference.get username in
-  Lwt.return
-    (match u with
-      | Some s -> div [p [txt "You are connected as "; txt s; ];
-                       disconnect_box () ]
-      | None ->
-        div [Form.post_form ~service:connection_service
-                (fun (name1, name2) ->
-...
-                ) ();
-             p [a new_user_form_service
-                  [txt "Create an account"] ()]]
-            )
-)

It looks great, but if we refresh the page after creating an account, -we can see there is a problem. We are creating the same account over and -over again. This is because we are calling the create_account_service each time -we refresh the page, obviously we don't want that. To solve this issue we will -implement a dynamically created and registered service with limited lifespan and -number of uses. -

Registration form with confirmation

Now we want to add a confirmation page before actually creating the -account. We replace the service create_account_service by a -new POST attached coservice called account_confirmation_service: -

let account_confirmation_service =
-  Eliom_service.create_attached_post
-    ~fallback:new_user_form_service
-    ~post_params:(Eliom_parameter.(string "name" **  string "password"))
-    ()

and we make the account creation form point at this new service. -

We register an HTML handler on this service, with the confirmation -page. As a side effect, this page will create the actual account -creation service: -

Eliom_registration.Html.register
-    ~service:account_confirmation_service
-    (fun () (name, pwd) ->
-      let create_account_service =
-	Eliom_service.create_attached_get
-          ~fallback:main_service
-          ~get_params:Eliom_parameter.unit
-          ~timeout:60.
-	  ~max_use:1
-	  ()
-      in
-      let _ = Eliom_registration.Action.register
-	~service:create_account_service
-        (fun () () ->
-          users := (name, pwd)::!users;
-          Lwt.return ())
-      in
-      Lwt.return
-	(html
-           (head (title (txt "")) [])
-           (body
-              [h1 [txt "Confirm account creation for "; txt name];
-               p [a ~service:create_account_service [txt "Yes"] ();
-                  txt " ";
-                  a ~service:main_service [txt "No"] ()]
-              ])));

Also remove the registration of the create_account_service -service and modify the user creation form to make it points towards -account_confirmation_service. -

We created dynamically a service with a limited lifespan as specified by the -?timeout parameter and a limited number of uses of this service defined -by the ?max_use parameter. Now if we refresh the page after creating the -account, it works as intended. -

A few enhancements

Displaying a "wrong password" message

In the current version, our web site fails silently when the password -is wrong. Let's improve this behavior by displaying an error message. -To do that, we need to pass information to the service occurring after -the action. We record this information in an Eliom reference with -scope Eliom_common.request_scope. -

Define an Eliom reference: -

let wrong_pwd =
-  Eliom_reference.eref ~scope:Eliom_common.request_scope false

Modify the connection box this way: -

let connection_box () = Eliom_content.Html.D.(
-  let%lwt u = Eliom_reference.get username in
-  let%lwt wp = Eliom_reference.get wrong_pwd in
-  Lwt.return
-    (match u with
-      | Some s -> div [p [txt "You are connected as "; txt s; ];
-                       disconnect_box () ]
-      | None ->
-        let l =
-          [Form.post_form ~service:connection_service
-            (fun (name1, name2) ->
-              [fieldset
-	         [label [txt "login: "];
-                  Form.input ~input_type:`Text ~name:name1 Form.string;
-                  br ();
-                  label [txt "password: "];
-                  Form.input ~input_type:`Password ~name:name2 Form.string;
-                  br ();
-                  Form.input ~input_type:`Submit ~value:"Connect" Form.string
-                 ]]) ();
-             p [a new_user_form_service
-                  [txt "Create an account"] ()]]
-        in
-        if wp
-        then div ((p [em [txt "Wrong user or password"]])::l)
-        else div l
-    )
-)

... and modify the connection_service handler: -

Eliom_registration.Action.register
-    ~service:connection_service
-    (fun () (name, password) ->
-      if check_pwd name password
-      then Eliom_reference.set username (Some name)
-      else Eliom_reference.set wrong_pwd true);

Sending 404 errors for non-existing users

Our service user_service responds to any request parameter, even -if the user does not exist in the database. We want to check that the -user is in the database before displaying the page, and send a 404 -error if the user is not. To do that, we will replace module - Eliom_registration.Html -by Eliom_registration.Any to register the service user_service: -

Eliom_registration.Any.register
-    ~service:user_service
-    (fun name () ->
-      if List.mem_assoc name !users then
-	begin
-	  let%lwt cf = connection_box () in
-	  Eliom_registration.Html.send
-            (html (head (title (txt name)) [])
-               (body [h1 [txt name];
-                      cf;
-                      p [a ~service:main_service [txt "Home"] ()]]))
-	end
-      else
-	Eliom_registration.Html.send
-          ~code:404
-          (html (head (title (txt "404")) [])
-             (body [h1 [txt "404"];
-                    p [txt "That page does not exist"]]))
-    );

Wrapping the server handler to easily get the user data

When you want to assume that you have informations available in -sessions, for instance when a site is mainly available to connected -users, it becomes tedious to check everywhere that a reference is not -None. A simple way to distinguish authenticated and anonymous -requests lies in the usage of Eliom_tools.wrap_handler. It allows to -provide two handler functions, one is called if a certain -information is not made available by the first argument, the other is -called when that information is available. -

let authenticated_handler f = Eliom_content.Html.D.(
-  let handle_anonymous _get _post =
-    let connection_box =
-      Form.post_form ~service:connection_service
-        (fun (name1, name2) ->
-	  [fieldset
-              [label [txt "login: "];
-               Form.input ~input_type:`Text ~name:name1 Form.string;
-               br ();
-               label [txt "password: "];
-               Form.input ~input_type:`Password ~name:name2 Form.string;
-               br ();
-               Form.input ~input_type:`Submit ~value:"Connect" Form.string
-              ]]) ()
-    in
-    Lwt.return
-      (html
-        (head (title (txt "")) [])
-        (body [h1 [txt "Please connect"];
-	       connection_box;]))
-    in
-    Eliom_tools.wrap_handler
-      (fun () -> Eliom_reference.get username)
-      handle_anonymous (* Called when [username] is [None] *)
-      f (* Called [username] contains something *)
-)
-
-let () = Eliom_content.Html.D.(
-  Eliom_registration.Html.register ~service:main_service
-    (authenticated_handler
-       (fun username _get _post ->
-       	 Lwt.return
-           (html
-              (head (title (txt "")) [])
-              (body [h1 [txt ("Hello " ^ username) ]]))))
-)

prev -next

diff --git a/7.1/manual/intro.html b/7.1/manual/intro.html deleted file mode 100644 index 45e824f3..00000000 --- a/7.1/manual/intro.html +++ /dev/null @@ -1,164 +0,0 @@ -Introduction

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

Introduction

Ocsigen is a complete framework for developing Web and mobile apps -using cutting edge techniques. It can be used to write simple server side -Web sites, client-side programs, or complex client-server Web -and mobile apps. -Its main characteristics are: -

  • The use of a strongly typed language, OCaml, and a very advanced use -of its type system to check many properties of a program at compile time. -This allows to drastically reduce the developement time of complex apps, -and makes it easier to refactor the code to follow evolutions of its features. -As an example, the conformance of generated HTML is checked at compile time, -which makes it almost impossible to generate invalid pages. -
  • Multi-tier programming: client and server sides of the application are -written using the same language, and as a single code. Annotations in -the code are used indicate on which side the code is to be run. -The client side parts are automatically extracted and compiled into JavaScript -to be executed in the browser. -This gives a lot of flexibility to the programmer, allowing for example to -generate some parts of pages either on client or server code according -to your needs. It also makes communication between server and client -straightforward, as you can use server side variables in client code, -or call a server side OCaml functions from client code. -
  • Web and mobile multi-platform apps: Android and iOS apps are -generated from the exact same code as the Web app, and run in a webview. -

To see some examples of mobile or Web apps written with Ocsigen, have a look -at the Be Sport social network (available in -Google Play Store and Apple app store), or -Ocsigen Start's demo app -(available on Google Play store). -

Ocsigen consists of several quite independant projects, all -released as open source software on Github. -The main projects are: -

  • Lwt, a general purpose concurrent programming library for OCaml, -
  • TyXML for generating typed XML, -
  • the Js_of_ocaml compiler (from Ocaml bytecode to JavaScript), -
  • Eliom, the multi-tier framework for client-server -Web and mobile apps, -
  • Ocsigen Server, a full-featured Web server, -
  • Ocsigen Toolkit, a client-server widget library written for Eliom and Js_of_ocaml, -
  • Ocsigen Start, an application template with -many code examples, to be used as a basis for your apps, or to learn. -

The installation is described here. -

Ocsigen originates from a research project by the CNRS, Université de Paris -and Inria. It innovates in many aspects, and you will have to learn several -new programming techniques to become autonomous. -Depending on your level and goals, we suggest to continue by reading -some of the following chapters: -

  • Chapter All Ocsigen in one page can be used as your training plan. -It provides a wide overview of each main concept, with links to more detailed -documentation. -
  • Then, a good starting point is this -Ocsigen Start tutorial, -if you plan to build a client-server Web (and/or mobile) app. -It will help you to build your first app very quickly, -with many code samples to study. -
  • The website programing guide explains how to write a -website in OCaml, if you don't plan to have client-side features yet -
  • If you are fluent in OCaml and want a quick introduction to -client-server Web programming with Eliom, -read tutorial -Eliom apps basics: writing client server widgets. -It illusrates the client-server syntax with an example, -and is a good starting point for understanding Eliom's client-server features. -
  • If you want a full step-by-step tutorial on how to write a client-server Web -application, read tutorial -Client-server application. -It describes step by step how to write a client/server -collaborative drawing application. You will learn, -from the very beginning, how to: -
    • Create new services -
    • Output valid HTML -
    • Send OCaml code to be executed on the client -
    • Call JavaScript methods from OCaml -
    • Catch mouse events -
    • Communicate with the server, in both directions -
    • Create services with non-HTML output -
  • If you want to build mobile applications, -read tutorial Mobile applications with Ocsigen. -It describes how to build a mobile app (e.g., for Android) -with the same codebase as for your Web application. -
  • If you want to write a more traditional Web site, with pages, forms, and sessions, read tutorial Service based Web programming. -It is devoted to server side programming. -It shows how to -create a new Web site with several pages and user connections. -You will learn how to: -
    • Create a link towards another service -
    • Create forms -
    • Register session data or session services -
    • Create services performing actions (with no output) -
    • Dynamically register new services (continuation based Web programming) -
  • If you want to learn more details about Ocsigen -read tutorial Miscellaneous features. -It will mix the client-server drawing application -with the session mechanism and user management -to produce a multi-user collaborative drawing -application. In this chapter, you will learn how to: -
    • Integrate a typical Web interaction (links, forms, …) with a client side program. -
    • Add sounds or videos to your application -
    • Change pages without stopping the client side program -
    • Connect with external accounts (openID) -
    • Add an Atom feed -
  • If you want to have a full application with user management -(registration, activation emails, authentication), -have a look at Ocsigen-start. -Ocsigen-start is a library and a template for Eliom distillery that -contains a working Eliom application with user and right -management. -
  • For a more comprehensive understanding refer to the manual of each project, -and/or the API documentation. -
  • You will also find more tutorials in the menu on the left. -

We recommend to ask your questions on -discuss.ocaml.org with tag ocsigen. -You can also come and chat with us on -IRC in channel #ocsigen on freenode.net (e.g. by using -their webchat)! -

Now, we recommend to read, chapter -All Ocsigen in one page -for a wide and complete overview. -

diff --git a/7.1/manual/lwt.html b/7.1/manual/lwt.html deleted file mode 100644 index db20b434..00000000 --- a/7.1/manual/lwt.html +++ /dev/null @@ -1,135 +0,0 @@ - Lwt in 5 minutes

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

Lwt in 5 minutes

Principles

The Lwt library implements cooperative threads for OCaml. Cooperative -threads are an alternative to preemptive threads (used in many -languages and in OCaml's Thread module) that solve most common -issues with preemptive threads: with Lwt, there is very limited risk -of deadlocks and very limited need for locks. Lwt threads are even -usable in programs compiled to JavaScript using Js_of_ocaml. -

Lwt is based on the fact that most programs spend most of their time -waiting for inputs, e.g., keys, data coming from sockets, and mouse -events. Instead of relying on a preemptive scheduler that switches -between threads at arbitrary moments, Lwt uses these waiting times as -cooperation points. This means that instead of blocking (for -example on a read), Lwt resumes another waiting thread, if there -is one that is ready to continue. All you have to do is to use the -cooperative version of each blocking function, for example -Lwt_unix.sleep instead of Unix.sleep -and Lwt_unix.read instead of Unix.read. -If one of your computations takes a lot of time, it is also possible -to manually insert cooperation points using the function -Lwt_main.yield. -

Promises

Lwt defines a type 'a Lwt.t, which is the type of promises. -For example, the function: -

val f : unit -> int Lwt.t

immediately returns a promise of int, that is, something that -will eventually become an integer once the computation is finished. -

The following code will launch the computation of f () -(asynchronously). If the code reaches a cooperation point (for example -when the integer is requested via the network), it will continue the -program (print "hello") and resume at a subsequent cooperation point, -when the data is available. -

let g1 () =
-  let p = f () in
-  print_endline "hello"

Bind: Using the value of promises

It is possible to tell Lwt to execute a function once a promise is -completed, by using the function: -

Lwt.bind : 'a Lwt.t -> ('a -> 'b Lwt.t) -> 'b Lwt.t

For instance Lwt.bind p h will call the function h with -the return value of the promise p as soon as the value is known. -The expression (Lwt.bind p h) is also a promise (it may take -time to complete). Function h must return a promise. -

To create a (terminated) promise from a value, use Lwt.return. -

Example: -

let g2 () =
-  let p = f () in
-  Lwt.bind p (fun i -> print_int i; Lwt.return ())

Function g2 calls function f to create a promise. -Then it waits (in a cooperative manner) for the result, -and prints the result. The expression g2 () has type unit Lwt.t. -

Syntax extension

A PPX (and also camlp4) syntax extension is available. -

let%lwt i = f () in
-...

is equivalent to -

Lwt.bind (f ()) (fun i -> ...)

Examples

A function that prints "tic" every second forever, without blocking the rest of the program

let rec tic () =
-    print_endline "tic";
-    let%lwt () = Lwt_unix.sleep 1.0 in
-    tic ()

Replace Lwt_unix.sleep by Lwt_js.sleep if your program is -running in a browser. -

Launching concurrent threads and waiting for their results

Suppose you have two cooperative functions: -

val f : unit -> unit Lwt.t
-val g : unit -> unit Lwt.t

The following code runs f () and g () sequentially: -

let%lwt () = f () in
-let%lwt () = g () in
-...

The following code launches f () and g () concurrently, -then waits for both to terminate before continuing: -

let p1 = f () in
-let p2 = g () in
-let%lwt () = p1 in
-let%lwt () = p2 in
-...

To detach a thread, it is recommended to use -

Lwt.async (fun () -> f ())

instead of -

ignore (f ())

to catch exceptions properly. -

Serial and concurrent map on lists

The following map function runs all computation concurrently -on all list elements: -

let rec map f l =
-  match l with
-  | [] -> Lwt.return []
-  | v :: r ->
-      let t = f v in
-      let rt = map f r in
-      let%lwt v' = t in
-      let%lwt l' = rt in
-      Lwt.return (v' :: l')

whereas the following one waits for the one to complete before -launching the next one: -

let rec map_serial f l =
-  match l with
-  | [] -> return []
-  | v :: r ->
-      let%lwt v' = f v in
-      let%lwt l' = map_serial f r in
-      Lwt.return (v' :: l')

More documentation

Have a look at Lwt's manual -for more details about Lwt. -You will learn how to handle exceptions (using Lwt.fail -and Lwt.catch or try%lwt). -You will also learn for example how to create a thread that waits until it is -awaken (using Lwt.wait and Lwt.wakeup).

diff --git a/7.1/manual/macaque.html b/7.1/manual/macaque.html deleted file mode 100644 index 6e93ac7f..00000000 --- a/7.1/manual/macaque.html +++ /dev/null @@ -1,130 +0,0 @@ -Type safe database requests using Macaque

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

Type safe database requests using Macaque

The Macaque library allows easy manipulation of Postgresql -database fully compatible with Lwt. (For more information see - Macaque manual). -

Macaque is fuly compatible with PGOcaml, and both can be used -in the same application. PGOcaml requires database access at compile -time and makes difficult to generate queries at compile time. -Macaque uses its own syntax, whereas PGOcaml uses SQL. -Macaque supports only a small subset of Postgresql features. -

Warning: Macaque's syntax extension uses camlp4, which is not compatible with ppx. If you want to use macaque with Eliom's ppx, place your macaque code in an isolated file. Some details can be found in the ppx migration guide. -

This tutorial shows how to store the login and the password -f users in a Postgresql -database. For more information on how to set up and run it, see -Postgresql -manual. -

When the base is up and running, we create the base by running in a shell: -

$ createdb testbase

Then we create the users table, by executing this sql script: -

CREATE TABLE users (
-       login text NOT NULL,
-       password text NOT NULL
-);

We save it under create_table.sql and run -

$ psql -d testbase -f create_table.sql

Macaque can use any thread library that provides a monadic -interface. The default one provides simple blocking access to the -database. It isn't good for us because an access to the base by one -user will prevent the server from handling anything else until the -request is finished. We need a version of Macaque specialised for -Lwt. It is obtained by -

module Lwt_thread = struct
-  include Lwt
-  include Lwt_chan
-end
-module Lwt_PGOCaml = PGOCaml_generic.Make(Lwt_thread)
-module Lwt_Query = Query.Make_with_Db(Lwt_thread)(Lwt_PGOCaml)

We can now open the database with our newly created Lwt_PGOCaml.connect. -

let get_db : unit -> unit Lwt_PGOCaml.t Lwt.t =
-  let db_handler = ref None in
-  fun () ->
-    match !db_handler with
-      | Some h -> Lwt.return h
-      | None -> Lwt_PGOCaml.connect ~database:"testbase" ()

Then we declare the table on which we will work and the different -requests we do on it. -

let table = <:table< users (
-  login text NOT NULL,
-  password text NOT NULL
-) >>
-
-let find name =
-  (get_db () >>= fun dbh ->
-   Lwt_Query.view dbh
-   <:view< {password = user_.password} |
-            user_ in $table$;
-            user_.login = $string:name$; >>)
-
-let insert name pwd =
-  get_db () >>= fun dbh ->
-  Lwt_Query.query dbh
-  <:insert< $table$ :=
-    { login = $string:name$; password = $string:pwd$; } >>

Finally, we modify the handling code : -

let check_pwd name pwd =
-  (get_db () >>= fun dbh ->
-   Lwt_Query.view dbh
-   <:view< {password = user_.password} |
-            user_ in $table$;
-            user_.login = $string:name$;
-	    user_.password = $string:pwd$ >>)
-  >|= (function [] -> false | _ -> true)
-
-let () = Eliom_registration.Action.register
-  ~service:create_account_service
-  (fun () (name, pwd) ->
-    find name >>=
-      (function
-	| [] -> insert name pwd
-	| _ -> Lwt.return ()) )
-
-let () = Eliom_registration.Action.register
-  ~service:connection_service
-  (fun () (name, password) ->
-    check_pwd name password >>=
-      (function
-	| true -> Eliom_reference.set username (Some name)
-	| false -> Lwt.return ()))

We need to reference macaque in the Makefile (or in Makefile.option if you used eliom-distillery) : -

SERVER_PACKAGE := macaque.syntax
-

and in ocsigenserver.conf (only if you don't use eliom-distillery) : -

<extension findlib-package="macaque"/>
-
diff --git a/7.1/manual/misc.html b/7.1/manual/misc.html deleted file mode 100644 index 3ec6df9f..00000000 --- a/7.1/manual/misc.html +++ /dev/null @@ -1,464 +0,0 @@ -Traditional web interaction in a client-server app

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

Traditional web interaction in a client-server app

The code of this tutorial has been tested with Eliom 6.0.
-

Multi-user collaborative drawing application

We now want to turn our collaborative drawing application into a -multi-user one. Each user will have their own drawing, where everyone -can draw. -

See the -full code of examples. -

Split application into multiple files and using several canvases

We first build a multi-canvas drawing application. Each drawing has -its own URL. Everyone can create a new drawing by going to the -corresponding URL. -

We need to refactor some parts. In particular, we need to handle -different drawings separately. To do this, we turn all global -variables, like the bus, into local ones. -

When an application grows, it becomes useful to split it into multiple -files. For example, we will split graffiti into 4 files. -

  • common.ml, which will be part of both client and server, -containing shared types and declarations, -
  • client.ml, client-only part of the application, -
  • server.ml, server-only part of the application, and -
  • graffiti.eliom, which is the only part where we need to -include both client-side and server-side code -

common.ml

It contains what was previously in [%shared ... ] -

type messages =
-  ((int * int * int) * int * (int * int) * (int * int))
-  [@@deriving json]
-
-let width = 700
-let height = 400

client.ml

It is almost the same code as what was enclosed in {{{ {client{ ... }} -}}}, with the difference that what was previously in the client value -init_client is now in the function launch_client_canvas. -

open Common
-open Js_of_ocaml
-open Eliom_content
-
-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##beginPath;
-  ctx##(moveTo (float x1) (float y1));
-  ctx##(lineTo (float x2) (float y2));
-  ctx##stroke
-
-(* type containing all informations we need to stop interaction
-   inside the page *)
-type drawing_canceller =
-    { message_thread : unit Lwt.t;
-      (* the thread reading messages from the bus *)
-      drawing_thread : unit Lwt.t;
-      (* the arrow handling mouse events *)
-    }
-
-let stop_drawing { message_thread; drawing_thread } =
-  Lwt.cancel message_thread;
-  (* cancelling this thread also close the bus *)
-  Lwt.cancel drawing_thread

Lwt.cancel t stops thread t. In this case it also closes the -bus on which t is listening. For more informations see the - Lwt programming guide and -Eliom_bus. -

let launch_client_canvas bus image_elt canvas_elt slider =
-  let canvas = Html.To_dom.of_canvas canvas_elt in
-  let ctx = canvas##(getContext (Dom_html._2d_)) in
-  ctx##.lineCap := Js.string "round";
-
-  let img = Html.To_dom.of_img image_elt in
-  let copy_image () = ctx##(drawImage img (0.) (0.)) in
-  if Js.to_bool (img##.complete)
-  then copy_image ()
-  else img##.onload := Dom_html.handler
-    (fun ev -> copy_image (); Js._false);
-
-  (* The color palette: *)
-  let colorpicker, cp_sig = Ot_color_picker.make () in
-  Html.(Manip.appendChild (Manip.Elt.body ()) colorpicker);
-
-  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 in
-  let compute_line ev =
-    let oldx = !x and oldy = !y in
-    set_coord ev;
-    let h, s, v = Eliom_shared.React.S.value cp_sig in
-    let r, g, b = Ot_color_picker.hsv_to_rgb h s v in
-    let rgb = int_of_float r, int_of_float g, int_of_float b in
-    let size_slider = Html.To_dom.of_input slider in
-    let size = int_of_string (Js.to_string size_slider##.value) in
-    (rgb, size, (oldx, oldy), (!x, !y))
-  in
-  let line ev =
-    let v = compute_line ev in
-    let _ = Eliom_bus.write bus v in
-    draw ctx v;
-    Lwt.return ()
-  in
-  let t = Lwt_stream.iter (draw ctx) (Eliom_bus.stream bus) in
-  let drawing_thread =
-    Js_of_ocaml_lwt.Lwt_js_events.(
-      mousedowns canvas (fun ev elt ->
-        Dom.preventDefault ev;
-        set_coord ev;
-        let%lwt () = line ev in
-        Lwt.pick [mousemoves Dom_html.document (fun a _ -> line a);
-	          let%lwt ev = mouseup Dom_html.document in line ev]))
-  in
-  { message_thread = t;
-    drawing_thread = drawing_thread }

server.ml

It contains almost all the server parts of the code. -

open Eliom_content
-open Common
-open Lwt
-
-module My_app =
-  Eliom_registration.App (struct
-    let application_name = "graffiti"
-    let global_data_path = None
-  end)

The main difference is that the bus is now local. -

let launch_server_canvas () =
-  let bus = Eliom_bus.create [%json: messages] in
-
-  let draw_server, image_string =
-    let rgb_ints_to_floats (r, g, b) =
-      float r /. 255., float g /. 255., float b /. 255. in
-    let surface = Cairo.Image.create Cairo.Image.ARGB32 ~w:width ~h:height in
-    let ctx = Cairo.create surface in
-    ((fun (rgb, size, (x1, y1), (x2, y2)) ->
-
-      (* Set thickness of brush *)
-      let r, g, b = rgb_ints_to_floats rgb in
-      Cairo.set_line_width ctx (float size) ;
-      Cairo.set_line_join ctx Cairo.JOIN_ROUND ;
-      Cairo.set_line_cap ctx Cairo.ROUND ;
-      Cairo.set_source_rgb ctx r g b ;
-
-      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 ;
-     ),
-     (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
-     ))
-  in
-  let _ = Lwt_stream.iter draw_server (Eliom_bus.stream bus) in
-  bus,image_string
-
-let graffiti_info = Hashtbl.create 0
-
-let imageservice =
-  Eliom_registration.String.create
-    ~path:(Eliom_service.Path ["image"])
-    ~headers:
-       (Cohttp.Header.add_list (Cohttp.Header.init ())
-          [(Ocsigen_header.Name.(to_string cache_control), "no-cache") ;
-           (Ocsigen_header.Name.(to_string expires), string_of_int 0)])
-    ~meth:
-      (Eliom_service.Get
-         (let open Eliom_parameter in string "name" ** int "q"))
-    (* we add another parameter for the browser not to cache: at least
-       for chrome, there is no way to force the browser to reload the
-       image without leaving the application *)
-    (fun (name,_) () ->
-      try%lwt
-        let _ ,image_string = Hashtbl.find graffiti_info name in
-	Lwt.return (image_string (), "image/png")
-      with
-	| Not_found -> Lwt.fail Eliom_common.Eliom_404)
-
-let get_bus (name:string) =
-  (* create a new bus and image_string function only if it did not exists *)
-  try
-    fst (Hashtbl.find graffiti_info name)
-  with
-    | Not_found ->
-      let bus,image_string = launch_server_canvas () in
-      Hashtbl.add graffiti_info name (bus, image_string);
-      bus

The main page now contains only a form to choose to which drawing you -want to go. The drawing will be sent by the -multigraffiti_service service, registered in -graffiti.eliom. -

let main_service =
-  Eliom_service.create
-    ~path:(Eliom_service.Path [""])
-    ~meth:(Eliom_service.Get (Eliom_parameter.unit))
-    ()
-
-let multigraffiti_service =
-  Eliom_service.create
-    ~path:(Eliom_service.Path [""])
-    ~meth:(Eliom_service.Get (Eliom_parameter.(suffix (string "name"))))
-    ()
-
-let choose_drawing_form () =
-  Html.D.Form.get_form ~service:multigraffiti_service
-    (fun (name) ->
-       [Html.D.p [
-           Html.D.txt "drawing name: ";
-           Html.D.Form.input ~input_type:`Text ~name
-             Html.D.Form.string;
-           Html.D.br ();
-           Html.D.Form.input ~input_type:`Submit ~value:"Go"
-             Html.D.Form.string
-         ]])
-
-let oclosure_script =
-  Html.Id.create_global_elt
-    (Html.D.js_script
-       ~uri:(Html.D.Raw.uri_of_string "./graffiti_oclosure.js") ())
-
-let make_page body =
-  Lwt.return
-    (Html.D.html
-       (Html.D.head
-	  (Html.D.title (Html.D.txt "Graffiti"))
- 	  [
-	    Html.D.css_link
-	      ~uri:(Html.D.Raw.uri_of_string"./css/closure/common.css") ();
-	    Html.D.css_link
-	      ~uri:(Html.D.Raw.uri_of_string"./css/closure/hsvpalette.css") ();
-	    Html.D.css_link
-	      ~uri:(Html.D.Raw.uri_of_string"./css/slider.css") ();
-            oclosure_script;
-	    Html.D.css_link
-	      ~uri:(Html.D.Raw.uri_of_string"./css/graffiti.css") ();
-          ])
-       (Html.D.body body))
-
-let () = My_app.register ~service:main_service
-  (fun () () ->
-    make_page [h1 [txt "Welcome to Multigraffiti"];
-	       choose_drawing_form ()])

graffiti.eliom

Here is the code that mixes client and server parts. -

We first open the corresponding modules for each parts of the -application. -

[%%shared
-    open Eliom_content.Html.D
-    open Common
-]
-[%%client
-    open Client
-]
-open Server

And then we define a function initializing the client application by -side effects in a client value. -

let start_drawing name image canvas slider =
-  let bus = get_bus name in
-  ignore [%client
-    (let canceller =
-       launch_client_canvas ~%bus ~%image ~%canvas ~%slider
-     in
-     Eliom_client.onunload (fun () -> stop_drawing canceller; None)
-     : unit)
-  ]

The function registered by Eliom_service.onunload will be called when -the page change inside the application. -

And we finally register the service sending a drawing: -

let counter = ref 0
-
-let () =
-  My_app.register ~service:multigraffiti_service (fun name () ->
-    (* Some browsers won't reload the image, so we force
-          them by changing the url each time. *)
-    incr counter;
-    let image =
-      img ~alt:name
-        ~src:(make_uri ~service:imageservice (name,!counter)) ()
-    in
-    let slider =
-      Form.input
-        ~a:[
-          a_id "slider";
-          a_input_min (`Number 1);
-          a_input_max (`Number 80)
-        ]
-        ~input_type:`Range
-        Form.int
-    in
-    let canvas =
-      canvas ~a:[a_width width; a_height height]
-        [txt "your browser doesn't support canvas"; br (); image]
-    in
-    start_drawing name image canvas slider;
-    make_page
-      [h1 [txt name];
-       choose_drawing_form ();
-       canvas;
-       div [slider]])

At this point, you can run your application on the server provided -that you installed the css and images directories in the main -directory of your application, build it using this -Makefile -along with the appropriate -Makefile.options, -and configured it using -graffiti.conf.in, -as the basis for your configuration file. -

Mixing client-server application with traditional web interaction

We now want to restrict the site to connected users. -

From the previous chapter, we copy the code handling users to -server.ml: -

let connection_service = Eliom_service.create
-    ~path:Eliom_service.No_path
-    ~meth:(Eliom_service.Post (
-      Eliom_parameter.unit,
-      Eliom_parameter.(string "name" ** string "password")
-    ))
-    ()
-
-let disconnection_service = Eliom_service.create
-    ~path:Eliom_service.No_path
-    ~meth:(Eliom_service.Post (Eliom_parameter.unit, Eliom_parameter.unit))
-    ()
-
-let create_account_service =
-  Eliom_service.create
-    ~path:(Eliom_service.Path [""])
-    ~meth:(Eliom_service.Post
-             (Eliom_parameter.unit,
-              Eliom_parameter.(string "name" ** string "password")))
-
-let user_table = Ocsipersist.Polymorphic.open_table "user_table"
-
-let check_pwd name pwd =
-  try%lwt
-    let%lwt saved_password = Ocsipersist.Polymorphic.find user_table name in
-    Lwt.return (pwd = saved_password)
-  with Not_found -> Lwt.return false
-
-let () = Eliom_registration.Action.register
-  ~service:create_account_service
-  (fun () (name, pwd) -> Ocsipersist.Polymorphic.add user_table name pwd)
-
-let () = Eliom_registration.Action.register
-  ~service:connection_service
-  (fun () (name, password) ->
-    match%lwt check_pwd name password with
-      | true -> Eliom_state.set_volatile_data_session_group
-	~scope:Eliom_common.default_session_scope name;
-	Lwt.return ()
-      | false -> Lwt.return ())
-
-let () =
-  Eliom_registration.Action.register
-    ~service:disconnection_service
-    (fun () () ->
-      Eliom_state.discard ~scope:Eliom_common.default_session_scope ())
-
-let disconnect_box () =
-  Html.D.Form.post_form ~service:disconnection_service
-    (fun _ ->
-       [Html.D.p [
-           Html.D.Form.input
-             ~input_type:`Submit ~value:"Log out"
-             Html.D.Form.string
-         ]
-       ]) ()
-
-let login_name_form service button_text =
-  Html.D.Form.post_form ~service
-    (fun (name1, name2) ->
-       [Html.D.p [
-           Html.D.txt "login: ";
-           Html.D.Form.input ~input_type:`Text ~name:name1
-             Html.D.Form.string;
-           Html.D.br ();
-           Html.D.txt "password: ";
-           Html.D.Form.input ~input_type:`Password ~name:name2
-             Html.D.Form.string;
-           Html.D.br ();
-           Html.D.Form.input ~input_type:`Submit ~value:button_text
-             Html.D.Form.string
-         ]]) ()

We make a customized registration module such that disconnected users -(those for which the username reference is not set), are automaticaly -shown a connection box. This way the other pages can assume that the -username is always available. -

let default_content () =
-  make_page
-    [Html.D.h1 [Html.D.txt "Welcome to Multigraffiti"];
-     Html.D.h2 [Html.D.txt "log in"];
-     login_name_form connection_service "Connect";
-     Html.D.h2 [Html.D.txt "create account"];
-     login_name_form create_account_service "Create account";]
-
-module Connected_translate =
-struct
-  type page = string -> My_app.page Lwt.t
-  let translate page =
-    match Eliom_state.get_volatile_data_session_group
-      ~scope:Eliom_common.default_session_scope () with
-	| None -> default_content ()
-	| Some username -> page username
-end
-
-module Connected =
-  Eliom_registration.Customize (My_app) (Connected_translate)

We replace the previous main_service registration : -

let () = My_app.register ~service:main_service
-  (fun () () ->
-    make_page [h1 [txt "Welcome to Multigraffiti"];
-	       choose_drawing_form ()])

by : -

let ( !% ) f = fun a b -> return (fun c -> f a b c)
-
-let () = Connected.register
-  ~service:main_service
-  !% (fun () () username ->
-    make_page
-      [Html.D.h1 [Html.D.txt ("Welcome to Multigraffiti " ^ username)];
-       choose_drawing_form ()])

to use that, in graffiti.eliom we just replace add a call -to disconnect_box -

[h1 [txt name];
-       disconnect_box ();
-       choose_drawing_form ();
-       canvas;])

prev

diff --git a/7.1/manual/mobile.html b/7.1/manual/mobile.html deleted file mode 100644 index d2aee714..00000000 --- a/7.1/manual/mobile.html +++ /dev/null @@ -1,79 +0,0 @@ - Mobile applications with Ocsigen

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

Mobile applications with Ocsigen

Since Eliom 6.0, the Ocsigen framework provides infrastructure for -building mobile applications. This enables rapid development of -Android, iOS, and Windows Mobile apps with the same APIs and -programming style as for a regular client-server Web application. In -fact, one single codebase can be used to produce all these -applications simultaneously. -

The easiest way to get started with Ocsigen-based mobile development -is to use Ocsigen-start. Ocsigen-start is a -library and skeleton for building "minimum viable product" Web -applications with standard functionality like users, notifications, -etc. This skeleton is immediately usable as a mobile application. You -can install ocsigen-start via OPAM -( opam install ocsigen-start ) -and instantiate its template with the command -eliom-distillery -name ocsimobile -template os.pgocaml. For -launching the mobile application, follow the instructions in the -template's README.md. -

Programming style

You need to follow a certain programming style for your Eliom code to -work flawlessly inside an Ocsigen-start mobile app. This style is -described in the chapter on -client services from the Eliom manual. The idea is that you -implement as much of your application as possible in shared sections -([%%shared ...] or let%shared ... = ...), and that you -register handlers for your Eliom services on both the client and the -server. The Ocsigen-start template already uses the above programming -style, so refer to the files generated by eliom-distillery for -inspiration. -

Going further

  • Ocsigen-toolkit provides a set of user -interface widgets that are particularly useful for mobile applications. -
diff --git a/7.1/manual/music.html b/7.1/manual/music.html deleted file mode 100644 index 22025e2f..00000000 --- a/7.1/manual/music.html +++ /dev/null @@ -1,67 +0,0 @@ -Listening music

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

Listening music

We will add an audio player to the page that will stay when page -changes. This emphasises the fact that browsing inside an application -does not stop the client side code: the music keeps playing when the -content of the page and the url change. -

We first create the player node at toplevel. -

let player =
-  Eliom_content.Html.Id.create_global_elt
-    (audio
-       ~srcs:(make_uri (Eliom_service.static_dir ())
-                  ["music.ogg"],
-              [])
-       ~a:[a_autoplay (`Autoplay);a_controls (`Controls)]
-       [txt "Your browser does not support audio element" ])

And we insert the player in the page. -

And that's all! Since the player node is declared unique, no new -player is created when the page changed: this is exact same node. -

To run this example, you will need to add an ogg file in the static directory. -If you can't find one, there is one here: -http://www.gnu.org/music/free-software-song.html.

diff --git a/7.1/manual/ocsipersist.html b/7.1/manual/ocsipersist.html deleted file mode 100644 index 8fe49464..00000000 --- a/7.1/manual/ocsipersist.html +++ /dev/null @@ -1,77 +0,0 @@ -Lightweight database using Ocsipersist

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

Lightweight database using Ocsipersist

Ocsipersist is a module for persistent references and tables. -

For persistent references, Eliom has a higher level interface, -called Eliom references, and you probably want to use it instead -of using Ocsipersist directly. -For example persistent Eliom references of scope session group -can be used to store information about a user (if the session group -corresponds to the user). -

Ocsipersist can still be useful for creating persistent key-value tables, -if you do not need the full power of a SQL database. -

This tutorial shows how to implement a table for storing users and passwords. -

We first create a table: -

let user_table = Ocsipersist.Polymorphic.open_table "user_table"

Then we can easily handle the user management code: -

let check_pwd name pwd =
-  try%lwt
-    lwt saved_password = Ocsipersist.Polymorphic.find user_table name in
-    Lwt.return (pwd = saved_password)
-  with
-    Not_found -> Lwt.return false
-
-let () = Eliom_registration.Action.register
-  ~service:create_account_service
-  (fun () (name, pwd) -> Ocsipersist.Polymorphic.add user_table name pwd)
-
-let () = Eliom_registration.Action.register
-  ~service:connection_service
-  (fun () (name, password) ->
-    match%lwt check_pwd name password with
-      | true -> Eliom_reference.set username (Some name)
-      | false -> Lwt.return ())
diff --git a/7.1/manual/pictures.html b/7.1/manual/pictures.html deleted file mode 100644 index 4396454d..00000000 --- a/7.1/manual/pictures.html +++ /dev/null @@ -1,195 +0,0 @@ -Saving favorite pictures

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

Saving favorite pictures

We will now add a button to the Graffiti application to save the current -image. The images will be saved to the filesystem using the module -Lwt_io. We will then make an Atom feed -with the saved images using Syndic. -

To install it, do: -

opam install syndic
-

We save the images in the directory containing the static contents -under the directory images_saved/username. The username -directory is created if needed. If it already exists mkdir fails -and we do nothing. -

We will add this code in a new file: -

feed.ml

open Eliom_content
-open Lwt.Infix
-open Html.F
-open Server
-
-let static_dir =
-  match Eliom_config.get_config () with
-  | [Element ("staticdir", [], [PCData dir])] ->
-    dir
-  | [] ->
-    raise (Ocsigen_extensions.Error_in_config_file
-             ("<staticdir> option required for <graffiti>"))
-  | _ ->
-    raise (Ocsigen_extensions.Error_in_config_file
-             ("Unexpected content inside graffiti config"))
-
-let create_dir dir =
-  try%lwt Lwt_unix.mkdir dir 0o777 with
-  | Unix.Unix_error (Unix.EEXIST, "mkdir", _) -> Lwt.return_unit
-  | _ ->
-      Eliom_lib.debug "could not create the directory %s" dir;
-      Lwt.return_unit
-
-let image_dir name =
-  let dir = static_dir ^ "/graffiti_saved/" in
-  let%lwt () = create_dir dir in
-  let dir = dir ^ Eliom_lib.Url.encode name in
-  let%lwt () = create_dir dir in
-  Lwt.return dir
-
-let make_filename name number =
-  image_dir name >|= ( fun dir -> (dir ^ "/" ^ (string_of_int number) ^ ".png") )
-
-let save image name number =
-  let%lwt file_name = make_filename name number in
-  let%lwt out_chan = Lwt_io.open_file ~mode:Lwt_io.output file_name in
-  Lwt_io.write out_chan image

We number images and associate to each image the time of creation. It -is stocked in an Ocsipersist -table. -

let image_info_table = Ocsipersist.Polymorphic.open_table "image_info_table"

For each user, we stock a value of type
- int * CalendarLib.Calendar.t * ((int * CalendarLib.Calendar.t) list). -The first integer is the name under which will be saved the image, the first -time is the last update for that user and the list contains the names and -times of old images. We need those times to timestamp the entries of the feed. -

let save_image username =
-  let now = CalendarLib.Calendar.now () in
-  let%lwt image_info_table = image_info_table in
-  let%lwt number,_,list =
-    try%lwt Ocsipersist.Polymorphic.find image_info_table username with
-    | Not_found -> Lwt.return (0,now,[])
-    | e -> Lwt.fail e
-  in
-  let%lwt () = Ocsipersist.Polymorphic.add image_info_table
-      username (number+1,now,(number,now)::list) in
-  let (_,image_string) = Hashtbl.find graffiti_info username in
-  save (image_string ()) username number
-
-let save_image_service =
-  Eliom_service.create
-    ~meth:(Eliom_service.Post
-             (Eliom_parameter.unit, Eliom_parameter.string "name"))
-    ~path:Eliom_service.No_path ()
-
-let () =
-  Eliom_registration.Action.register
-    ~service:save_image_service (fun () name -> save_image name)
-
-let save_image_box name =
-  Lwt.return
-    (Html.D.Form.post_form ~service:save_image_service
-       (fun param_name ->
-         [p [Html.D.Form.input ~input_type:`Hidden ~name:param_name
-              ~value:name Html.D.Form.string;
-             Html.D.Form.button_no_value ~button_type:`Submit [txt "save"]]])
-       ())

We find the url of the images with Eliom_service.static_dir. It is a service -taking file path as parameter, serving the content of the static -directory. We use Eliom_uri.make_string_uri to get the url as a string. -

let feed_service =
-  Eliom_service.create
-    ~path:(Eliom_service.Path ["feed"])
-    ~meth:(Eliom_service.Get (Eliom_parameter.string "name"))
-    ()
-
-let local_filename name number =
-  ["graffiti_saved"; Eliom_lib.Url.encode name ; (string_of_int number) ^ ".png"]
-
-let rec entries name list = function
-  | 0 -> []
-  | len ->
-    match list with
-    | [] -> []
-    | (n,saved)::q ->
-      let uri =
-        Html.D.make_uri ~absolute:true
-          ~service:(Eliom_service.static_dir ())
-          (local_filename name n)
-        |> Xml.string_of_uri
-        |> Uri.of_string in
-      let content = Syndic.Atom.Src (None, uri) in
-      let authors = (Syndic.Atom.author name), [] in
-      let title : Syndic.Atom.text_construct =
-        Syndic.Atom.Text ("graffiti " ^ name ^ " " ^ (string_of_int n)) in
-      let entry =
-        Syndic.Atom.entry ~content ~id:uri ~authors ~title ~updated:saved () in
-      entry::(entries name q (len - 1))
-
-let feed_of_string_page xml =
-  xml
-  |> Syndic.Atom.to_xml
-  |> Syndic.XML.to_string
-  |> fun string -> string, ""
-
-let feed name () =
-  let id =
-    Xml.string_of_uri
-      (Html.D.make_uri ~absolute:true ~service:feed_service name)
-    |> Uri.of_string in
-  let title : Syndic.Atom.text_construct =
-    Syndic.Atom.Text ("nice drawings of " ^ name) in
-  Lwt.catch
-    (fun () ->
-       let%lwt image_info_table = image_info_table in
-       Ocsipersist.Polymorphic.find image_info_table name >|=
-      (fun (number,updated,list) ->
-         Syndic.Atom.feed ~id ~updated ~title (entries name list 10)
-       |> feed_of_string_page))
-    ( function Not_found ->
-      let now = Option.get (Ptime.of_float_s (Unix.gettimeofday ())) in
-      Lwt.return (Syndic.Atom.feed ~id ~updated:now ~title []
-                  |> feed_of_string_page)
-             | e -> Lwt.fail e )
-
-let () = Eliom_registration.String.register ~service:feed_service feed

And then use the new module as follow: -

[ a Feed.feed_service [txt "atom feed"] name;
-    div (if name = username
-      then [Feed.save_image_box name]
-      else [txt "no saving"]);
-  ]
diff --git a/7.1/manual/reactivemediaplayer.html b/7.1/manual/reactivemediaplayer.html deleted file mode 100644 index eb94b95b..00000000 --- a/7.1/manual/reactivemediaplayer.html +++ /dev/null @@ -1,236 +0,0 @@ -Reactive Media Player

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

Reactive Media Player

You should read the Playing Music tutorial before this one. -

Since version 4, Eliom embeds the -React library in order to -provide reactive HTML elements -(Eliom_content.Html.R). -

The final Eliom code is available -for download.

Basics

A reactive element, or more generally a reactive value, depends on the -current value of a signal. For instance : -

[%%shared
-open Eliom_content
-open Html
-]
let%client s, set_s = React.S.create 0 (* signal creation *)
let%shared example_div () =
-  C.node {{R.txt (React.S.map string_of_int s)}}
-
-let%shared incr_button =
-  D.(button
-      ~button_type:`Button
-      ~a:[a_onclick [%client fun _ -> set_s (succ (React.S.value s)) ]]
-      [txt "Increment"])

The signal s carries an int value initialized at 0 and -set_s is the update function generating an occurence of the -signal. -

example_div is a <div> containing a string which depends -on the value of s. -

The magic part: we never have to explicitly update -example_div. Its behavior is declaratively described in it's own -code, and not in the code of the button. -

Functional Reactive Media Player

This part explains how to create a simple media player, similar to the -Playing Music tutorial but with custom -controls.We will apply -FRP (Functional Reactive Programming). -

In order to provide a short tutorial, we only create three controls: -play, pause and seek/progress bar. So, let's write the corresponding -type: -

[%%shared
-    open Eliom_content
-    open Html
-
-    type action = Play | Pause | Seek of float
-]
let%client media_s, set_media_s = React.S.create Pause

Each HTML element emits a signal value corresponding to its action. -It is enough to create our "play" and "pause" inputs. -

let pause_button () =
-  D.(Form.button_no_value
-       ~button_type:`Button
-       ~a:[a_onclick  [%client  fun _ -> set_media_s Pause ]]
-       [txt "Pause"])
-
-let play_button () =
-  D.(Form.button_no_value
-       ~button_type:`Button
-       ~a:[a_onclick  [%client  fun _ -> set_media_s Play ]]
-       [txt "Play"])

To use our buttons, we now create a media (audio or video) HTML -element on the server side. -

let media_uri =
-  Html.D.make_uri
-    ~service:(Eliom_service.static_dir ())
-    ["hb.mp3"]
-
-let media_tag () =
-  let media = D.(audio ~src:media_uri [txt "alt"]) in
-  let _ = [%client
-    (Lwt.async (fun () ->
-         let media = To_dom.of_audio ~%media in
-         let media_map = function
-           | Play ->
-             media##play
-           | Pause ->
-             media##pause
-           | Seek f ->
-             media##.currentTime := (f /. 100. *. media##.duration)
-         in Lwt_react.S.keep (React.S.map media_map media_s) ;
-         Lwt_js_events.timeupdates media (fun _ _ ->
-             set_progress_s (media##.currentTime, media##.duration) ;
-             Lwt.return ()
-           ))
-     : unit)
-  ] in
-  media

The function media_tag builds an <audio> element. The -code in [%client ... ] is on the client part. It's an Lwt -thread that maps a function media_action -> unit to the signal -media_s. -

module React_Player_app =
-  Eliom_registration.App
-    (struct
-      let application_name = "react_player"
-    end)
-
-let media_service =
-  Eliom_service.create
-    ~path:(Eliom_service.Path [])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    ()
-
-let () =
-  React_Player_app.register
-    ~service:media_service
-    (fun name () ->
-       let body =
-         D.(body [
-             h2 [txt "Media"];
-             media_tag ();
-             div [play_button (); pause_button (); progress_bar ()]
-           ])
-       in
-       Lwt.return (Eliom_tools.D.html ~title:"Media" ~css:[] body))

Now you should have an ΗΤΜL node with an audio tag, and two buttons: -play and pause. The progress bar is slightly harder to understand, but -thanks to FRP, very easy to write. It's basically an input with -range type. In our program, the progress bar must emit the -signal media_s with the value Seek f at input handling. -Then, it must evolve during media playback, for which we need another -signal. To conclude, we must check that the display (the value) of the -progress bar is not modified when the user is seeking. -

let%client progress_s, set_progress_s = React.S.create (0., 0.)
-
-let%client unblock_s, set_unblock_s = React.S.create true
let progress_bar () =
-  let progress_value =
-    [%client
-      (let f (time, duration) =
-         if duration = 0. then 0. else time /. duration *. 100.
-       in
-       React.S.map f progress_s
-       : float React.signal)
-    ] in
-  let attrs = D.([
-      a_input_min 0.;
-      a_input_max 100.;
-      a_onmousedown [%client fun _ -> set_unblock_s false];
-      a_onmouseup [%client fun _ -> set_unblock_s true];
-      C.attr [%client
-        R.a_value
-          (React.S.map (Printf.sprintf "%0.f")
-             (React.S.on unblock_s 0. ~%progress_value))]
-    ])
-  in
-  let d_input =
-    D.Form.input ~input_type:`Range ~value:0. ~a:attrs
-      D.Form.float
-  in
-  let _ = [%client
-    (Lwt.async (fun () ->
-         let d_input = To_dom.of_input ~%d_input in
-         Lwt_js_events.inputs d_input (fun _ _ ->
-             set_media_s (Seek (Js.parseFloat d_input##.value)) ;
-             Lwt.return ()
-           ))
-     : unit)
-  ] in
-  d_input

To end this tutorial, you can add a progress_bar () call inside -the div containing play and pause. We also need a mechanism which -emits the progress_s signal. We modify the media tag with an -eventhandler on timeupdate. -

let media_tag () =
-  let media = D.(audio ~src:media_uri [txt "alt"]) in
-  let _ = [%client
-    (Lwt.async (fun () ->
-         let media = To_dom.of_audio ~%media in
-         let media_map = function
-           | Play ->
-             media##play
-           | Pause ->
-             media##pause
-           | Seek f ->
-             media##.currentTime := (f /. 100. *. media##.duration)
-         in Lwt_react.S.keep (React.S.map media_map media_s) ;
-         Lwt_js_events.timeupdates media (fun _ _ ->
-             set_progress_s (media##.currentTime, media##.duration) ;
-             Lwt.return ()
-           ))
-     : unit)
-  ] in
-  media

Exercises

  • Add a control to set the volume -
  • Add an Eliom_bus to control several clients
diff --git a/7.1/manual/rest.html b/7.1/manual/rest.html deleted file mode 100644 index 1212359d..00000000 --- a/7.1/manual/rest.html +++ /dev/null @@ -1,451 +0,0 @@ -RESTful JSON API using Eliom

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

RESTful JSON API using Eliom

This tutorial will show you how to create a simple, yet complete, REST API -using JSON as the serialization format. -

To illustrate our example, let's say we want to give access to a database of -locations storing a description and coordinates (latitude and longitude). -

To be RESTful, our interface will comply with the following principles: -

  • URLs and GET params identify resources -
  • HTTP methods are used to define actions to perform (GET, POST, PUT, DELETE) -
  • GET action is safe (no side-effect) -
  • PUT and DELETE actions are idempotent -
  • Requests are stateless -

With this in mind, our goal will be to implement CRUD (Create, Read, Update, -Delete) functions to handle our resources. We want the following requests to be -valid: -

GET http://localhost/ will return all available locations. -

GET http://localhost/ID will return location associated to ID. -

POST http://localhost/ID with content: -

{
-  "description": "Paris",
-  "coordinates": {
-    "latitude": 48.8567,
-    "longitude": 2.3508
-  }
-}

will store this location in the database. -

PUT http://localhost/ID, with some content, will update the location -associated to ID. -

DELETE http://localhost/ID will delete the location associated to -ID. -

Requirements

  • eliom >= 4.0 -
  • yojson -
  • ppx_deriving -
  • ppx_deriving_yojson -

You need some knowledge about Eliom to be able to fully understand this -tutorial. It's not meant to be an Eliom introduction. -

The following browser extensions are useful to manually test your REST APIs: -

Data types

We start by defining our database types, that is to say the way we will -represent our locations and associated information. Each location will be -associated to a unique and arbitrary identifier, and will hold the following -information: a description and coordinates (made of a latitude and a -longitude). -

We represent coordinates with decimal degrees, and use the -ppx_deriving_yojson library to parse and generate JSON serialization of our -types. -

We use a dedicated error type returned when something is wrong with the -request itself or with the processing of the request. -

As for the database, we use a simple Ocsipersist table. -

type coordinates = {
-  latitude : float;
-  longitude : float;
-} [@@deriving yojson]
-
-type location = {
-  description : string option;
-  coordinates : coordinates;
-} [@@deriving yojson]
-
-(* List of pairs (identifier * location) *)
-type locations =
-  (string * location) list
-  [@@deriving yojson]
-
-type error = {
-  error_message : string;
-} [@@deriving yojson]
-
-let db : location Ocsipersist.table Lwt.t =
-  Ocsipersist.Polymorphic.open_table "locations"

Services definition

First, let's define common service parameters: -

  • The path of the API: it's the same for all services. -
  • The GET parameter, which is an optional identifier given as a URL suffix. We -set it as optional so we can distinguish GET requests for all resources or a -single one, and return a detailed error if the identifier is missing in POST, -PUT and DELETE requests. An alternative would be to use two services at the same -path (one with id and the other without). -
let path = Eliom_service.Path [""]
-
-let get_params =
-  Eliom_parameter.(suffix (neopt (string "id")))

Next step is to define our API services. We define four of them with the same -path, using the four HTTP methods at our disposal: -

  • GET method will be used to access the database, either all of it if no -identifier is provided, or a single resource otherwise. An error will be -returned if no resource matches the identifier. -
  • POST method will be used to create a new resource (or update it if it already -exists). We set a single POST parameter: Eliom_parameter.raw_post_data, -in order to retrieve raw JSON and bypass POST parameters decoding. -
  • PUT method will be used to update an existing resource. An error will be -returned if no resource matches the identifier. We don't need to define POST -parameter, PUT services takes Eliom_parameter.raw_post_data as default -content. -
  • DELETE method will be used to delete an existing resource. An error will be -returned if no resource matches the identifier. -
let read_service =
-  Eliom_service.create
-    ~path
-    ~meth:(Eliom_service.Get get_params)
-    ()
-
-let create_service =
-  Eliom_service.create
-    ~path
-    ~meth:(Eliom_service.Post (get_params, Eliom_parameter.raw_post_data))
-    ()
-
-let update_service =
-  Eliom_service.create
-    ~path
-    ~meth:(Eliom_service.Put get_params)
-    ()
-
-let delete_service =
-  Eliom_service.create
-    ~path
-    ~meth:(Eliom_service.Delete get_params)
-    ()

Handlers

Let's start the handlers definition with a few helper values and functions -used by handlers. -

Since we use the low-level Eliom_registration.String.send function to -send our response, we wrap it in three specialized functions: send_json, -send_error and send_success (this one only send the 200 OK status -code, without any content). -

Another function helps us to check the received Content-type is the -expected one, by matching it against a MIME type. In our example, we'll verify -that we are receiving JSON. -

The read_raw_content function retrieve at most length characters -from the raw_content Ocsigen stream. -

let json_mime_type = "application/json"
-
-let send_json ~code json =
-  Eliom_registration.String.send ~code (json, json_mime_type)
-
-let send_error ~code error_message =
-  let json = Yojson.Safe.to_string (error_to_yojson {error_message}) in
-  send_json ~code json
-
-let send_success () =
-  Eliom_registration.String.send ~code:200 ("", "")
-
-let check_content_type ~mime_type content_type =
-  match content_type with
-  | Some ((type_, subtype), _)
-      when (type_ ^ "/" ^ subtype) = mime_type -> true
-  | _ -> false
-
-let read_raw_content ?(length = 4096) raw_content =
-  let content_stream = Ocsigen_stream.get raw_content in
-  Ocsigen_stream.string_of_stream length content_stream

Then we define our handlers in order to perform the required actions and -return a response. -

POST and PUT handlers will read raw body content as JSON and use Yojson -to convert it to our types. -

We use HTTP status codes in responses, with these meaning: -

  • 200 (OK): the request succeeded. -
  • 400 (Bad request): something is wrong with the request (missing parameter, -parsing error...). -
  • 404 (Not found): no resource matches the provided identifier. -

The GET handler either returns a single location if an identifier is provided, -or a list of all existing locations otherwise. -

let read_handler id_opt () =
-  let%lwt db = db in
-  match id_opt with
-  | None ->
-    Ocsipersist.Polymorphic.fold_step
-      (fun id loc acc -> Lwt.return ((id, loc) :: acc)) db []
-    >>= fun locations ->
-    let json = Yojson.Safe.to_string (locations_to_yojson locations) in
-    send_json ~code:200 json
-  | Some id ->
-    catch
-      (fun () ->
-         Ocsipersist.Polymorphic.find db  id >>= fun location ->
-         let json =
-           Yojson.Safe.to_string (location_to_yojson location) in
-         send_json ~code:200 json)
-      (function
-        | Not_found ->
-          (* [id] hasn't been found, return a "Not found" message *)
-          send_error ~code:404 ("Resource not found: " ^ id)
-        | _ ->  send_error ~code:500 "Internal server error" )

Next, let's create a common function for POST and PUT handlers, which have a -very similar behaviour. The only difference is that a PUT request with a -non-existing identifier will fail (thus only accepting update requests, and -rejecting creations), whereas the same request with POST method will succeed (a -new location associated to the identifier will be created). -

let edit_handler_aux ?(create = false) id_opt (content_type, raw_content_opt) =
-  let%lwt db = db in
-  if not (check_content_type ~mime_type:json_mime_type content_type) then
-    send_error ~code:400 "Content-type is wrong, it must be JSON"
-  else
-    match id_opt, raw_content_opt with
-    | None, _ ->
-      send_error ~code:400 "Location identifier is missing"
-    | _, None ->
-      send_error ~code:400 "Body content is missing"
-    | Some id, Some raw_content ->
-      read_raw_content raw_content >>= fun location_str ->
-      catch (fun () ->
-          (if create then
-            Lwt.return_unit
-           else
-            Ocsipersist.Polymorphic.find db id >>= fun _ -> Lwt.return_unit)
-          >>= fun () ->
-          let location =
-            (let open Result in
-            let loc_result = location_of_yojson (Yojson.Safe.from_string location_str) in
-            match loc_result with
-              Ok loc-> loc
-            | Error _ -> raise Deriving_Yojson.Failed )  in
-          Ocsipersist.Polymorphic.add db  id location >>= fun () ->
-          send_success ())
-      (function
-        | Not_found ->
-          send_error ~code:404 ("Location not found: " ^ id)
-        | Deriving_Yojson.Failed ->
-          send_error ~code:400 "Provided JSON is not valid"
-        | _ -> send_error ~code:500 "Internal server error")
-
-let create_handler id_opt content =
-  edit_handler_aux ~create:true id_opt content
-
-let update_handler id_opt content =
-  edit_handler_aux ~create:false id_opt content

We need a fourth handler to delete locations: -

let%lwt db = db in
-  match id_opt with
-  | None ->
-    send_error ~code:400 "An id must be provided to delete a location"
-  | Some id ->
-    Ocsipersist.Polymorphic.remove db id >>= fun () ->
-    send_success ()

Services registration

Finally we register services with the Eliom_registration.Any module in -order to have full control on the sent response. This way, we'll be able to -send an appropriate HTTP status code depending on what happens during the -request processing (parsing error, resource not found...), as seen above when -defining handlers. -

let () =
-  Eliom_registration.Any.register read_service read_handler;
-  Eliom_registration.Any.register create_service create_handler;
-  Eliom_registration.Any.register update_service update_handler;
-  Eliom_registration.Any.register delete_service delete_handler;
-  ()

Full source

open Lwt
-
-(**** Data types ****)
-
-type coordinates = {
-  latitude : float;
-  longitude : float;
-} [@@deriving yojson]
-
-type location = {
-  description : string option;
-  coordinates : coordinates;
-} [@@deriving yojson]
-
-(* List of pairs (identifier * location) *)
-type locations =
-  (string * location) list
-   [@@deriving yojson]
-
-type error = {
-  error_message : string;
-} [@@deriving yojson]
-
-let db : location Ocsipersist.table Lwt.t =
-  Ocsipersist.Polymorphic.open_table "locations"
-
-
-(**** Services ****)
-
-let path = Eliom_service.Path [""]
-
-let get_params =
-  Eliom_parameter.(suffix (neopt (string "id")))
-
-let read_service =
-  Eliom_service.create
-    ~path
-    ~meth:(Eliom_service.Get get_params)
-    ()
-
-let create_service =
-  Eliom_service.create
-    ~path
-    ~meth:(Eliom_service.Post (get_params, Eliom_parameter.raw_post_data))
-    ()
-
-let update_service =
-  Eliom_service.create
-    ~path
-    ~meth:(Eliom_service.Put get_params)
-    ()
-
-let delete_service =
-  Eliom_service.create
-    ~path
-    ~meth:(Eliom_service.Delete get_params)
-    ()
-
-(**** Handler helpers ****)
-
-let json_mime_type = "application/json"
-
-let send_json ~code json =
-  Eliom_registration.String.send ~code (json, json_mime_type)
-
-let send_error ~code error_message =
-  let json = Yojson.Safe.to_string (error_to_yojson {error_message}) in
-  send_json ~code json
-
-let send_success () =
-  Eliom_registration.String.send ~code:200 ("", "")
-
-let check_content_type ~mime_type content_type =
-  match content_type with
-  | Some ((type_, subtype), _)
-      when (type_ ^ "/" ^ subtype) = mime_type -> true
-  | _ -> false
-
-let read_raw_content ?(length = 4096) raw_content =
-  let content_stream = Ocsigen_stream.get raw_content in
-  Ocsigen_stream.string_of_stream length content_stream
-
-(**** Handlers ****)
-
-let read_handler id_opt () =
-  let%lwt db = db in
-  match id_opt with
-  | None ->
-    Ocsipersist.Polymorphic.fold_step
-      (fun id loc acc -> Lwt.return ((id, loc) :: acc)) db []
-    >>= fun locations ->
-    let json = Yojson.Safe.to_string (locations_to_yojson locations) in
-    send_json ~code:200 json
-  | Some id ->
-    catch
-      (fun () ->
-         Ocsipersist.Polymorphic.find db  id >>= fun location ->
-         let json =
-           Yojson.Safe.to_string (location_to_yojson location) in
-         send_json ~code:200 json)
-      (function
-        | Not_found ->
-          (* [id] hasn't been found, return a "Not found" message *)
-          send_error ~code:404 ("Resource not found: " ^ id)
-        | _ -> assert false)
-
-let edit_handler_aux ?(create = false) id_opt (content_type, raw_content_opt) =
-  let%lwt db = db in
-  if not (check_content_type ~mime_type:json_mime_type content_type) then
-    send_error ~code:400 "Content-type is wrong, it must be JSON"
-  else
-    match id_opt, raw_content_opt with
-    | None, _ ->
-      send_error ~code:400 "Location identifier is missing"
-    | _, None ->
-      send_error ~code:400 "Body content is missing"
-    | Some id, Some raw_content ->
-      read_raw_content raw_content >>= fun location_str ->
-      catch (fun () ->
-          (if create then
-            Lwt.return_unit
-           else
-            Ocsipersist.Polymorphic.find db id >>= fun _ -> Lwt.return_unit)
-          >>= fun () ->
-          let location =
-            (let open Result in
-            let loc_result = location_of_yojson (Yojson.Safe.from_string location_str) in
-            (function
-              Ok loc-> loc
-            | Error _ -> raise Deriving_Yojson.Failed ) loc_result)  in
-          Ocsipersist.Polymorphic.add db  id location >>= fun () ->
-          send_success ())
-      (function
-        | Not_found ->
-          send_error ~code:404 ("Location not found: " ^ id)
-        | Deriving_Yojson.Failed ->
-          send_error ~code:400 "Provided JSON is not valid"
-        | _ -> assert false)
-
-let create_handler id_opt content =
-  edit_handler_aux ~create:true id_opt content
-
-let update_handler id_opt content =
-  edit_handler_aux ~create:false id_opt content
-
-let delete_handler id_opt _ =
-  let%lwt db = db in
-  match id_opt with
-  | None ->
-    send_error ~code:400 "An id must be provided to delete a location"
-  | Some id ->
-    Ocsipersist.Polymorphic.remove db id >>= fun () ->
-    send_success ()
-
-(* Register services *)
-
-let () =
-  Eliom_registration.Any.register read_service read_handler;
-  Eliom_registration.Any.register create_service create_handler;
-  Eliom_registration.Any.register update_service update_handler;
-  Eliom_registration.Any.register delete_service delete_handler;
-  ()
diff --git a/7.1/manual/start.html b/7.1/manual/start.html deleted file mode 100644 index 889d3619..00000000 --- a/7.1/manual/start.html +++ /dev/null @@ -1,113 +0,0 @@ -Your first app in 5 minutes

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

Your first app in 5 minutes

The code of this tutorial has been tested against Eliom 6.0.
-

This tutorial describes how to get started with Ocsigen -quickly. Thanks to an application template provided by the Ocsigen -team, you will get to a working application with standard -functionality like users and notifications in only a few minutes. This -template can be used for learning purposes, or even as a starting -point for your own product. -

The template you will use comes from Ocsigen Start. -Ocsigen Start provides a complete mechanism for adding and managing -users, including activation links and password reset. It additionally -contains a module for displaying tips, a module for sending -notifications to your users, and other utilities typically needed in -modern Web applications. -

The template comes with a default style (developed with -SASS) that follows modern Web practices, e.g., it is responsive and -thus mobile-friendly. Of course, you are free to modify the style to -match your own brand or personality. -

The template is multi-platform. This means that it can run on a Web -browser or as a mobile app for Android, iOS, or Windows. -

-Ocsigen Start - -Ocsigen Start - -Ocsigen Start

Installation

Install the dependencies (postgresql and one of sass, sassc). -On Debian-based systems: -

apt install postgresql ruby-sass
-

Install Ocsigen Start and all its OCaml dependencies using OPAM: -

opam install ocsigen-start
-

Create your application using Eliom distillery: -

eliom-distillery -name myapp -template os.pgocaml
-

Have a look at the -README -file to learn how to compile it -and, possibly, to generate the mobile applications. -

I installed it and compiled it, what next?

It is now time to learn Ocsigen! The template is a great way to get -started, because it comes with multiple runnable examples that show -you: -

  • How to do remote procedure calls (RPCs); -
  • How to save session data (in Eliom references); -
  • How to use the notification system; -
  • How to create reactive pages; -
  • Many examples of widgets from Ocsigen Toolkit: -
    • Carousel -
    • Pages with several tabs -
    • Time and date pickers -
  • ... -

The corresponding files are well-commented, so you can quickly find -out how things work. -

-Ocsigen Start - -Ocsigen Start - -Ocsigen Start - -Ocsigen Start

-Ocsigen Start - -Ocsigen Start - -Ocsigen Start - -Ocsigen Start

Have fun!

diff --git a/7.1/manual/tutoreact.html b/7.1/manual/tutoreact.html deleted file mode 100644 index b2e6f124..00000000 --- a/7.1/manual/tutoreact.html +++ /dev/null @@ -1,654 +0,0 @@ - Client server reactive application with Ocsigen

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

Client server reactive application with Ocsigen

This is a short tutorial showing how to implement a simple reactive -client-server application using Js_of_ocaml, Eliom and -Ocsigen Start. -

We are going to implement an application that can display a list of messages and -that allows connected users to add new messages. -

You will learn: -

  • How to use Ocsigen Start to quickly build an application with user -management. -
  • How to create a client-server reactive interface: the HTML is -generated indifferently server-side or client-side, and -contains reactive parts that are updated automatically when the data -change. -
  • How to implement a notification system for your application. Users -are notified when a new item (a message in our case) arrives. -

First step: a basic application with user management

Ocsigen Start contains a set of higher level libraries for Eliom -(user management, tips, notifications). It also contains a template -for eliom-distillery that creates an application with user -management. You can use this template as a starting point for your -project. -

eliom-distillery -name tutoreact -template os.pgocaml
-

This template is using PostgreSQL to store the data. You need a recent -version of Postgresql installed on your system. With that available, -you can create the local database and start the database server: -

make db-init
-make db-create
-make db-schema
-

Compile and run the program: -

make test.byte
-

Go to http://localhost:8080, you should see the welcome page. You can now -register a user and log in. Because the send mail function is not configured, -the activation links will be printed on the console you started the server with. -

At any point, if you want to get back to this tutorial later, you may need to -start the database again: -

make db-start
-

While doing this tutorial, if you plan to work on another Ocsigen project -requiring a database, do not forget to stop the tutorial's database beforehand: -

make db-stop
-

Display messages from db

To make this example more realistic, let's suppose that we do not want to -display all the messages in the database, but only a few of them. -

In this tutorial, we will not focus on the implementation details of the -database part. Create a new file named tutoreact_messages.eliom. From now -on, if not explicitely specified, the code we are going to write will go there. -We are going to create a module Db containing these functions: -

val get_messages : unit -> int list Lwt.t
-val get_message : int -> string Lwt.t
-val add_message : string -> int Lwt.t

You can try to make your own implementation using for instance pgocaml. -Here's our implementation using Ocsipersist: -

[%%server
-module Db = struct
-  let db = Ocsipersist.Polymorphic.open_table "messages"
-
-  let last_key =
-    Eliom_reference.eref
-      ~persistent:"index"
-      ~scope:Eliom_common.global_scope (-1)
-
-  let get_message id =
-    let%lwt db = db in
-    Ocsipersist.Polymorphic.find db (string_of_int id)
-
-  let get_messages () =
-    let%lwt index = Eliom_reference.get last_key in
-    let rec aux n l = if n > index then l else aux (n+1) (n::l) in
-    Lwt.return (aux 0 [])
-
-  let lock = Lwt_mutex.create ()
-
-  let add_message v =
-    let%lwt () = Lwt_mutex.lock lock in
-    let%lwt index = Eliom_reference.get last_key in
-    let index = index + 1 in
-    let%lwt () = Eliom_reference.set last_key index in
-    Lwt_mutex.unlock lock;
-    let%lwt db = db in
-    let%lwt () = Ocsipersist.Polymorphic.add db (string_of_int index) v in
-    Lwt.return index
-end]

Add the following code: -

[%%shared
-    open Eliom_content.Html
-    open Eliom_content.Html.D]
let%server display userid_o =
-  let%lwt messages = Db.get_messages () in
-  let%lwt l =
-    Lwt_list.map_s
-      (fun id ->
-        let%lwt msg = Db.get_message id in
-        Lwt.return (li [txt msg]))
-      messages
-  in
-  Lwt.return [ul l]

Depending on your database, it is probably more efficient to -fetch all messages and their identifiers using only one request. -Here we use Lwt_list.map_s to do the requests sequentially. -

The content of the main page is defined in -the file tutoreact_handlers.eliom. -Replace the code of main_service_handler by: -

let%server main_service_handler userid_o () () =
-  let%lwt content = Tutoreact_messages.display userid_o in
-  Tutoreact_container.page userid_o content

The main_service_handler you just replaced was in a shared section. -Therefore, we also need to change two other files to take into consideration -this modification. -

In the file tutoreact_handlers.eliomi, move the definition of -main_service_handler from the shared section to the server section. -

In the file tutoreact.eliom, move the registration of main_service -from the shared section to the server section. -

Try to compile in order to see if everything is fine. -

Adding new messages

Add an input in the page, for connected users

To add an input in the page for connected users, -replace the function display by the following version: -

let%server display_messages () =
-  let%lwt messages = Db.get_messages () in
-  let%lwt l =
-    Lwt_list.map_s
-      (fun id ->
-        let%lwt msg = Db.get_message id in
-        Lwt.return (li [txt msg]))
-      messages
-  in
-  Lwt.return (ul l)
-
-let%server display userid_o =
-  let%lwt messages = display_messages () in
-  let l =
-    match userid_o with
-    | None -> []
-    | _ -> [Raw.input ~a:[a_input_type `Text;
-                          a_style "border-style:solid"] ()]
-  in
-  Lwt.return (messages :: l)

Make function Db.add_message accessible from the client

To be able to call a function from the client-side program, -use let%rpc: -

let%rpc add_message (value : string) : unit Lwt.t =
-  let%lwt _ = Os_current_user.get_current_userid () (* fails if not connected *) in
-  Db.add_message value

The parameter [%json: string] describes the type of -the function parameter. This exhibits the syntax provided by -ppx_deriving extended -with our JSON plugin. We use this for safe server-side unmarshalling of data -sent by the client. -

Bind the input to call the function

To call the function from the client program, we will define a -client value, a client-side expression that is accessible -server-side. The client value will be executed client-side -after the page is loaded. The syntax for client values of type -t is [%client (... : t)]. -

Replace the second branch of the match in the function display by: -

let inp = Raw.input ~a:[a_input_type `Text; a_style "border-style:solid"] () in
-let _ = [%client
-  (let open Js_of_ocaml_lwt.Lwt_js_events in
-   let inp = To_dom.of_input ~%inp in
-   async (fun () -> changes inp (fun _ _ ->
-     let value = Js_of_ocaml.Js.to_string inp##.value in
-     inp##.value := Js_of_ocaml.Js.string "";
-     let%lwt _ = add_message value in
-     Lwt.return ()))
-   : unit)
-] in
-[inp]
  • We use module Lwt_js_events to manage events. -
  • The syntax ~%v allows using a server-side value v client-side. -
  • To_dom.of_input returns the JS element corresponding to the -OCaml value ~%inp. -
  • Lwt_js_events.async is similar to Lwt.async. -
  • obj##.a allows the access to the field a -of the JavaScript object obj (see Js_of_ocaml PPX extension). -
  • changes takes a JS element and a function that will be -executed every time a "change" event is received on this element. -

This function gets the value of the input, resets the content of the -input, and calls our server-side function. Do not forget the -conversions between OCaml strings and JS strings since they are different! -

Compile and run the program again. Now the messages should be added in the -database whenever you use the input. However you need to refresh the page to -display them. -

Structure of a client-server application

We have seen how to send data to the server without stopping the -client-side program. Now we want to automatically update the page -when new messages are sent. Generally, the main difference between a -web application and a website is that in the case of a web -application, a client-side program runs and persists accross HTTP -calls (remote procedure calls or page changes). The client process -must be able to receive notifications from the server and update the -page accordingly, without regenerating it entirely. It is common -practice to generate the full interface client-side. But this is -not suitable for all cases. It is usually better to keep the old-style -web interaction and generate pages server-side, for example -to enable search engine indexing. In this tutorial, we will see how to -generate pages indifferently (and with the same code) from both sides. -

In this section, we will see how to implement this kind of applications -very concisely thanks to three notions: -

  • The client-server cache of data -
  • Reactive pages -
  • Notification system -

You will be able to test once you finish the three following sections! -

Client-server cache

The module -Eliom_cscache -implements a cache of data, an association table where you -will put the data of your application client-side. For the sake of -uniformity (as we want to use it in shared sections), the -cache is also implemented server-side, with scope -"request". This avoids retrieving the same data from the database -twice for the same request. -

We create a client-server cache by calling the function -Eliom_cscache.create -server-side. The server-side cache cache created by this -function will be accessible client-side through an injection ~%cache. -

We implement a function get_data to fetch the -data from the database. This function must have an implementation both -server-side and client-side: -

let%rpc get_data (id : int) : string Lwt.t = Db.get_message id
let%server cache : (int, string) Eliom_cscache.t =
-  Eliom_cscache.create ()

Reactive interface

Updating the interface when some data change is usually not -straightforward. This is usually done by putting identifiers on -elements to find them, and manually modifying page elements using -low-level JS functions. -

A very elegant solution to simplify this consists in using Functional -Reactive Programming (FRP). In reactive programming, you define -relations between different pieces of data once, and each update -automatically produces the recomputation of all the dependent data. In -Ocsigen we use the module React -combined with ReactiveData, -which extends React to deal with incremental updates in -lists. Have a look at the documentation of the above modules if you -are not familiar with FRP. -

The client-side module -Eliom_content.Html.R -enables defining reactive page elements. -

The module Eliom_shared -enables defining shared reactive signals server-side. -In order to do that, we use shared values, values defined -both server-side and client-side. The server-side module -Eliom_content.Html.R -enables constructing HTML5 elements that get updated automatically -based on the signals of Eliom_shared. The modules -Eliom_shared.React -and -Eliom_shared.ReactiveData -implement interfaces very similar to React and -ReactiveData, but operate on shared signals. -

Implementation of the reactive interface

display_message now needs to be implemented in a shared fashion and take -its data from the cache. In order to do that, -we call Eliom_cscache.find cache get_data key from either side to get the -value associated to key. If the value is not present in the cache, it will -be fetched using the function get_data and added to the cache. -

let%shared display_message id =
-  let%lwt msg = Eliom_cscache.find ~%cache get_data id in
-  Lwt.return (li [txt msg])

The function display_messages now creates a reactive list of -message identifiers, and maps page content from this reactive value -using module Eliom_shared.ReactiveData. Note that rmessage is -a tuple, the first element is the list, the second element is the update -function. -

let%server display_messages () =
-  let%lwt messages = Db.get_messages () in
-  let rmessages = Eliom_shared.ReactiveData.RList.create messages in
-  let%lwt content =
-    Eliom_shared.ReactiveData.RList.Lwt.map_p
-      [%shared display_message ]
-      (fst rmessages)
-  in
-  Lwt.return (R.ul content)

Notifications

We now want to be notified when a message has been added. To do that -easily, we use the module Os_notif from Ocsigen Start. -

We first define a notification module for the type of data we want -clients to be able to listen on (here the lists of message identifiers): -

[%%server
-module Forum_notif = Os_notif.Make_Simple (struct
-  type key = unit
-  type notification = int
-end)
-]

key is the type of the identifier of the data we want to listen -on. In our case, there is a single message list (thus unit -suffices as the identifier since we don't need to be specific). -

notification is the type of the notifications to send. Here: the -identifier of the new message to be added in the list. -

We define a function to handle notifications. It adds the new -identifier in the reactive list of messages: -

let%client handle_notif_message_list rmessages (_, msgid) =
-  Eliom_shared.ReactiveData.RList.cons msgid (snd rmessages)

We notify the server that we are listening on this piece of data by -calling Forum_notif.listen (server-side). Notifications are -received client-side through a React event -Forum_notif.client_ev (). We map this event to the function -handle_notif_message_list, meaning that we will execute this function -when this event happens. -

let%server display_messages () =
-  Forum_notif.listen ();
-  let%lwt messages = Db.get_messages () in
-  let rmessages = Eliom_shared.ReactiveData.RList.create messages in
-  ignore [%client
-    (ignore
-       (React.E.map (handle_notif_message_list ~%rmessages)
-          ~%(Forum_notif.client_ev () : (unit * int) Eliom_react.Down.t))
-     : unit)
-  ];
-  let%lwt content =
-    Eliom_shared.ReactiveData.RList.Lwt.map_p
-      [%shared display_message ]
-      (fst rmessages)
-  in
-  Lwt.return (R.ul content)

When we add a message, we notify all the clients listening on this -piece of data: -

let%rpc add_message (value : string) : unit Lwt.t =
-   let%lwt id = Db.add_message value in
-   Forum_notif.notify () id;
-   Lwt.return ()

The program is now fully functional, you can now test it! You should see the -messages being added without the need to reload the page, even if messages are -added by another user! Try with several browser windows. -

More information on cache and client-server reactive data

In this section we will demonstrate additional Eliom functionality for -client-server programming by implementing some new features in our -forum: -

  • Multi-page forum -
  • Client-side spinner while loading data -

Multi-page forum

We now want a forum with several pages, located at URLs -http://localhost:8080/i, where i represents the forumid -as an integer. -

Services

In the file tutoreact_services.eliom, we define the new following service: -

let%server forum_service =
-  Eliom_service.create
-    ~path:(Eliom_service.Path [""])
-    ~meth:(Eliom_service.Get
-            (Eliom_parameter.(suffix (int "i"))))
-    ()

In the file tutoreact_services.eliomi, we define its signature, do not -forget to put it in a server section: -

[%%server.start]
-
-val forum_service
-  : ( int
-    , unit
-    , Eliom_service.get
-    , Eliom_service.att
-    , Eliom_service.non_co
-    , Eliom_service.non_ext
-    , Eliom_service.reg
-    , [`WithSuffix]
-    , [`One of int] Eliom_parameter.param_name
-    , unit
-    , Eliom_service.non_ocaml )
-    Eliom_service.t

In the file tutoreact_handlers.eliom, we define the handler we will -associate to our new service: -

let%server forum_service_handler userid_o forumid () =
-  let%lwt content = Tutoreact_messages.display userid_o forumid in
-  Tutoreact_container.page userid_o content

In the file tutoreact_handlers.eliomi, we define its signature, in the -server section: -

val forum_service_handler
-  :  Os_types.User.id option
-  -> int
-  -> unit
-  -> Os_page.content Lwt.t

In the file tutoreact.eliom, we register our handler to our new service in -the server section: -

Tutoreact_base.App.register ~service:Tutoreact_services.forum_service
-    (Tutoreact_page.Opt.connected_page Tutoreact_handlers.forum_service_handler)

Since we have a new parameter forumid, we need to take it into -consideration in many places. -

In the file tutoreact_messages.eliom, the functions display_messages -and display take it as a new parameter. Do not forget to also replace -the latter in the call of display_messages in function display: -

let%server display_messages forumid =
-  ...
let%server display userid_o forumid =
-  let%lwt messages = display_messages forumid in
-  ...

In the file tutoreact_handlers.eliom, update the code of -main_service_handler: -

let%server main_service_handler forumid userid_o () () =
-  let%lwt content = Tutoreact_messages.display userid_o forumid in
-  Tutoreact_container.page userid_o content

In the file tutoreact_handlers.eliomi, update its signature: -

val main_service_handler
-  :  int
-  -> Os_types.User.id option
-  -> unit
-  -> unit
-  -> Os_page.content Lwt.t

In the file tutoreact.eliom, in the main_service, we have to specify -the forumid of the forum we want to reach when we arrive in our application. -We will take 0 for instance and give it as the parameter of -main_service_handler. We update the registration of main_service: -

Tutoreact_base.App.register
-    ~service:Os_services.main_service
-    (Tutoreact_page.Opt.connected_page
-    (Tutoreact_handlers.main_service_handler 0));

Db

The functions Db.get_messages and Db.add_message now take the forum -identifier: -

[%%server
-module Db = struct
-
-  let db = Ocsipersist.Polymorphic.open_table "messages"
-
-  let dbf = Ocsipersist.Polymorphic.open_table "forums"
-
-  let last_key =
-    Eliom_reference.eref
-      ~persistent:"index" ~scope:Eliom_common.global_scope (-1)
-
-  let get_message id =
-    let%lwt db = db in
-    Ocsipersist.Polymorphic.find db (string_of_int id)
-
-  let get_messages forumid =
-    let%lwt dbf = dbf in
-    try%lwt
-      Ocsipersist.Polymorphic.find dbf (string_of_int forumid)
-    with Not_found ->
-      Lwt.return []
-
-  let add_message forumid v =
-    let%lwt index = Eliom_reference.get last_key in
-    let index = index + 1 in
-    let%lwt () = Eliom_reference.set last_key index in
-    let%lwt db = db in
-    let%lwt () = Ocsipersist.Polymorphic.add db (string_of_int index) v in
-    let%lwt l = get_messages forumid in
-    let%lwt dbf = dbf in
-    let%lwt () =
-      Ocsipersist.Polymorphic.add dbf
-        (string_of_int forumid)
-        (index :: l)
-    in
-    Lwt.return index
-
-end
-]

Message type

Since we are now adding besides the message, the forumid as well in our -database, we need to specify a new type: -

[%%shared
-    type add_message_type = int * string [@@deriving json]
-]

We don't forget to take that into consideration in the function -add_message. -

let%rpc add_message ((forumid, value) : add_message_type) : unit Lwt.t =
-   let%lwt id = Db.add_message forumid value in
-   Forum_notif.notify () id;
-   Lwt.return ()

In the function display, in the client section: -

...
-  add_message (~%forumid, value)
-...

Cache of forum message identifiers

We must send the notifications only to the clients listening on the -same forum. -

We will create a new client-server cache to keep the reactive list of -message identifiers for each forums: -

let%server forumcache :
-  (int,
-   int Eliom_shared.ReactiveData.RList.t *
-   int Eliom_shared.ReactiveData.RList.handle) Eliom_cscache.t =
-  Eliom_cscache.create ()

We will now implement the equivalent of get_data for this new cache. -

Be very careful: -

In get_data_forum, we must find the reactive list of messages in -the new cache —if it exists — instead of creating a new one! -Otherwise you will have several reactive data for the same forum and -the page updates will fail! -

To do that, we provide an optional argument ?default to the function -Eliom_shared.ReactiveData.RList.create, a client value -(optionally) containing the current reactive list. If it does not -exist in the cache, a new one will be created like previously: -

let%rpc get_data_forum (forumid : int) : _ Lwt.t =
-  let%lwt messages = Db.get_messages forumid in
-  let default = [%client
-    ((try Some (Eliom_cscache.find_if_ready ~%forumcache ~%forumid)
-      with _ -> None)
-     : 'a option)
-  ] in
-  Lwt.return (Eliom_shared.ReactiveData.RList.create ~default messages)

display_messages now takes the reactive list from the cache: -

let%server display_messages forumid =
-  Forum_notif.listen ();
-  let%lwt rmessages =
-    Eliom_cscache.find forumcache get_data_forum forumid
-  in
-  ...

Notifications dependent on forum ID

Since we now want to be specific about the data we want to listen to, the unit -parameter we defined can't be used anymore. Indeed, notifications now depend on -the identifier. We want to receive notifications only for the forums present in -the client-side cache of forums. Therefore, we just change the type key of -module Forum_notif to use an integer (instead of unit): -

[%%server
-module Forum_notif = Os_notif.Make_Simple (struct
-  type key = int
-  type notification = int
-end)
-]

The function Forum_notif.notify used in the function add_message -now takes the forumid parameter. -

let%rpc add_message ... =
-  ...
-  Forum_notif.notify forumid id;
-  ...

In the function display_messages, we need to take care of the -forumid parameter and the type annotation of client_ev: -

let%server display_messages forumid =
-  Forum_notif.listen (forumid : int);
-  ...
-  ~%(Forum_notif.client_ev () : (int * int) Eliom_react.Down.t))
-  ...

We annotate the type of forumid in the call of the function listen -to help the typing system. -

The function handle_notif_message now takes the reactive -list rmessage from the cache, therefore we no longer need it as a -parameter: -

let%client handle_notif_message_list (forumid, msgid) =
-  try
-    let rmessages = Eliom_cscache.find_if_ready ~%forumcache forumid in
-    Eliom_shared.ReactiveData.RList.cons msgid (snd rmessages)
-  with Not_found | Eliom_cscache.Not_ready -> ()

In the function display_messages, do not forget to remove the injection -of rmessage in the call of handle_notif_message_list in the client -section: -

...
-(React.E.map handle_notif_message_list
-...

Display a spinner while loading the messages

Retrieving messages from server can take time. -To display a spinner while loading the messages when you send them, replace -the function display_message by: -

let%shared display_message id =
-  let th =
-    let%lwt msg = Eliom_cscache.find ~%cache get_data id in
-    Lwt.return [div [txt msg]]
-  in
-  let%lwt v = Ot_spinner.with_spinner th in
-  Lwt.return (li [v])

To simulate network latency, you can add a Lwt_unix.sleep in the -server-side get_data function. -

let%server get_data id =
-  let%lwt () = Lwt_unix.sleep 2.0 in
-  Db.get_message id

The full code (tutoreact_messages.eliom):

[%%shared
-    open Eliom_content.Html
-    open Eliom_content.Html.D
-]
[%%server
-module Db = struct
-  let db = Ocsipersist.Polymorphic.open_table "messages"
-  let dbf = Ocsipersist.Polymorphic.open_table "forums"
-
-  let last_key =
-    Eliom_reference.eref ~persistent:"index" ~scope:Eliom_common.global_scope
-      (-1)
-
-  let get_message id =
-    let%lwt db = db in
-    Ocsipersist.Polymorphic.find db (string_of_int id)
-
-  let get_messages forumid =
-    let%lwt dbf = dbf in
-    try%lwt Ocsipersist.Polymorphic.find dbf (string_of_int forumid)
-    with Not_found -> Lwt.return []
-
-  let add_message forumid v =
-    let%lwt index = Eliom_reference.get last_key in
-    let index = index + 1 in
-    let%lwt () = Eliom_reference.set last_key index in
-    let%lwt db = db in
-    let%lwt () = Ocsipersist.Polymorphic.add db (string_of_int index) v in
-    let%lwt l = get_messages forumid in
-    let%lwt dbf = dbf in
-    let%lwt () =
-      Ocsipersist.Polymorphic.add dbf (string_of_int forumid) (index :: l)
-    in
-    Lwt.return index
-
-end
-
-module Forum_notif = Os_notif.Make_Simple (struct
-  type key = int
-  type notification = int
-end)
-]
[%%shared
-    type add_message_type = int * string [@@deriving json]
-]
let%rpc add_message ((forumid, value) : add_message_type) : unit Lwt.t =
-   let%lwt id = Db.add_message forumid value in
-   Forum_notif.notify forumid (id : int);
-   Lwt.return ()
let%server cache : (int, string) Eliom_cscache.t = Eliom_cscache.create ()
-
-let%server forumcache :
-  (int,
-   int Eliom_shared.ReactiveData.RList.t *
-   int Eliom_shared.ReactiveData.RList.handle) Eliom_cscache.t =
-  Eliom_cscache.create ()
ler%rpc get_data (id : int) : string Lwt.t =
-  let%lwt () = Lwt_unix.sleep 2.0 in
-  Db.get_message id
let%rpc get_data_forum (forumid : int) : _ Lwt.t =
-  let%lwt messages = Db.get_messages forumid in
-  let default = [%client
-    (try
-       Some (Eliom_cscache.find_if_ready ~%forumcache ~%forumid)
-     with _ ->
-       None
-       : 'a option)
-  ] in
-  Lwt.return (Eliom_shared.ReactiveData.RList.create ~default messages)
let%shared display_message id =
-  let th =
-    let%lwt msg = Eliom_cscache.find ~%cache get_data id in
-    Lwt.return [div [txt msg]]
-  in
-  let%lwt v = Ot_spinner.with_spinner th in
-  Lwt.return (li [v])
let%client handle_notif_message_list (forumid, msgid) =
-  try
-    let rmessages = Eliom_cscache.find_if_ready ~%forumcache forumid in
-    Eliom_shared.ReactiveData.RList.cons msgid (snd rmessages)
-  with Not_found | Eliom_cscache.Not_ready -> ()
let%server display_messages forumid =
-  Forum_notif.listen (forumid : int);
-  let%lwt rmessages =
-    Eliom_cscache.find forumcache get_data_forum forumid
-  in
-  ignore [%client
-    (ignore
-       (React.E.map handle_notif_message_list
-          ~%(Forum_notif.client_ev () : (int * int) Eliom_react.Down.t))
-     : unit)
-  ];
-  let%lwt content =
-    Eliom_shared.ReactiveData.RList.Lwt.map_p
-      [%shared display_message]
-      (fst rmessages)
-  in
-  Lwt.return (R.ul content)
-
-let%server display userid_o forumid =
-  let%lwt messages = display_messages forumid in
-  let l =
-    match userid_o with
-    | None -> []
-    | _ ->
-        let inp =
-          Raw.input ~a:[a_input_type `Text; a_style "border-style:solid"] ()
-        in
-        let _ =
-          [%client
-            (let open Js_of_ocaml_lwt.Lwt_js_events in
-             let inp = To_dom.of_input ~%inp in
-             async (fun () ->
-                 changes inp (fun _ _ ->
-                     let value = Js_of_ocaml.Js.to_string inp##.value in
-                     inp##.value := Js_of_ocaml.Js.string "";
-                     let%lwt _ = add_message (~%forumid, value) in
-                     Lwt.return ()))
-              : unit)]
-        in
-        [inp]
-  in
-  Lwt.return (messages :: l)
diff --git a/7.1/manual/tutowidgets.html b/7.1/manual/tutowidgets.html deleted file mode 100644 index b5b7b1e0..00000000 --- a/7.1/manual/tutowidgets.html +++ /dev/null @@ -1,334 +0,0 @@ - Mini-tutorial: client-server widgets

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

Mini-tutorial: client-server widgets

This short tutorial is an example of client-server Eliom application. -It gives an example of client-server widgets. -

It is probably a good starting point if you know OCaml well, and want -to quickly learn how to write a client-server Eliom application with a -short example and concise explanations. For more detailed explanations, see -the -"graffiti" tutorial, -or read the manuals. -

The goal is to show that, unlike many JavaScript libraries that build -their widgets programmatically (by instantiating classes or calling -functions), Eliom enables server-side widget generation, before -sending them to the client. Pages can thus be indexed by search -engines. -

This tutorial also shows that it is possible to use the same code to -build the widget either on client or server side. -

We choose a very simple widget, that could be the base for example for -implementing a drop-down menu. It consists of several boxes with a -title and a content. Clicking on the title opens or closes the -content. Furthermore, it is possible to group some of the boxes -together to make them behave like radio buttons: when you open one of -them, the previously opened one is closed. -

screenshot -

Table of contents

First step: define an application with a basic service

The following code defines a client-server Web application with only -one service, registered at URL / (the root of the website). -

The code also defines a client-side application (let%client or section - [%%client ... ] ) -that appends a client-side generated widget to the page. -Section [%%shared ... ] is compiled on both the server and the -client-side programs. -Inside such a section, you can write let%server or - let%client to override [%%shared ... ] -and define a server-only or client-only value (similarly for - [%%server ... ] and [%%client ... ] ). -

module%server Ex_app =
-  Eliom_registration.App (struct
-    let application_name = "ex"
-    let global_data_path = None
-  end)
-
-let%server _ = Eliom_content.Html.D.(
-  Ex_app.create
-    ~path:(Eliom_service.Path [""])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    (fun () () ->
-       Lwt.return
-         (Eliom_tools.D.html ~title:"tutowidgets" ~css:[["css"; "ex.css"]]
-            (body [h2 [txt "Welcome to Ocsigen!"]])))
-)
let%client mywidget s1 s2 = Eliom_content.Html.D.(
-  let button  = div ~a:[a_class ["button"]] [txt s1] in
-  let content = div ~a:[a_class ["content"]] [txt s2] in
-  div ~a:[a_class ["mywidget"]] [button; content]
-)
-
-let%client _ =
-  let%lwt _ = Js_of_ocaml_lwt.Lwt_js_events.onload () in
-  Js_of_ocaml.Dom.appendChild
-    (Js_of_ocaml.Dom_html.document##.body)
-    (Eliom_content.Html.To_dom.of_element (mywidget "Click me" "Hello!"));
-  Lwt.return ()

To compile it, first create a project by calling -

  eliom-distillery -name ex -template client-server.basic
-

The name of the project must match the name given to the functor -Eliom_registration.App. -

After you adapt the file ex.eliom, -you can compile by calling make, -and run the server by calling make test.byte. -Download the -CSS file -and place it in directory static/css. -Then open a browser window and go to URL http://localhost:8080. -

screenshot -

More explanations

This section gives very quick explanations on the rest of the program. -For more detailed explanations, see the tutorial for the graffiti app -or the manual of each of the projects. -

  • The client side program is sent with the first page belonging to the -application (registered through module Ex_app). -
  • The ## is used to call a JS method from OCaml -and ##. to access a JS object field -(See Js_of_ocaml's documentation: -Ppx_js). -
  • If there are several services in your application, the client-side -program will be sent only with the first page, and will not stop if -you go to another page of the application. -
  • Lwt is the concurrent library used to program threads on both -client and server sides. The syntax let%lwt a = e1 in e2 -allows waiting (without blocking the rest of the program) for an Lwt -thread to terminate before continuing. e2 must ben a Lwt -thread itself. Lwt.return enables creating an -already-terminated Lwt thread. -
  • Js_of_ocaml_lwt.Lwt_js_events defines a convenient way to program -interface events (mouse, keyboard, ...). -

Js_of_ocaml_lwt.Lwt_js_events.onload is a Lwt thread that waits until the page -is loaded. There are similar functions to wait for other events, e.g., -for a click on an element of the page, or for a key press. -

Second step: bind the button

To make the widget work, we must bind the click event. -Replace function mywidget by the following lines: -

let%client switch_visibility elt =
-  let elt = Eliom_content.Html.To_dom.of_element elt in
-  if Js_of_ocaml.Js.to_bool (elt##.classList##contains (Js_of_ocaml.Js.string "hidden")) then
-    elt##.classList##remove (Js_of_ocaml.Js.string "hidden")
-  else
-    elt##.classList##add (Js_of_ocaml.Js.string "hidden")
-
-let%client mywidget s1 s2 = Eliom_content.Html.D.(
-  let button  = div ~a:[a_class ["button"]] [txt s1] in
-  let content = div ~a:[a_class ["content"]] [txt s2] in
-  Lwt.async (fun () ->
-    Js_of_ocaml_lwt.Lwt_js_events.clicks (Eliom_content.Html.To_dom.of_element button)
-      (fun _ _ -> switch_visibility content; Lwt.return ()));
-  div ~a:[a_class ["mywidget"]] [button; content]
-)
  • Once again, we use Js_of_ocaml_lwt.Lwt_js_events. Function clicks is -used to bind a handler to clicks on a specific element. -
  • Function async runs an Lwt thread asynchronously -(without waiting for its result). -
  • Js_of_ocaml_lwt.Lwt_js_events.clicks elt f calls function f for each -mouseclick on element elt. -
  • Eliom_content.Html.To_dom.of_element, Js_of_ocaml.Js.string and Js_of_ocaml.Js.to_bool are -conversion functions between OCaml values and JS values. -

Third step: Generating the widget on server side

The following version of the program shows how to generate the widget -on server side, before sending it to the client. -

The code is exactly the same, with the following modifications: -

  • We place function mywidget in server section. -
  • The portion of code that must be run on client side (binding the -click event) is written as a client value, inside - [%client (... : unit) ] . -This code will be executed by the client-side program when it -receives the page. Note that you must give the type (here -unit), as the type inference for client values is currently -very limited. The client section may refer to server side values, -using the ~%x syntax. These values will be serialized and sent -to the client automatically with the page. -
  • We include the widget on the server side generated page instead of -adding it to the page from client side. -
module%server Ex_app =
-  Eliom_registration.App (struct
-    let application_name = "ex"
-    let global_data_path = None
-  end)
let%client switch_visibility elt =
-  let elt = Eliom_content.Html.To_dom.of_element elt in
-  if Js_of_ocaml.Js.to_bool (elt##.classList##(contains (Js_of_ocaml.Js.string "hidden"))) then
-    elt##.classList##remove (Js_of_ocaml.Js.string "hidden")
-  else
-    elt##.classList##add (Js_of_ocaml.Js.string "hidden")
let%server mywidget s1 s2 = Eliom_content.Html.D.(
-  let button  = div ~a:[a_class ["button"]] [txt s1] in
-  let content = div ~a:[a_class ["content"]] [txt s2] in
-  let _ = [%client
-    (Lwt.async (fun () ->
-       Js_of_ocaml_lwt.Lwt_js_events.clicks (Eliom_content.Html.To_dom.of_element ~%button)
-         (fun _ _ -> switch_visibility ~%content; Lwt.return ()))
-     : unit)
-  ] in
-  div ~a:[a_class ["mywidget"]] [button; content]
-)
-
-let%server _ = Eliom_content.Html.D.(
-  Ex_app.create
-    ~path:(Eliom_service.Path [""])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    (fun () () ->
-       Lwt.return
-         (Eliom_tools.D.html ~title:"ex" ~css:[["css"; "ex.css"]]
-            (body [h2 [txt "Welcome to Ocsigen!"];
-                   mywidget "Click me" "Hello!"])))
-)

Fourth step: widget usable either on client or server sides

If you make function mywidget shared, it will be available -both on server and client sides: -

let%shared mywidget s1 s2 =
-  ...

screenshot -

Fifth step: close last window when opening a new one

To implement this, we record a client-side reference to a function for -closing the currently opened window. -

module%server Ex_app =
-  Eliom_registration.App (struct
-    let application_name = "ex"
-    let global_data_path = None
-  end)
let%client close_last = ref (fun () -> ())
-
-let%client switch_visibility elt =
-  let elt = Eliom_content.Html.To_dom.of_element elt in
-  if Js_of_ocaml.Js.to_bool (elt##.classList##(contains (Js_of_ocaml.Js.string "hidden"))) then
-    elt##.classList##remove (Js_of_ocaml.Js.string "hidden")
-  else
-    elt##.classList##add (Js_of_ocaml.Js.string "hidden")
let%shared mywidget s1 s2 = Eliom_content.Html.D.(
-  let button  = div ~a:[a_class ["button"]] [txt s1] in
-  let content = div ~a:[a_class ["content"]] [txt s2] in
-  let _ = [%client
-    (Lwt.async (fun () ->
-       Js_of_ocaml_lwt.Lwt_js_events.clicks (Eliom_content.Html.To_dom.of_element ~%button) (fun _ _ ->
-	 !close_last ();
-	 close_last := (fun () -> switch_visibility ~%content);
-	 switch_visibility ~%content;
-	 Lwt.return ()
-       ))
-     : unit)
-  ] in
-  div ~a:[a_class ["mywidget"]] [button; content]
-)
let%server _ = Eliom_content.Html.D.(
-  Ex_app.create
-    ~path:(Eliom_service.Path [""])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    (fun () () ->
-      let _ =
-	[%client
-	    (Js_of_ocaml.Dom.appendChild
-	       (Js_of_ocaml.Dom_html.document##.body)
-               (Eliom_content.Html.To_dom.of_element (mywidget "Click me" "client side"))
-               : unit)
-	] in
-      Lwt.return
-        (Eliom_tools.D.html ~title:"ex" ~css:[["css"; "ex.css"]]
-           (body [
-             h2 [txt "Welcome to Ocsigen!"];
-             mywidget "Click me" "server side";
-             mywidget "Click me" "server side";
-             mywidget "Click me" "server side"
-           ])))
-)

Last step: several sets of widgets

Now we want to enable several sets of widgets in the same page. A -single reference no longer suffices. In the following version, the -server-side program asks the client-side program to generate two -different references, by calling function new_set. This function -returns what we call a client value. On server side, it is not -evaluated, and it has an abstract type. -

module%server Ex_app =
-  Eliom_registration.App (struct
-    let application_name = "ex"
-    let global_data_path = None
-  end)
-
-let%server new_set () = [%client ( ref (fun () -> ()) : (unit -> unit) ref)]
let%client switch_visibility elt =
-  let elt = Eliom_content.Html.To_dom.of_element elt in
-  if Js_of_ocaml.Js.to_bool (elt##.classList##(contains (Js_of_ocaml.Js.string "hidden"))) then
-    elt##.classList##remove (Js_of_ocaml.Js.string "hidden")
-  else
-    elt##.classList##add (Js_of_ocaml.Js.string "hidden")
let%shared mywidget set s1 s2 = Eliom_content.Html.D.(
-  let button  = div ~a:[a_class ["button"]] [txt s1] in
-  let content = div ~a:[a_class ["content"; "hidden"]] [txt s2] in
-  let _ = [%client
-    (Lwt.async (fun () ->
-       Js_of_ocaml_lwt.Lwt_js_events.clicks (Eliom_content.Html.To_dom.of_element ~%button) (fun _ _ ->
-         ! ~%set ();
-         ~%set := (fun () -> switch_visibility ~%content);
-         switch_visibility ~%content;
-	 Lwt.return ()))
-       : unit)]
-  in
-  div ~a:[a_class ["mywidget"]] [button; content]
-)
let%server _ = Eliom_content.Html.D.(
-  Ex_app.create
-    ~path:(Eliom_service.Path [""])
-    ~meth:(Eliom_service.Get Eliom_parameter.unit)
-    (fun () () ->
-      let set1 = new_set () in
-      let set2 = new_set () in
-      let _ = [%client (
-	Js_of_ocaml.Dom.appendChild
-	  (Js_of_ocaml.Dom_html.document##.body)
-	  (Eliom_content.Html.To_dom.of_element (mywidget ~%set2 "Click me" "client side"))
-	  : unit)
-	      ] in
-      Lwt.return
-        (Eliom_tools.D.html ~title:"ex" ~css:[["css"; "ex.css"]]
-           (body [
-             h2 [txt "Welcome to Ocsigen!"];
-             mywidget set1 "Click me" "server side";
-             mywidget set1 "Click me" "server side";
-             mywidget set2 "Click me" "server side"
-           ])))
-)

screenshot -

And now?

Calling server functions

An important feature missing from this tutorial is the ability -to call server functions from the client-side program ("server functions"). -You can find a quick description of this -in this mini HOWTO or -in -Eliom's manual. -

Services

For many applications, you will need several services. By default, -client-side Eliom programs do not stop when you follow a link or send -a form. This enables combining rich client side features (playing -music, animations, stateful applications ...) with traditional Web -interaction (links, forms, bookmarks, back button ...). Eliom -proposes several ways to identify services, either by the URL (and -parameters), or by a session identifier (we call this kind of service -a coservice). Eliom also allows creating new (co-)services -dynamically, for example coservices depending on previous interaction -with a user. More information on the service identification mechanism -in Eliom's manual. -

Sessions

Eliom also offers a rich session mechanism, with scopes -(see Eliom's manual).

diff --git a/8.0/manual/application.html b/8.0/manual/application.html index 171efe30..c457f0f3 100644 --- a/8.0/manual/application.html +++ b/8.0/manual/application.html @@ -44,7 +44,7 @@
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    Writing a client/server Eliom application

    In this chapter, we will write a collaborative +

    Writing a client/server Eliom application

    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. diff --git a/8.0/manual/basics-server.html b/8.0/manual/basics-server.html index 20979c8c..945c7dcd 100644 --- a/8.0/manual/basics-server.html +++ b/8.0/manual/basics-server.html @@ -44,7 +44,7 @@

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

    Server-side website programming guide

    While Eliom is well known for its unique client-server programming +

    Server-side website programming guide

    While Eliom is well known for its unique client-server programming model, it is also perfectly suited to programming more traditional websites. This page describes how you can generate Web pages in OCaml, and handle links, forms, page parameters, sessions, etc. You will see @@ -92,8 +92,8 @@

    Install Ocsigen Server with:

    opam install ocsigenserver
     

    Use as a library

    Let's create a new OCaml project with Dune: -dune init project mysite -

    To include a Web server in your OCaml program, just add +

    dune init project mysite
    +

    To include a Web server in your OCaml program, just add package ocsigenserver to your Dune file, together with all the extensions you need. For example, modify file bin/dune like this:

    (executable
    @@ -143,14 +143,16 @@
     and takes any GET parameter. The first parameter of function f is an association list of GET parameters.
     

    Module Eliom_registration.Html_text is used to register a service sending HTML as strings. But we recommend to used typed-HTML instead (see below).

    Compiling

    In this section, we will show how to compile and run a server-side only Web site by creating your project manually. -

    First, create the directories will we use for data (logs, etc.): -

    mkdir -p local/var/log/mysite
    -mkdir -p local/var/data/mysite
    -mkdir -p local/var/run
    -

    Build an executable

    This section shows how to create a static executable for you program +

    Build an executable

    This section shows how to create a static executable for you program (without configuration file).

    Run the following command:

    opam install ocsipersist-sqlite-config eliom
    +dune init project --kind=lib mysite
    +cd mysite
    +

    Create the directories will we use for data (logs, etc.): +

    mkdir -p local/var/log/mysite
    +mkdir -p local/var/data/mysite
    +mkdir -p local/var/run
     

    Add packages ocsipersist-sqlite and eliom.server to file bin/dune, in the "libraries" section.

    Copy the definition and registration of service myservice at the beginning @@ -174,7 +176,11 @@ dune init proj --kind=lib mysite cd mysite

    Add (libraries eliom.server) into file lib/dune. -

    Create your .ml files in directory lib. +

    Create the directories will we use for data (logs, etc.): +

    mkdir -p local/var/log/mysite
    +mkdir -p local/var/data/mysite
    +mkdir -p local/var/run
    +

    Create your .ml files in directory lib. For example, copy the definition and registration of service myservice above.

    Compile:

    dune build
    @@ -359,8 +365,8 @@
      ~service:connection_service
        (fun (name, password) ->
          [fieldset
    -       [label ~a:[a_for name] [txt "Name: "];
    -        Form.input ~input_type:`Text ~name:name Form.int;
    +       [label ~a:[a_label_for "loginname"] [txt "Name: "];
    +        Form.input ~a:[a_id "loginname"] ~input_type:`Text ~name Form.int;
             br ();
             Form.input
               ~a:[a_placeholder "Password"]
    diff --git a/8.0/manual/basics.html b/8.0/manual/basics.html
    index 5ffcee07..8552385e 100644
    --- a/8.0/manual/basics.html
    +++ b/8.0/manual/basics.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    Client-server application programming guide

    This tutorial has been tested with Eliom 11.0.0. +

    Client-server application programming guide

    This tutorial has been tested with Eliom 11.0.0.

    This page describes the main concepts you need to master to become fully operational with Ocsigen. Use it as your training plan or as a cheatcheet while programming.

    Depending on your needs, you may not need to learn all this. Ocsigen is @@ -87,8 +87,8 @@

    Install Ocsigen Server with:

    opam install ocsigenserver
     

    Use as a library

    Let's create a new OCaml project with Dune: -dune init project mysite -

    To include a Web server in your OCaml program, just add +

    dune init project mysite
    +

    To include a Web server in your OCaml program, just add package ocsigenserver to your Dune file, together with all the extensions you need. For example, modify file bin/dune like this:

    (executable
    @@ -226,14 +226,16 @@
              (Eliom_service.static_dir ())
              ["dir" ; "image.jpg"])
       ()

    Compiling

    In this section, we will show how to compile and run a server-side only Web site by creating your project manually. -

    First, create the directories will we use for data (logs, etc.): +

    Build an executable

    This section shows how to create a static executable for you program +(without configuration file). +

    Run the following commands: +

    opam install ocsipersist-sqlite-config eliom
    +dune init project --kind=executable mysite
    +cd mysite
    +

    Create the directories will we use for data (logs, etc.):

    mkdir -p local/var/log/mysite
     mkdir -p local/var/data/mysite
     mkdir -p local/var/run
    -

    Build an executable

    This section shows how to create a static executable for you program -(without configuration file). -

    Run the following command: -

    opam install ocsipersist-sqlite-config eliom
     

    Add packages ocsipersist-sqlite and eliom.server to file bin/dune, in the "libraries" section.

    Copy the definition and registration of service myservice at the beginning @@ -255,12 +257,16 @@

    Use with ocsigenserver

    Alternatively, you can decide to build your Eliom app as a library and load it dynamically into ocsigenserver using a configuration file.

    opam install ocsipersist-sqlite-config eliom
    -dune init proj --kind=lib mysite
    +dune init project --kind=lib mysite
     cd mysite
     

    Add (libraries eliom.server) into file lib/dune.

    Create your .ml files in directory lib. For example, copy the definition and registration of service myservice above. -

    Compile: +

    Create the directories will we use for data (logs, etc.): +

    mkdir -p local/var/log/mysite
    +mkdir -p local/var/data/mysite
    +mkdir -p local/var/run
    +

    Compile:

    dune build
     

    Create a configuration file mysite.conf with this content on your project root directory: diff --git a/8.0/manual/chat.html b/8.0/manual/chat.html deleted file mode 100644 index a7b97388..00000000 --- a/8.0/manual/chat.html +++ /dev/null @@ -1,123 +0,0 @@ - Chat: Design Overview

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

    Chat: Design Overview

    Chat is a chatting module and application, currently for conversations -between two users each. (Extension for multi-user channels is left as an -exercise to the user.) -

    You can find the code here. -

    Description

    When the user is logged in, he sees a list of available users. He can choose -one of them to start a conversation with him. The conversation then becomes -visible to both users as a message prompt and a growing list of messages. The -participating users can enter messages and they are made visible to the other -participants. If a user becomes unavailable for Chat, he is removed from the -list of available users, and all conversations he was participating are removed -from the opponent's UI. -

    Central Data Types

    The following data are crucial to the functionality of Chat. -

    Conversation

    A conversation (type Shared.Conversation.t) is shared between all its -client participants. It comprises of two values: Firstly, it contains a bus for -transferring conversation messages (c.f. type Shared.Conversation.message) -between the participants. Those are displayed as they come on every client. -Secondly, it contains the set of participating users. This is used when the -conversation is teared down: An event for removing the UI element for the -conversation is sent to every participant. -

    Client Process

    When a user enters a web page which contains Chat, it is rendered in HTML and -a new client process is created in doing so. Every client process holds a -channel, where the server can send messages to add or to remove conversations -(c.f. type Shared.event). -

    User Info

    On the server side, the channel for those events is stored for each user along -with a list of conversations he participates in. This is done with a -Eliom-reference of scope session_group, such that it is shared among all -sessions of a user (and likewise all client processes). -

    But, when one user requests to start (or end) a conversation with any another -user, it is also necessary to access the event channel of any user. -As it is only stored in an Eliom-reference specific to the user, it is -impossible to access. In favour of this, the user info is moreover -stored in a weak hash table (c.f. module Weak_info) which associates the -user info to each user in a globally accessible way. This association is weak -in its value, the user info, such that it is garbage-collected as soon as the -Eliom-reference storing it is cleared, i.e. when the last session of the -respective user is closed. -

    Available Users

    The client's UI contains a list of users which are currently available for -chatting. This is achieved as follows. -

    If it is the user's first client process he is added to the set of available -users, which is available in an react signal Chat.users_signal on a set of -user (i.e. User_set.t React.S.t). -As long as a user is available with at least one client he is kept in the value -of the signal. This signal is send to the client when initializing Chat. -Changes in its value are then directly reflected in the list of available users -in the client's UI (c.f. Client.change_users). -

    To observe when a user is not available through a given client process anymore, -Chat awaits for every client process that it is inactive for a given -time. Eliom_comet.Channels.wait_timeout is used for for detecting a client -process to be abandoned. -

    When it is observed that a user isn't avaible through any client process -anymore, he is removed from the Chat.users_signal, and all conversation he -is participating are teared down (c.f. Chat.remove_client_process). -

    Starting a conversation

    When the user selects one of the available users for a conversation, one of two -things may happen (c.f. Client.create_or_focus_conversation): -

    Firstly, if there is already a conversation between exactly those two users, -the message prompt is focused. -Secondly, if there is no conversation between those users yet, a new one is created -by calling the server's Chat.create_dialog_service (dialog is a conversatio -of two) with the selected user as argument. -

    This service establishes the conversation between the users: It creates the -conversation on the server side (i.e. the set of participants and the bus for -messages) and sends an event to append the conversation to each participant. -Each client is streaming such events (c.f. Client.dispatch_event) and -creates the respective UI-element for the conversation with event handlers for -sending and receiving messages in the conversation. -

    Sending a message in a conversationg

    Sending messages within an conversation is achieved without any server-side -code; the messages are sent "directly" to all all participants of the -conversation through its Eliom-bus. Two things make this work. -

    Firstly, an event listener on the message prompt of the conversation -(c.f. Client.handle_enter_pressed) awaits that the enter-key is hit in -the message prompt of a conversation. It then creates a message and sends it -to the conversation's event bus. -

    Secondly, every participant of a conversation is streaming the messages which -are sent over the conversation bus. -A stream iterator on that bus then displays those messages as they arrive -(c.f. Client.dispatch_message). -

    Have fun. Be communicative!

    diff --git a/8.0/manual/custom-conf.html b/8.0/manual/custom-conf.html index 7c274ebf..7f76a578 100644 --- a/8.0/manual/custom-conf.html +++ b/8.0/manual/custom-conf.html @@ -44,7 +44,7 @@
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    Custom configuration options

    It is not convenient to have to edit the code to change some +

    Custom configuration options

    It is not convenient to have to edit the code to change some configurations, like the location where are saved the favorite images in the Graffiti tutorial (see: Saving favorite pictures). diff --git a/8.0/manual/hash-password.html b/8.0/manual/hash-password.html index c1ee2bc8..9ce451fd 100644 --- a/8.0/manual/hash-password.html +++ b/8.0/manual/hash-password.html @@ -44,7 +44,7 @@

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

    Protecting your passwords

    For protecting the user passwords or other sensitive data, +

    Protecting your passwords

    For protecting the user passwords or other sensitive data, we can use ocaml-safepass.

    We can now write the encrypted password in our database diff --git a/8.0/manual/how-do-i-create-a-cryptographically-safe-identifier.html b/8.0/manual/how-do-i-create-a-cryptographically-safe-identifier.html index 03763358..edc88014 100644 --- a/8.0/manual/how-do-i-create-a-cryptographically-safe-identifier.html +++ b/8.0/manual/how-do-i-create-a-cryptographically-safe-identifier.html @@ -44,4 +44,4 @@

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

    +

    diff --git a/8.0/manual/how-does-a-page-s-source-code-look.html b/8.0/manual/how-does-a-page-s-source-code-look.html index 6a9fa213..3fc22c73 100644 --- a/8.0/manual/how-does-a-page-s-source-code-look.html +++ b/8.0/manual/how-does-a-page-s-source-code-look.html @@ -44,7 +44,7 @@
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How does a client-server app source code look like?

    Eliom client-server applications

    Eliom client-server applications are running in your browser for a certain lifetime and consist of one or several pages/URLs. +

    How does a client-server app source code look like?

    Eliom client-server applications

    Eliom client-server applications are running in your browser for a certain lifetime and consist of one or several pages/URLs. An application has its associated js file, which must have the same name (generated automatically by the default makefile and added automatically by Eliom in the page).

    For example, we define an application called example:

    module Example =
    diff --git a/8.0/manual/how-to-add-a-div.html b/8.0/manual/how-to-add-a-div.html
    index 37c6e9ae..fe703492 100644
    --- a/8.0/manual/how-to-add-a-div.html
    +++ b/8.0/manual/how-to-add-a-div.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to add a div?

    div ~a:[a_class ["firstclass"; "secondclass"]] [txt "Hello!"]

    Required parameter: list containing other elements +

    How to add a div?

    div ~a:[a_class ["firstclass"; "secondclass"]] [txt "Hello!"]

    Required parameter: list containing other elements (Details of available elements in type Html_types.flow5).

    Optional parameter for attributes "a" (How to set and id, classes or other attributes to HTML elements?). diff --git a/8.0/manual/how-to-add-a-favicon.html b/8.0/manual/how-to-add-a-favicon.html index c248712a..12ff7cc6 100644 --- a/8.0/manual/how-to-add-a-favicon.html +++ b/8.0/manual/how-to-add-a-favicon.html @@ -44,7 +44,7 @@

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

    How to add a Favicon?

    A favicon is a file of type "ico" which contain a picture of size 16x16px. It is the picture that you can ususally see next to the title of the page on a browser. +

    How to add a Favicon?

    A favicon is a file of type "ico" which contain a picture of size 16x16px. It is the picture that you can ususally see next to the title of the page on a browser.

    favicon for Ocsigen.org

    By default, all browsers look for a file favicon.ico at the root of the website: diff --git a/8.0/manual/how-to-add-a-javascript-script.html b/8.0/manual/how-to-add-a-javascript-script.html index 8480727a..b5a0675b 100644 --- a/8.0/manual/how-to-add-a-javascript-script.html +++ b/8.0/manual/how-to-add-a-javascript-script.html @@ -44,7 +44,7 @@

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

    How to add a Javacript script?

    If you have client-side programs on your website, you can use Eliom's client-server features, that will compile client side parts to JS using Ocsigen Js_of_ocaml, and include automatically the script in the page. But in some cases you may also want to include yourselves external JS scripts. +

    How to add a Javacript script?

    If you have client-side programs on your website, you can use Eliom's client-server features, that will compile client side parts to JS using Ocsigen Js_of_ocaml, and include automatically the script in the page. But in some cases you may also want to include yourselves external JS scripts.

    Include the script on the html header

    Javascript scripts are included in the header using the js_script function (defined in Eliom_content.Html.D).

    open Eliom_content.Html.D (* for make_uri an js_script *)
     
    diff --git a/8.0/manual/how-to-add-a-list.html b/8.0/manual/how-to-add-a-list.html
    index 3ed975fe..24c379d3 100644
    --- a/8.0/manual/how-to-add-a-list.html
    +++ b/8.0/manual/how-to-add-a-list.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to add lists in a page?

    Simple list and ordered list

    Simple list
    ul
    +

    How to add lists in a page?

    Simple list and ordered list

    Simple list
    ul
         [li [txt "first item"];
          li [txt "second item"];
          li [txt "third item"];
    diff --git a/8.0/manual/how-to-add-a-select-or-other-form-element.html b/8.0/manual/how-to-add-a-select-or-other-form-element.html
    index b6024095..c3bab9d7 100644
    --- a/8.0/manual/how-to-add-a-select-or-other-form-element.html
    +++ b/8.0/manual/how-to-add-a-select-or-other-form-element.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to add a select (or other form element)?

    In forms towards Eliom services:

    open Eliom_content
    +

    How to add a select (or other form element)?

    In forms towards Eliom services:

    open Eliom_content
     
     Html.D.Form.select ~name:select_name
       Html.D.Form.string (* type of the parameter *)
    diff --git a/8.0/manual/how-to-add-an-image.html b/8.0/manual/how-to-add-an-image.html
    index 5a6f9a2f..a490bb94 100644
    --- a/8.0/manual/how-to-add-an-image.html
    +++ b/8.0/manual/how-to-add-an-image.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to add an image?

    Internal image +

    How to add an image?

    Internal image

    img ~alt:("Ocsigen Logo")
           ~src:(make_uri
                   ~service:(Eliom_service.static_dir ())
    diff --git a/8.0/manual/how-to-add-css-stylesheet.html b/8.0/manual/how-to-add-css-stylesheet.html
    index 12a4ad8e..6d0fada1 100644
    --- a/8.0/manual/how-to-add-css-stylesheet.html
    +++ b/8.0/manual/how-to-add-css-stylesheet.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to add a CSS stylesheet?

    Warning: css_link and make_uri come from Eliom_content.Html.D module. This module is opened for each piece of code +

    How to add a CSS stylesheet?

    Warning: css_link and make_uri come from Eliom_content.Html.D module. This module is opened for each piece of code

    CSS stylesheet are included in the header using the css_link function.

    css_link
          ~uri:(make_uri (Eliom_service.static_dir ())
    diff --git a/8.0/manual/how-to-attach-ocaml-values-to-dom-elements.html b/8.0/manual/how-to-attach-ocaml-values-to-dom-elements.html
    index ac227577..7700a35c 100644
    --- a/8.0/manual/how-to-attach-ocaml-values-to-dom-elements.html
    +++ b/8.0/manual/how-to-attach-ocaml-values-to-dom-elements.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to attach OCaml values to DOM elements?

    It is often convenient to attach OCaml values to certain elements of +

    How to attach OCaml values to DOM elements?

    It is often convenient to attach OCaml values to certain elements of the page. There are several ways to achieve this.

    • The first possibility is to use DATA attributes (for example if the page is generated on server side). diff --git a/8.0/manual/how-to-attach-ocaml-values-to-the-html-nodes-sent-to-the-client.html b/8.0/manual/how-to-attach-ocaml-values-to-the-html-nodes-sent-to-the-client.html index 6906be6e..dfe29b93 100644 --- a/8.0/manual/how-to-attach-ocaml-values-to-the-html-nodes-sent-to-the-client.html +++ b/8.0/manual/how-to-attach-ocaml-values-to-the-html-nodes-sent-to-the-client.html @@ -44,4 +44,4 @@
    • Source code

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

    +

    diff --git a/8.0/manual/how-to-build-js-object.html b/8.0/manual/how-to-build-js-object.html index 2be91c12..801a1607 100644 --- a/8.0/manual/how-to-build-js-object.html +++ b/8.0/manual/how-to-build-js-object.html @@ -44,7 +44,7 @@
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to build js object?

    Use syntax new%js: +

    How to build js object?

    Use syntax new%js:

    Example:

    let get_timestamp () =
       let date = new%js Js.date_now in
    diff --git a/8.0/manual/how-to-call-a-server-side-function-from-client-side.html b/8.0/manual/how-to-call-a-server-side-function-from-client-side.html
    index 61ce8745..fed91a08 100644
    --- a/8.0/manual/how-to-call-a-server-side-function-from-client-side.html
    +++ b/8.0/manual/how-to-call-a-server-side-function-from-client-side.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to call a server-side function from client-side?

    It is possible to call server-side functions in client-side. +

    How to call a server-side function from client-side?

    It is possible to call server-side functions in client-side. For security reasons, these functions must first be declared explicitely as RPCs (with the type of their argument). diff --git a/8.0/manual/how-to-call-an-ocaml-function-from-js-code.html b/8.0/manual/how-to-call-an-ocaml-function-from-js-code.html index c9bfcef2..8b9cc13a 100644 --- a/8.0/manual/how-to-call-an-ocaml-function-from-js-code.html +++ b/8.0/manual/how-to-call-an-ocaml-function-from-js-code.html @@ -44,4 +44,4 @@

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

    How to call an OCaml function from JS code?

    Have a look at function Js.wrap_callback

    +

    How to call an OCaml function from JS code?

    Have a look at function Js.wrap_callback

    diff --git a/8.0/manual/how-to-compile-my-ocsigen-pages.html b/8.0/manual/how-to-compile-my-ocsigen-pages.html index 720a7377..482cca2d 100644 --- a/8.0/manual/how-to-compile-my-ocsigen-pages.html +++ b/8.0/manual/how-to-compile-my-ocsigen-pages.html @@ -44,7 +44,7 @@
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to compile my Ocsigen pages?

    Eliom distillery

    Eliom-distillery will help you to build your client-server application +

    How to compile my Ocsigen pages?

    Eliom distillery

    Eliom-distillery will help you to build your client-server application using Eliom. It comes with several templates ("client-server.basic", "os.pgocaml", and more to come ...). diff --git a/8.0/manual/how-to-configure-and-launch-the-ocsigen-server.html b/8.0/manual/how-to-configure-and-launch-the-ocsigen-server.html index 976853fb..84b26776 100644 --- a/8.0/manual/how-to-configure-and-launch-the-ocsigen-server.html +++ b/8.0/manual/how-to-configure-and-launch-the-ocsigen-server.html @@ -44,7 +44,7 @@

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

    How to configure and launch the Ocsigen Server?

    Default configuration file

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

    How to create a link to the current page (without knowing its URL)?

    Void coservices are here for that: +

    How to create a link to the current page (without knowing its URL)?

    Void coservices are here for that:

    a ~service:Eliom_service.reload_action
       [txt "Click to reload"] ();

    More information in Eliom's manual, and API documentation of diff --git a/8.0/manual/how-to-detect-channel-disconnection.html b/8.0/manual/how-to-detect-channel-disconnection.html index fe7ec23f..5061cb4e 100644 --- a/8.0/manual/how-to-detect-channel-disconnection.html +++ b/8.0/manual/how-to-detect-channel-disconnection.html @@ -44,7 +44,7 @@

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

    How to detect channel disconnection

    Question

    Is there a way to detect that some Eliom_comet channel became +

    How to detect channel disconnection

    Question

    Is there a way to detect that some Eliom_comet channel became disconnected? I would like to warn the user if the server becomes unreachable.

    Answer

    If you are using Ocsigen-start, you probably have nothing to do. diff --git a/8.0/manual/how-to-detect-on-client-side-that-the-server-side-state-for-the-process-is-closed.html b/8.0/manual/how-to-detect-on-client-side-that-the-server-side-state-for-the-process-is-closed.html index e22c5ac1..302685c6 100644 --- a/8.0/manual/how-to-detect-on-client-side-that-the-server-side-state-for-the-process-is-closed.html +++ b/8.0/manual/how-to-detect-on-client-side-that-the-server-side-state-for-the-process-is-closed.html @@ -44,7 +44,7 @@

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

    How to detect on client side that the server side state for the process is closed?

    If you are using Ocsigen-start, you probably have nothing to do. +

    How to detect on client side that the server side state for the process is closed?

    If you are using Ocsigen-start, you probably have nothing to do. Ocsigen-start will monitor the life of sessions and close the process when needed.

    If you are not using Ocsigen-start, you must catch exceptions diff --git a/8.0/manual/how-to-do-links-to-other-pages.html b/8.0/manual/how-to-do-links-to-other-pages.html index ec304683..a3ceb8fd 100644 --- a/8.0/manual/how-to-do-links-to-other-pages.html +++ b/8.0/manual/how-to-do-links-to-other-pages.html @@ -44,7 +44,7 @@

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

    How to do links to other pages?

    Examples: +

    How to do links to other pages?

    Examples:

    (* Link to a service without parameter: *)
     Html.D.a ~service:coucou [txt "coucou"] ();
     
    diff --git a/8.0/manual/how-to-implement-a-notification-system.html b/8.0/manual/how-to-implement-a-notification-system.html
    index acde20f9..e6f10d61 100644
    --- a/8.0/manual/how-to-implement-a-notification-system.html
    +++ b/8.0/manual/how-to-implement-a-notification-system.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to implement a notification system?

    The easiest way for the server to send notifications to the client is +

    How to implement a notification system?

    The easiest way for the server to send notifications to the client is to use module Os_notif from Ocsigen-start (OS), but it requires to use OS's user management system. If you are not using OS, we recommend to get inspiration from diff --git a/8.0/manual/how-to-insert-raw-form-elements-not-belonging-to-a-form-towards-a-service.html b/8.0/manual/how-to-insert-raw-form-elements-not-belonging-to-a-form-towards-a-service.html index b2eae636..b602501e 100644 --- a/8.0/manual/how-to-insert-raw-form-elements-not-belonging-to-a-form-towards-a-service.html +++ b/8.0/manual/how-to-insert-raw-form-elements-not-belonging-to-a-form-towards-a-service.html @@ -44,7 +44,7 @@

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

    How to insert "raw" form elements (not belonging to a form towards a service)?

    Eliom redefines most forms elements (inputs, textareas, checkboxes, etc.) +

    How to insert "raw" form elements (not belonging to a form towards a service)?

    Eliom redefines most forms elements (inputs, textareas, checkboxes, etc.) to make possible to check the type of the form w.r.t. the type of the service.

    If you don't want that (for example if you want to use it only from a client side program), you can use "raw form elements" (that is, basic tyxml elements), using diff --git a/8.0/manual/how-to-iterate-on-all-sessions-for-one-user-or-all-tabs.html b/8.0/manual/how-to-iterate-on-all-sessions-for-one-user-or-all-tabs.html index 57a4d954..f97f9a93 100644 --- a/8.0/manual/how-to-iterate-on-all-sessions-for-one-user-or-all-tabs.html +++ b/8.0/manual/how-to-iterate-on-all-sessions-for-one-user-or-all-tabs.html @@ -44,7 +44,7 @@

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

    How to iterate on all session belonging to the same user, or all tabs?

    You must create a session group for each user, then iterate on all +

    How to iterate on all session belonging to the same user, or all tabs?

    You must create a session group for each user, then iterate on all the sessions from this group, and possibly on all client processes for each session:

    (* We get the session group state for this user: *)
    diff --git a/8.0/manual/how-to-know-whether-the-browser-window-has-the-focus-or-not.html b/8.0/manual/how-to-know-whether-the-browser-window-has-the-focus-or-not.html
    index 8189b732..628b9caa 100644
    --- a/8.0/manual/how-to-know-whether-the-browser-window-has-the-focus-or-not.html
    +++ b/8.0/manual/how-to-know-whether-the-browser-window-has-the-focus-or-not.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to know whether the browser window has the focus or not?

    Example: +

    How to know whether the browser window has the focus or not?

    Example:

    let has_focus = ref true
     
     let _ =
    diff --git a/8.0/manual/how-to-make-hello-world-in-ocsigen.html b/8.0/manual/how-to-make-hello-world-in-ocsigen.html
    index c1a84a19..f5ad63d0 100644
    --- a/8.0/manual/how-to-make-hello-world-in-ocsigen.html
    +++ b/8.0/manual/how-to-make-hello-world-in-ocsigen.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to make a "hello world" in Ocsigen?

    Here it is! The famous "Hello World" for a client/server Eliom application: +

    How to make a "hello world" in Ocsigen?

    Here it is! The famous "Hello World" for a client/server Eliom application:

    open Eliom_content
     open Html.D
     open Eliom_parameter
    diff --git a/8.0/manual/how-to-make-page-a-skeleton.html b/8.0/manual/how-to-make-page-a-skeleton.html
    index 5805a550..44094e1f 100644
    --- a/8.0/manual/how-to-make-page-a-skeleton.html
    +++ b/8.0/manual/how-to-make-page-a-skeleton.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to make a page skeleton?

    The same header for all your pages

    When your site will grow, you will have several different services for pages which will often contain the same header informations. +

    How to make a page skeleton?

    The same header for all your pages

    When your site will grow, you will have several different services for pages which will often contain the same header informations.

    A great solutions to avoid code copy-pasting of these recurrent informations are to make a page skeleton function:

    let skeleton body_content =
       Lwt.return
    diff --git a/8.0/manual/how-to-make-responsive-css.html b/8.0/manual/how-to-make-responsive-css.html
    index 6fb62cb9..b81e9d02 100644
    --- a/8.0/manual/how-to-make-responsive-css.html
    +++ b/8.0/manual/how-to-make-responsive-css.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to make reponsive CSS with ocsigen?

    The best way to do that is to make one general css sheet plus three css sheets, small, medium and large screen using media queries, a feature introduced in CSS3. +

    How to make reponsive CSS with ocsigen?

    The best way to do that is to make one general css sheet plus three css sheets, small, medium and large screen using media queries, a feature introduced in CSS3.

    Write theses lines in your css sheets:

    @media only screen and (max-device-width: 480px)
     @media only screen and (min-device-width: 481px) and (max-device-width: 768px)
    diff --git a/8.0/manual/how-to-make-the-client-side-program-get-an-html-element-from-the-server-and-insert-it-in-the-page.html b/8.0/manual/how-to-make-the-client-side-program-get-an-html-element-from-the-server-and-insert-it-in-the-page.html
    index e00cd126..326c5fdf 100644
    --- a/8.0/manual/how-to-make-the-client-side-program-get-an-html-element-from-the-server-and-insert-it-in-the-page.html
    +++ b/8.0/manual/how-to-make-the-client-side-program-get-an-html-element-from-the-server-and-insert-it-in-the-page.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to make the client side program get an HTML element from the server and insert it in the page?

    A very convenient way to do that is to use RPCs : +

    How to make the client side program get an HTML element from the server and insert it in the page?

    A very convenient way to do that is to use RPCs :

    let%rpc get_mydiv (() : unit) : _ Lwt.t = div [ ... ]
    [%client
       ...
       let%lwt mydiv = get_mydiv () in
    diff --git a/8.0/manual/how-to-register-a-service-that-decides-itself-what-to-send.html b/8.0/manual/how-to-register-a-service-that-decides-itself-what-to-send.html
    index f8669c57..6e951019 100644
    --- a/8.0/manual/how-to-register-a-service-that-decides-itself-what-to-send.html
    +++ b/8.0/manual/how-to-register-a-service-that-decides-itself-what-to-send.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to register a service that decides itself what to send?

    Use Eliom_registration.Any. +

    How to register a service that decides itself what to send?

    Use Eliom_registration.Any.

    In the following example, we send an Html page or a redirection:

    let send_any =
       Eliom_registration.Any.create
    diff --git a/8.0/manual/how-to-register-session-data.html b/8.0/manual/how-to-register-session-data.html
    index 3492205b..a684abfb 100644
    --- a/8.0/manual/how-to-register-session-data.html
    +++ b/8.0/manual/how-to-register-session-data.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to register session data?

    It is very easy to register session data using Eliom references. +

    How to register session data?

    It is very easy to register session data using Eliom references. Just create an Eliom reference of scope session and its value will be different for each session (one session = one browser process).

    But most of the time, what we want is to store data for one user, diff --git a/8.0/manual/how-to-send-a-file-to-server-without-stopping-the-client-process.html b/8.0/manual/how-to-send-a-file-to-server-without-stopping-the-client-process.html index b34de5b9..0c1810e2 100644 --- a/8.0/manual/how-to-send-a-file-to-server-without-stopping-the-client-process.html +++ b/8.0/manual/how-to-send-a-file-to-server-without-stopping-the-client-process.html @@ -44,7 +44,7 @@

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

    How to send a file to the server without stopping the client process?

    This requires Eliom ≥ 3.1. +

    How to send a file to the server without stopping the client process?

    This requires Eliom ≥ 3.1.

    Due to security reasons, browsers have limitations on sending files. But if the file is chosen by the user through an input file element, there is a way to send it to the server. You can't use the server_function diff --git a/8.0/manual/how-to-send-file-download.html b/8.0/manual/how-to-send-file-download.html index 84415217..6c12c1b7 100644 --- a/8.0/manual/how-to-send-file-download.html +++ b/8.0/manual/how-to-send-file-download.html @@ -44,7 +44,7 @@

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

    How to send a file (download)?

    To serve file, you can use Ocsigen Server's module staticmod. +

    How to send a file (download)?

    To serve file, you can use Ocsigen Server's module staticmod. But it is also possible to ask Eliom to send files using module Eliom_registration.File, for example if you want Eliom to perform some privacy checks before sending, diff --git a/8.0/manual/how-to-send-file-upload.html b/8.0/manual/how-to-send-file-upload.html index 0cda7836..c717a6c1 100644 --- a/8.0/manual/how-to-send-file-upload.html +++ b/8.0/manual/how-to-send-file-upload.html @@ -44,7 +44,7 @@

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

    How to send a file (upload)?

    To upload a file, use Eliom_parameter.file as service parameter type. +

    How to send a file (upload)?

    To upload a file, use Eliom_parameter.file as service parameter type.

    Ocsigen server will save the file at a temporary location and keep it there during the request. Then the file will be removed. You must link it somewhere else on the disk yourself if you want to keep it. diff --git a/8.0/manual/how-to-set-and-id-classes-or-other-attributes-to-html-elements.html b/8.0/manual/how-to-set-and-id-classes-or-other-attributes-to-html-elements.html index aeab5b81..66815d86 100644 --- a/8.0/manual/how-to-set-and-id-classes-or-other-attributes-to-html-elements.html +++ b/8.0/manual/how-to-set-and-id-classes-or-other-attributes-to-html-elements.html @@ -44,7 +44,7 @@

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

    How to set and id, classes or other attributes to HTML elements?

    Mandatory element attributes are given as OCaml named parameters to +

    How to set and id, classes or other attributes to HTML elements?

    Mandatory element attributes are given as OCaml named parameters to constructions function.

    Optional element attributes are added using the optional OCaml parameter "?a" which is more or less available for every HTML5 elements. This parameter is taking a list of attributes compatible with the element.

    div ~a:[a_class ["red";"shadow"];
    diff --git a/8.0/manual/how-to-stop-default-behaviour-of-events.html b/8.0/manual/how-to-stop-default-behaviour-of-events.html
    index 2aa6e5b0..bf339033 100644
    --- a/8.0/manual/how-to-stop-default-behaviour-of-events.html
    +++ b/8.0/manual/how-to-stop-default-behaviour-of-events.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to stop default behaviour of events?

    Example: +

    How to stop default behaviour of events?

    Example:

    (** Disable Js event with stopping propagation during capture phase **)
     let disable_event event html_elt =
       Lwt.async (fun () ->
    diff --git a/8.0/manual/how-to-use-get-parameters-or-parameters-in-the-url.html b/8.0/manual/how-to-use-get-parameters-or-parameters-in-the-url.html
    index 47402d22..652375b7 100644
    --- a/8.0/manual/how-to-use-get-parameters-or-parameters-in-the-url.html
    +++ b/8.0/manual/how-to-use-get-parameters-or-parameters-in-the-url.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to use GET parameters (parameters in the URL)?

    Pages are generated by services. +

    How to use GET parameters (parameters in the URL)?

    Pages are generated by services.

    Funcrions to create services using GET HTTP method take two mandatory parameters: ~path and ~get_params.

    path

    This argument is a list of string, corresponding to the URL where your page service can be found. diff --git a/8.0/manual/how-to-write-a-json-service.html b/8.0/manual/how-to-write-a-json-service.html index aec0eef9..c1df345a 100644 --- a/8.0/manual/how-to-write-a-json-service.html +++ b/8.0/manual/how-to-write-a-json-service.html @@ -44,7 +44,7 @@

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

    How to write a JSON service?

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

    How to write forms?

    To write an HTML form towards an Eliom service

    Just as we do for links, we provide form-building functions that call +

    How to write forms?

    To write an HTML form towards an Eliom service

    Just as we do for links, we provide form-building functions that call Eliom services in a safe manner. These functions are provided in the module Eliom_content.Html.D.Form (and diff --git a/8.0/manual/how-to-write-titles-and-paragraphs.html b/8.0/manual/how-to-write-titles-and-paragraphs.html index eaee7929..f3532a1e 100644 --- a/8.0/manual/how-to-write-titles-and-paragraphs.html +++ b/8.0/manual/how-to-write-titles-and-paragraphs.html @@ -44,7 +44,7 @@

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

    How to write titles and paragrahs?

    Titles +

    How to write titles and paragrahs?

    Titles

    h3 [txt "Hello world"]

    There are 6 types of titles: h1, h2, h3, h4, h5 and h6. h1 is the largest and h6 is the smallest.

    Pagragraph

    p [txt "Some text, blah blah blah"]

    Required parameter: list containing other elements (content: Html_types.flow5 elements). diff --git a/8.0/manual/html.html b/8.0/manual/html.html index 88aed486..94e12f4e 100644 --- a/8.0/manual/html.html +++ b/8.0/manual/html.html @@ -44,7 +44,7 @@

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

    HTML in 5 minutes

    The Tyxml library makes it possible to type-check HTML pages. +

    HTML in 5 minutes

    The Tyxml library makes it possible to type-check HTML pages. This means that your Ocsigen program will never generate pages which do not follow the recommendations from the W3C. For example a program that could generate a page with a paragraph diff --git a/8.0/manual/interaction.html b/8.0/manual/interaction.html index d641b77b..1d445c9d 100644 --- a/8.0/manual/interaction.html +++ b/8.0/manual/interaction.html @@ -44,7 +44,7 @@

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

    Implementing Web Interaction Using Eliom

    The code of this tutorial has been tested with Eliom 6.0.
    +

    Implementing Web Interaction Using Eliom

    The code of this tutorial has been tested with Eliom 6.0.

    This chapter of the tutorial explains how to create a small web site with several pages, users, sessions, and other elements of classical web development. Then, in diff --git a/8.0/manual/intro.html b/8.0/manual/intro.html index 45ea8c05..2cda3b67 100644 --- a/8.0/manual/intro.html +++ b/8.0/manual/intro.html @@ -44,7 +44,7 @@

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

    Introduction

    Ocsigen is a complete framework for developing Web and mobile apps +

    Introduction

    Ocsigen is a complete framework for developing Web and mobile apps using cutting edge techniques. It can be used to write simple server side Web sites, client-side programs, or complex client-server Web and mobile apps. diff --git a/8.0/manual/lwt.html b/8.0/manual/lwt.html index a94d75ba..c4c010f6 100644 --- a/8.0/manual/lwt.html +++ b/8.0/manual/lwt.html @@ -44,7 +44,7 @@

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

    Lwt in 5 minutes

    Principles

    The Lwt library implements cooperative threads for OCaml. Cooperative +

    Lwt in 5 minutes

    Principles

    The Lwt library implements cooperative threads for OCaml. Cooperative threads are an alternative to preemptive threads (used in many languages and in OCaml's Thread module) that solve most common issues with preemptive threads: with Lwt, there is very limited risk diff --git a/8.0/manual/macaque.html b/8.0/manual/macaque.html index 64662c8a..ee039ce4 100644 --- a/8.0/manual/macaque.html +++ b/8.0/manual/macaque.html @@ -44,7 +44,7 @@

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

    Type safe database requests using Macaque

    The Macaque library allows easy manipulation of Postgresql +

    Type safe database requests using Macaque

    The Macaque library allows easy manipulation of Postgresql database fully compatible with Lwt. (For more information see Macaque manual).

    Macaque is fuly compatible with PGOcaml, and both can be used diff --git a/8.0/manual/misc.html b/8.0/manual/misc.html index 0acf80c7..9637b1bb 100644 --- a/8.0/manual/misc.html +++ b/8.0/manual/misc.html @@ -44,7 +44,7 @@

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

    Traditional web interaction in a client-server app

    The code of this tutorial has been tested with Eliom 6.0.
    +

    Traditional web interaction in a client-server app

    The code of this tutorial has been tested with Eliom 6.0.

    Multi-user collaborative drawing application

    We now want to turn our collaborative drawing application into a multi-user one. Each user will have their own drawing, where everyone can draw. diff --git a/8.0/manual/mobile.html b/8.0/manual/mobile.html index 431c981a..2bb43359 100644 --- a/8.0/manual/mobile.html +++ b/8.0/manual/mobile.html @@ -44,7 +44,7 @@

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

    Mobile applications with Ocsigen

    Since Eliom 6.0, the Ocsigen framework provides infrastructure for +

    Mobile applications with Ocsigen

    Since Eliom 6.0, the Ocsigen framework provides infrastructure for building mobile applications. This enables rapid development of Android, iOS, and Windows Mobile apps with the same APIs and programming style as for a regular client-server Web application. In diff --git a/8.0/manual/music.html b/8.0/manual/music.html index 87251d26..a4a48671 100644 --- a/8.0/manual/music.html +++ b/8.0/manual/music.html @@ -44,7 +44,7 @@

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

    Listening music

    We will add an audio player to the page that will stay when page +

    Listening music

    We will add an audio player to the page that will stay when page changes. This emphasises the fact that browsing inside an application does not stop the client side code: the music keeps playing when the content of the page and the url change. diff --git a/8.0/manual/ocsipersist.html b/8.0/manual/ocsipersist.html index ad065d31..9932d8ee 100644 --- a/8.0/manual/ocsipersist.html +++ b/8.0/manual/ocsipersist.html @@ -44,7 +44,7 @@

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

    Lightweight database using Ocsipersist

    Ocsipersist is a module for persistent references and tables. +

    Lightweight database using Ocsipersist

    Ocsipersist is a module for persistent references and tables.

    For persistent references, Eliom has a higher level interface, called Eliom references, and you probably want to use it instead of using Ocsipersist directly. diff --git a/8.0/manual/pictures.html b/8.0/manual/pictures.html index 442ba17f..dbca351f 100644 --- a/8.0/manual/pictures.html +++ b/8.0/manual/pictures.html @@ -44,7 +44,7 @@

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

    Saving favorite pictures

    We will now add a button to the Graffiti application to save the current +

    Saving favorite pictures

    We will now add a button to the Graffiti application to save the current image. The images will be saved to the filesystem using the module Lwt_io. We will then make an Atom feed with the saved images using Syndic. diff --git a/8.0/manual/reactivemediaplayer.html b/8.0/manual/reactivemediaplayer.html index c2193336..78d97a41 100644 --- a/8.0/manual/reactivemediaplayer.html +++ b/8.0/manual/reactivemediaplayer.html @@ -44,7 +44,7 @@

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

    Reactive Media Player

    You should read the Playing Music tutorial before this one. +

    Reactive Media Player

    You should read the Playing Music tutorial before this one.

    Since version 4, Eliom embeds the React library in order to provide reactive HTML elements diff --git a/8.0/manual/rest.html b/8.0/manual/rest.html index 5bdb25d7..4ded8c1a 100644 --- a/8.0/manual/rest.html +++ b/8.0/manual/rest.html @@ -44,7 +44,7 @@

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

    RESTful JSON API using Eliom

    This tutorial will show you how to create a simple, yet complete, REST API +

    RESTful JSON API using Eliom

    This tutorial will show you how to create a simple, yet complete, REST API using JSON as the serialization format.

    To illustrate our example, let's say we want to give access to a database of locations storing a description and coordinates (latitude and longitude). diff --git a/8.0/manual/start.html b/8.0/manual/start.html index aca4bb92..e0e8547a 100644 --- a/8.0/manual/start.html +++ b/8.0/manual/start.html @@ -44,7 +44,7 @@

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

    Your first app in 5 minutes

    The code of this tutorial has been tested against Eliom 6.0.
    +

    Your first app in 5 minutes

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

    This tutorial describes how to get started with Ocsigen quickly. Thanks to an application template provided by the Ocsigen team, you will get to a working application with standard diff --git a/8.0/manual/tutoreact.html b/8.0/manual/tutoreact.html index a018e755..7ef67a71 100644 --- a/8.0/manual/tutoreact.html +++ b/8.0/manual/tutoreact.html @@ -44,7 +44,7 @@

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

    Client server reactive application with Ocsigen

    This is a short tutorial showing how to implement a simple reactive +

    Client server reactive application with Ocsigen

    This is a short tutorial showing how to implement a simple reactive client-server application using Js_of_ocaml, Eliom and Ocsigen Start.

    We are going to implement an application that can display a list of messages and diff --git a/8.0/manual/tutowidgets.html b/8.0/manual/tutowidgets.html index 626962fa..46b35910 100644 --- a/8.0/manual/tutowidgets.html +++ b/8.0/manual/tutowidgets.html @@ -44,7 +44,7 @@

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

    Mini-tutorial: client-server widgets

    This short tutorial is an example of client-server Eliom application. +

    Mini-tutorial: client-server widgets

    This short tutorial is an example of client-server Eliom application. It gives an example of client-server widgets.

    It is probably a good starting point if you know OCaml well, and want to quickly learn how to write a client-server Eliom application with a diff --git a/dev/manual/application.html b/dev/manual/application.html index 614230ca..f92dca38 100644 --- a/dev/manual/application.html +++ b/dev/manual/application.html @@ -44,7 +44,7 @@

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

    Writing a client/server Eliom application

    In this chapter, we will write a collaborative +

    Writing a client/server Eliom application

    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. diff --git a/dev/manual/basics-server.html b/dev/manual/basics-server.html index 7bb9fe63..16645b7f 100644 --- a/dev/manual/basics-server.html +++ b/dev/manual/basics-server.html @@ -44,7 +44,7 @@

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

    Server-side website programming guide

    While Eliom is well known for its unique client-server programming +

    Server-side website programming guide

    While Eliom is well known for its unique client-server programming model, it is also perfectly suited to programming more traditional websites. This page describes how you can generate Web pages in OCaml, and handle links, forms, page parameters, sessions, etc. You will see @@ -92,8 +92,8 @@

    Install Ocsigen Server with:

    opam install ocsigenserver
     

    Use as a library

    Let's create a new OCaml project with Dune: -dune init project mysite -

    To include a Web server in your OCaml program, just add +

    dune init project mysite
    +

    To include a Web server in your OCaml program, just add package ocsigenserver to your Dune file, together with all the extensions you need. For example, modify file bin/dune like this:

    (executable
    @@ -143,14 +143,16 @@
     and takes any GET parameter. The first parameter of function f is an association list of GET parameters.
     

    Module Eliom_registration.Html_text is used to register a service sending HTML as strings. But we recommend to used typed-HTML instead (see below).

    Compiling

    In this section, we will show how to compile and run a server-side only Web site by creating your project manually. -

    First, create the directories will we use for data (logs, etc.): -

    mkdir -p local/var/log/mysite
    -mkdir -p local/var/data/mysite
    -mkdir -p local/var/run
    -

    Build an executable

    This section shows how to create a static executable for you program +

    Build an executable

    This section shows how to create a static executable for you program (without configuration file).

    Run the following command:

    opam install ocsipersist-sqlite-config eliom
    +dune init project --kind=lib mysite
    +cd mysite
    +

    Create the directories will we use for data (logs, etc.): +

    mkdir -p local/var/log/mysite
    +mkdir -p local/var/data/mysite
    +mkdir -p local/var/run
     

    Add packages ocsipersist-sqlite and eliom.server to file bin/dune, in the "libraries" section.

    Copy the definition and registration of service myservice at the beginning @@ -174,7 +176,11 @@ dune init proj --kind=lib mysite cd mysite

    Add (libraries eliom.server) into file lib/dune. -

    Create your .ml files in directory lib. +

    Create the directories will we use for data (logs, etc.): +

    mkdir -p local/var/log/mysite
    +mkdir -p local/var/data/mysite
    +mkdir -p local/var/run
    +

    Create your .ml files in directory lib. For example, copy the definition and registration of service myservice above.

    Compile:

    dune build
    @@ -359,8 +365,8 @@
      ~service:connection_service
        (fun (name, password) ->
          [fieldset
    -       [label ~a:[a_for name] [txt "Name: "];
    -        Form.input ~input_type:`Text ~name:name Form.int;
    +       [label ~a:[a_label_for "loginname"] [txt "Name: "];
    +        Form.input ~a:[a_id "loginname"] ~input_type:`Text ~name Form.int;
             br ();
             Form.input
               ~a:[a_placeholder "Password"]
    diff --git a/dev/manual/basics.html b/dev/manual/basics.html
    index 489624ba..3fe2441c 100644
    --- a/dev/manual/basics.html
    +++ b/dev/manual/basics.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    Client-server application programming guide

    This tutorial has been tested with Eliom 11.0.0. +

    Client-server application programming guide

    This tutorial has been tested with Eliom 11.0.0.

    This page describes the main concepts you need to master to become fully operational with Ocsigen. Use it as your training plan or as a cheatcheet while programming.

    Depending on your needs, you may not need to learn all this. Ocsigen is @@ -87,8 +87,8 @@

    Install Ocsigen Server with:

    opam install ocsigenserver
     

    Use as a library

    Let's create a new OCaml project with Dune: -dune init project mysite -

    To include a Web server in your OCaml program, just add +

    dune init project mysite
    +

    To include a Web server in your OCaml program, just add package ocsigenserver to your Dune file, together with all the extensions you need. For example, modify file bin/dune like this:

    (executable
    @@ -226,14 +226,16 @@
              (Eliom_service.static_dir ())
              ["dir" ; "image.jpg"])
       ()

    Compiling

    In this section, we will show how to compile and run a server-side only Web site by creating your project manually. -

    First, create the directories will we use for data (logs, etc.): +

    Build an executable

    This section shows how to create a static executable for you program +(without configuration file). +

    Run the following commands: +

    opam install ocsipersist-sqlite-config eliom
    +dune init project --kind=executable mysite
    +cd mysite
    +

    Create the directories will we use for data (logs, etc.):

    mkdir -p local/var/log/mysite
     mkdir -p local/var/data/mysite
     mkdir -p local/var/run
    -

    Build an executable

    This section shows how to create a static executable for you program -(without configuration file). -

    Run the following command: -

    opam install ocsipersist-sqlite-config eliom
     

    Add packages ocsipersist-sqlite and eliom.server to file bin/dune, in the "libraries" section.

    Copy the definition and registration of service myservice at the beginning @@ -255,12 +257,16 @@

    Use with ocsigenserver

    Alternatively, you can decide to build your Eliom app as a library and load it dynamically into ocsigenserver using a configuration file.

    opam install ocsipersist-sqlite-config eliom
    -dune init proj --kind=lib mysite
    +dune init project --kind=lib mysite
     cd mysite
     

    Add (libraries eliom.server) into file lib/dune.

    Create your .ml files in directory lib. For example, copy the definition and registration of service myservice above. -

    Compile: +

    Create the directories will we use for data (logs, etc.): +

    mkdir -p local/var/log/mysite
    +mkdir -p local/var/data/mysite
    +mkdir -p local/var/run
    +

    Compile:

    dune build
     

    Create a configuration file mysite.conf with this content on your project root directory: diff --git a/dev/manual/chat.html b/dev/manual/chat.html deleted file mode 100644 index 251ff2f5..00000000 --- a/dev/manual/chat.html +++ /dev/null @@ -1,123 +0,0 @@ - Chat: Design Overview

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

    Chat: Design Overview

    Chat is a chatting module and application, currently for conversations -between two users each. (Extension for multi-user channels is left as an -exercise to the user.) -

    You can find the code here. -

    Description

    When the user is logged in, he sees a list of available users. He can choose -one of them to start a conversation with him. The conversation then becomes -visible to both users as a message prompt and a growing list of messages. The -participating users can enter messages and they are made visible to the other -participants. If a user becomes unavailable for Chat, he is removed from the -list of available users, and all conversations he was participating are removed -from the opponent's UI. -

    Central Data Types

    The following data are crucial to the functionality of Chat. -

    Conversation

    A conversation (type Shared.Conversation.t) is shared between all its -client participants. It comprises of two values: Firstly, it contains a bus for -transferring conversation messages (c.f. type Shared.Conversation.message) -between the participants. Those are displayed as they come on every client. -Secondly, it contains the set of participating users. This is used when the -conversation is teared down: An event for removing the UI element for the -conversation is sent to every participant. -

    Client Process

    When a user enters a web page which contains Chat, it is rendered in HTML and -a new client process is created in doing so. Every client process holds a -channel, where the server can send messages to add or to remove conversations -(c.f. type Shared.event). -

    User Info

    On the server side, the channel for those events is stored for each user along -with a list of conversations he participates in. This is done with a -Eliom-reference of scope session_group, such that it is shared among all -sessions of a user (and likewise all client processes). -

    But, when one user requests to start (or end) a conversation with any another -user, it is also necessary to access the event channel of any user. -As it is only stored in an Eliom-reference specific to the user, it is -impossible to access. In favour of this, the user info is moreover -stored in a weak hash table (c.f. module Weak_info) which associates the -user info to each user in a globally accessible way. This association is weak -in its value, the user info, such that it is garbage-collected as soon as the -Eliom-reference storing it is cleared, i.e. when the last session of the -respective user is closed. -

    Available Users

    The client's UI contains a list of users which are currently available for -chatting. This is achieved as follows. -

    If it is the user's first client process he is added to the set of available -users, which is available in an react signal Chat.users_signal on a set of -user (i.e. User_set.t React.S.t). -As long as a user is available with at least one client he is kept in the value -of the signal. This signal is send to the client when initializing Chat. -Changes in its value are then directly reflected in the list of available users -in the client's UI (c.f. Client.change_users). -

    To observe when a user is not available through a given client process anymore, -Chat awaits for every client process that it is inactive for a given -time. Eliom_comet.Channels.wait_timeout is used for for detecting a client -process to be abandoned. -

    When it is observed that a user isn't avaible through any client process -anymore, he is removed from the Chat.users_signal, and all conversation he -is participating are teared down (c.f. Chat.remove_client_process). -

    Starting a conversation

    When the user selects one of the available users for a conversation, one of two -things may happen (c.f. Client.create_or_focus_conversation): -

    Firstly, if there is already a conversation between exactly those two users, -the message prompt is focused. -Secondly, if there is no conversation between those users yet, a new one is created -by calling the server's Chat.create_dialog_service (dialog is a conversatio -of two) with the selected user as argument. -

    This service establishes the conversation between the users: It creates the -conversation on the server side (i.e. the set of participants and the bus for -messages) and sends an event to append the conversation to each participant. -Each client is streaming such events (c.f. Client.dispatch_event) and -creates the respective UI-element for the conversation with event handlers for -sending and receiving messages in the conversation. -

    Sending a message in a conversationg

    Sending messages within an conversation is achieved without any server-side -code; the messages are sent "directly" to all all participants of the -conversation through its Eliom-bus. Two things make this work. -

    Firstly, an event listener on the message prompt of the conversation -(c.f. Client.handle_enter_pressed) awaits that the enter-key is hit in -the message prompt of a conversation. It then creates a message and sends it -to the conversation's event bus. -

    Secondly, every participant of a conversation is streaming the messages which -are sent over the conversation bus. -A stream iterator on that bus then displays those messages as they arrive -(c.f. Client.dispatch_message). -

    Have fun. Be communicative!

    diff --git a/dev/manual/custom-conf.html b/dev/manual/custom-conf.html index ab3dde58..10292df8 100644 --- a/dev/manual/custom-conf.html +++ b/dev/manual/custom-conf.html @@ -44,7 +44,7 @@
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    Custom configuration options

    It is not convenient to have to edit the code to change some +

    Custom configuration options

    It is not convenient to have to edit the code to change some configurations, like the location where are saved the favorite images in the Graffiti tutorial (see: Saving favorite pictures). diff --git a/dev/manual/hash-password.html b/dev/manual/hash-password.html index c279a9c0..b5206ba2 100644 --- a/dev/manual/hash-password.html +++ b/dev/manual/hash-password.html @@ -44,7 +44,7 @@

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

    Protecting your passwords

    For protecting the user passwords or other sensitive data, +

    Protecting your passwords

    For protecting the user passwords or other sensitive data, we can use ocaml-safepass.

    We can now write the encrypted password in our database diff --git a/dev/manual/how-do-i-create-a-cryptographically-safe-identifier.html b/dev/manual/how-do-i-create-a-cryptographically-safe-identifier.html index f37ac850..6c3dcaab 100644 --- a/dev/manual/how-do-i-create-a-cryptographically-safe-identifier.html +++ b/dev/manual/how-do-i-create-a-cryptographically-safe-identifier.html @@ -44,4 +44,4 @@

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

    +

    diff --git a/dev/manual/how-does-a-page-s-source-code-look.html b/dev/manual/how-does-a-page-s-source-code-look.html index e1040076..69b386f6 100644 --- a/dev/manual/how-does-a-page-s-source-code-look.html +++ b/dev/manual/how-does-a-page-s-source-code-look.html @@ -44,7 +44,7 @@
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How does a client-server app source code look like?

    Eliom client-server applications

    Eliom client-server applications are running in your browser for a certain lifetime and consist of one or several pages/URLs. +

    How does a client-server app source code look like?

    Eliom client-server applications

    Eliom client-server applications are running in your browser for a certain lifetime and consist of one or several pages/URLs. An application has its associated js file, which must have the same name (generated automatically by the default makefile and added automatically by Eliom in the page).

    For example, we define an application called example:

    module Example =
    diff --git a/dev/manual/how-to-add-a-div.html b/dev/manual/how-to-add-a-div.html
    index 56b13cae..cae612e2 100644
    --- a/dev/manual/how-to-add-a-div.html
    +++ b/dev/manual/how-to-add-a-div.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to add a div?

    div ~a:[a_class ["firstclass"; "secondclass"]] [txt "Hello!"]

    Required parameter: list containing other elements +

    How to add a div?

    div ~a:[a_class ["firstclass"; "secondclass"]] [txt "Hello!"]

    Required parameter: list containing other elements (Details of available elements in type Html_types.flow5).

    Optional parameter for attributes "a" (How to set and id, classes or other attributes to HTML elements?). diff --git a/dev/manual/how-to-add-a-favicon.html b/dev/manual/how-to-add-a-favicon.html index 7e5bf693..61704dc9 100644 --- a/dev/manual/how-to-add-a-favicon.html +++ b/dev/manual/how-to-add-a-favicon.html @@ -44,7 +44,7 @@

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

    How to add a Favicon?

    A favicon is a file of type "ico" which contain a picture of size 16x16px. It is the picture that you can ususally see next to the title of the page on a browser. +

    How to add a Favicon?

    A favicon is a file of type "ico" which contain a picture of size 16x16px. It is the picture that you can ususally see next to the title of the page on a browser.

    favicon for Ocsigen.org

    By default, all browsers look for a file favicon.ico at the root of the website: diff --git a/dev/manual/how-to-add-a-javascript-script.html b/dev/manual/how-to-add-a-javascript-script.html index 8e6754b4..ce63b9d5 100644 --- a/dev/manual/how-to-add-a-javascript-script.html +++ b/dev/manual/how-to-add-a-javascript-script.html @@ -44,7 +44,7 @@

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

    How to add a Javacript script?

    If you have client-side programs on your website, you can use Eliom's client-server features, that will compile client side parts to JS using Ocsigen Js_of_ocaml, and include automatically the script in the page. But in some cases you may also want to include yourselves external JS scripts. +

    How to add a Javacript script?

    If you have client-side programs on your website, you can use Eliom's client-server features, that will compile client side parts to JS using Ocsigen Js_of_ocaml, and include automatically the script in the page. But in some cases you may also want to include yourselves external JS scripts.

    Include the script on the html header

    Javascript scripts are included in the header using the js_script function (defined in Eliom_content.Html.D).

    open Eliom_content.Html.D (* for make_uri an js_script *)
     
    diff --git a/dev/manual/how-to-add-a-list.html b/dev/manual/how-to-add-a-list.html
    index 25b23287..e7cdd225 100644
    --- a/dev/manual/how-to-add-a-list.html
    +++ b/dev/manual/how-to-add-a-list.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to add lists in a page?

    Simple list and ordered list

    Simple list
    ul
    +

    How to add lists in a page?

    Simple list and ordered list

    Simple list
    ul
         [li [txt "first item"];
          li [txt "second item"];
          li [txt "third item"];
    diff --git a/dev/manual/how-to-add-a-select-or-other-form-element.html b/dev/manual/how-to-add-a-select-or-other-form-element.html
    index 298b9cb8..7352185a 100644
    --- a/dev/manual/how-to-add-a-select-or-other-form-element.html
    +++ b/dev/manual/how-to-add-a-select-or-other-form-element.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to add a select (or other form element)?

    In forms towards Eliom services:

    open Eliom_content
    +

    How to add a select (or other form element)?

    In forms towards Eliom services:

    open Eliom_content
     
     Html.D.Form.select ~name:select_name
       Html.D.Form.string (* type of the parameter *)
    diff --git a/dev/manual/how-to-add-an-image.html b/dev/manual/how-to-add-an-image.html
    index a6d52ff0..1f9fa008 100644
    --- a/dev/manual/how-to-add-an-image.html
    +++ b/dev/manual/how-to-add-an-image.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to add an image?

    Internal image +

    How to add an image?

    Internal image

    img ~alt:("Ocsigen Logo")
           ~src:(make_uri
                   ~service:(Eliom_service.static_dir ())
    diff --git a/dev/manual/how-to-add-css-stylesheet.html b/dev/manual/how-to-add-css-stylesheet.html
    index 6960f375..e3f9813f 100644
    --- a/dev/manual/how-to-add-css-stylesheet.html
    +++ b/dev/manual/how-to-add-css-stylesheet.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to add a CSS stylesheet?

    Warning: css_link and make_uri come from Eliom_content.Html.D module. This module is opened for each piece of code +

    How to add a CSS stylesheet?

    Warning: css_link and make_uri come from Eliom_content.Html.D module. This module is opened for each piece of code

    CSS stylesheet are included in the header using the css_link function.

    css_link
          ~uri:(make_uri (Eliom_service.static_dir ())
    diff --git a/dev/manual/how-to-attach-ocaml-values-to-dom-elements.html b/dev/manual/how-to-attach-ocaml-values-to-dom-elements.html
    index c6406f87..180b0884 100644
    --- a/dev/manual/how-to-attach-ocaml-values-to-dom-elements.html
    +++ b/dev/manual/how-to-attach-ocaml-values-to-dom-elements.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to attach OCaml values to DOM elements?

    It is often convenient to attach OCaml values to certain elements of +

    How to attach OCaml values to DOM elements?

    It is often convenient to attach OCaml values to certain elements of the page. There are several ways to achieve this.

    • The first possibility is to use DATA attributes (for example if the page is generated on server side). diff --git a/dev/manual/how-to-attach-ocaml-values-to-the-html-nodes-sent-to-the-client.html b/dev/manual/how-to-attach-ocaml-values-to-the-html-nodes-sent-to-the-client.html index 53a1410b..b7ca79d8 100644 --- a/dev/manual/how-to-attach-ocaml-values-to-the-html-nodes-sent-to-the-client.html +++ b/dev/manual/how-to-attach-ocaml-values-to-the-html-nodes-sent-to-the-client.html @@ -44,4 +44,4 @@
    • Source code

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

    +

    diff --git a/dev/manual/how-to-build-js-object.html b/dev/manual/how-to-build-js-object.html index 9ff63818..2271b1a8 100644 --- a/dev/manual/how-to-build-js-object.html +++ b/dev/manual/how-to-build-js-object.html @@ -44,7 +44,7 @@
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to build js object?

    Use syntax new%js: +

    How to build js object?

    Use syntax new%js:

    Example:

    let get_timestamp () =
       let date = new%js Js.date_now in
    diff --git a/dev/manual/how-to-call-a-server-side-function-from-client-side.html b/dev/manual/how-to-call-a-server-side-function-from-client-side.html
    index 3e1bd9ec..8a01fb7e 100644
    --- a/dev/manual/how-to-call-a-server-side-function-from-client-side.html
    +++ b/dev/manual/how-to-call-a-server-side-function-from-client-side.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to call a server-side function from client-side?

    It is possible to call server-side functions in client-side. +

    How to call a server-side function from client-side?

    It is possible to call server-side functions in client-side. For security reasons, these functions must first be declared explicitely as RPCs (with the type of their argument). diff --git a/dev/manual/how-to-call-an-ocaml-function-from-js-code.html b/dev/manual/how-to-call-an-ocaml-function-from-js-code.html index 4504d032..361ab142 100644 --- a/dev/manual/how-to-call-an-ocaml-function-from-js-code.html +++ b/dev/manual/how-to-call-an-ocaml-function-from-js-code.html @@ -44,4 +44,4 @@

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

    How to call an OCaml function from JS code?

    Have a look at function Js.wrap_callback

    +

    How to call an OCaml function from JS code?

    Have a look at function Js.wrap_callback

    diff --git a/dev/manual/how-to-compile-my-ocsigen-pages.html b/dev/manual/how-to-compile-my-ocsigen-pages.html index 8a2c98e2..d0d7330f 100644 --- a/dev/manual/how-to-compile-my-ocsigen-pages.html +++ b/dev/manual/how-to-compile-my-ocsigen-pages.html @@ -44,7 +44,7 @@
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to compile my Ocsigen pages?

    Eliom distillery

    Eliom-distillery will help you to build your client-server application +

    How to compile my Ocsigen pages?

    Eliom distillery

    Eliom-distillery will help you to build your client-server application using Eliom. It comes with several templates ("client-server.basic", "os.pgocaml", and more to come ...). diff --git a/dev/manual/how-to-configure-and-launch-the-ocsigen-server.html b/dev/manual/how-to-configure-and-launch-the-ocsigen-server.html index d6d71803..70fdb29c 100644 --- a/dev/manual/how-to-configure-and-launch-the-ocsigen-server.html +++ b/dev/manual/how-to-configure-and-launch-the-ocsigen-server.html @@ -44,7 +44,7 @@

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

    How to configure and launch the Ocsigen Server?

    Default configuration file

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

    How to create a link to the current page (without knowing its URL)?

    Void coservices are here for that: +

    How to create a link to the current page (without knowing its URL)?

    Void coservices are here for that:

    a ~service:Eliom_service.reload_action
       [txt "Click to reload"] ();

    More information in Eliom's manual, and API documentation of diff --git a/dev/manual/how-to-detect-channel-disconnection.html b/dev/manual/how-to-detect-channel-disconnection.html index 8f10967a..3af7d370 100644 --- a/dev/manual/how-to-detect-channel-disconnection.html +++ b/dev/manual/how-to-detect-channel-disconnection.html @@ -44,7 +44,7 @@

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

    How to detect channel disconnection

    Question

    Is there a way to detect that some Eliom_comet channel became +

    How to detect channel disconnection

    Question

    Is there a way to detect that some Eliom_comet channel became disconnected? I would like to warn the user if the server becomes unreachable.

    Answer

    If you are using Ocsigen-start, you probably have nothing to do. diff --git a/dev/manual/how-to-detect-on-client-side-that-the-server-side-state-for-the-process-is-closed.html b/dev/manual/how-to-detect-on-client-side-that-the-server-side-state-for-the-process-is-closed.html index 754d1983..c7560be4 100644 --- a/dev/manual/how-to-detect-on-client-side-that-the-server-side-state-for-the-process-is-closed.html +++ b/dev/manual/how-to-detect-on-client-side-that-the-server-side-state-for-the-process-is-closed.html @@ -44,7 +44,7 @@

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

    How to detect on client side that the server side state for the process is closed?

    If you are using Ocsigen-start, you probably have nothing to do. +

    How to detect on client side that the server side state for the process is closed?

    If you are using Ocsigen-start, you probably have nothing to do. Ocsigen-start will monitor the life of sessions and close the process when needed.

    If you are not using Ocsigen-start, you must catch exceptions diff --git a/dev/manual/how-to-do-links-to-other-pages.html b/dev/manual/how-to-do-links-to-other-pages.html index 1be36571..934d5386 100644 --- a/dev/manual/how-to-do-links-to-other-pages.html +++ b/dev/manual/how-to-do-links-to-other-pages.html @@ -44,7 +44,7 @@

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

    How to do links to other pages?

    Examples: +

    How to do links to other pages?

    Examples:

    (* Link to a service without parameter: *)
     Html.D.a ~service:coucou [txt "coucou"] ();
     
    diff --git a/dev/manual/how-to-implement-a-notification-system.html b/dev/manual/how-to-implement-a-notification-system.html
    index ff83c04f..e751d227 100644
    --- a/dev/manual/how-to-implement-a-notification-system.html
    +++ b/dev/manual/how-to-implement-a-notification-system.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to implement a notification system?

    The easiest way for the server to send notifications to the client is +

    How to implement a notification system?

    The easiest way for the server to send notifications to the client is to use module Os_notif from Ocsigen-start (OS), but it requires to use OS's user management system. If you are not using OS, we recommend to get inspiration from diff --git a/dev/manual/how-to-insert-raw-form-elements-not-belonging-to-a-form-towards-a-service.html b/dev/manual/how-to-insert-raw-form-elements-not-belonging-to-a-form-towards-a-service.html index 2c8cc296..35b9c5c5 100644 --- a/dev/manual/how-to-insert-raw-form-elements-not-belonging-to-a-form-towards-a-service.html +++ b/dev/manual/how-to-insert-raw-form-elements-not-belonging-to-a-form-towards-a-service.html @@ -44,7 +44,7 @@

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

    How to insert "raw" form elements (not belonging to a form towards a service)?

    Eliom redefines most forms elements (inputs, textareas, checkboxes, etc.) +

    How to insert "raw" form elements (not belonging to a form towards a service)?

    Eliom redefines most forms elements (inputs, textareas, checkboxes, etc.) to make possible to check the type of the form w.r.t. the type of the service.

    If you don't want that (for example if you want to use it only from a client side program), you can use "raw form elements" (that is, basic tyxml elements), using diff --git a/dev/manual/how-to-iterate-on-all-sessions-for-one-user-or-all-tabs.html b/dev/manual/how-to-iterate-on-all-sessions-for-one-user-or-all-tabs.html index c6a20630..71e2f5b6 100644 --- a/dev/manual/how-to-iterate-on-all-sessions-for-one-user-or-all-tabs.html +++ b/dev/manual/how-to-iterate-on-all-sessions-for-one-user-or-all-tabs.html @@ -44,7 +44,7 @@

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

    How to iterate on all session belonging to the same user, or all tabs?

    You must create a session group for each user, then iterate on all +

    How to iterate on all session belonging to the same user, or all tabs?

    You must create a session group for each user, then iterate on all the sessions from this group, and possibly on all client processes for each session:

    (* We get the session group state for this user: *)
    diff --git a/dev/manual/how-to-know-whether-the-browser-window-has-the-focus-or-not.html b/dev/manual/how-to-know-whether-the-browser-window-has-the-focus-or-not.html
    index a41707bd..6fd3e11e 100644
    --- a/dev/manual/how-to-know-whether-the-browser-window-has-the-focus-or-not.html
    +++ b/dev/manual/how-to-know-whether-the-browser-window-has-the-focus-or-not.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to know whether the browser window has the focus or not?

    Example: +

    How to know whether the browser window has the focus or not?

    Example:

    let has_focus = ref true
     
     let _ =
    diff --git a/dev/manual/how-to-make-hello-world-in-ocsigen.html b/dev/manual/how-to-make-hello-world-in-ocsigen.html
    index 5ef4a47d..c86d12de 100644
    --- a/dev/manual/how-to-make-hello-world-in-ocsigen.html
    +++ b/dev/manual/how-to-make-hello-world-in-ocsigen.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to make a "hello world" in Ocsigen?

    Here it is! The famous "Hello World" for a client/server Eliom application: +

    How to make a "hello world" in Ocsigen?

    Here it is! The famous "Hello World" for a client/server Eliom application:

    open Eliom_content
     open Html.D
     open Eliom_parameter
    diff --git a/dev/manual/how-to-make-page-a-skeleton.html b/dev/manual/how-to-make-page-a-skeleton.html
    index 25c0123b..210be269 100644
    --- a/dev/manual/how-to-make-page-a-skeleton.html
    +++ b/dev/manual/how-to-make-page-a-skeleton.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to make a page skeleton?

    The same header for all your pages

    When your site will grow, you will have several different services for pages which will often contain the same header informations. +

    How to make a page skeleton?

    The same header for all your pages

    When your site will grow, you will have several different services for pages which will often contain the same header informations.

    A great solutions to avoid code copy-pasting of these recurrent informations are to make a page skeleton function:

    let skeleton body_content =
       Lwt.return
    diff --git a/dev/manual/how-to-make-responsive-css.html b/dev/manual/how-to-make-responsive-css.html
    index 09a4727e..9bb0e66f 100644
    --- a/dev/manual/how-to-make-responsive-css.html
    +++ b/dev/manual/how-to-make-responsive-css.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to make reponsive CSS with ocsigen?

    The best way to do that is to make one general css sheet plus three css sheets, small, medium and large screen using media queries, a feature introduced in CSS3. +

    How to make reponsive CSS with ocsigen?

    The best way to do that is to make one general css sheet plus three css sheets, small, medium and large screen using media queries, a feature introduced in CSS3.

    Write theses lines in your css sheets:

    @media only screen and (max-device-width: 480px)
     @media only screen and (min-device-width: 481px) and (max-device-width: 768px)
    diff --git a/dev/manual/how-to-make-the-client-side-program-get-an-html-element-from-the-server-and-insert-it-in-the-page.html b/dev/manual/how-to-make-the-client-side-program-get-an-html-element-from-the-server-and-insert-it-in-the-page.html
    index 51a91a46..c85c60d9 100644
    --- a/dev/manual/how-to-make-the-client-side-program-get-an-html-element-from-the-server-and-insert-it-in-the-page.html
    +++ b/dev/manual/how-to-make-the-client-side-program-get-an-html-element-from-the-server-and-insert-it-in-the-page.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to make the client side program get an HTML element from the server and insert it in the page?

    A very convenient way to do that is to use RPCs : +

    How to make the client side program get an HTML element from the server and insert it in the page?

    A very convenient way to do that is to use RPCs :

    let%rpc get_mydiv (() : unit) : _ Lwt.t = div [ ... ]
    [%client
       ...
       let%lwt mydiv = get_mydiv () in
    diff --git a/dev/manual/how-to-register-a-service-that-decides-itself-what-to-send.html b/dev/manual/how-to-register-a-service-that-decides-itself-what-to-send.html
    index 2ea61395..0171fa7a 100644
    --- a/dev/manual/how-to-register-a-service-that-decides-itself-what-to-send.html
    +++ b/dev/manual/how-to-register-a-service-that-decides-itself-what-to-send.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to register a service that decides itself what to send?

    Use Eliom_registration.Any. +

    How to register a service that decides itself what to send?

    Use Eliom_registration.Any.

    In the following example, we send an Html page or a redirection:

    let send_any =
       Eliom_registration.Any.create
    diff --git a/dev/manual/how-to-register-session-data.html b/dev/manual/how-to-register-session-data.html
    index 83bd0de2..d54928b6 100644
    --- a/dev/manual/how-to-register-session-data.html
    +++ b/dev/manual/how-to-register-session-data.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to register session data?

    It is very easy to register session data using Eliom references. +

    How to register session data?

    It is very easy to register session data using Eliom references. Just create an Eliom reference of scope session and its value will be different for each session (one session = one browser process).

    But most of the time, what we want is to store data for one user, diff --git a/dev/manual/how-to-send-a-file-to-server-without-stopping-the-client-process.html b/dev/manual/how-to-send-a-file-to-server-without-stopping-the-client-process.html index 88af66b8..31e42886 100644 --- a/dev/manual/how-to-send-a-file-to-server-without-stopping-the-client-process.html +++ b/dev/manual/how-to-send-a-file-to-server-without-stopping-the-client-process.html @@ -44,7 +44,7 @@

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

    How to send a file to the server without stopping the client process?

    This requires Eliom ≥ 3.1. +

    How to send a file to the server without stopping the client process?

    This requires Eliom ≥ 3.1.

    Due to security reasons, browsers have limitations on sending files. But if the file is chosen by the user through an input file element, there is a way to send it to the server. You can't use the server_function diff --git a/dev/manual/how-to-send-file-download.html b/dev/manual/how-to-send-file-download.html index 40fd9508..376fa271 100644 --- a/dev/manual/how-to-send-file-download.html +++ b/dev/manual/how-to-send-file-download.html @@ -44,7 +44,7 @@

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

    How to send a file (download)?

    To serve file, you can use Ocsigen Server's module staticmod. +

    How to send a file (download)?

    To serve file, you can use Ocsigen Server's module staticmod. But it is also possible to ask Eliom to send files using module Eliom_registration.File, for example if you want Eliom to perform some privacy checks before sending, diff --git a/dev/manual/how-to-send-file-upload.html b/dev/manual/how-to-send-file-upload.html index 5fa86f7d..7f7143f6 100644 --- a/dev/manual/how-to-send-file-upload.html +++ b/dev/manual/how-to-send-file-upload.html @@ -44,7 +44,7 @@

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

    How to send a file (upload)?

    To upload a file, use Eliom_parameter.file as service parameter type. +

    How to send a file (upload)?

    To upload a file, use Eliom_parameter.file as service parameter type.

    Ocsigen server will save the file at a temporary location and keep it there during the request. Then the file will be removed. You must link it somewhere else on the disk yourself if you want to keep it. diff --git a/dev/manual/how-to-set-and-id-classes-or-other-attributes-to-html-elements.html b/dev/manual/how-to-set-and-id-classes-or-other-attributes-to-html-elements.html index 186faf51..13eb905d 100644 --- a/dev/manual/how-to-set-and-id-classes-or-other-attributes-to-html-elements.html +++ b/dev/manual/how-to-set-and-id-classes-or-other-attributes-to-html-elements.html @@ -44,7 +44,7 @@

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

    How to set and id, classes or other attributes to HTML elements?

    Mandatory element attributes are given as OCaml named parameters to +

    How to set and id, classes or other attributes to HTML elements?

    Mandatory element attributes are given as OCaml named parameters to constructions function.

    Optional element attributes are added using the optional OCaml parameter "?a" which is more or less available for every HTML5 elements. This parameter is taking a list of attributes compatible with the element.

    div ~a:[a_class ["red";"shadow"];
    diff --git a/dev/manual/how-to-stop-default-behaviour-of-events.html b/dev/manual/how-to-stop-default-behaviour-of-events.html
    index e32c4fc2..e9263e2e 100644
    --- a/dev/manual/how-to-stop-default-behaviour-of-events.html
    +++ b/dev/manual/how-to-stop-default-behaviour-of-events.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to stop default behaviour of events?

    Example: +

    How to stop default behaviour of events?

    Example:

    (** Disable Js event with stopping propagation during capture phase **)
     let disable_event event html_elt =
       Lwt.async (fun () ->
    diff --git a/dev/manual/how-to-use-get-parameters-or-parameters-in-the-url.html b/dev/manual/how-to-use-get-parameters-or-parameters-in-the-url.html
    index be504d23..8832fe41 100644
    --- a/dev/manual/how-to-use-get-parameters-or-parameters-in-the-url.html
    +++ b/dev/manual/how-to-use-get-parameters-or-parameters-in-the-url.html
    @@ -44,7 +44,7 @@
     
  • Source code
  • Warning: Reason support is experimental. We are looking for beta-tester and contributors. -

    How to use GET parameters (parameters in the URL)?

    Pages are generated by services. +

    How to use GET parameters (parameters in the URL)?

    Pages are generated by services.

    Funcrions to create services using GET HTTP method take two mandatory parameters: ~path and ~get_params.

    path

    This argument is a list of string, corresponding to the URL where your page service can be found. diff --git a/dev/manual/how-to-write-a-json-service.html b/dev/manual/how-to-write-a-json-service.html index c67c3229..722b3aa8 100644 --- a/dev/manual/how-to-write-a-json-service.html +++ b/dev/manual/how-to-write-a-json-service.html @@ -44,7 +44,7 @@

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

    How to write a JSON service?

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

    How to write forms?

    To write an HTML form towards an Eliom service

    Just as we do for links, we provide form-building functions that call +

    How to write forms?

    To write an HTML form towards an Eliom service

    Just as we do for links, we provide form-building functions that call Eliom services in a safe manner. These functions are provided in the module Eliom_content.Html.D.Form (and diff --git a/dev/manual/how-to-write-titles-and-paragraphs.html b/dev/manual/how-to-write-titles-and-paragraphs.html index 6f065fa0..1f09969d 100644 --- a/dev/manual/how-to-write-titles-and-paragraphs.html +++ b/dev/manual/how-to-write-titles-and-paragraphs.html @@ -44,7 +44,7 @@

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

    How to write titles and paragrahs?

    Titles +

    How to write titles and paragrahs?

    Titles

    h3 [txt "Hello world"]

    There are 6 types of titles: h1, h2, h3, h4, h5 and h6. h1 is the largest and h6 is the smallest.

    Pagragraph

    p [txt "Some text, blah blah blah"]

    Required parameter: list containing other elements (content: Html_types.flow5 elements). diff --git a/dev/manual/html.html b/dev/manual/html.html index 6d413ab9..36643e05 100644 --- a/dev/manual/html.html +++ b/dev/manual/html.html @@ -44,7 +44,7 @@

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

    HTML in 5 minutes

    The Tyxml library makes it possible to type-check HTML pages. +

    HTML in 5 minutes

    The Tyxml library makes it possible to type-check HTML pages. This means that your Ocsigen program will never generate pages which do not follow the recommendations from the W3C. For example a program that could generate a page with a paragraph diff --git a/dev/manual/interaction.html b/dev/manual/interaction.html index 24d873a7..7983e1b3 100644 --- a/dev/manual/interaction.html +++ b/dev/manual/interaction.html @@ -44,7 +44,7 @@

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

    Implementing Web Interaction Using Eliom

    The code of this tutorial has been tested with Eliom 6.0.
    +

    Implementing Web Interaction Using Eliom

    The code of this tutorial has been tested with Eliom 6.0.

    This chapter of the tutorial explains how to create a small web site with several pages, users, sessions, and other elements of classical web development. Then, in diff --git a/dev/manual/intro.html b/dev/manual/intro.html index 3e703754..52428ea6 100644 --- a/dev/manual/intro.html +++ b/dev/manual/intro.html @@ -44,7 +44,7 @@

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

    Introduction

    Ocsigen is a complete framework for developing Web and mobile apps +

    Introduction

    Ocsigen is a complete framework for developing Web and mobile apps using cutting edge techniques. It can be used to write simple server side Web sites, client-side programs, or complex client-server Web and mobile apps. diff --git a/dev/manual/lwt.html b/dev/manual/lwt.html index be9706bc..8cb87e2c 100644 --- a/dev/manual/lwt.html +++ b/dev/manual/lwt.html @@ -44,7 +44,7 @@

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

    Lwt in 5 minutes

    Principles

    The Lwt library implements cooperative threads for OCaml. Cooperative +

    Lwt in 5 minutes

    Principles

    The Lwt library implements cooperative threads for OCaml. Cooperative threads are an alternative to preemptive threads (used in many languages and in OCaml's Thread module) that solve most common issues with preemptive threads: with Lwt, there is very limited risk diff --git a/dev/manual/macaque.html b/dev/manual/macaque.html index 6827b999..83a64f1d 100644 --- a/dev/manual/macaque.html +++ b/dev/manual/macaque.html @@ -44,7 +44,7 @@

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

    Type safe database requests using Macaque

    The Macaque library allows easy manipulation of Postgresql +

    Type safe database requests using Macaque

    The Macaque library allows easy manipulation of Postgresql database fully compatible with Lwt. (For more information see Macaque manual).

    Macaque is fuly compatible with PGOcaml, and both can be used diff --git a/dev/manual/misc.html b/dev/manual/misc.html index f5a322fa..94f3d6c3 100644 --- a/dev/manual/misc.html +++ b/dev/manual/misc.html @@ -44,7 +44,7 @@

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

    Traditional web interaction in a client-server app

    The code of this tutorial has been tested with Eliom 6.0.
    +

    Traditional web interaction in a client-server app

    The code of this tutorial has been tested with Eliom 6.0.

    Multi-user collaborative drawing application

    We now want to turn our collaborative drawing application into a multi-user one. Each user will have their own drawing, where everyone can draw. diff --git a/dev/manual/mobile.html b/dev/manual/mobile.html index b6bb64b9..9ab90016 100644 --- a/dev/manual/mobile.html +++ b/dev/manual/mobile.html @@ -44,7 +44,7 @@

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

    Mobile applications with Ocsigen

    Since Eliom 6.0, the Ocsigen framework provides infrastructure for +

    Mobile applications with Ocsigen

    Since Eliom 6.0, the Ocsigen framework provides infrastructure for building mobile applications. This enables rapid development of Android, iOS, and Windows Mobile apps with the same APIs and programming style as for a regular client-server Web application. In diff --git a/dev/manual/music.html b/dev/manual/music.html index 2dd8c162..a4d0a97e 100644 --- a/dev/manual/music.html +++ b/dev/manual/music.html @@ -44,7 +44,7 @@

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

    Listening music

    We will add an audio player to the page that will stay when page +

    Listening music

    We will add an audio player to the page that will stay when page changes. This emphasises the fact that browsing inside an application does not stop the client side code: the music keeps playing when the content of the page and the url change. diff --git a/dev/manual/ocsipersist.html b/dev/manual/ocsipersist.html index dbb6600e..260e97d0 100644 --- a/dev/manual/ocsipersist.html +++ b/dev/manual/ocsipersist.html @@ -44,7 +44,7 @@

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

    Lightweight database using Ocsipersist

    Ocsipersist is a module for persistent references and tables. +

    Lightweight database using Ocsipersist

    Ocsipersist is a module for persistent references and tables.

    For persistent references, Eliom has a higher level interface, called Eliom references, and you probably want to use it instead of using Ocsipersist directly. diff --git a/dev/manual/pictures.html b/dev/manual/pictures.html index 2ce96ce4..58a65c64 100644 --- a/dev/manual/pictures.html +++ b/dev/manual/pictures.html @@ -44,7 +44,7 @@

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

    Saving favorite pictures

    We will now add a button to the Graffiti application to save the current +

    Saving favorite pictures

    We will now add a button to the Graffiti application to save the current image. The images will be saved to the filesystem using the module Lwt_io. We will then make an Atom feed with the saved images using Syndic. diff --git a/dev/manual/reactivemediaplayer.html b/dev/manual/reactivemediaplayer.html index 563ee6e5..33f15547 100644 --- a/dev/manual/reactivemediaplayer.html +++ b/dev/manual/reactivemediaplayer.html @@ -44,7 +44,7 @@

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

    Reactive Media Player

    You should read the Playing Music tutorial before this one. +

    Reactive Media Player

    You should read the Playing Music tutorial before this one.

    Since version 4, Eliom embeds the React library in order to provide reactive HTML elements diff --git a/dev/manual/rest.html b/dev/manual/rest.html index 1ffe152c..f0445118 100644 --- a/dev/manual/rest.html +++ b/dev/manual/rest.html @@ -44,7 +44,7 @@

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

    RESTful JSON API using Eliom

    This tutorial will show you how to create a simple, yet complete, REST API +

    RESTful JSON API using Eliom

    This tutorial will show you how to create a simple, yet complete, REST API using JSON as the serialization format.

    To illustrate our example, let's say we want to give access to a database of locations storing a description and coordinates (latitude and longitude). diff --git a/dev/manual/start.html b/dev/manual/start.html index 748ebf58..fd48e2a5 100644 --- a/dev/manual/start.html +++ b/dev/manual/start.html @@ -44,7 +44,7 @@

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

    Your first app in 5 minutes

    The code of this tutorial has been tested against Eliom 6.0.
    +

    Your first app in 5 minutes

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

    This tutorial describes how to get started with Ocsigen quickly. Thanks to an application template provided by the Ocsigen team, you will get to a working application with standard diff --git a/dev/manual/tutoreact.html b/dev/manual/tutoreact.html index 7d79dca9..5da2b7dc 100644 --- a/dev/manual/tutoreact.html +++ b/dev/manual/tutoreact.html @@ -44,7 +44,7 @@

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

    Client server reactive application with Ocsigen

    This is a short tutorial showing how to implement a simple reactive +

    Client server reactive application with Ocsigen

    This is a short tutorial showing how to implement a simple reactive client-server application using Js_of_ocaml, Eliom and Ocsigen Start.

    We are going to implement an application that can display a list of messages and diff --git a/dev/manual/tutowidgets.html b/dev/manual/tutowidgets.html index 9600f84a..119bb836 100644 --- a/dev/manual/tutowidgets.html +++ b/dev/manual/tutowidgets.html @@ -44,7 +44,7 @@

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

    Mini-tutorial: client-server widgets

    This short tutorial is an example of client-server Eliom application. +

    Mini-tutorial: client-server widgets

    This short tutorial is an example of client-server Eliom application. It gives an example of client-server widgets.

    It is probably a good starting point if you know OCaml well, and want to quickly learn how to write a client-server Eliom application with a