├── .gitattributes ├── LICENSE ├── README.md ├── eliom ├── tutorial │ ├── 00100-hello-world │ │ ├── README.md │ │ └── src │ │ │ └── mysite │ │ │ ├── .ocp-indent │ │ │ ├── Makefile │ │ │ ├── Makefile.options │ │ │ ├── README │ │ │ ├── mysite.conf.in │ │ │ ├── mysite.eliom │ │ │ └── static │ │ │ └── css │ │ │ └── mysite.css │ ├── 00200-static-table │ │ ├── README.md │ │ └── src │ │ │ └── mysite │ │ │ ├── .ocp-indent │ │ │ ├── Makefile │ │ │ ├── Makefile.options │ │ │ ├── README │ │ │ ├── config.eliom │ │ │ ├── config.eliomi │ │ │ ├── i18n.eliom │ │ │ ├── i18n.eliomi │ │ │ ├── id.eliom │ │ │ ├── id.eliomi │ │ │ ├── language.eliom │ │ │ ├── language.eliomi │ │ │ ├── model.eliom │ │ │ ├── model.eliomi │ │ │ ├── money.eliom │ │ │ ├── money.eliomi │ │ │ ├── mysite.conf.in │ │ │ ├── mysite.eliom │ │ │ ├── product.eliom │ │ │ ├── product.eliomi │ │ │ ├── static │ │ │ └── css │ │ │ │ └── mysite.css │ │ │ ├── view.eliom │ │ │ └── view.eliomi │ ├── 00300-url-param │ │ ├── README.md │ │ └── src │ │ │ └── mysite │ │ │ ├── .ocp-indent │ │ │ ├── Makefile │ │ │ ├── Makefile.options │ │ │ ├── README │ │ │ ├── config.eliom │ │ │ ├── config.eliomi │ │ │ ├── i18n.eliom │ │ │ ├── i18n.eliomi │ │ │ ├── id.eliom │ │ │ ├── id.eliomi │ │ │ ├── language.eliom │ │ │ ├── language.eliomi │ │ │ ├── model.eliom │ │ │ ├── model.eliomi │ │ │ ├── money.eliom │ │ │ ├── money.eliomi │ │ │ ├── mysite.conf.in │ │ │ ├── mysite.eliom │ │ │ ├── product.eliom │ │ │ ├── product.eliomi │ │ │ ├── service.eliom │ │ │ ├── service.eliomi │ │ │ ├── static │ │ │ └── css │ │ │ │ └── mysite.css │ │ │ ├── view.eliom │ │ │ └── view.eliomi │ ├── 00400-add-product │ │ ├── README.md │ │ └── src │ │ │ └── mysite │ │ │ ├── Makefile │ │ │ ├── Makefile.options │ │ │ ├── README │ │ │ ├── config.eliom │ │ │ ├── config.eliomi │ │ │ ├── i18n.eliom │ │ │ ├── i18n.eliomi │ │ │ ├── id.eliom │ │ │ ├── id.eliomi │ │ │ ├── language.eliom │ │ │ ├── language.eliomi │ │ │ ├── model.eliom │ │ │ ├── model.eliomi │ │ │ ├── money.eliom │ │ │ ├── money.eliomi │ │ │ ├── mysite.conf.in │ │ │ ├── mysite.eliom │ │ │ ├── product.eliom │ │ │ ├── product.eliomi │ │ │ ├── service.eliom │ │ │ ├── service.eliomi │ │ │ ├── static │ │ │ └── css │ │ │ │ └── mysite.css │ │ │ ├── view.eliom │ │ │ └── view.eliomi │ ├── Makefile │ └── shared │ │ ├── config.eliom │ │ ├── config.eliomi │ │ ├── i18n.eliom │ │ ├── i18n.eliomi │ │ ├── id.eliom │ │ ├── id.eliomi │ │ ├── language.eliom │ │ ├── language.eliomi │ │ ├── money.eliom │ │ ├── money.eliomi │ │ ├── product.eliom │ │ └── product.eliomi └── with-ocaml-vdom │ └── simple │ ├── .depend │ ├── .ocp-indent │ ├── Makefile │ ├── Makefile.options │ ├── README │ ├── README.md │ ├── mixvdomandeliom.conf.in │ ├── mixvdomandeliom.eliom │ └── static │ └── css │ └── mixvdomandeliom.css └── jsoo ├── build-local-csv-file ├── blob.ml ├── blob.mli ├── build.sh ├── main.ml ├── textEncoder.ml ├── textEncoder.mli └── www │ ├── css │ └── style.css │ ├── index.html │ └── js │ ├── main.js │ └── text-encoding │ └── encoding.js ├── curriculum-vitae ├── _tags ├── action.ml ├── build.sh ├── controller.ml ├── img │ ├── ID-100165338.jpg │ ├── ID-100209817.jpg │ ├── ID-100371977.jpg │ └── darth-vader.jpg ├── index.html ├── js │ └── main.js ├── main.ml ├── model.ml ├── rl.ml ├── style.css ├── types.ml └── view.ml ├── lsystem ├── build.sh ├── lsystem.ml └── www │ ├── css │ └── style.css │ ├── index.html │ └── js │ └── lsystem.js ├── pico-spreadsheet ├── build.sh ├── spreadsheet.ml └── www │ ├── css │ └── style.css │ ├── index.html │ └── js │ └── spreadsheet.js ├── pong ├── build.sh ├── pong.ml └── www │ ├── css │ └── style.css │ ├── index.html │ └── js │ └── pong.js ├── tic-tac-toe ├── .gitignore ├── action.ml ├── build.sh ├── controller.ml ├── index.html ├── js │ └── main.js ├── main.ml ├── model.ml ├── style.css ├── types.ml └── view.ml └── todomvc-react ├── .gitignore ├── .jshintignore ├── build.sh ├── index.html ├── js └── todomvc.js ├── node_modules ├── todomvc-app-css │ ├── index.css │ ├── package.json │ └── readme.md └── todomvc-common │ ├── base.css │ ├── base.js │ ├── package.json │ └── readme.md ├── package.json ├── readme.md └── todomvc.ml /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-vendored 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Stéphane Legrand 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Examples with Eliom 3 | 4 | - Work in progress: [Eliom tutorial](https://github.com/slegrand45/examples_ocsigen/tree/master/eliom/tutorial/). 5 | 6 | - A very simple example of using [Eliom services](https://ocsigen.org/eliom/dev/manual/clientserver-communication) for the server side with [OCaml Vdom](https://github.com/LexiFi/ocaml-vdom) for the client side. 7 | - Source: [https://github.com/slegrand45/examples_ocsigen/tree/master/eliom/with-ocaml-vdom/simple/](https://github.com/slegrand45/examples_ocsigen/tree/master/eliom/with-ocaml-vdom/simple/) 8 | 9 | 10 | ## Examples with js_of_ocaml 11 | 12 | - A Reactive version of TodoMVC. 13 | - Source: [https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/todomvc-react](https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/todomvc-react) 14 | - Demo: [http://slegrand45.github.io/examples_ocsigen.site/jsoo/todomvc-react/](http://slegrand45.github.io/examples_ocsigen.site/jsoo/todomvc-react/) 15 | 16 | - A Reactive version of Tic-Tac-Toe game. The idea of this demo came from the talk ["Enterprise Tic-Tac-Toe"](http://fsharpforfunandprofit.com/ettt/) by Scott Wlaschin. 17 | - Source: [https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/tic-tac-toe](https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/tic-tac-toe) 18 | - Demo: [http://slegrand45.github.io/examples_ocsigen.site/jsoo/tic-tac-toe/](http://slegrand45.github.io/examples_ocsigen.site/jsoo/tic-tac-toe/) 19 | 20 | - Even :space_invader: Darth Vader :space_invader: uses js_of_ocaml. This is a port of the [Darth Vader Résumé](http://articles.novoresume.com/luke-who-is-searching-for-a-job/) from Novorésumé with a [Material Design template](http://demo.themesafari.net/materialize-responsive-resume/). The code uses the force and [Material Design Lite](http://www.getmdl.io). 21 | - Source: [https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/curriculum-vitae](https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/curriculum-vitae) 22 | - Demo: [http://slegrand45.github.io/examples_ocsigen.site/jsoo/curriculum-vitae/](http://slegrand45.github.io/examples_ocsigen.site/jsoo/curriculum-vitae/) 23 | 24 | - A Pong game. Inspired by the article ["Client-side haskell"](http://ifeanyi.co/posts/client-side-haskell/) written by Ifeanyi Ubah. 25 | - Source: [https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/pong](https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/pong) 26 | - Demo: [http://slegrand45.github.io/examples_ocsigen.site/jsoo/pong/](http://slegrand45.github.io/examples_ocsigen.site/jsoo/pong/) 27 | 28 | - Draw a plant with SVG and L-System: http://algorithmicbotany.org/papers/abop/abop.pdf 29 | - Source: [https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/lsystem](https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/lsystem) 30 | - Demo: [http://slegrand45.github.io/examples_ocsigen.site/jsoo/lsystem/](http://slegrand45.github.io/examples_ocsigen.site/jsoo/lsystem/) 31 | 32 | - Pico spreadsheet: editable cells with a reactive SVG graph and a reactive sums line. 33 | - Source: [https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/pico-spreadsheet](https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/pico-spreadsheet) 34 | - Demo: [http://slegrand45.github.io/examples_ocsigen.site/jsoo/pico-spreadsheet/](http://slegrand45.github.io/examples_ocsigen.site/jsoo/pico-spreadsheet/) 35 | 36 | - How to build and download a CSV file without any server processing and only from client side data. 37 | - Source: [https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/build-local-csv-file](https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/build-local-csv-file) 38 | - Demo: [http://slegrand45.github.io/examples_ocsigen.site/jsoo/build-local-csv-file/](http://slegrand45.github.io/examples_ocsigen.site/jsoo/build-local-csv-file/) -------------------------------------------------------------------------------- /eliom/tutorial/00100-hello-world/README.md: -------------------------------------------------------------------------------- 1 | # Hello world! 2 | 3 | 4 | ## What you will learn 5 | 6 | In this first tutorial, we will see how to build this classical example. The goal is simply to display "Hello world!" on a web page. 7 | 8 | 9 | ## Prerequisites 10 | 11 | In order to complete this tutorial, you need to install at a bare minimum: 12 | 13 | - [The OCaml language](http://www.ocaml.org/docs/install.html) 14 | - The OCaml package manager [OPAM](https://opam.ocaml.org/doc/Install.html) 15 | 16 | This tutorial has been tested with OCaml version 4.03.0. So i would recommend to install this version or a greater one. 17 | 18 | 19 | ## Install Eliom 20 | 21 | The easiest way to install Eliom and all its dependencies is to use OPAM: 22 | 23 | ``` 24 | $ opam install eliom 25 | ``` 26 | 27 | This tutorial has been tested with Eliom version 6.2.0. So i would recommend to install this version or a greater one. 28 | 29 | 30 | ## Create the project 31 | 32 | Now that everything needed is installed, we can create our first project. Eliom provides a shell command named `eliom-distillery` in order to initialize a new project: 33 | 34 | ``` 35 | $ eliom-distillery -name mysite -template basic.ppx 36 | ``` 37 | 38 | This command creates a new project named `mysite`. The project directory is populated with some files coming from the predefined template named `basic.ppx`: 39 | 40 | ``` 41 | $ cd mysite/ 42 | $ ls 43 | Makefile Makefile.options mysite.conf.in mysite.eliom README static 44 | ``` 45 | 46 | 47 | ## First test 48 | 49 | To test the application, we can run the Eliom server in test mode. You can use one of these commands: 50 | 51 | ``` 52 | $ make test.byte 53 | ``` 54 | 55 | or 56 | 57 | ``` 58 | $ make test.opt 59 | ``` 60 | 61 | The first command will compile the server in bytecode mode whereas the second one will compile the server in native executable mode. 62 | 63 | After the compilation, the Eliom server is automatically launched and ready to answer to client requests. Open your web browser and go to [http://localhost:8080/](http://localhost:8080/). You should see "Welcome from Eliom's distillery!". 64 | 65 | 66 | ## Modify the message 67 | 68 | Now we want to display our own message and say hello to the world. To change the default text, we need to edit the main source code file. By default, its name is the project name with the `.eliom` extension. So open the file named `mysite.eliom` with a text editor, change the text "Welcome from Eliom's distillery!" to "Hello world!" and save the file. 69 | 70 | Then we have to rebuild the application to take into account the change. If needed, stop the current server with `Ctrl+C`. Then rerun either `make test.byte` or `make test.opt`. Refresh the web page [http://localhost:8080/](http://localhost:8080/), you should now see "Hello world!". 71 | 72 | Congratulations, you have made your first Eliom application! 73 | 74 | 75 | ## Next step 76 | 77 | In the [next tutorial](../00200-static-table/), we will see how to create a much more interesting web page. 78 | -------------------------------------------------------------------------------- /eliom/tutorial/00100-hello-world/src/mysite/.ocp-indent: -------------------------------------------------------------------------------- 1 | normal 2 | with=0 3 | syntax=lwt mll 4 | max_indent=2 5 | ppx_stritem_ext=0 6 | -------------------------------------------------------------------------------- /eliom/tutorial/00100-hello-world/src/mysite/Makefile.options: -------------------------------------------------------------------------------- 1 | 2 | #---------------------------------------------------------------------- 3 | # SETTINGS FOR THE ELIOM PROJECT mysite 4 | #---------------------------------------------------------------------- 5 | 6 | PROJECT_NAME := mysite 7 | 8 | # Source files for the server 9 | SERVER_FILES := $(wildcard *.eliomi *.eliom) 10 | # Source files for the client 11 | CLIENT_FILES := $(wildcard *.eliomi *.eliom) 12 | 13 | # OCamlfind packages for the server 14 | SERVER_PACKAGES := lwt.ppx js_of_ocaml.deriving.ppx 15 | # OCamlfind packages for the client 16 | CLIENT_PACKAGES := lwt.ppx js_of_ocaml.ppx js_of_ocaml.deriving.ppx 17 | 18 | # Directory with files to be statically served 19 | LOCAL_STATIC = static 20 | 21 | # The backend for persistent data. Can be dbm or sqlite. 22 | # Make sure you have the following packages installed 23 | # - *dbm* if you use dbm --> opam install dbm. 24 | # - *sqlite3* if you use sqlite --> opam install sqlite3. 25 | PERSISTENT_DATA_BACKEND = sqlite 26 | 27 | # Debug application (yes/no): Debugging info in compilation, 28 | # JavaScript, ocsigenserver 29 | DEBUG := no 30 | 31 | # User to run server with (make run.*) 32 | WWWUSER := www-data 33 | WWWGROUP := www-data 34 | 35 | # Port for running the server (make run.*) 36 | PORT := 80 37 | 38 | # Port for testing (make test.*) 39 | TEST_PORT := 8080 40 | 41 | # Root of installation (must end with /) 42 | PREFIX := /usr/local/ 43 | 44 | # Local folder for make test.* (must end with /) 45 | # Do not add files manually in this directory. 46 | # It is just here to test your installation before installing in / 47 | TEST_PREFIX := local/ 48 | 49 | # The installation tree (relative to $(PREFIX) when 50 | # installing/running or $(TEST_PREFIX) when testing). 51 | # Configuration file $(PROJECT_NAME).conf 52 | ETCDIR := etc/${PROJECT_NAME} 53 | # Project's library $(PROJECT_NAME).cma (cmxs) 54 | LIBDIR := lib/${PROJECT_NAME} 55 | # Command pipe, eg. $ echo reload > $(INSTALL_PREFIX)$(CMDPIPE) 56 | CMDPIPE := var/run/${PROJECT_NAME}-cmd 57 | # Ocsigenserver's logging files 58 | LOGDIR := var/log/${PROJECT_NAME} 59 | # Ocsigenserver's persistent data files 60 | DATADIR := var/data/${PROJECT_NAME} 61 | # Copy of $(LOCAL_STATIC) 62 | STATICDIR := var/www/${PROJECT_NAME}/static 63 | # Project's JavaScript file 64 | ELIOMSTATICDIR := var/www/${PROJECT_NAME}/eliom 65 | -------------------------------------------------------------------------------- /eliom/tutorial/00100-hello-world/src/mysite/README: -------------------------------------------------------------------------------- 1 | 2 | Instructions 3 | ============ 4 | 5 | This project is (initially) generated by eliom-distillery as the basic 6 | project "mysite". 7 | 8 | Generally, you can compile it and run ocsigenserver on it by 9 | $ make test.byte (or test.opt) 10 | See below for other useful targets for make. 11 | 12 | Generated files 13 | --------------- 14 | 15 | The following files in this directory have been generated by 16 | eliom-distillery: 17 | 18 | - mysite.eliom 19 | This is your initial source file. 20 | 21 | - static/ 22 | The content of this folder is statically served. Put your CSS or 23 | additional JavaScript files here! 24 | 25 | - Makefile.options 26 | Configure your project here! 27 | 28 | - mysite.conf.in 29 | This file is a template for the configuration file for 30 | ocsigenserver. You will rarely have to edit itself - it takes its 31 | variables from the Makefile.options. This way, the installation 32 | rules and the configuration files are synchronized with respect to 33 | the different folders. 34 | 35 | - Makefile 36 | This contains all rules necessary to build, test, and run your 37 | Eliom application. You better don't touch it ;) See below for the 38 | relevant targets. 39 | 40 | - local/ 41 | This directory is the target of the temporary installation of 42 | your application, to test locally before doing a system-wide 43 | installation in /. Do not put anything manually here. 44 | 45 | - README 46 | Not completely describable here. 47 | 48 | 49 | Makefile targets 50 | ---------------- 51 | 52 | Here's some help on how to work with this basic distillery project: 53 | 54 | - Test your application by compiling it and running ocsigenserver locally 55 | $ make test.byte (or test.opt) 56 | 57 | - Compile it only 58 | $ make all (or byte or opt) 59 | 60 | - Deploy your project on your system 61 | $ sudo make install (or install.byte or install.opt) 62 | 63 | - Run the server on the deployed project 64 | $ sudo make run.byte (or run.opt) 65 | 66 | If WWWUSER in the Makefile.options is you, you don't need the 67 | `sudo'. If Eliom isn't installed globally, however, you need to 68 | re-export some environment variables to make this work: 69 | $ sudo PATH=$PATH OCAMLPATH=$OCAMLPATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH make run.byte/run.opt 70 | 71 | - If you need a findlib package in your project, add it to the 72 | variables SERVER_PACKAGES and/or CLIENT_PACKAGES. The configuration 73 | file will be automatically updated. 74 | -------------------------------------------------------------------------------- /eliom/tutorial/00100-hello-world/src/mysite/mysite.conf.in: -------------------------------------------------------------------------------- 1 | %%% This is the template for your configuration file. The %%VALUES%% below are 2 | %%% taken from the Makefile to generate the actual configuration files. 3 | %%% This comment will disappear. 4 | 5 | 6 | 7 | %%PORT%% 8 | %%% Only set for running, not for testing 9 | %%USERGROUP%% 10 | %%LOGDIR%% 11 | %%DATADIR%% 12 | utf-8 13 | %%% Only set when debugging 14 | %%DEBUGMODE%% 15 | %%CMDPIPE%% 16 | 17 | 18 | 19 | %%% This will include the packages defined as SERVER_PACKAGES in your Makefile: 20 | %%PACKAGES%% 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /eliom/tutorial/00100-hello-world/src/mysite/mysite.eliom: -------------------------------------------------------------------------------- 1 | [%%shared 2 | open Eliom_lib 3 | open Eliom_content 4 | open Html.D 5 | ] 6 | 7 | module Mysite_app = 8 | Eliom_registration.App ( 9 | struct 10 | let application_name = "mysite" 11 | let global_data_path = None 12 | end) 13 | 14 | let main_service = 15 | Eliom_service.create 16 | ~path:(Eliom_service.Path []) 17 | ~meth:(Eliom_service.Get Eliom_parameter.unit) 18 | () 19 | 20 | let () = 21 | Mysite_app.register 22 | ~service:main_service 23 | (fun () () -> 24 | Lwt.return 25 | (Eliom_tools.F.html 26 | ~title:"mysite" 27 | ~css:[["css";"mysite.css"]] 28 | Html.F.(body [ 29 | h1 [pcdata "Hello world!"]; 30 | ]))) 31 | -------------------------------------------------------------------------------- /eliom/tutorial/00100-hello-world/src/mysite/static/css/mysite.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: sans-serif; 3 | } 4 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/README.md: -------------------------------------------------------------------------------- 1 | # Display a static HTML table 2 | 3 | ## What you will learn 4 | 5 | In this tutorial, we will see how to build a static HTML table in order to display a list of products. 6 | 7 | ## Files structure 8 | 9 | This structure is not mandatory. Eliom doesn't require any specific structure so you are free to organize your code as you want. 10 | 11 | - `product.*` 12 | 13 | These files define a product record. Note that the `.eliomi` file defines the interface (this is the counterpart of the `.mli` file with OCaml). 14 | 15 | Each product has an identifier, a code, a list of names and a list of prices. The names are localized so they can be translated in several languages. The prices are localized too so they can be displayed in several currencies. 16 | 17 | - `i18n.*`, `id.*`, `money.*`, `language.*` 18 | 19 | Helper files for the product fields. 20 | 21 | - `model.*` 22 | 23 | Contains the list of products. For now, this list is static. 24 | 25 | - `view.*` 26 | 27 | Functions to build the HTML for the web page. 28 | 29 | - `mysite.eliom` 30 | 31 | The default main file for the application. 32 | 33 | ## Add the style sheets 34 | 35 | We use the [Bulma](http://bulma.io/) CSS framework. Pure CSS frameworks like this one are easier to integrate with an Eliom application. Indeed, Eliom can be used to dynamically modify some parts of the web page on the client side. And there is a risk of bad interferences with other CSS frameworks which include some Javascript code. 36 | 37 | In `mysite.eliom`, we create an external CSS link to download the Bulma style sheet: 38 | 39 | ```ocaml 40 | let css_bulma = 41 | Eliom_content.Html.F.( 42 | css_link 43 | ~uri:( 44 | make_uri 45 | ~service:(Eliom_service.static_dir ()) 46 | ~https:true ~hostname:"cdnjs.cloudflare.com" 47 | ["ajax"; "libs"; "bulma"; "0.4.0"; "css"; "bulma.min.css"] 48 | ) () 49 | ) 50 | ``` 51 | 52 | And to download our local CSS file, we create a local CSS link: 53 | 54 | ```ocaml 55 | let css_mysite = 56 | Eliom_content.Html.F.( 57 | css_link 58 | ~uri:( 59 | make_uri 60 | ~service:(Eliom_service.static_dir ()) 61 | ~absolute:true 62 | ["css";"mysite.css"] 63 | ) () 64 | ) 65 | ``` 66 | 67 | Then we include these links in the head part of the main page thanks to the `other_head` argument: 68 | 69 | ```ocaml 70 | Mysite_app.register 71 | ~service:main_service 72 | (fun () () -> 73 | Lwt.return 74 | (Eliom_tools.F.html 75 | ~title:"mysite" 76 | ~other_head:[css_bulma; css_mysite] 77 | View.body)) 78 | ``` 79 | 80 | ## Add packages 81 | 82 | As we need the `Num` package in `money.*` code, both on the server side and on the client side, we have to add this package in `Makefile.options`: 83 | 84 | ``` 85 | SERVER_PACKAGES := num lwt.ppx js_of_ocaml.deriving.ppx 86 | CLIENT_PACKAGES := num lwt.ppx js_of_ocaml.ppx js_of_ocaml.deriving.ppx 87 | ``` 88 | 89 | We also need to include the missing primitives of `Num` for js_of_ocaml with the `+nat.js` option in `Makefile`: 90 | 91 | ``` 92 | JS_OF_ELIOM := js_of_eliom -ppx -jsopt +nat.js 93 | ``` 94 | 95 | ## How to build the HTML 96 | 97 | All the HTML for the page is built in `view.eliom` with functions from `Eliom_content.​Html.F`. Under the hood, this module uses [TyXML](http://ocsigen.org/tyxml/) which is, like Eliom, a part of the [Ocsigen project](http://ocsigen.org/). `TyXML` stands for `Typed XML` and is a library for building statically correct HTML5 and SVG documents. Indeed, thanks to the strong typing of OCaml language, the library is able to forbid invalid HTML5 or SVG code at compile time. 98 | 99 | As an example, to build a `tr` in a table, we use this code: 100 | 101 | ```ocaml 102 | tr [ 103 | td [ pcdata product_code ] ; 104 | td [ pcdata product_name ] ; 105 | td ~a:[a_class ["price"]] [ pcdata product_price ] ; 106 | ] 107 | ``` 108 | 109 | `tr`, `td`, `pcdata` and `a_class` are functions coming from `TyXML`. The optional argument `~a` can be used to add attributes to the HTML entity (a class name for instance). The `pcdata` function is used to add plain text. 110 | 111 | Obviously, `TyXML` needs a multitude of functions to be able to build all the HTML5 entities and attributes. In most of the cases, the function name is the same than the HTML5 entity or argument. You can consult [this page](http://ocsigen.org/tyxml/dev/api/index_values) to have a complete list and find the right function. 112 | 113 | ## Test the application 114 | 115 | As usual, run the Eliom server in test mode with one of these commands: 116 | 117 | ``` 118 | $ make test.byte 119 | ``` 120 | 121 | or 122 | 123 | ``` 124 | $ make test.opt 125 | ``` 126 | 127 | Then go to [http://localhost:8080/](http://localhost:8080/). You should see the products list. 128 | 129 | 130 | ## Next step 131 | 132 | In the [next tutorial](../00300-url-param/), we will see how to use a parameter in the URL to set the language and how to add an input select to choose the currency. -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/.ocp-indent: -------------------------------------------------------------------------------- 1 | normal 2 | with=0 3 | syntax=lwt mll 4 | max_indent=2 5 | ppx_stritem_ext=0 6 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/Makefile.options: -------------------------------------------------------------------------------- 1 | 2 | #---------------------------------------------------------------------- 3 | # SETTINGS FOR THE ELIOM PROJECT mysite 4 | #---------------------------------------------------------------------- 5 | 6 | PROJECT_NAME := mysite 7 | 8 | # Source files for the server 9 | SERVER_FILES := $(wildcard *.eliomi *.eliom) 10 | # Source files for the client 11 | CLIENT_FILES := $(wildcard *.eliomi *.eliom) 12 | 13 | # OCamlfind packages for the server 14 | SERVER_PACKAGES := num lwt.ppx js_of_ocaml.deriving.ppx 15 | # OCamlfind packages for the client 16 | CLIENT_PACKAGES := num lwt.ppx js_of_ocaml.ppx js_of_ocaml.deriving.ppx 17 | 18 | # Directory with files to be statically served 19 | LOCAL_STATIC = static 20 | 21 | # The backend for persistent data. Can be dbm or sqlite. 22 | # Make sure you have the following packages installed 23 | # - *dbm* if you use dbm --> opam install dbm. 24 | # - *sqlite3* if you use sqlite --> opam install sqlite3. 25 | PERSISTENT_DATA_BACKEND = sqlite 26 | 27 | # Debug application (yes/no): Debugging info in compilation, 28 | # JavaScript, ocsigenserver 29 | DEBUG := no 30 | 31 | # User to run server with (make run.*) 32 | WWWUSER := www-data 33 | WWWGROUP := www-data 34 | 35 | # Port for running the server (make run.*) 36 | PORT := 80 37 | 38 | # Port for testing (make test.*) 39 | TEST_PORT := 8080 40 | 41 | # Root of installation (must end with /) 42 | PREFIX := /usr/local/ 43 | 44 | # Local folder for make test.* (must end with /) 45 | # Do not add files manually in this directory. 46 | # It is just here to test your installation before installing in / 47 | TEST_PREFIX := local/ 48 | 49 | # The installation tree (relative to $(PREFIX) when 50 | # installing/running or $(TEST_PREFIX) when testing). 51 | # Configuration file $(PROJECT_NAME).conf 52 | ETCDIR := etc/${PROJECT_NAME} 53 | # Project's library $(PROJECT_NAME).cma (cmxs) 54 | LIBDIR := lib/${PROJECT_NAME} 55 | # Command pipe, eg. $ echo reload > $(INSTALL_PREFIX)$(CMDPIPE) 56 | CMDPIPE := var/run/${PROJECT_NAME}-cmd 57 | # Ocsigenserver's logging files 58 | LOGDIR := var/log/${PROJECT_NAME} 59 | # Ocsigenserver's persistent data files 60 | DATADIR := var/data/${PROJECT_NAME} 61 | # Copy of $(LOCAL_STATIC) 62 | STATICDIR := var/www/${PROJECT_NAME}/static 63 | # Project's JavaScript file 64 | ELIOMSTATICDIR := var/www/${PROJECT_NAME}/eliom 65 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/README: -------------------------------------------------------------------------------- 1 | 2 | Instructions 3 | ============ 4 | 5 | This project is (initially) generated by eliom-distillery as the basic 6 | project "mysite". 7 | 8 | Generally, you can compile it and run ocsigenserver on it by 9 | $ make test.byte (or test.opt) 10 | See below for other useful targets for make. 11 | 12 | Generated files 13 | --------------- 14 | 15 | The following files in this directory have been generated by 16 | eliom-distillery: 17 | 18 | - mysite.eliom 19 | This is your initial source file. 20 | 21 | - static/ 22 | The content of this folder is statically served. Put your CSS or 23 | additional JavaScript files here! 24 | 25 | - Makefile.options 26 | Configure your project here! 27 | 28 | - mysite.conf.in 29 | This file is a template for the configuration file for 30 | ocsigenserver. You will rarely have to edit itself - it takes its 31 | variables from the Makefile.options. This way, the installation 32 | rules and the configuration files are synchronized with respect to 33 | the different folders. 34 | 35 | - Makefile 36 | This contains all rules necessary to build, test, and run your 37 | Eliom application. You better don't touch it ;) See below for the 38 | relevant targets. 39 | 40 | - local/ 41 | This directory is the target of the temporary installation of 42 | your application, to test locally before doing a system-wide 43 | installation in /. Do not put anything manually here. 44 | 45 | - README 46 | Not completely describable here. 47 | 48 | 49 | Makefile targets 50 | ---------------- 51 | 52 | Here's some help on how to work with this basic distillery project: 53 | 54 | - Test your application by compiling it and running ocsigenserver locally 55 | $ make test.byte (or test.opt) 56 | 57 | - Compile it only 58 | $ make all (or byte or opt) 59 | 60 | - Deploy your project on your system 61 | $ sudo make install (or install.byte or install.opt) 62 | 63 | - Run the server on the deployed project 64 | $ sudo make run.byte (or run.opt) 65 | 66 | If WWWUSER in the Makefile.options is you, you don't need the 67 | `sudo'. If Eliom isn't installed globally, however, you need to 68 | re-export some environment variables to make this work: 69 | $ sudo PATH=$PATH OCAMLPATH=$OCAMLPATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH make run.byte/run.opt 70 | 71 | - If you need a findlib package in your project, add it to the 72 | variables SERVER_PACKAGES and/or CLIENT_PACKAGES. The configuration 73 | file will be automatically updated. 74 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/config.eliom: -------------------------------------------------------------------------------- 1 | 2 | let default_language = Language.En 3 | 4 | let default_iso4217 = Money.Iso4217.(Iso usd) 5 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/config.eliomi: -------------------------------------------------------------------------------- 1 | 2 | val default_language : Language.t 3 | 4 | val default_iso4217 : Money.Iso4217.iso -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/i18n.eliom: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | type t = Language.t * string 5 | 6 | let make lang s = (lang, s) 7 | 8 | let translate lang (l: t list) = 9 | try 10 | let s = List.assoc lang l in 11 | Some (lang, s) 12 | with 13 | | Not_found -> None 14 | 15 | let to_string (_, s) = s 16 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/i18n.eliomi: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | type t 5 | 6 | val make : Language.t -> string -> t 7 | val translate : Language.t -> t list -> t option 8 | val to_string : t -> string 9 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/id.eliom: -------------------------------------------------------------------------------- 1 | type t = Int64.t 2 | 3 | let empty = Int64.zero 4 | 5 | let of_int64 v = v 6 | 7 | let to_string = Int64.to_string 8 | 9 | let compare = Int64.compare 10 | 11 | let succ = Int64.succ -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/id.eliomi: -------------------------------------------------------------------------------- 1 | type t 2 | 3 | val empty : t 4 | val of_int64 : Int64.t -> t 5 | val to_string : t -> string 6 | val compare : t -> t -> int 7 | val succ : t -> t -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/language.eliom: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | exception Unknown_language of string 5 | 6 | type t = 7 | | En 8 | | Fr 9 | 10 | let of_string = function 11 | | "en" -> En 12 | | "fr" -> Fr 13 | | s -> raise(Unknown_language s) 14 | 15 | let to_string = function 16 | | En -> "en" 17 | | Fr -> "fr" -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/language.eliomi: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | exception Unknown_language of string 5 | 6 | type t = 7 | | En 8 | | Fr 9 | 10 | val of_string : string -> t 11 | val to_string : t -> string -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/model.eliom: -------------------------------------------------------------------------------- 1 | 2 | open Money 3 | 4 | let p1 = 5 | let price_usd = Vat.excl(Currency.(of_int 19 Iso4217.usd)) in 6 | let price_eur = Vat.exchange Iso4217.eur price_usd in 7 | Product.( 8 | empty 9 | |> set_id (Id.of_int64 1L) 10 | |> set_code "WG04" 11 | |> set_names I18n.( 12 | [ make Language.En "Waffle gun"; make Language.Fr "Pistolet à gaufre"]) 13 | |> set_prices Vat.([ Amount price_usd; Amount price_eur ]) 14 | ) 15 | 16 | let p2 = 17 | let price_eur = Vat.excl(Currency.(of_int 5 Iso4217.eur)) in 18 | let price_usd = Vat.exchange Iso4217.usd price_eur in 19 | Product.( 20 | empty 21 | |> set_id (Id.of_int64 2L) 22 | |> set_code "EW-2000" 23 | |> set_names I18n.( 24 | [ make Language.En "Electric whisker"; make Language.Fr "Tourniquette"]) 25 | |> set_prices Vat.([ Amount price_usd; Amount price_eur ]) 26 | ) 27 | 28 | let p3 = 29 | let price_usd = Vat.excl(Currency.(of_int 599 Iso4217.usd)) in 30 | let price_eur = Vat.exchange Iso4217.eur price_usd in 31 | Product.( 32 | empty 33 | |> set_id (Id.of_int64 3L) 34 | |> set_code "PROGLAGLA" 35 | |> set_names I18n.( 36 | [ make Language.En "Refrigerator"; make Language.Fr "Frigidaire"]) 37 | |> set_prices Vat.([ Amount price_usd; Amount price_eur ]) 38 | ) 39 | 40 | let products = [ 41 | p1; p2; p3 42 | ] 43 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/model.eliomi: -------------------------------------------------------------------------------- 1 | val products : Product.t list -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/money.eliom: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | module Iso4217 = struct 5 | 6 | exception Unknown_code of string 7 | 8 | type usd 9 | type eur 10 | 11 | type 'a t = 12 | | Usd : usd t 13 | | Eur : eur t 14 | 15 | type iso = Iso : 'a t -> iso 16 | 17 | let iso v = Iso v 18 | 19 | let usd : usd t = Usd 20 | 21 | let eur : eur t = Eur 22 | 23 | let of_string (type a) (s:string) : iso = 24 | match s with 25 | | "usd" -> Iso Usd 26 | | "eur" -> Iso Eur 27 | | _ -> raise(Unknown_code s) 28 | 29 | let to_string (type a) (v:a t) : string = 30 | match v with 31 | | Usd -> "usd" 32 | | Eur -> "eur" 33 | 34 | let one = Num.num_of_int 1 35 | 36 | let exchange_rate (type a b) ~(for_one:a t) ~(get:b t) : Num.num = 37 | match for_one, get with 38 | | Usd, Eur -> Num.num_of_string "934813/1000000" 39 | | Eur, Usd -> Num.num_of_string "106951/100000" 40 | | Usd, Usd -> one 41 | | Eur, Eur -> one 42 | 43 | let symbol (type a) (v:a t) : string = 44 | match v with 45 | | Eur -> "€" 46 | | Usd -> "$" 47 | 48 | let eq (type a b) (v1:a t) (v2:b t) : bool = 49 | match v1, v2 with 50 | | Eur, Eur -> true 51 | | Usd, Usd -> true 52 | | _ -> false 53 | 54 | end 55 | 56 | module Currency = struct 57 | 58 | type 'a t = Num.num * 'a Iso4217.t 59 | 60 | type amount = Amount : 'a t -> amount 61 | 62 | let of_num (v:Num.num) (iso:'a Iso4217.t) : 'a t = 63 | (v, iso) 64 | 65 | let of_int v iso = 66 | of_num (Num.num_of_int v) iso 67 | 68 | let of_int64 v iso = 69 | of_num (Num.num_of_string(Int64.to_string v)) iso 70 | 71 | let of_string v iso = 72 | let accuracy_float = 10000. in 73 | let accuracy_int = 10000 in 74 | let x = 75 | (float_of_string v) *. accuracy_float 76 | |> Int64.of_float 77 | |> Int64.to_string 78 | |> Num.num_of_string 79 | in 80 | let x = Num.div_num x (Num.num_of_int accuracy_int) in 81 | of_num x iso 82 | 83 | let (+) (v1:'a t) (v2:'a t) : 'a t = 84 | let x1, iso = v1 in 85 | let x2, iso = v2 in 86 | (Num.add_num x1 x2, iso) 87 | 88 | let op_num op (n:Num.num) (v:'a t) : 'a t = 89 | let x, iso = v in 90 | (op x n, iso) 91 | 92 | let mult_by_num n v = 93 | op_num Num.mult_num n v 94 | 95 | let div_by_num n v = 96 | op_num Num.div_num n v 97 | 98 | let exchange (type a b) (to_iso: b Iso4217.t) (v: a t) : b t = 99 | let x, from_iso = v in 100 | (Num.mult_num x (Iso4217.exchange_rate ~for_one:from_iso ~get:to_iso), to_iso) 101 | 102 | let to_string_x f (x, _) = 103 | f x 104 | 105 | let to_string v = 106 | let f v = 107 | let s = Num.approx_num_fix 2 v in 108 | let s = String.sub s 1 ((String.length s) - 1) in 109 | if Num.sign_num v < 0 then 110 | "- " ^ s 111 | else 112 | s 113 | in 114 | to_string_x f v 115 | 116 | let to_string_fractional v = 117 | to_string_x Num.string_of_num v 118 | 119 | let to_string_decimal digits v = 120 | to_string_x (Num.approx_num_fix digits) v 121 | 122 | let to_string_scientific digits v = 123 | to_string_x (Num.approx_num_exp digits) v 124 | 125 | let symbol (_, iso) = 126 | Iso4217.symbol iso 127 | 128 | let filter_one_currency iso l = 129 | let f v = 130 | match v with 131 | | Amount (_, x) -> Iso4217.eq x iso 132 | in 133 | List.filter f l 134 | 135 | let same_currency (_, iso1) (_, iso2) = 136 | Iso4217.eq iso1 iso2 137 | 138 | end 139 | 140 | module Vat = struct 141 | 142 | type excl 143 | type incl 144 | 145 | type ('a, 'b) t = 146 | | Excl : 'a Currency.t -> ('a, excl) t 147 | | Incl : 'a Currency.t -> ('a, incl) t 148 | 149 | type amount = Amount : ('a, 'b) t -> amount 150 | 151 | let apply1_x (type b) f (v:('a, b) t) = 152 | match v with 153 | | Incl v -> f v 154 | | Excl v -> f v 155 | 156 | let apply1 (type b) f (v:('a, b) t) : ('a, b) t = 157 | match v with 158 | | Incl v -> Incl(f v) 159 | | Excl v -> Excl(f v) 160 | 161 | let apply2 (type b) f (v1:('a, b) t) (v2:('a, b) t) : ('a, b) t = 162 | match v1, v2 with 163 | | Incl v1, Incl v2 -> Incl(f v1 v2) 164 | | Excl v1, Excl v2 -> Excl(f v1 v2) 165 | 166 | let (+) v1 v2 = 167 | apply2 Currency.(+) v1 v2 168 | 169 | let mult_by_num x v = 170 | apply1 (Currency.mult_by_num x) v 171 | 172 | let div_by_num x v = 173 | apply1 (Currency.div_by_num x) v 174 | 175 | let exchange (type a b c) iso (v:(a, b) t) : (c, b) t = 176 | match v with 177 | | Incl v -> Incl(Currency.exchange iso v) 178 | | Excl v -> Excl(Currency.exchange iso v) 179 | 180 | let to_string v = 181 | apply1_x Currency.to_string v 182 | 183 | let to_string_fractional v = 184 | apply1_x Currency.to_string_fractional v 185 | 186 | let to_string_decimal digits = 187 | apply1_x (Currency.to_string_decimal digits) 188 | 189 | let to_string_scientific digits = 190 | apply1_x (Currency.to_string_scientific digits) 191 | 192 | let excl v = Excl v 193 | let incl v = Incl v 194 | 195 | let tax_rate = 196 | Num.num_of_string "120/100" 197 | 198 | let to_excl (v:('a, incl) t) : ('a, excl) t = 199 | match v with 200 | | Incl v -> Excl(Currency.div_by_num tax_rate v) 201 | 202 | let to_incl (v:('a, excl) t) : ('a, incl) t = 203 | match v with 204 | | Excl v -> Incl(Currency.mult_by_num tax_rate v) 205 | 206 | let symbol_currency (type b) (v:('a, b) t) = 207 | apply1_x Currency.symbol v 208 | 209 | let filter_one_currency iso l = 210 | let f v = 211 | match v with 212 | | Amount a -> 213 | match a with 214 | | Incl i -> Currency.same_currency i (Currency.of_int 0 iso) 215 | | Excl e -> Currency.same_currency e (Currency.of_int 0 iso) 216 | in 217 | List.filter f l 218 | 219 | let idiom (type b) lang (v:('a, b) t) : I18n.t option = 220 | let l = 221 | match v with 222 | | Excl _ -> [ 223 | I18n.make Language.En "excl. VAT" ; 224 | I18n.make Language.Fr "HT" ; 225 | ] 226 | | Incl _ -> [ 227 | I18n.make Language.En "incl. VAT" ; 228 | I18n.make Language.Fr "TTC" ; 229 | ] 230 | in 231 | I18n.translate lang l 232 | 233 | end 234 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/money.eliomi: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | module Iso4217 : sig 5 | 6 | exception Unknown_code of string 7 | 8 | type usd 9 | type eur 10 | 11 | type 'a t 12 | 13 | type iso = Iso : 'a t -> iso 14 | 15 | val iso : 'a t -> iso 16 | 17 | val usd : usd t 18 | val eur : eur t 19 | 20 | val of_string : string -> iso 21 | val to_string : 'a t -> string 22 | val symbol : 'a t -> string 23 | 24 | end 25 | 26 | module Currency : sig 27 | 28 | type 'a t 29 | 30 | type amount = Amount : 'a t -> amount 31 | 32 | val of_num : Num.num -> 'a Iso4217.t -> 'a t 33 | val of_int : int -> 'a Iso4217.t -> 'a t 34 | val of_int64 : Int64.t -> 'a Iso4217.t -> 'a t 35 | val of_string : string -> 'a Iso4217.t -> 'a t 36 | 37 | val (+) : 'a t -> 'a t -> 'a t 38 | val mult_by_num : Num.num -> 'a t -> 'a t 39 | val div_by_num : Num.num -> 'a t -> 'a t 40 | 41 | val exchange : 'a Iso4217.t -> 'b t -> 'a t 42 | 43 | val to_string : 'a t -> string 44 | val to_string_fractional : 'a t -> string 45 | val to_string_decimal : int -> 'a t -> string 46 | val to_string_scientific : int -> 'a t -> string 47 | 48 | val symbol : 'a t -> string 49 | 50 | val filter_one_currency : 'a Iso4217.t -> amount list -> amount list 51 | val same_currency : 'a t -> 'a t -> bool 52 | 53 | end 54 | 55 | module Vat : sig 56 | 57 | type excl 58 | type incl 59 | 60 | type ('a, 'b) t 61 | 62 | type amount = Amount : ('a, 'b) t -> amount 63 | 64 | val (+) : ('a, 'b) t -> ('a, 'b) t -> ('a, 'b) t 65 | val mult_by_num : Num.num -> ('a, 'b) t -> ('a, 'b) t 66 | val div_by_num : Num.num -> ('a, 'b) t -> ('a, 'b) t 67 | 68 | val exchange : 'a Iso4217.t -> ('b, 'c) t -> ('a, 'c) t 69 | 70 | val to_string : ('a, 'b) t -> string 71 | val to_string_fractional : ('a, 'b) t -> string 72 | val to_string_decimal : int -> ('a, 'b) t -> string 73 | val to_string_scientific : int -> ('a, 'b) t -> string 74 | 75 | val excl : 'a Currency.t -> ('a, excl) t 76 | val incl : 'a Currency.t -> ('a, incl) t 77 | 78 | val to_excl : ('a, incl) t -> ('a, excl) t 79 | val to_incl : ('a, excl) t -> ('a, incl) t 80 | 81 | val symbol_currency : ('a, 'b) t -> string 82 | 83 | val filter_one_currency : 'a Iso4217.t -> amount list -> amount list 84 | 85 | val idiom : Language.t -> ('a, 'b) t -> I18n.t option 86 | 87 | end 88 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/mysite.conf.in: -------------------------------------------------------------------------------- 1 | %%% This is the template for your configuration file. The %%VALUES%% below are 2 | %%% taken from the Makefile to generate the actual configuration files. 3 | %%% This comment will disappear. 4 | 5 | 6 | 7 | %%PORT%% 8 | %%% Only set for running, not for testing 9 | %%USERGROUP%% 10 | %%LOGDIR%% 11 | %%DATADIR%% 12 | utf-8 13 | %%% Only set when debugging 14 | %%DEBUGMODE%% 15 | %%CMDPIPE%% 16 | 17 | 18 | 19 | %%% This will include the packages defined as SERVER_PACKAGES in your Makefile: 20 | %%PACKAGES%% 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/mysite.eliom: -------------------------------------------------------------------------------- 1 | [%%shared 2 | open Eliom_lib 3 | open Eliom_content 4 | open Html.D 5 | ] 6 | 7 | module Mysite_app = 8 | Eliom_registration.App ( 9 | struct 10 | let application_name = "mysite" 11 | let global_data_path = None 12 | end) 13 | 14 | let main_service = 15 | Eliom_service.create 16 | ~path:(Eliom_service.Path []) 17 | ~meth:(Eliom_service.Get Eliom_parameter.unit) 18 | () 19 | 20 | let () = 21 | let css_bulma = 22 | Eliom_content.Html.F.( 23 | css_link 24 | ~uri:( 25 | make_uri 26 | ~service:(Eliom_service.static_dir ()) 27 | ~https:true ~hostname:"cdnjs.cloudflare.com" 28 | ["ajax"; "libs"; "bulma"; "0.4.0"; "css"; "bulma.min.css"] 29 | ) () 30 | ) 31 | in 32 | let css_mysite = 33 | Eliom_content.Html.F.( 34 | css_link 35 | ~uri:( 36 | make_uri 37 | ~service:(Eliom_service.static_dir ()) 38 | ~absolute:true 39 | ["css";"mysite.css"] 40 | ) () 41 | ) 42 | in 43 | Mysite_app.register 44 | ~service:main_service 45 | (fun () () -> 46 | Lwt.return 47 | (Eliom_tools.F.html 48 | ~title:"mysite" 49 | ~other_head:[css_bulma; css_mysite] 50 | View.body)) 51 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/product.eliom: -------------------------------------------------------------------------------- 1 | 2 | type t = { 3 | id : Id.t ; 4 | code : string ; 5 | names : I18n.t list ; 6 | prices : Money.Vat.amount list ; 7 | } 8 | 9 | let empty = { 10 | id = Id.empty ; 11 | code = "" ; 12 | names = [] ; 13 | prices = [] ; 14 | } 15 | 16 | let get_id v = v.id 17 | 18 | let set_id id v = { v with id } 19 | 20 | let get_code v = v.code 21 | 22 | let set_code code v = { v with code } 23 | 24 | let get_names v = v.names 25 | 26 | let set_names names v = { v with names } 27 | 28 | let get_prices v = v.prices 29 | 30 | let set_prices prices v = { v with prices } 31 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/product.eliomi: -------------------------------------------------------------------------------- 1 | 2 | type t 3 | 4 | val empty : t 5 | 6 | val get_id : t -> Id.t 7 | val set_id : Id.t -> t -> t 8 | 9 | val get_code : t -> string 10 | val set_code : string -> t -> t 11 | 12 | val get_names : t -> I18n.t list 13 | val set_names : I18n.t list -> t -> t 14 | 15 | val get_prices : t -> Money.Vat.amount list 16 | val set_prices : Money.Vat.amount list -> t -> t 17 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/static/css/mysite.css: -------------------------------------------------------------------------------- 1 | .table th.price, .table td.price { 2 | text-align: right; 3 | } -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/view.eliom: -------------------------------------------------------------------------------- 1 | open Eliom_content 2 | 3 | type msg = 4 | | List of I18n.t list 5 | | Title 6 | | No_product 7 | | Col_code 8 | | Col_name 9 | | Col_price 10 | 11 | let price_to_string lang l iso4217 = 12 | let string_idiom p = 13 | match Money.Vat.idiom lang p with 14 | | None -> ( 15 | match Money.Vat.idiom Config.default_language p with 16 | | None -> assert false 17 | | Some v -> I18n.to_string v 18 | ) 19 | | Some v -> I18n.to_string v 20 | in 21 | match iso4217 with 22 | | Money.Iso4217.Iso v -> 23 | match Money.Vat.filter_one_currency v l with 24 | | [] -> "price not available" 25 | | e :: [] -> ( 26 | let open Money.Vat in 27 | match e with 28 | | Amount p -> ( 29 | Printf.sprintf "%s %s %s" 30 | (to_string p) (symbol_currency p) (string_idiom p) 31 | ) 32 | ) 33 | | _ -> "price not available" (* several prices !!?? *) 34 | 35 | let _t msg lang = 36 | let l = 37 | I18n.( 38 | match msg with 39 | | List v -> v 40 | | Title -> [ 41 | make Language.En "List of products" ; 42 | make Language.Fr "Liste des produits" ; 43 | ] 44 | | No_product -> [ 45 | make Language.En "No product" ; 46 | make Language.Fr "Aucun produit" ; 47 | ] 48 | | Col_code -> [ 49 | make Language.En "Code" ; 50 | make Language.Fr "Code" ; 51 | ] 52 | | Col_name -> [ 53 | make Language.En "Name" ; 54 | make Language.Fr "Nom" ; 55 | ] 56 | | Col_price -> [ 57 | make Language.En "Unit price" ; 58 | make Language.Fr "Prix à l'unité" ; 59 | ] 60 | ) 61 | in 62 | match (I18n.translate lang l) with 63 | | None -> "translation not available" 64 | | Some v -> I18n.to_string v 65 | 66 | let table_of_products lp = 67 | let products = 68 | let f acc e = 69 | let product_code = Product.get_code e in 70 | let product_name = _t (List(Product.get_names e)) Config.default_language in 71 | let product_price = price_to_string Config.default_language (Product.get_prices e) Config.default_iso4217 in 72 | let tr = 73 | Html.F.( 74 | tr [ 75 | td [ pcdata product_code ] ; 76 | td [ pcdata product_name ] ; 77 | td ~a:[a_class ["price"]] [ pcdata product_price ] ; 78 | ] 79 | ) 80 | in 81 | tr :: acc 82 | in 83 | List.rev(List.fold_left f [] lp) 84 | in 85 | Html.F.( 86 | tablex 87 | ~a:[a_class ["table"; "is-striped"]] 88 | ~thead:(thead [ 89 | tr [ 90 | th [ pcdata (_t Col_code Config.default_language) ] ; 91 | th [ pcdata (_t Col_name Config.default_language) ] ; 92 | th ~a:[a_class ["price"]] [ pcdata (_t Col_price Config.default_language) ] ; 93 | ] 94 | ]) [ 95 | tbody products 96 | ] 97 | ) 98 | 99 | let body = 100 | Html.F.( 101 | body [ 102 | div ~a:[a_class ["columns"]] [ 103 | div ~a:[a_class ["column is-8 is-offset-2"]] [ 104 | h1 ~a:[a_class ["title"]] [ pcdata (_t Title Config.default_language) ] ; 105 | div [ 106 | if List.length Model.products = 0 then ( 107 | p [ pcdata (_t No_product Config.default_language)] 108 | ) else ( 109 | table_of_products Model.products 110 | ) 111 | ] 112 | ] 113 | ] 114 | ] 115 | ) 116 | -------------------------------------------------------------------------------- /eliom/tutorial/00200-static-table/src/mysite/view.eliomi: -------------------------------------------------------------------------------- 1 | val body : [ `Body ] Eliom_content.Html.elt -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/README.md: -------------------------------------------------------------------------------- 1 | # Use URL parameters 2 | 3 | ## What you will learn 4 | 5 | In this tutorial, we will see how to use URL parameters to change the language and the currency of our products list. 6 | 7 | 8 | ## Define service parameters 9 | 10 | We have two URL parameters: 11 | 12 | - `lang` 13 | 14 | `en` for the english translation, `fr` for the french translation. 15 | 16 | - `iso4217` 17 | 18 | `usd` for US dollar, `eur` for Euro. 19 | 20 | Each of these parameters is optional. The default values are defined in `config.eliom`. 21 | 22 | The service is created in `service.eliom`: 23 | 24 | ```ocaml 25 | Eliom_service.create 26 | ~path:(Eliom_service.Path []) 27 | ~meth:(Eliom_service.Get( 28 | Eliom_parameter.( 29 | (opt( 30 | user_type 31 | ~client_to_and_of:([%client { Eliom_parameter.of_string = Language.of_string; Eliom_parameter.to_string = Language.to_string } ]) 32 | ~of_string:Language.of_string 33 | ~to_string:Language.to_string 34 | "lang")) 35 | ** 36 | (opt( 37 | user_type 38 | ~client_to_and_of:([%client { Eliom_parameter.of_string = Money.Iso4217.of_string; 39 | Eliom_parameter.to_string = (fun v -> match v with Money.Iso4217.Iso iso -> Money.Iso4217.to_string iso) } ]) 40 | ~of_string:Money.Iso4217.of_string 41 | ~to_string:(fun v -> match v with Money.Iso4217.Iso iso -> Money.Iso4217.to_string iso) 42 | "iso4217")) 43 | ) 44 | )) 45 | () 46 | 47 | ``` 48 | 49 | We use 3 functions of `Eliom_parameter` module to define the URL parameters: 50 | 51 | - `opt` to specify that the parameter is optional 52 | 53 | - `**` to define a pair of parameters 54 | 55 | - `user_type` to be able to use our custom types `Language.t` and `Money.Iso4217.iso`. The last argument is the name of the parameter in the URL. The other arguments define the functions used to convert a value to a string and vice versa. 56 | 57 | 58 | ## Display a select element and set an onchanges handler 59 | 60 | We need to display a list of available currencies. And if the user chooses a new currency, we refresh the page to display the right prices. 61 | 62 | In `view.eliom`, we create the select element: 63 | 64 | ```ocaml 65 | let select_iso4217 iso4217 = 66 | let selected v = 67 | if (Money.Iso4217.iso v) = iso4217 then 68 | [Html.D.a_selected ()] 69 | else 70 | [] 71 | in 72 | (* 73 | /!\ Use Html.D instead of Html.F 74 | Otherwise the event binding with Lwt_js_events.changes doesn't work 75 | *) 76 | Html.D.(Raw.select [ 77 | option ~a:(Money.Iso4217.([a_value (to_string usd)] @ (selected usd))) (pcdata Money.Iso4217.(to_string usd)) ; 78 | option ~a:(Money.Iso4217.([a_value (to_string eur)] @ (selected eur))) (pcdata Money.Iso4217.(to_string eur)) ; 79 | ]) 80 | 81 | ``` 82 | 83 | Note that we use `Html.D` instead of `Html.F` because we bind the `onchanges` event to this select element: 84 | 85 | ```ocaml 86 | let select_element = select_iso4217 iso4217 in 87 | let _ = [%client 88 | (Lwt.async (fun () -> 89 | Lwt_js_events.changes (Eliom_content.Html.To_dom.of_element ~%select_element) 90 | (fun evt _ -> 91 | (* let _ = Lwt_log_js.log "Select changes!" in *) 92 | let iso4217 = 93 | let tgt = Dom_html.CoerceTo.select(Dom.eventTarget evt) in 94 | Js.Opt.case tgt 95 | (fun () -> assert false) 96 | (fun e -> 97 | let option = e##.options##item e##.selectedIndex in 98 | Js.Opt.case option 99 | (fun () -> assert false) 100 | (fun e -> 101 | try 102 | Money.Iso4217.(of_string (Js.to_string e##.value)) 103 | with 104 | | _ -> ~%Config.default_iso4217)) 105 | in 106 | let _ = Eliom_client.change_page ~replace:true ~service:~%Service.main (Some ~%lang, Some iso4217) () in 107 | Lwt.return ())) 108 | : unit) 109 | ] in 110 | 111 | ``` 112 | 113 | As this binding is obviously only on the browser side, we put the code in a `[%client ... ]` section. Next, we use `Lwt_js_events.changes` to execute a function each time a change event happens on the select element. Note that, as `select_element` is defined on the server side and as this code section is on the client side, we get access to `select_element` with the `~%` syntax. In this function, we get the selected option value as a string and convert it to a value of type `Money.Iso4217.iso`. Then we use `Eliom_client.change_page` to refresh the page with the new URL parameter. 114 | 115 | 116 | ## Display a link with parameter 117 | 118 | To display a link to a service, we use the `Eliom_content.Html.F.a` function. The arguments are the service, the link text and the URL service parameters. 119 | 120 | ```ocaml 121 | let links_languages lang iso4217 = 122 | let l = [(Language.En, "EN"); (Language.Fr, "FR")] in 123 | let f acc (lang', s) = 124 | match lang' with 125 | | v when v = lang -> 126 | Html.F.( 127 | span ~a:[a_class ["level-item"]] [pcdata s]) :: acc 128 | | v -> 129 | Html.F.( 130 | span ~a:[a_class ["level-item"]] [a Service.main [pcdata s] (Some v, Some iso4217)]) 131 | :: acc 132 | in 133 | List.rev(List.fold_left f [] l) 134 | ``` 135 | 136 | 137 | ## Next step 138 | 139 | In the next tutorial, we will see how to add a new product. -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/.ocp-indent: -------------------------------------------------------------------------------- 1 | normal 2 | with=0 3 | syntax=lwt mll 4 | max_indent=2 5 | ppx_stritem_ext=0 6 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/Makefile.options: -------------------------------------------------------------------------------- 1 | 2 | #---------------------------------------------------------------------- 3 | # SETTINGS FOR THE ELIOM PROJECT mysite 4 | #---------------------------------------------------------------------- 5 | 6 | PROJECT_NAME := mysite 7 | 8 | # Source files for the server 9 | SERVER_FILES := $(wildcard *.eliomi *.eliom) 10 | # Source files for the client 11 | CLIENT_FILES := $(wildcard *.eliomi *.eliom) 12 | 13 | # OCamlfind packages for the server 14 | SERVER_PACKAGES := num lwt.ppx js_of_ocaml.deriving.ppx 15 | # OCamlfind packages for the client 16 | CLIENT_PACKAGES := num lwt.ppx js_of_ocaml.ppx js_of_ocaml.deriving.ppx 17 | 18 | # Directory with files to be statically served 19 | LOCAL_STATIC = static 20 | 21 | # The backend for persistent data. Can be dbm or sqlite. 22 | # Make sure you have the following packages installed 23 | # - *dbm* if you use dbm --> opam install dbm. 24 | # - *sqlite3* if you use sqlite --> opam install sqlite3. 25 | PERSISTENT_DATA_BACKEND = sqlite 26 | 27 | # Debug application (yes/no): Debugging info in compilation, 28 | # JavaScript, ocsigenserver 29 | DEBUG := no 30 | 31 | # User to run server with (make run.*) 32 | WWWUSER := www-data 33 | WWWGROUP := www-data 34 | 35 | # Port for running the server (make run.*) 36 | PORT := 80 37 | 38 | # Port for testing (make test.*) 39 | TEST_PORT := 8080 40 | 41 | # Root of installation (must end with /) 42 | PREFIX := /usr/local/ 43 | 44 | # Local folder for make test.* (must end with /) 45 | # Do not add files manually in this directory. 46 | # It is just here to test your installation before installing in / 47 | TEST_PREFIX := local/ 48 | 49 | # The installation tree (relative to $(PREFIX) when 50 | # installing/running or $(TEST_PREFIX) when testing). 51 | # Configuration file $(PROJECT_NAME).conf 52 | ETCDIR := etc/${PROJECT_NAME} 53 | # Project's library $(PROJECT_NAME).cma (cmxs) 54 | LIBDIR := lib/${PROJECT_NAME} 55 | # Command pipe, eg. $ echo reload > $(INSTALL_PREFIX)$(CMDPIPE) 56 | CMDPIPE := var/run/${PROJECT_NAME}-cmd 57 | # Ocsigenserver's logging files 58 | LOGDIR := var/log/${PROJECT_NAME} 59 | # Ocsigenserver's persistent data files 60 | DATADIR := var/data/${PROJECT_NAME} 61 | # Copy of $(LOCAL_STATIC) 62 | STATICDIR := var/www/${PROJECT_NAME}/static 63 | # Project's JavaScript file 64 | ELIOMSTATICDIR := var/www/${PROJECT_NAME}/eliom 65 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/README: -------------------------------------------------------------------------------- 1 | 2 | Instructions 3 | ============ 4 | 5 | This project is (initially) generated by eliom-distillery as the basic 6 | project "mysite". 7 | 8 | Generally, you can compile it and run ocsigenserver on it by 9 | $ make test.byte (or test.opt) 10 | See below for other useful targets for make. 11 | 12 | Generated files 13 | --------------- 14 | 15 | The following files in this directory have been generated by 16 | eliom-distillery: 17 | 18 | - mysite.eliom 19 | This is your initial source file. 20 | 21 | - static/ 22 | The content of this folder is statically served. Put your CSS or 23 | additional JavaScript files here! 24 | 25 | - Makefile.options 26 | Configure your project here! 27 | 28 | - mysite.conf.in 29 | This file is a template for the configuration file for 30 | ocsigenserver. You will rarely have to edit itself - it takes its 31 | variables from the Makefile.options. This way, the installation 32 | rules and the configuration files are synchronized with respect to 33 | the different folders. 34 | 35 | - Makefile 36 | This contains all rules necessary to build, test, and run your 37 | Eliom application. You better don't touch it ;) See below for the 38 | relevant targets. 39 | 40 | - local/ 41 | This directory is the target of the temporary installation of 42 | your application, to test locally before doing a system-wide 43 | installation in /. Do not put anything manually here. 44 | 45 | - README 46 | Not completely describable here. 47 | 48 | 49 | Makefile targets 50 | ---------------- 51 | 52 | Here's some help on how to work with this basic distillery project: 53 | 54 | - Test your application by compiling it and running ocsigenserver locally 55 | $ make test.byte (or test.opt) 56 | 57 | - Compile it only 58 | $ make all (or byte or opt) 59 | 60 | - Deploy your project on your system 61 | $ sudo make install (or install.byte or install.opt) 62 | 63 | - Run the server on the deployed project 64 | $ sudo make run.byte (or run.opt) 65 | 66 | If WWWUSER in the Makefile.options is you, you don't need the 67 | `sudo'. If Eliom isn't installed globally, however, you need to 68 | re-export some environment variables to make this work: 69 | $ sudo PATH=$PATH OCAMLPATH=$OCAMLPATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH make run.byte/run.opt 70 | 71 | - If you need a findlib package in your project, add it to the 72 | variables SERVER_PACKAGES and/or CLIENT_PACKAGES. The configuration 73 | file will be automatically updated. 74 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/config.eliom: -------------------------------------------------------------------------------- 1 | 2 | let default_language = Language.En 3 | 4 | let default_iso4217 = Money.Iso4217.(Iso usd) 5 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/config.eliomi: -------------------------------------------------------------------------------- 1 | 2 | val default_language : Language.t 3 | 4 | val default_iso4217 : Money.Iso4217.iso -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/i18n.eliom: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | type t = Language.t * string 5 | 6 | let make lang s = (lang, s) 7 | 8 | let translate lang (l: t list) = 9 | try 10 | let s = List.assoc lang l in 11 | Some (lang, s) 12 | with 13 | | Not_found -> None 14 | 15 | let to_string (_, s) = s 16 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/i18n.eliomi: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | type t 5 | 6 | val make : Language.t -> string -> t 7 | val translate : Language.t -> t list -> t option 8 | val to_string : t -> string 9 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/id.eliom: -------------------------------------------------------------------------------- 1 | type t = Int64.t 2 | 3 | let empty = Int64.zero 4 | 5 | let of_int64 v = v 6 | 7 | let to_string = Int64.to_string 8 | 9 | let compare = Int64.compare 10 | 11 | let succ = Int64.succ -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/id.eliomi: -------------------------------------------------------------------------------- 1 | type t 2 | 3 | val empty : t 4 | val of_int64 : Int64.t -> t 5 | val to_string : t -> string 6 | val compare : t -> t -> int 7 | val succ : t -> t -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/language.eliom: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | exception Unknown_language of string 5 | 6 | type t = 7 | | En 8 | | Fr 9 | 10 | let of_string = function 11 | | "en" -> En 12 | | "fr" -> Fr 13 | | s -> raise(Unknown_language s) 14 | 15 | let to_string = function 16 | | En -> "en" 17 | | Fr -> "fr" -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/language.eliomi: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | exception Unknown_language of string 5 | 6 | type t = 7 | | En 8 | | Fr 9 | 10 | val of_string : string -> t 11 | val to_string : t -> string -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/model.eliom: -------------------------------------------------------------------------------- 1 | 2 | open Money 3 | 4 | let p1 = 5 | let price_usd = Vat.excl(Currency.(of_int 19 Iso4217.usd)) in 6 | let price_eur = Vat.exchange Iso4217.eur price_usd in 7 | Product.( 8 | empty 9 | |> set_id (Id.of_int64 1L) 10 | |> set_code "WG04" 11 | |> set_names I18n.( 12 | [ make Language.En "Waffle gun"; make Language.Fr "Pistolet à gaufre"]) 13 | |> set_prices Vat.([ Amount price_usd; Amount price_eur ]) 14 | ) 15 | 16 | let p2 = 17 | let price_eur = Vat.excl(Currency.(of_int 5 Iso4217.eur)) in 18 | let price_usd = Vat.exchange Iso4217.usd price_eur in 19 | Product.( 20 | empty 21 | |> set_id (Id.of_int64 2L) 22 | |> set_code "EW-2000" 23 | |> set_names I18n.( 24 | [ make Language.En "Electric whisker"; make Language.Fr "Tourniquette"]) 25 | |> set_prices Vat.([ Amount price_usd; Amount price_eur ]) 26 | ) 27 | 28 | let p3 = 29 | let price_usd = Vat.excl(Currency.(of_int 599 Iso4217.usd)) in 30 | let price_eur = Vat.exchange Iso4217.eur price_usd in 31 | Product.( 32 | empty 33 | |> set_id (Id.of_int64 3L) 34 | |> set_code "PROGLAGLA" 35 | |> set_names I18n.( 36 | [ make Language.En "Refrigerator"; make Language.Fr "Frigidaire"]) 37 | |> set_prices Vat.([ Amount price_usd; Amount price_eur ]) 38 | ) 39 | 40 | let products = [ 41 | p1; p2; p3 42 | ] 43 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/model.eliomi: -------------------------------------------------------------------------------- 1 | val products : Product.t list -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/money.eliom: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | module Iso4217 = struct 5 | 6 | exception Unknown_code of string 7 | 8 | type usd 9 | type eur 10 | 11 | type 'a t = 12 | | Usd : usd t 13 | | Eur : eur t 14 | 15 | type iso = Iso : 'a t -> iso 16 | 17 | let iso v = Iso v 18 | 19 | let usd : usd t = Usd 20 | 21 | let eur : eur t = Eur 22 | 23 | let of_string (type a) (s:string) : iso = 24 | match s with 25 | | "usd" -> Iso Usd 26 | | "eur" -> Iso Eur 27 | | _ -> raise(Unknown_code s) 28 | 29 | let to_string (type a) (v:a t) : string = 30 | match v with 31 | | Usd -> "usd" 32 | | Eur -> "eur" 33 | 34 | let one = Num.num_of_int 1 35 | 36 | let exchange_rate (type a b) ~(for_one:a t) ~(get:b t) : Num.num = 37 | match for_one, get with 38 | | Usd, Eur -> Num.num_of_string "934813/1000000" 39 | | Eur, Usd -> Num.num_of_string "106951/100000" 40 | | Usd, Usd -> one 41 | | Eur, Eur -> one 42 | 43 | let symbol (type a) (v:a t) : string = 44 | match v with 45 | | Eur -> "€" 46 | | Usd -> "$" 47 | 48 | let eq (type a b) (v1:a t) (v2:b t) : bool = 49 | match v1, v2 with 50 | | Eur, Eur -> true 51 | | Usd, Usd -> true 52 | | _ -> false 53 | 54 | end 55 | 56 | module Currency = struct 57 | 58 | type 'a t = Num.num * 'a Iso4217.t 59 | 60 | type amount = Amount : 'a t -> amount 61 | 62 | let of_num (v:Num.num) (iso:'a Iso4217.t) : 'a t = 63 | (v, iso) 64 | 65 | let of_int v iso = 66 | of_num (Num.num_of_int v) iso 67 | 68 | let of_int64 v iso = 69 | of_num (Num.num_of_string(Int64.to_string v)) iso 70 | 71 | let of_string v iso = 72 | let accuracy_float = 10000. in 73 | let accuracy_int = 10000 in 74 | let x = 75 | (float_of_string v) *. accuracy_float 76 | |> Int64.of_float 77 | |> Int64.to_string 78 | |> Num.num_of_string 79 | in 80 | let x = Num.div_num x (Num.num_of_int accuracy_int) in 81 | of_num x iso 82 | 83 | let (+) (v1:'a t) (v2:'a t) : 'a t = 84 | let x1, iso = v1 in 85 | let x2, iso = v2 in 86 | (Num.add_num x1 x2, iso) 87 | 88 | let op_num op (n:Num.num) (v:'a t) : 'a t = 89 | let x, iso = v in 90 | (op x n, iso) 91 | 92 | let mult_by_num n v = 93 | op_num Num.mult_num n v 94 | 95 | let div_by_num n v = 96 | op_num Num.div_num n v 97 | 98 | let exchange (type a b) (to_iso: b Iso4217.t) (v: a t) : b t = 99 | let x, from_iso = v in 100 | (Num.mult_num x (Iso4217.exchange_rate ~for_one:from_iso ~get:to_iso), to_iso) 101 | 102 | let to_string_x f (x, _) = 103 | f x 104 | 105 | let to_string v = 106 | let f v = 107 | let s = Num.approx_num_fix 2 v in 108 | let s = String.sub s 1 ((String.length s) - 1) in 109 | if Num.sign_num v < 0 then 110 | "- " ^ s 111 | else 112 | s 113 | in 114 | to_string_x f v 115 | 116 | let to_string_fractional v = 117 | to_string_x Num.string_of_num v 118 | 119 | let to_string_decimal digits v = 120 | to_string_x (Num.approx_num_fix digits) v 121 | 122 | let to_string_scientific digits v = 123 | to_string_x (Num.approx_num_exp digits) v 124 | 125 | let symbol (_, iso) = 126 | Iso4217.symbol iso 127 | 128 | let filter_one_currency iso l = 129 | let f v = 130 | match v with 131 | | Amount (_, x) -> Iso4217.eq x iso 132 | in 133 | List.filter f l 134 | 135 | let same_currency (_, iso1) (_, iso2) = 136 | Iso4217.eq iso1 iso2 137 | 138 | end 139 | 140 | module Vat = struct 141 | 142 | type excl 143 | type incl 144 | 145 | type ('a, 'b) t = 146 | | Excl : 'a Currency.t -> ('a, excl) t 147 | | Incl : 'a Currency.t -> ('a, incl) t 148 | 149 | type amount = Amount : ('a, 'b) t -> amount 150 | 151 | let apply1_x (type b) f (v:('a, b) t) = 152 | match v with 153 | | Incl v -> f v 154 | | Excl v -> f v 155 | 156 | let apply1 (type b) f (v:('a, b) t) : ('a, b) t = 157 | match v with 158 | | Incl v -> Incl(f v) 159 | | Excl v -> Excl(f v) 160 | 161 | let apply2 (type b) f (v1:('a, b) t) (v2:('a, b) t) : ('a, b) t = 162 | match v1, v2 with 163 | | Incl v1, Incl v2 -> Incl(f v1 v2) 164 | | Excl v1, Excl v2 -> Excl(f v1 v2) 165 | 166 | let (+) v1 v2 = 167 | apply2 Currency.(+) v1 v2 168 | 169 | let mult_by_num x v = 170 | apply1 (Currency.mult_by_num x) v 171 | 172 | let div_by_num x v = 173 | apply1 (Currency.div_by_num x) v 174 | 175 | let exchange (type a b c) iso (v:(a, b) t) : (c, b) t = 176 | match v with 177 | | Incl v -> Incl(Currency.exchange iso v) 178 | | Excl v -> Excl(Currency.exchange iso v) 179 | 180 | let to_string v = 181 | apply1_x Currency.to_string v 182 | 183 | let to_string_fractional v = 184 | apply1_x Currency.to_string_fractional v 185 | 186 | let to_string_decimal digits = 187 | apply1_x (Currency.to_string_decimal digits) 188 | 189 | let to_string_scientific digits = 190 | apply1_x (Currency.to_string_scientific digits) 191 | 192 | let excl v = Excl v 193 | let incl v = Incl v 194 | 195 | let tax_rate = 196 | Num.num_of_string "120/100" 197 | 198 | let to_excl (v:('a, incl) t) : ('a, excl) t = 199 | match v with 200 | | Incl v -> Excl(Currency.div_by_num tax_rate v) 201 | 202 | let to_incl (v:('a, excl) t) : ('a, incl) t = 203 | match v with 204 | | Excl v -> Incl(Currency.mult_by_num tax_rate v) 205 | 206 | let symbol_currency (type b) (v:('a, b) t) = 207 | apply1_x Currency.symbol v 208 | 209 | let filter_one_currency iso l = 210 | let f v = 211 | match v with 212 | | Amount a -> 213 | match a with 214 | | Incl i -> Currency.same_currency i (Currency.of_int 0 iso) 215 | | Excl e -> Currency.same_currency e (Currency.of_int 0 iso) 216 | in 217 | List.filter f l 218 | 219 | let idiom (type b) lang (v:('a, b) t) : I18n.t option = 220 | let l = 221 | match v with 222 | | Excl _ -> [ 223 | I18n.make Language.En "excl. VAT" ; 224 | I18n.make Language.Fr "HT" ; 225 | ] 226 | | Incl _ -> [ 227 | I18n.make Language.En "incl. VAT" ; 228 | I18n.make Language.Fr "TTC" ; 229 | ] 230 | in 231 | I18n.translate lang l 232 | 233 | end 234 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/money.eliomi: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | module Iso4217 : sig 5 | 6 | exception Unknown_code of string 7 | 8 | type usd 9 | type eur 10 | 11 | type 'a t 12 | 13 | type iso = Iso : 'a t -> iso 14 | 15 | val iso : 'a t -> iso 16 | 17 | val usd : usd t 18 | val eur : eur t 19 | 20 | val of_string : string -> iso 21 | val to_string : 'a t -> string 22 | val symbol : 'a t -> string 23 | 24 | end 25 | 26 | module Currency : sig 27 | 28 | type 'a t 29 | 30 | type amount = Amount : 'a t -> amount 31 | 32 | val of_num : Num.num -> 'a Iso4217.t -> 'a t 33 | val of_int : int -> 'a Iso4217.t -> 'a t 34 | val of_int64 : Int64.t -> 'a Iso4217.t -> 'a t 35 | val of_string : string -> 'a Iso4217.t -> 'a t 36 | 37 | val (+) : 'a t -> 'a t -> 'a t 38 | val mult_by_num : Num.num -> 'a t -> 'a t 39 | val div_by_num : Num.num -> 'a t -> 'a t 40 | 41 | val exchange : 'a Iso4217.t -> 'b t -> 'a t 42 | 43 | val to_string : 'a t -> string 44 | val to_string_fractional : 'a t -> string 45 | val to_string_decimal : int -> 'a t -> string 46 | val to_string_scientific : int -> 'a t -> string 47 | 48 | val symbol : 'a t -> string 49 | 50 | val filter_one_currency : 'a Iso4217.t -> amount list -> amount list 51 | val same_currency : 'a t -> 'a t -> bool 52 | 53 | end 54 | 55 | module Vat : sig 56 | 57 | type excl 58 | type incl 59 | 60 | type ('a, 'b) t 61 | 62 | type amount = Amount : ('a, 'b) t -> amount 63 | 64 | val (+) : ('a, 'b) t -> ('a, 'b) t -> ('a, 'b) t 65 | val mult_by_num : Num.num -> ('a, 'b) t -> ('a, 'b) t 66 | val div_by_num : Num.num -> ('a, 'b) t -> ('a, 'b) t 67 | 68 | val exchange : 'a Iso4217.t -> ('b, 'c) t -> ('a, 'c) t 69 | 70 | val to_string : ('a, 'b) t -> string 71 | val to_string_fractional : ('a, 'b) t -> string 72 | val to_string_decimal : int -> ('a, 'b) t -> string 73 | val to_string_scientific : int -> ('a, 'b) t -> string 74 | 75 | val excl : 'a Currency.t -> ('a, excl) t 76 | val incl : 'a Currency.t -> ('a, incl) t 77 | 78 | val to_excl : ('a, incl) t -> ('a, excl) t 79 | val to_incl : ('a, excl) t -> ('a, incl) t 80 | 81 | val symbol_currency : ('a, 'b) t -> string 82 | 83 | val filter_one_currency : 'a Iso4217.t -> amount list -> amount list 84 | 85 | val idiom : Language.t -> ('a, 'b) t -> I18n.t option 86 | 87 | end 88 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/mysite.conf.in: -------------------------------------------------------------------------------- 1 | %%% This is the template for your configuration file. The %%VALUES%% below are 2 | %%% taken from the Makefile to generate the actual configuration files. 3 | %%% This comment will disappear. 4 | 5 | 6 | 7 | %%PORT%% 8 | %%% Only set for running, not for testing 9 | %%USERGROUP%% 10 | %%LOGDIR%% 11 | %%DATADIR%% 12 | utf-8 13 | %%% Only set when debugging 14 | %%DEBUGMODE%% 15 | %%CMDPIPE%% 16 | 17 | 18 | 19 | %%% This will include the packages defined as SERVER_PACKAGES in your Makefile: 20 | %%PACKAGES%% 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/mysite.eliom: -------------------------------------------------------------------------------- 1 | [%%shared 2 | open Eliom_lib 3 | open Eliom_content 4 | open Html.D 5 | ] 6 | 7 | module Mysite_app = 8 | Eliom_registration.App ( 9 | struct 10 | let application_name = "mysite" 11 | let global_data_path = None 12 | end) 13 | 14 | let () = 15 | let css_bulma = 16 | Eliom_content.Html.F.( 17 | css_link 18 | ~uri:( 19 | make_uri 20 | ~service:(Eliom_service.static_dir ()) 21 | ~https:true ~hostname:"cdnjs.cloudflare.com" 22 | ["ajax"; "libs"; "bulma"; "0.4.0"; "css"; "bulma.min.css"] 23 | ) () 24 | ) 25 | in 26 | let css_mysite = 27 | Eliom_content.Html.F.( 28 | css_link 29 | ~uri:( 30 | make_uri 31 | ~service:(Eliom_service.static_dir ()) 32 | ~absolute:true 33 | ["css";"mysite.css"] 34 | ) () 35 | ) 36 | in 37 | 38 | let page lang iso4217 = 39 | Eliom_tools.F.html 40 | ~title:"mysite" 41 | ~other_head:[css_bulma; css_mysite] 42 | (View.body lang iso4217) 43 | in 44 | Mysite_app.register 45 | ~service:Service.main 46 | ~error_handler:(fun l -> 47 | match l with 48 | | [] 49 | | ("lang", Language.Unknown_language _) :: [] -> Lwt.return(page Config.default_language Config.default_iso4217) 50 | | ("iso4217", Money.Iso4217.Unknown_code _) :: [] -> Lwt.return(page Config.default_language Config.default_iso4217) 51 | | (_, e) :: _ -> raise e 52 | ) 53 | (fun (lang, iso4217) () -> 54 | match lang, iso4217 with 55 | | None, None -> Lwt.return(page Config.default_language Config.default_iso4217) 56 | | Some vl, None -> Lwt.return(page vl Config.default_iso4217) 57 | | None, Some vi -> Lwt.return(page Config.default_language vi) 58 | | Some vl, Some vi -> Lwt.return(page vl vi)) 59 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/product.eliom: -------------------------------------------------------------------------------- 1 | 2 | type t = { 3 | id : Id.t ; 4 | code : string ; 5 | names : I18n.t list ; 6 | prices : Money.Vat.amount list ; 7 | } 8 | 9 | let empty = { 10 | id = Id.empty ; 11 | code = "" ; 12 | names = [] ; 13 | prices = [] ; 14 | } 15 | 16 | let get_id v = v.id 17 | 18 | let set_id id v = { v with id } 19 | 20 | let get_code v = v.code 21 | 22 | let set_code code v = { v with code } 23 | 24 | let get_names v = v.names 25 | 26 | let set_names names v = { v with names } 27 | 28 | let get_prices v = v.prices 29 | 30 | let set_prices prices v = { v with prices } 31 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/product.eliomi: -------------------------------------------------------------------------------- 1 | 2 | type t 3 | 4 | val empty : t 5 | 6 | val get_id : t -> Id.t 7 | val set_id : Id.t -> t -> t 8 | 9 | val get_code : t -> string 10 | val set_code : string -> t -> t 11 | 12 | val get_names : t -> I18n.t list 13 | val set_names : I18n.t list -> t -> t 14 | 15 | val get_prices : t -> Money.Vat.amount list 16 | val set_prices : Money.Vat.amount list -> t -> t 17 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/service.eliom: -------------------------------------------------------------------------------- 1 | let main = 2 | Eliom_service.create 3 | ~path:(Eliom_service.Path []) 4 | ~meth:(Eliom_service.Get( 5 | Eliom_parameter.( 6 | (opt( 7 | user_type 8 | ~client_to_and_of:([%client { Eliom_parameter.of_string = Language.of_string; Eliom_parameter.to_string = Language.to_string } ]) 9 | ~of_string:Language.of_string 10 | ~to_string:Language.to_string 11 | "lang")) 12 | ** 13 | (opt( 14 | user_type 15 | ~client_to_and_of:([%client { Eliom_parameter.of_string = Money.Iso4217.of_string; 16 | Eliom_parameter.to_string = (fun v -> match v with Money.Iso4217.Iso iso -> Money.Iso4217.to_string iso) } ]) 17 | ~of_string:Money.Iso4217.of_string 18 | ~to_string:(fun v -> match v with Money.Iso4217.Iso iso -> Money.Iso4217.to_string iso) 19 | "iso4217")) 20 | ) 21 | )) 22 | () 23 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/service.eliomi: -------------------------------------------------------------------------------- 1 | (* 2 | Trick to get the type: 3 | 1 - use (_, _, _, _, _, _, _, _, _, _, _) Eliom_service.t 4 | 2 - copy-paste the type from the compiler error message 5 | *) 6 | 7 | val main : (Language.t option * Money.Iso4217.iso option, 8 | unit, 9 | Eliom_service.get, 10 | Eliom_service.att, 11 | Eliom_service.non_co, 12 | Eliom_service.non_ext, 13 | Eliom_service.reg, 14 | [ `WithoutSuffix ], 15 | [ `One of Language.t ] Eliom_parameter.param_name * [ `One of Money.Iso4217.iso ] Eliom_parameter.param_name, 16 | unit, 17 | Eliom_service.non_ocaml) 18 | Eliom_service.t 19 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/static/css/mysite.css: -------------------------------------------------------------------------------- 1 | .table th.price, .table td.price { 2 | text-align: right; 3 | } 4 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/view.eliom: -------------------------------------------------------------------------------- 1 | open Eliom_content 2 | 3 | type msg = 4 | | List of I18n.t list 5 | | Title 6 | | No_product 7 | | Col_code 8 | | Col_name 9 | | Col_price 10 | 11 | let price_to_string lang l iso4217 = 12 | let string_idiom p = 13 | match Money.Vat.idiom lang p with 14 | | None -> ( 15 | match Money.Vat.idiom Config.default_language p with 16 | | None -> assert false 17 | | Some v -> I18n.to_string v 18 | ) 19 | | Some v -> I18n.to_string v 20 | in 21 | match iso4217 with 22 | | Money.Iso4217.Iso v -> 23 | match Money.Vat.filter_one_currency v l with 24 | | [] -> "price not available" 25 | | e :: [] -> ( 26 | let open Money.Vat in 27 | match e with 28 | | Amount p -> ( 29 | Printf.sprintf "%s %s %s" 30 | (to_string p) (symbol_currency p) (string_idiom p) 31 | ) 32 | ) 33 | | _ -> "price not available" (* several prices !!?? *) 34 | 35 | let _t msg lang = 36 | let l = 37 | I18n.( 38 | match msg with 39 | | List v -> v 40 | | Title -> [ 41 | make Language.En "List of products" ; 42 | make Language.Fr "Liste des produits" ; 43 | ] 44 | | No_product -> [ 45 | make Language.En "No product" ; 46 | make Language.Fr "Aucun produit" ; 47 | ] 48 | | Col_code -> [ 49 | make Language.En "Code" ; 50 | make Language.Fr "Code" ; 51 | ] 52 | | Col_name -> [ 53 | make Language.En "Name" ; 54 | make Language.Fr "Nom" ; 55 | ] 56 | | Col_price -> [ 57 | make Language.En "Unit price" ; 58 | make Language.Fr "Prix à l'unité" ; 59 | ] 60 | ) 61 | in 62 | match (I18n.translate lang l) with 63 | | None -> "translation not available" 64 | | Some v -> I18n.to_string v 65 | 66 | let select_iso4217 iso4217 = 67 | let selected v = 68 | if (Money.Iso4217.iso v) = iso4217 then 69 | [Html.D.a_selected ()] 70 | else 71 | [] 72 | in 73 | (* 74 | /!\ Use Html.D instead of Html.F 75 | Otherwise the event binding with Lwt_js_events.changes doesn't work 76 | *) 77 | Html.D.(Raw.select [ 78 | option ~a:(Money.Iso4217.([a_value (to_string usd)] @ (selected usd))) (pcdata Money.Iso4217.(to_string usd)) ; 79 | option ~a:(Money.Iso4217.([a_value (to_string eur)] @ (selected eur))) (pcdata Money.Iso4217.(to_string eur)) ; 80 | ]) 81 | 82 | let table_of_products lp lang iso4217 = 83 | let products = 84 | let f acc e = 85 | let product_code = Product.get_code e in 86 | let product_name = _t (List(Product.get_names e)) lang in 87 | let product_price = price_to_string lang (Product.get_prices e) iso4217 in 88 | let tr = 89 | Html.F.( 90 | tr [ 91 | td [ pcdata product_code ] ; 92 | td [ pcdata product_name ] ; 93 | td ~a:[a_class ["price"]] [ pcdata product_price ] ; 94 | ] 95 | ) 96 | in 97 | tr :: acc 98 | in 99 | List.rev(List.fold_left f [] lp) 100 | in 101 | (* create only one select element and use it everywhere *) 102 | let select_element = select_iso4217 iso4217 in 103 | let _ = [%client 104 | (Lwt.async (fun () -> 105 | Lwt_js_events.changes (Eliom_content.Html.To_dom.of_element ~%select_element) 106 | (fun evt _ -> 107 | (* let _ = Lwt_log_js.log "Select changes!" in *) 108 | let iso4217 = 109 | let tgt = Dom_html.CoerceTo.select(Dom.eventTarget evt) in 110 | Js.Opt.case tgt 111 | (fun () -> assert false) 112 | (fun e -> 113 | let option = e##.options##item e##.selectedIndex in 114 | Js.Opt.case option 115 | (fun () -> assert false) 116 | (fun e -> 117 | try 118 | Money.Iso4217.(of_string (Js.to_string e##.value)) 119 | with 120 | | _ -> ~%Config.default_iso4217)) 121 | in 122 | let _ = Eliom_client.change_page ~replace:true ~service:~%Service.main (Some ~%lang, Some iso4217) () in 123 | Lwt.return ())) 124 | : unit) 125 | ] in 126 | Html.F.( 127 | tablex 128 | ~a:[a_class ["table"; "is-striped"]] 129 | ~thead:(thead [ 130 | tr [ 131 | th [ pcdata (_t Col_code lang) ] ; 132 | th [ pcdata (_t Col_name lang) ] ; 133 | th ~a:[a_class ["price"]] [ 134 | pcdata (_t Col_price lang) ; 135 | select_element ; 136 | ] ; 137 | ] 138 | ]) [ 139 | tbody products 140 | ] 141 | ) 142 | 143 | let links_languages lang iso4217 = 144 | let l = [(Language.En, "EN"); (Language.Fr, "FR")] in 145 | let f acc (lang', s) = 146 | match lang' with 147 | | v when v = lang -> 148 | Html.F.( 149 | span ~a:[a_class ["level-item"]] [pcdata s]) :: acc 150 | | v -> 151 | Html.F.( 152 | span ~a:[a_class ["level-item"]] [a Service.main [pcdata s] (Some v, Some iso4217)]) 153 | :: acc 154 | in 155 | List.rev(List.fold_left f [] l) 156 | 157 | let navigation lang iso4217 = 158 | Html.F.( 159 | nav ~a:[a_class ["level is-mobile"]] [ 160 | div ~a:[a_class ["level-left"]] [ 161 | div ~a:[a_class ["level-item"]] [ 162 | h1 ~a:[a_class ["title"]] [ pcdata (_t Title lang) ] 163 | ] 164 | ] ; 165 | div ~a:[a_class ["level-right"]] (links_languages lang iso4217) ; 166 | ] 167 | ) 168 | 169 | let body lang iso4217 = 170 | Html.F.( 171 | body [ 172 | div ~a:[a_class ["container"]] [ 173 | navigation lang iso4217 ; 174 | div [ 175 | if List.length Model.products = 0 then ( 176 | p [ pcdata (_t No_product lang)] 177 | ) else ( 178 | table_of_products Model.products lang iso4217 179 | ) 180 | ] 181 | ] 182 | ] 183 | ) 184 | -------------------------------------------------------------------------------- /eliom/tutorial/00300-url-param/src/mysite/view.eliomi: -------------------------------------------------------------------------------- 1 | val body : Language.t -> Money.Iso4217.iso -> [ `Body ] Eliom_content.Html.elt -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/README.md: -------------------------------------------------------------------------------- 1 | # Add a new product 2 | 3 | ## What you will learn 4 | 5 | In this tutorial, we will see how to use a basic form in order to add a new product. 6 | 7 | 8 | ## Define the services 9 | 10 | We need to define two new services in `service.eliom`: 11 | 12 | - One `GET` service to display the form. 13 | 14 | - One `POST` service to add the product after a form submit. 15 | 16 | Each of those services have the same access path `/product/add`. The POST service defines three parameters: the product code, the product name and the product price. 17 | 18 | ```ocaml 19 | 20 | let add = 21 | let path = Eliom_service.Path ["product"; "add"] in 22 | (Eliom_service.create ~path 23 | ~meth:(Eliom_service.Get lang_and_iso4217_parameters) 24 | (), 25 | Eliom_service.create ~path 26 | ~meth:(Eliom_service.Post (lang_and_iso4217_parameters, 27 | Eliom_parameter.( 28 | (string "code") ** (string "name") ** (string "price")))) 29 | ()) 30 | 31 | ``` 32 | 33 | 34 | ## Register the handlers 35 | 36 | Then we have to register the new services in `mysite.eliom`: 37 | 38 | ```ocaml 39 | Mysite_app.register 40 | ~service:(fst Service.add) (* GET to display the form *) 41 | ~error_handler:(error_handler View.form_add ()) 42 | (register View.form_add) ; 43 | Mysite_app.register 44 | ~service:(snd Service.add) (* POST to add the product *) 45 | ~error_handler:(error_handler View.add ("", ("", ""))) 46 | (register View.add) ; 47 | ``` 48 | 49 | 50 | ## Form views 51 | 52 | We define a simple form in `view.eliom` with three input fields, a button to submit the form and a button to go back to the products list: 53 | 54 | ```ocaml 55 | let create_form_add lang iso4217 (product_code, (product_name, product_price)) = 56 | Html.F.([ 57 | div ~a:[a_class ["field"]] [ 58 | label ~a:[a_class ["label"]] [ pcdata (_t Field_code lang) ] ; 59 | p ~a:[a_class ["control"]] [ 60 | Form.input ~a:[a_class ["input"]] 61 | ~input_type:`Text ~name:product_code 62 | Form.string 63 | ] 64 | ] ; 65 | div ~a:[a_class ["field"]] [ 66 | label ~a:[a_class ["label"]] [ pcdata (_t Field_name lang) ] ; 67 | p ~a:[a_class ["control"]] [ 68 | Form.input ~a:[a_class ["input"]] 69 | ~input_type:`Text ~name:product_name 70 | Form.string 71 | ] 72 | ] ; 73 | div ~a:[a_class ["field"]] [ 74 | label ~a:[a_class ["label"]] [ pcdata (_t Field_price lang) ] ; 75 | p ~a:[a_class ["control"]] [ 76 | Form.input ~a:[a_class ["input"]] 77 | ~input_type:`Text ~name:product_price 78 | Form.string 79 | ] 80 | ] ; 81 | div ~a:[a_class ["field"; "is-grouped"]] [ 82 | div ~a:[a_class ["control"; "is-expanded"]] [ 83 | Form.input ~a:[a_class ["input"]] 84 | ~input_type:`Submit ~value:(_t Save lang) 85 | Form.string ; 86 | ] ; 87 | div ~a:[a_class ["control"; "is-expanded"]] [ 88 | a ~a:[a_class ["button"; "is-fullwidth"]] ~service:Service.main [ 89 | pcdata (_t Quit lang) 90 | ] (Some lang, Some iso4217) 91 | ] 92 | ] 93 | ]) 94 | 95 | ``` 96 | 97 | When the user submits this form, the `POST` service is used: 98 | 99 | ```ocaml 100 | let form_add lang iso4217 () = 101 | Html.F.( 102 | body [ 103 | div ~a:[a_class ["container"]] [ 104 | navigation Title_add lang iso4217 ; 105 | div [ 106 | (* Use the POST service to manage the form submit *) 107 | Form.post_form ~service:(snd Service.add) (create_form_add lang iso4217) (Some lang, Some iso4217) 108 | ] 109 | ] 110 | ] 111 | ) 112 | ``` 113 | 114 | And after adding the product, we display the new products list: 115 | 116 | ```ocaml 117 | let add lang iso4217 (product_code, (product_name, product_price)) = 118 | let () = 119 | let price = 120 | match iso4217 with 121 | | Money.Iso4217.Iso iso -> 122 | Money.(Vat.Amount(Vat.excl(Currency.of_string product_price iso))) 123 | in 124 | let p = Product.(empty 125 | |> set_code product_code 126 | |> set_names [ I18n.make lang product_name ] 127 | |> set_prices [ price ]) 128 | in 129 | Model.add_product p 130 | in 131 | list lang iso4217 () 132 | ``` 133 | 134 | 135 | ## Products store 136 | 137 | For this tutorial, we simply store the products in a non persistent session variable initialized in `model.eliom`: 138 | 139 | ```ocaml 140 | let products = Eliom_reference.Volatile.eref 141 | ~scope:Eliom_common.default_session_scope 142 | [ p1; p2; p3 ] 143 | ``` 144 | 145 | If you restart the server, the variable content is reset. 146 | 147 | 148 | ## Next step 149 | 150 | For now, the form is really raw. For instance, there is no field validation and you can't set several prices in different currencies. In the next tutorial, we will see how to add these features. 151 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/Makefile.options: -------------------------------------------------------------------------------- 1 | 2 | #---------------------------------------------------------------------- 3 | # SETTINGS FOR THE ELIOM PROJECT mysite 4 | #---------------------------------------------------------------------- 5 | 6 | PROJECT_NAME := mysite 7 | 8 | # Source files for the server 9 | SERVER_FILES := $(wildcard *.eliomi *.eliom) 10 | # Source files for the client 11 | CLIENT_FILES := $(wildcard *.eliomi *.eliom) 12 | 13 | # OCamlfind packages for the server 14 | SERVER_PACKAGES := num lwt.ppx js_of_ocaml.deriving.ppx 15 | # OCamlfind packages for the client 16 | CLIENT_PACKAGES := num lwt.ppx js_of_ocaml.ppx js_of_ocaml.deriving.ppx 17 | 18 | # Directory with files to be statically served 19 | LOCAL_STATIC = static 20 | 21 | # The backend for persistent data. Can be dbm or sqlite. 22 | # Make sure you have the following packages installed 23 | # - *dbm* if you use dbm --> opam install dbm. 24 | # - *sqlite3* if you use sqlite --> opam install sqlite3. 25 | PERSISTENT_DATA_BACKEND = sqlite 26 | 27 | # Debug application (yes/no): Debugging info in compilation, 28 | # JavaScript, ocsigenserver 29 | DEBUG := no 30 | 31 | # User to run server with (make run.*) 32 | WWWUSER := www-data 33 | WWWGROUP := www-data 34 | 35 | # Port for running the server (make run.*) 36 | PORT := 80 37 | 38 | # Port for testing (make test.*) 39 | TEST_PORT := 8080 40 | 41 | # Root of installation (must end with /) 42 | PREFIX := /usr/local/ 43 | 44 | # Local folder for make test.* (must end with /) 45 | # Do not add files manually in this directory. 46 | # It is just here to test your installation before installing in / 47 | TEST_PREFIX := local/ 48 | 49 | # The installation tree (relative to $(PREFIX) when 50 | # installing/running or $(TEST_PREFIX) when testing). 51 | # Configuration file $(PROJECT_NAME).conf 52 | ETCDIR := etc/${PROJECT_NAME} 53 | # Project's library $(PROJECT_NAME).cma (cmxs) 54 | LIBDIR := lib/${PROJECT_NAME} 55 | # Command pipe, eg. $ echo reload > $(INSTALL_PREFIX)$(CMDPIPE) 56 | CMDPIPE := var/run/${PROJECT_NAME}-cmd 57 | # Ocsigenserver's logging files 58 | LOGDIR := var/log/${PROJECT_NAME} 59 | # Ocsigenserver's persistent data files 60 | DATADIR := var/data/${PROJECT_NAME} 61 | # Copy of $(LOCAL_STATIC) 62 | STATICDIR := var/www/${PROJECT_NAME}/static 63 | # Project's JavaScript file 64 | ELIOMSTATICDIR := var/www/${PROJECT_NAME}/eliom 65 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/README: -------------------------------------------------------------------------------- 1 | 2 | Instructions 3 | ============ 4 | 5 | This project is (initially) generated by eliom-distillery as the basic 6 | project "mysite". 7 | 8 | Generally, you can compile it and run ocsigenserver on it by 9 | $ make test.byte (or test.opt) 10 | See below for other useful targets for make. 11 | 12 | Generated files 13 | --------------- 14 | 15 | The following files in this directory have been generated by 16 | eliom-distillery: 17 | 18 | - mysite.eliom 19 | This is your initial source file. 20 | 21 | - static/ 22 | The content of this folder is statically served. Put your CSS or 23 | additional JavaScript files here! 24 | 25 | - Makefile.options 26 | Configure your project here! 27 | 28 | - mysite.conf.in 29 | This file is a template for the configuration file for 30 | ocsigenserver. You will rarely have to edit itself - it takes its 31 | variables from the Makefile.options. This way, the installation 32 | rules and the configuration files are synchronized with respect to 33 | the different folders. 34 | 35 | - Makefile 36 | This contains all rules necessary to build, test, and run your 37 | Eliom application. You better don't touch it ;) See below for the 38 | relevant targets. 39 | 40 | - local/ 41 | This directory is the target of the temporary installation of 42 | your application, to test locally before doing a system-wide 43 | installation in /. Do not put anything manually here. 44 | 45 | - README 46 | Not completely describable here. 47 | 48 | 49 | Makefile targets 50 | ---------------- 51 | 52 | Here's some help on how to work with this basic distillery project: 53 | 54 | - Test your application by compiling it and running ocsigenserver locally 55 | $ make test.byte (or test.opt) 56 | 57 | - Compile it only 58 | $ make all (or byte or opt) 59 | 60 | - Deploy your project on your system 61 | $ sudo make install (or install.byte or install.opt) 62 | 63 | - Run the server on the deployed project 64 | $ sudo make run.byte (or run.opt) 65 | 66 | If WWWUSER in the Makefile.options is you, you don't need the 67 | `sudo'. If Eliom isn't installed globally, however, you need to 68 | re-export some environment variables to make this work: 69 | $ sudo PATH=$PATH OCAMLPATH=$OCAMLPATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH make run.byte/run.opt 70 | 71 | - If you need a findlib package in your project, add it to the 72 | variables SERVER_PACKAGES and/or CLIENT_PACKAGES. The configuration 73 | file will be automatically updated. 74 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/config.eliom: -------------------------------------------------------------------------------- 1 | 2 | let default_language = Language.En 3 | 4 | let default_iso4217 = Money.Iso4217.(Iso usd) 5 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/config.eliomi: -------------------------------------------------------------------------------- 1 | 2 | val default_language : Language.t 3 | 4 | val default_iso4217 : Money.Iso4217.iso -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/i18n.eliom: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | type t = Language.t * string 5 | 6 | let make lang s = (lang, s) 7 | 8 | let translate lang (l: t list) = 9 | try 10 | let s = List.assoc lang l in 11 | Some (lang, s) 12 | with 13 | | Not_found -> None 14 | 15 | let to_string (_, s) = s 16 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/i18n.eliomi: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | type t 5 | 6 | val make : Language.t -> string -> t 7 | val translate : Language.t -> t list -> t option 8 | val to_string : t -> string 9 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/id.eliom: -------------------------------------------------------------------------------- 1 | type t = Int64.t 2 | 3 | let empty = Int64.zero 4 | 5 | let of_int64 v = v 6 | 7 | let to_string = Int64.to_string 8 | 9 | let compare = Int64.compare 10 | 11 | let succ = Int64.succ -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/id.eliomi: -------------------------------------------------------------------------------- 1 | type t 2 | 3 | val empty : t 4 | val of_int64 : Int64.t -> t 5 | val to_string : t -> string 6 | val compare : t -> t -> int 7 | val succ : t -> t -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/language.eliom: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | exception Unknown_language of string 5 | 6 | type t = 7 | | En 8 | | Fr 9 | 10 | let of_string = function 11 | | "en" -> En 12 | | "fr" -> Fr 13 | | s -> raise(Unknown_language s) 14 | 15 | let to_string = function 16 | | En -> "en" 17 | | Fr -> "fr" -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/language.eliomi: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | exception Unknown_language of string 5 | 6 | type t = 7 | | En 8 | | Fr 9 | 10 | val of_string : string -> t 11 | val to_string : t -> string -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/model.eliom: -------------------------------------------------------------------------------- 1 | 2 | open Money 3 | 4 | let p1 = 5 | let price_usd = Vat.excl(Currency.(of_int 19 Iso4217.usd)) in 6 | let price_eur = Vat.exchange Iso4217.eur price_usd in 7 | Product.( 8 | empty 9 | |> set_id (Id.of_int64 1L) 10 | |> set_code "WG04" 11 | |> set_names I18n.( 12 | [ make Language.En "Waffle gun"; make Language.Fr "Pistolet à gaufre"]) 13 | |> set_prices Vat.([ Amount price_usd; Amount price_eur ]) 14 | ) 15 | 16 | let p2 = 17 | let price_eur = Vat.excl(Currency.(of_int 5 Iso4217.eur)) in 18 | let price_usd = Vat.exchange Iso4217.usd price_eur in 19 | Product.( 20 | empty 21 | |> set_id (Id.of_int64 2L) 22 | |> set_code "EW-2000" 23 | |> set_names I18n.( 24 | [ make Language.En "Electric whisker"; make Language.Fr "Tourniquette"]) 25 | |> set_prices Vat.([ Amount price_usd; Amount price_eur ]) 26 | ) 27 | 28 | let p3 = 29 | let price_usd = Vat.excl(Currency.(of_int 599 Iso4217.usd)) in 30 | let price_eur = Vat.exchange Iso4217.eur price_usd in 31 | Product.( 32 | empty 33 | |> set_id (Id.of_int64 3L) 34 | |> set_code "PROGLAGLA" 35 | |> set_names I18n.( 36 | [ make Language.En "Refrigerator"; make Language.Fr "Frigidaire"]) 37 | |> set_prices Vat.([ Amount price_usd; Amount price_eur ]) 38 | ) 39 | 40 | let products = Eliom_reference.Volatile.eref 41 | ~scope:Eliom_common.default_session_scope 42 | [ p1; p2; p3 ] 43 | 44 | let get_new_id () = 45 | let l = 46 | Eliom_reference.Volatile.get products 47 | |> List.fast_sort (fun p1 p2 -> - (Id.compare (Product.get_id p1) (Product.get_id p2))) 48 | in 49 | match l with 50 | | [] -> Id.of_int64 Int64.one 51 | | v :: _ -> Id.succ (Product.get_id v) 52 | 53 | let get_products () = 54 | Eliom_reference.Volatile.get products 55 | 56 | let add_product p = 57 | let p = 58 | if Product.get_id p = Id.empty then 59 | Product.set_id (get_new_id ()) p 60 | else 61 | p 62 | in 63 | let l = Eliom_reference.Volatile.get products in 64 | Eliom_reference.Volatile.set products (p :: l) -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/model.eliomi: -------------------------------------------------------------------------------- 1 | val get_products : unit -> Product.t list 2 | val add_product : Product.t -> unit -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/money.eliom: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | module Iso4217 = struct 5 | 6 | exception Unknown_code of string 7 | 8 | type usd 9 | type eur 10 | 11 | type 'a t = 12 | | Usd : usd t 13 | | Eur : eur t 14 | 15 | type iso = Iso : 'a t -> iso 16 | 17 | let iso v = Iso v 18 | 19 | let usd : usd t = Usd 20 | 21 | let eur : eur t = Eur 22 | 23 | let of_string (type a) (s:string) : iso = 24 | match s with 25 | | "usd" -> Iso Usd 26 | | "eur" -> Iso Eur 27 | | _ -> raise(Unknown_code s) 28 | 29 | let to_string (type a) (v:a t) : string = 30 | match v with 31 | | Usd -> "usd" 32 | | Eur -> "eur" 33 | 34 | let one = Num.num_of_int 1 35 | 36 | let exchange_rate (type a b) ~(for_one:a t) ~(get:b t) : Num.num = 37 | match for_one, get with 38 | | Usd, Eur -> Num.num_of_string "934813/1000000" 39 | | Eur, Usd -> Num.num_of_string "106951/100000" 40 | | Usd, Usd -> one 41 | | Eur, Eur -> one 42 | 43 | let symbol (type a) (v:a t) : string = 44 | match v with 45 | | Eur -> "€" 46 | | Usd -> "$" 47 | 48 | let eq (type a b) (v1:a t) (v2:b t) : bool = 49 | match v1, v2 with 50 | | Eur, Eur -> true 51 | | Usd, Usd -> true 52 | | _ -> false 53 | 54 | end 55 | 56 | module Currency = struct 57 | 58 | type 'a t = Num.num * 'a Iso4217.t 59 | 60 | type amount = Amount : 'a t -> amount 61 | 62 | let of_num (v:Num.num) (iso:'a Iso4217.t) : 'a t = 63 | (v, iso) 64 | 65 | let of_int v iso = 66 | of_num (Num.num_of_int v) iso 67 | 68 | let of_int64 v iso = 69 | of_num (Num.num_of_string(Int64.to_string v)) iso 70 | 71 | let of_string v iso = 72 | let accuracy_float = 10000. in 73 | let accuracy_int = 10000 in 74 | let x = 75 | (float_of_string v) *. accuracy_float 76 | |> Int64.of_float 77 | |> Int64.to_string 78 | |> Num.num_of_string 79 | in 80 | let x = Num.div_num x (Num.num_of_int accuracy_int) in 81 | of_num x iso 82 | 83 | let (+) (v1:'a t) (v2:'a t) : 'a t = 84 | let x1, iso = v1 in 85 | let x2, iso = v2 in 86 | (Num.add_num x1 x2, iso) 87 | 88 | let op_num op (n:Num.num) (v:'a t) : 'a t = 89 | let x, iso = v in 90 | (op x n, iso) 91 | 92 | let mult_by_num n v = 93 | op_num Num.mult_num n v 94 | 95 | let div_by_num n v = 96 | op_num Num.div_num n v 97 | 98 | let exchange (type a b) (to_iso: b Iso4217.t) (v: a t) : b t = 99 | let x, from_iso = v in 100 | (Num.mult_num x (Iso4217.exchange_rate ~for_one:from_iso ~get:to_iso), to_iso) 101 | 102 | let to_string_x f (x, _) = 103 | f x 104 | 105 | let to_string v = 106 | let f v = 107 | let s = Num.approx_num_fix 2 v in 108 | let s = String.sub s 1 ((String.length s) - 1) in 109 | if Num.sign_num v < 0 then 110 | "- " ^ s 111 | else 112 | s 113 | in 114 | to_string_x f v 115 | 116 | let to_string_fractional v = 117 | to_string_x Num.string_of_num v 118 | 119 | let to_string_decimal digits v = 120 | to_string_x (Num.approx_num_fix digits) v 121 | 122 | let to_string_scientific digits v = 123 | to_string_x (Num.approx_num_exp digits) v 124 | 125 | let symbol (_, iso) = 126 | Iso4217.symbol iso 127 | 128 | let filter_one_currency iso l = 129 | let f v = 130 | match v with 131 | | Amount (_, x) -> Iso4217.eq x iso 132 | in 133 | List.filter f l 134 | 135 | let same_currency (_, iso1) (_, iso2) = 136 | Iso4217.eq iso1 iso2 137 | 138 | end 139 | 140 | module Vat = struct 141 | 142 | type excl 143 | type incl 144 | 145 | type ('a, 'b) t = 146 | | Excl : 'a Currency.t -> ('a, excl) t 147 | | Incl : 'a Currency.t -> ('a, incl) t 148 | 149 | type amount = Amount : ('a, 'b) t -> amount 150 | 151 | let apply1_x (type b) f (v:('a, b) t) = 152 | match v with 153 | | Incl v -> f v 154 | | Excl v -> f v 155 | 156 | let apply1 (type b) f (v:('a, b) t) : ('a, b) t = 157 | match v with 158 | | Incl v -> Incl(f v) 159 | | Excl v -> Excl(f v) 160 | 161 | let apply2 (type b) f (v1:('a, b) t) (v2:('a, b) t) : ('a, b) t = 162 | match v1, v2 with 163 | | Incl v1, Incl v2 -> Incl(f v1 v2) 164 | | Excl v1, Excl v2 -> Excl(f v1 v2) 165 | 166 | let (+) v1 v2 = 167 | apply2 Currency.(+) v1 v2 168 | 169 | let mult_by_num x v = 170 | apply1 (Currency.mult_by_num x) v 171 | 172 | let div_by_num x v = 173 | apply1 (Currency.div_by_num x) v 174 | 175 | let exchange (type a b c) iso (v:(a, b) t) : (c, b) t = 176 | match v with 177 | | Incl v -> Incl(Currency.exchange iso v) 178 | | Excl v -> Excl(Currency.exchange iso v) 179 | 180 | let to_string v = 181 | apply1_x Currency.to_string v 182 | 183 | let to_string_fractional v = 184 | apply1_x Currency.to_string_fractional v 185 | 186 | let to_string_decimal digits = 187 | apply1_x (Currency.to_string_decimal digits) 188 | 189 | let to_string_scientific digits = 190 | apply1_x (Currency.to_string_scientific digits) 191 | 192 | let excl v = Excl v 193 | let incl v = Incl v 194 | 195 | let tax_rate = 196 | Num.num_of_string "120/100" 197 | 198 | let to_excl (v:('a, incl) t) : ('a, excl) t = 199 | match v with 200 | | Incl v -> Excl(Currency.div_by_num tax_rate v) 201 | 202 | let to_incl (v:('a, excl) t) : ('a, incl) t = 203 | match v with 204 | | Excl v -> Incl(Currency.mult_by_num tax_rate v) 205 | 206 | let symbol_currency (type b) (v:('a, b) t) = 207 | apply1_x Currency.symbol v 208 | 209 | let filter_one_currency iso l = 210 | let f v = 211 | match v with 212 | | Amount a -> 213 | match a with 214 | | Incl i -> Currency.same_currency i (Currency.of_int 0 iso) 215 | | Excl e -> Currency.same_currency e (Currency.of_int 0 iso) 216 | in 217 | List.filter f l 218 | 219 | let idiom (type b) lang (v:('a, b) t) : I18n.t option = 220 | let l = 221 | match v with 222 | | Excl _ -> [ 223 | I18n.make Language.En "excl. VAT" ; 224 | I18n.make Language.Fr "HT" ; 225 | ] 226 | | Incl _ -> [ 227 | I18n.make Language.En "incl. VAT" ; 228 | I18n.make Language.Fr "TTC" ; 229 | ] 230 | in 231 | I18n.translate lang l 232 | 233 | end 234 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/money.eliomi: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | module Iso4217 : sig 5 | 6 | exception Unknown_code of string 7 | 8 | type usd 9 | type eur 10 | 11 | type 'a t 12 | 13 | type iso = Iso : 'a t -> iso 14 | 15 | val iso : 'a t -> iso 16 | 17 | val usd : usd t 18 | val eur : eur t 19 | 20 | val of_string : string -> iso 21 | val to_string : 'a t -> string 22 | val symbol : 'a t -> string 23 | 24 | end 25 | 26 | module Currency : sig 27 | 28 | type 'a t 29 | 30 | type amount = Amount : 'a t -> amount 31 | 32 | val of_num : Num.num -> 'a Iso4217.t -> 'a t 33 | val of_int : int -> 'a Iso4217.t -> 'a t 34 | val of_int64 : Int64.t -> 'a Iso4217.t -> 'a t 35 | val of_string : string -> 'a Iso4217.t -> 'a t 36 | 37 | val (+) : 'a t -> 'a t -> 'a t 38 | val mult_by_num : Num.num -> 'a t -> 'a t 39 | val div_by_num : Num.num -> 'a t -> 'a t 40 | 41 | val exchange : 'a Iso4217.t -> 'b t -> 'a t 42 | 43 | val to_string : 'a t -> string 44 | val to_string_fractional : 'a t -> string 45 | val to_string_decimal : int -> 'a t -> string 46 | val to_string_scientific : int -> 'a t -> string 47 | 48 | val symbol : 'a t -> string 49 | 50 | val filter_one_currency : 'a Iso4217.t -> amount list -> amount list 51 | val same_currency : 'a t -> 'a t -> bool 52 | 53 | end 54 | 55 | module Vat : sig 56 | 57 | type excl 58 | type incl 59 | 60 | type ('a, 'b) t 61 | 62 | type amount = Amount : ('a, 'b) t -> amount 63 | 64 | val (+) : ('a, 'b) t -> ('a, 'b) t -> ('a, 'b) t 65 | val mult_by_num : Num.num -> ('a, 'b) t -> ('a, 'b) t 66 | val div_by_num : Num.num -> ('a, 'b) t -> ('a, 'b) t 67 | 68 | val exchange : 'a Iso4217.t -> ('b, 'c) t -> ('a, 'c) t 69 | 70 | val to_string : ('a, 'b) t -> string 71 | val to_string_fractional : ('a, 'b) t -> string 72 | val to_string_decimal : int -> ('a, 'b) t -> string 73 | val to_string_scientific : int -> ('a, 'b) t -> string 74 | 75 | val excl : 'a Currency.t -> ('a, excl) t 76 | val incl : 'a Currency.t -> ('a, incl) t 77 | 78 | val to_excl : ('a, incl) t -> ('a, excl) t 79 | val to_incl : ('a, excl) t -> ('a, incl) t 80 | 81 | val symbol_currency : ('a, 'b) t -> string 82 | 83 | val filter_one_currency : 'a Iso4217.t -> amount list -> amount list 84 | 85 | val idiom : Language.t -> ('a, 'b) t -> I18n.t option 86 | 87 | end 88 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/mysite.conf.in: -------------------------------------------------------------------------------- 1 | %%% This is the template for your configuration file. The %%VALUES%% below are 2 | %%% taken from the Makefile to generate the actual configuration files. 3 | %%% This comment will disappear. 4 | 5 | 6 | 7 | %%PORT%% 8 | %%% Only set for running, not for testing 9 | %%USERGROUP%% 10 | %%LOGDIR%% 11 | %%DATADIR%% 12 | utf-8 13 | %%% Only set when debugging 14 | %%DEBUGMODE%% 15 | %%CMDPIPE%% 16 | 17 | 18 | 19 | %%% This will include the packages defined as SERVER_PACKAGES in your Makefile: 20 | %%PACKAGES%% 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/mysite.eliom: -------------------------------------------------------------------------------- 1 | [%%shared 2 | open Eliom_lib 3 | open Eliom_content 4 | open Html.D 5 | ] 6 | 7 | module Mysite_app = 8 | Eliom_registration.App ( 9 | struct 10 | let application_name = "mysite" 11 | let global_data_path = None 12 | end) 13 | 14 | let () = 15 | let css_bulma = 16 | Eliom_content.Html.F.( 17 | css_link 18 | ~uri:( 19 | make_uri 20 | ~service:(Eliom_service.static_dir ()) 21 | ~https:true ~hostname:"cdnjs.cloudflare.com" 22 | ["ajax"; "libs"; "bulma"; "0.4.0"; "css"; "bulma.min.css"] 23 | ) () 24 | ) 25 | in 26 | let css_mysite = 27 | Eliom_content.Html.F.( 28 | css_link 29 | ~uri:( 30 | make_uri 31 | ~service:(Eliom_service.static_dir ()) 32 | ~absolute:true 33 | ["css";"mysite.css"] 34 | ) () 35 | ) 36 | in 37 | 38 | let page f lang iso4217 params = 39 | Eliom_tools.F.html 40 | ~title:"mysite" 41 | ~other_head:[css_bulma; css_mysite] 42 | (f lang iso4217 params) 43 | in 44 | let error_handler f params l = 45 | let html = 46 | match l with 47 | | [] 48 | | ("lang", Language.Unknown_language _) :: [] -> 49 | page f Config.default_language Config.default_iso4217 params 50 | | ("iso4217", Money.Iso4217.Unknown_code _) :: [] -> 51 | page f Config.default_language Config.default_iso4217 params 52 | | (_, e) :: _ -> raise e 53 | in 54 | Lwt.return html 55 | in 56 | let register f (lang, iso4217) params = 57 | let html = 58 | match lang, iso4217 with 59 | | None, None -> page f Config.default_language Config.default_iso4217 params 60 | | Some vl, None -> page f vl Config.default_iso4217 params 61 | | None, Some vi -> page f Config.default_language vi params 62 | | Some vl, Some vi -> page f vl vi params 63 | in 64 | Lwt.return html 65 | in 66 | Mysite_app.register 67 | ~service:Service.main 68 | ~error_handler:(error_handler View.list ()) 69 | (register View.list) ; 70 | Mysite_app.register 71 | ~service:(fst Service.add) (* GET to display the form *) 72 | ~error_handler:(error_handler View.form_add ()) 73 | (register View.form_add) ; 74 | Mysite_app.register 75 | ~service:(snd Service.add) (* POST to add the product *) 76 | ~error_handler:(error_handler View.add ("", ("", ""))) 77 | (register View.add) ; 78 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/product.eliom: -------------------------------------------------------------------------------- 1 | 2 | type t = { 3 | id : Id.t ; 4 | code : string ; 5 | names : I18n.t list ; 6 | prices : Money.Vat.amount list ; 7 | } 8 | 9 | let empty = { 10 | id = Id.empty ; 11 | code = "" ; 12 | names = [] ; 13 | prices = [] ; 14 | } 15 | 16 | let get_id v = v.id 17 | 18 | let set_id id v = { v with id } 19 | 20 | let get_code v = v.code 21 | 22 | let set_code code v = { v with code } 23 | 24 | let get_names v = v.names 25 | 26 | let set_names names v = { v with names } 27 | 28 | let get_prices v = v.prices 29 | 30 | let set_prices prices v = { v with prices } 31 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/product.eliomi: -------------------------------------------------------------------------------- 1 | 2 | type t 3 | 4 | val empty : t 5 | 6 | val get_id : t -> Id.t 7 | val set_id : Id.t -> t -> t 8 | 9 | val get_code : t -> string 10 | val set_code : string -> t -> t 11 | 12 | val get_names : t -> I18n.t list 13 | val set_names : I18n.t list -> t -> t 14 | 15 | val get_prices : t -> Money.Vat.amount list 16 | val set_prices : Money.Vat.amount list -> t -> t 17 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/service.eliom: -------------------------------------------------------------------------------- 1 | let lang_and_iso4217_parameters = 2 | Eliom_parameter.( 3 | (opt( 4 | user_type 5 | ~client_to_and_of:([%client { Eliom_parameter.of_string = Language.of_string; Eliom_parameter.to_string = Language.to_string } ]) 6 | ~of_string:Language.of_string 7 | ~to_string:Language.to_string 8 | "lang")) 9 | ** 10 | (opt( 11 | user_type 12 | ~client_to_and_of:([%client { Eliom_parameter.of_string = Money.Iso4217.of_string; 13 | Eliom_parameter.to_string = (fun v -> match v with Money.Iso4217.Iso iso -> Money.Iso4217.to_string iso) } ]) 14 | ~of_string:Money.Iso4217.of_string 15 | ~to_string:(fun v -> match v with Money.Iso4217.Iso iso -> Money.Iso4217.to_string iso) 16 | "iso4217")) 17 | ) 18 | 19 | let main = 20 | Eliom_service.create 21 | ~path:(Eliom_service.Path []) 22 | ~meth:(Eliom_service.Get lang_and_iso4217_parameters) 23 | () 24 | 25 | let add = 26 | let path = Eliom_service.Path ["product"; "add"] in 27 | (Eliom_service.create ~path 28 | ~meth:(Eliom_service.Get lang_and_iso4217_parameters) 29 | (), 30 | Eliom_service.create ~path 31 | ~meth:(Eliom_service.Post (lang_and_iso4217_parameters, 32 | Eliom_parameter.( 33 | (string "code") ** (string "name") ** (string "price")))) 34 | ()) 35 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/service.eliomi: -------------------------------------------------------------------------------- 1 | (* 2 | Trick to get the type: 3 | 1 - use (_, _, _, _, _, _, _, _, _, _, _) Eliom_service.t 4 | 2 - copy-paste the type from the compiler error message 5 | *) 6 | 7 | val main : (Language.t option * Money.Iso4217.iso option, 8 | unit, 9 | Eliom_service.get, 10 | Eliom_service.att, 11 | Eliom_service.non_co, 12 | Eliom_service.non_ext, 13 | Eliom_service.reg, 14 | [ `WithoutSuffix ], 15 | [ `One of Language.t ] Eliom_parameter.param_name * [ `One of Money.Iso4217.iso ] Eliom_parameter.param_name, 16 | unit, 17 | Eliom_service.non_ocaml) 18 | Eliom_service.t 19 | 20 | val add : (Language.t option * Money.Iso4217.iso option, unit, 21 | Eliom_service.get, Eliom_service.att, Eliom_service.non_co, 22 | Eliom_service.non_ext, Eliom_service.reg, [ `WithoutSuffix ], 23 | [ `One of Language.t ] Eliom_parameter.param_name * 24 | [ `One of Money.Iso4217.iso ] Eliom_parameter.param_name, 25 | unit, Eliom_service.non_ocaml) 26 | Eliom_service.t * 27 | (Language.t option * Money.Iso4217.iso option, 28 | string * (string * string), Eliom_service.post, 29 | Eliom_service.att, Eliom_service.non_co, Eliom_service.non_ext, 30 | Eliom_service.reg, [ `WithoutSuffix ], 31 | [ `One of Language.t ] Eliom_parameter.param_name * 32 | [ `One of Money.Iso4217.iso ] Eliom_parameter.param_name, 33 | [ `One of string ] Eliom_parameter.param_name * 34 | ([ `One of string ] Eliom_parameter.param_name * 35 | [ `One of string ] Eliom_parameter.param_name), 36 | Eliom_service.non_ocaml) 37 | Eliom_service.t 38 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/static/css/mysite.css: -------------------------------------------------------------------------------- 1 | .table th.price, .table td.price { 2 | text-align: right; 3 | } 4 | -------------------------------------------------------------------------------- /eliom/tutorial/00400-add-product/src/mysite/view.eliomi: -------------------------------------------------------------------------------- 1 | val list : Language.t -> Money.Iso4217.iso -> unit -> [ `Body ] Eliom_content.Html.elt 2 | val form_add : Language.t -> Money.Iso4217.iso -> unit -> [ `Body ] Eliom_content.Html.elt 3 | val add : Language.t -> Money.Iso4217.iso -> (string * (string * string)) -> [ `Body ] Eliom_content.Html.elt -------------------------------------------------------------------------------- /eliom/tutorial/Makefile: -------------------------------------------------------------------------------- 1 | copy: 2 | cp -f shared/* 00200-static-table/src/mysite/ 3 | cp -f shared/* 00300-url-param/src/mysite/ 4 | cp -f shared/* 00400-add-product/src/mysite/ 5 | 6 | all: copy 7 | (cd 00100-hello-world/src/mysite/ && $(MAKE)) 8 | (cd 00200-static-table/src/mysite/ && $(MAKE)) 9 | (cd 00300-url-param/src/mysite/ && $(MAKE)) 10 | (cd 00400-add-product/src/mysite/ && $(MAKE)) 11 | 12 | distclean: 13 | (cd 00100-hello-world/src/mysite/ && $(MAKE) distclean) 14 | (cd 00200-static-table/src/mysite/ && $(MAKE) distclean) 15 | (cd 00300-url-param/src/mysite/ && $(MAKE) distclean) 16 | (cd 00400-add-product/src/mysite/ && $(MAKE) distclean) 17 | -------------------------------------------------------------------------------- /eliom/tutorial/shared/config.eliom: -------------------------------------------------------------------------------- 1 | 2 | let default_language = Language.En 3 | 4 | let default_iso4217 = Money.Iso4217.(Iso usd) 5 | -------------------------------------------------------------------------------- /eliom/tutorial/shared/config.eliomi: -------------------------------------------------------------------------------- 1 | 2 | val default_language : Language.t 3 | 4 | val default_iso4217 : Money.Iso4217.iso -------------------------------------------------------------------------------- /eliom/tutorial/shared/i18n.eliom: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | type t = Language.t * string 5 | 6 | let make lang s = (lang, s) 7 | 8 | let translate lang (l: t list) = 9 | try 10 | let s = List.assoc lang l in 11 | Some (lang, s) 12 | with 13 | | Not_found -> None 14 | 15 | let to_string (_, s) = s 16 | -------------------------------------------------------------------------------- /eliom/tutorial/shared/i18n.eliomi: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | type t 5 | 6 | val make : Language.t -> string -> t 7 | val translate : Language.t -> t list -> t option 8 | val to_string : t -> string 9 | -------------------------------------------------------------------------------- /eliom/tutorial/shared/id.eliom: -------------------------------------------------------------------------------- 1 | type t = Int64.t 2 | 3 | let empty = Int64.zero 4 | 5 | let of_int64 v = v 6 | 7 | let to_string = Int64.to_string 8 | 9 | let compare = Int64.compare 10 | 11 | let succ = Int64.succ -------------------------------------------------------------------------------- /eliom/tutorial/shared/id.eliomi: -------------------------------------------------------------------------------- 1 | type t 2 | 3 | val empty : t 4 | val of_int64 : Int64.t -> t 5 | val to_string : t -> string 6 | val compare : t -> t -> int 7 | val succ : t -> t -------------------------------------------------------------------------------- /eliom/tutorial/shared/language.eliom: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | exception Unknown_language of string 5 | 6 | type t = 7 | | En 8 | | Fr 9 | 10 | let of_string = function 11 | | "en" -> En 12 | | "fr" -> Fr 13 | | s -> raise(Unknown_language s) 14 | 15 | let to_string = function 16 | | En -> "en" 17 | | Fr -> "fr" -------------------------------------------------------------------------------- /eliom/tutorial/shared/language.eliomi: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | exception Unknown_language of string 5 | 6 | type t = 7 | | En 8 | | Fr 9 | 10 | val of_string : string -> t 11 | val to_string : t -> string -------------------------------------------------------------------------------- /eliom/tutorial/shared/money.eliom: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | module Iso4217 = struct 5 | 6 | exception Unknown_code of string 7 | 8 | type usd 9 | type eur 10 | 11 | type 'a t = 12 | | Usd : usd t 13 | | Eur : eur t 14 | 15 | type iso = Iso : 'a t -> iso 16 | 17 | let iso v = Iso v 18 | 19 | let usd : usd t = Usd 20 | 21 | let eur : eur t = Eur 22 | 23 | let of_string (type a) (s:string) : iso = 24 | match s with 25 | | "usd" -> Iso Usd 26 | | "eur" -> Iso Eur 27 | | _ -> raise(Unknown_code s) 28 | 29 | let to_string (type a) (v:a t) : string = 30 | match v with 31 | | Usd -> "usd" 32 | | Eur -> "eur" 33 | 34 | let one = Num.num_of_int 1 35 | 36 | let exchange_rate (type a b) ~(for_one:a t) ~(get:b t) : Num.num = 37 | match for_one, get with 38 | | Usd, Eur -> Num.num_of_string "934813/1000000" 39 | | Eur, Usd -> Num.num_of_string "106951/100000" 40 | | Usd, Usd -> one 41 | | Eur, Eur -> one 42 | 43 | let symbol (type a) (v:a t) : string = 44 | match v with 45 | | Eur -> "€" 46 | | Usd -> "$" 47 | 48 | let eq (type a b) (v1:a t) (v2:b t) : bool = 49 | match v1, v2 with 50 | | Eur, Eur -> true 51 | | Usd, Usd -> true 52 | | _ -> false 53 | 54 | end 55 | 56 | module Currency = struct 57 | 58 | type 'a t = Num.num * 'a Iso4217.t 59 | 60 | type amount = Amount : 'a t -> amount 61 | 62 | let of_num (v:Num.num) (iso:'a Iso4217.t) : 'a t = 63 | (v, iso) 64 | 65 | let of_int v iso = 66 | of_num (Num.num_of_int v) iso 67 | 68 | let of_int64 v iso = 69 | of_num (Num.num_of_string(Int64.to_string v)) iso 70 | 71 | let of_string v iso = 72 | let accuracy_float = 10000. in 73 | let accuracy_int = 10000 in 74 | let x = 75 | (float_of_string v) *. accuracy_float 76 | |> Int64.of_float 77 | |> Int64.to_string 78 | |> Num.num_of_string 79 | in 80 | let x = Num.div_num x (Num.num_of_int accuracy_int) in 81 | of_num x iso 82 | 83 | let (+) (v1:'a t) (v2:'a t) : 'a t = 84 | let x1, iso = v1 in 85 | let x2, iso = v2 in 86 | (Num.add_num x1 x2, iso) 87 | 88 | let op_num op (n:Num.num) (v:'a t) : 'a t = 89 | let x, iso = v in 90 | (op x n, iso) 91 | 92 | let mult_by_num n v = 93 | op_num Num.mult_num n v 94 | 95 | let div_by_num n v = 96 | op_num Num.div_num n v 97 | 98 | let exchange (type a b) (to_iso: b Iso4217.t) (v: a t) : b t = 99 | let x, from_iso = v in 100 | (Num.mult_num x (Iso4217.exchange_rate ~for_one:from_iso ~get:to_iso), to_iso) 101 | 102 | let to_string_x f (x, _) = 103 | f x 104 | 105 | let to_string v = 106 | let f v = 107 | let s = Num.approx_num_fix 2 v in 108 | let s = String.sub s 1 ((String.length s) - 1) in 109 | if Num.sign_num v < 0 then 110 | "- " ^ s 111 | else 112 | s 113 | in 114 | to_string_x f v 115 | 116 | let to_string_fractional v = 117 | to_string_x Num.string_of_num v 118 | 119 | let to_string_decimal digits v = 120 | to_string_x (Num.approx_num_fix digits) v 121 | 122 | let to_string_scientific digits v = 123 | to_string_x (Num.approx_num_exp digits) v 124 | 125 | let symbol (_, iso) = 126 | Iso4217.symbol iso 127 | 128 | let filter_one_currency iso l = 129 | let f v = 130 | match v with 131 | | Amount (_, x) -> Iso4217.eq x iso 132 | in 133 | List.filter f l 134 | 135 | let same_currency (_, iso1) (_, iso2) = 136 | Iso4217.eq iso1 iso2 137 | 138 | end 139 | 140 | module Vat = struct 141 | 142 | type excl 143 | type incl 144 | 145 | type ('a, 'b) t = 146 | | Excl : 'a Currency.t -> ('a, excl) t 147 | | Incl : 'a Currency.t -> ('a, incl) t 148 | 149 | type amount = Amount : ('a, 'b) t -> amount 150 | 151 | let apply1_x (type b) f (v:('a, b) t) = 152 | match v with 153 | | Incl v -> f v 154 | | Excl v -> f v 155 | 156 | let apply1 (type b) f (v:('a, b) t) : ('a, b) t = 157 | match v with 158 | | Incl v -> Incl(f v) 159 | | Excl v -> Excl(f v) 160 | 161 | let apply2 (type b) f (v1:('a, b) t) (v2:('a, b) t) : ('a, b) t = 162 | match v1, v2 with 163 | | Incl v1, Incl v2 -> Incl(f v1 v2) 164 | | Excl v1, Excl v2 -> Excl(f v1 v2) 165 | 166 | let (+) v1 v2 = 167 | apply2 Currency.(+) v1 v2 168 | 169 | let mult_by_num x v = 170 | apply1 (Currency.mult_by_num x) v 171 | 172 | let div_by_num x v = 173 | apply1 (Currency.div_by_num x) v 174 | 175 | let exchange (type a b c) iso (v:(a, b) t) : (c, b) t = 176 | match v with 177 | | Incl v -> Incl(Currency.exchange iso v) 178 | | Excl v -> Excl(Currency.exchange iso v) 179 | 180 | let to_string v = 181 | apply1_x Currency.to_string v 182 | 183 | let to_string_fractional v = 184 | apply1_x Currency.to_string_fractional v 185 | 186 | let to_string_decimal digits = 187 | apply1_x (Currency.to_string_decimal digits) 188 | 189 | let to_string_scientific digits = 190 | apply1_x (Currency.to_string_scientific digits) 191 | 192 | let excl v = Excl v 193 | let incl v = Incl v 194 | 195 | let tax_rate = 196 | Num.num_of_string "120/100" 197 | 198 | let to_excl (v:('a, incl) t) : ('a, excl) t = 199 | match v with 200 | | Incl v -> Excl(Currency.div_by_num tax_rate v) 201 | 202 | let to_incl (v:('a, excl) t) : ('a, incl) t = 203 | match v with 204 | | Excl v -> Incl(Currency.mult_by_num tax_rate v) 205 | 206 | let symbol_currency (type b) (v:('a, b) t) = 207 | apply1_x Currency.symbol v 208 | 209 | let filter_one_currency iso l = 210 | let f v = 211 | match v with 212 | | Amount a -> 213 | match a with 214 | | Incl i -> Currency.same_currency i (Currency.of_int 0 iso) 215 | | Excl e -> Currency.same_currency e (Currency.of_int 0 iso) 216 | in 217 | List.filter f l 218 | 219 | let idiom (type b) lang (v:('a, b) t) : I18n.t option = 220 | let l = 221 | match v with 222 | | Excl _ -> [ 223 | I18n.make Language.En "excl. VAT" ; 224 | I18n.make Language.Fr "HT" ; 225 | ] 226 | | Incl _ -> [ 227 | I18n.make Language.En "incl. VAT" ; 228 | I18n.make Language.Fr "TTC" ; 229 | ] 230 | in 231 | I18n.translate lang l 232 | 233 | end 234 | -------------------------------------------------------------------------------- /eliom/tutorial/shared/money.eliomi: -------------------------------------------------------------------------------- 1 | 2 | [%%shared.start] 3 | 4 | module Iso4217 : sig 5 | 6 | exception Unknown_code of string 7 | 8 | type usd 9 | type eur 10 | 11 | type 'a t 12 | 13 | type iso = Iso : 'a t -> iso 14 | 15 | val iso : 'a t -> iso 16 | 17 | val usd : usd t 18 | val eur : eur t 19 | 20 | val of_string : string -> iso 21 | val to_string : 'a t -> string 22 | val symbol : 'a t -> string 23 | 24 | end 25 | 26 | module Currency : sig 27 | 28 | type 'a t 29 | 30 | type amount = Amount : 'a t -> amount 31 | 32 | val of_num : Num.num -> 'a Iso4217.t -> 'a t 33 | val of_int : int -> 'a Iso4217.t -> 'a t 34 | val of_int64 : Int64.t -> 'a Iso4217.t -> 'a t 35 | val of_string : string -> 'a Iso4217.t -> 'a t 36 | 37 | val (+) : 'a t -> 'a t -> 'a t 38 | val mult_by_num : Num.num -> 'a t -> 'a t 39 | val div_by_num : Num.num -> 'a t -> 'a t 40 | 41 | val exchange : 'a Iso4217.t -> 'b t -> 'a t 42 | 43 | val to_string : 'a t -> string 44 | val to_string_fractional : 'a t -> string 45 | val to_string_decimal : int -> 'a t -> string 46 | val to_string_scientific : int -> 'a t -> string 47 | 48 | val symbol : 'a t -> string 49 | 50 | val filter_one_currency : 'a Iso4217.t -> amount list -> amount list 51 | val same_currency : 'a t -> 'a t -> bool 52 | 53 | end 54 | 55 | module Vat : sig 56 | 57 | type excl 58 | type incl 59 | 60 | type ('a, 'b) t 61 | 62 | type amount = Amount : ('a, 'b) t -> amount 63 | 64 | val (+) : ('a, 'b) t -> ('a, 'b) t -> ('a, 'b) t 65 | val mult_by_num : Num.num -> ('a, 'b) t -> ('a, 'b) t 66 | val div_by_num : Num.num -> ('a, 'b) t -> ('a, 'b) t 67 | 68 | val exchange : 'a Iso4217.t -> ('b, 'c) t -> ('a, 'c) t 69 | 70 | val to_string : ('a, 'b) t -> string 71 | val to_string_fractional : ('a, 'b) t -> string 72 | val to_string_decimal : int -> ('a, 'b) t -> string 73 | val to_string_scientific : int -> ('a, 'b) t -> string 74 | 75 | val excl : 'a Currency.t -> ('a, excl) t 76 | val incl : 'a Currency.t -> ('a, incl) t 77 | 78 | val to_excl : ('a, incl) t -> ('a, excl) t 79 | val to_incl : ('a, excl) t -> ('a, incl) t 80 | 81 | val symbol_currency : ('a, 'b) t -> string 82 | 83 | val filter_one_currency : 'a Iso4217.t -> amount list -> amount list 84 | 85 | val idiom : Language.t -> ('a, 'b) t -> I18n.t option 86 | 87 | end 88 | -------------------------------------------------------------------------------- /eliom/tutorial/shared/product.eliom: -------------------------------------------------------------------------------- 1 | 2 | type t = { 3 | id : Id.t ; 4 | code : string ; 5 | names : I18n.t list ; 6 | prices : Money.Vat.amount list ; 7 | } 8 | 9 | let empty = { 10 | id = Id.empty ; 11 | code = "" ; 12 | names = [] ; 13 | prices = [] ; 14 | } 15 | 16 | let get_id v = v.id 17 | 18 | let set_id id v = { v with id } 19 | 20 | let get_code v = v.code 21 | 22 | let set_code code v = { v with code } 23 | 24 | let get_names v = v.names 25 | 26 | let set_names names v = { v with names } 27 | 28 | let get_prices v = v.prices 29 | 30 | let set_prices prices v = { v with prices } 31 | -------------------------------------------------------------------------------- /eliom/tutorial/shared/product.eliomi: -------------------------------------------------------------------------------- 1 | 2 | type t 3 | 4 | val empty : t 5 | 6 | val get_id : t -> Id.t 7 | val set_id : Id.t -> t -> t 8 | 9 | val get_code : t -> string 10 | val set_code : string -> t -> t 11 | 12 | val get_names : t -> I18n.t list 13 | val set_names : I18n.t list -> t -> t 14 | 15 | val get_prices : t -> Money.Vat.amount list 16 | val set_prices : Money.Vat.amount list -> t -> t 17 | -------------------------------------------------------------------------------- /eliom/with-ocaml-vdom/simple/.depend: -------------------------------------------------------------------------------- 1 | _server/mixvdomandeliom.cmo : 2 | _server/mixvdomandeliom.cmx : 3 | _server/mixvdomandeliom.cmo : _server/mixvdomandeliom.type_mli 4 | _server/mixvdomandeliom.cmx : _server/mixvdomandeliom.type_mli 5 | _client/mixvdomandeliom.cmo : 6 | _client/mixvdomandeliom.cmx : 7 | _client/mixvdomandeliom.cmo : _server/mixvdomandeliom.type_mli 8 | _client/mixvdomandeliom.cmx : _server/mixvdomandeliom.type_mli 9 | -------------------------------------------------------------------------------- /eliom/with-ocaml-vdom/simple/.ocp-indent: -------------------------------------------------------------------------------- 1 | normal 2 | with=0 3 | syntax=lwt mll 4 | max_indent=2 5 | ppx_stritem_ext=0 6 | -------------------------------------------------------------------------------- /eliom/with-ocaml-vdom/simple/Makefile.options: -------------------------------------------------------------------------------- 1 | 2 | #---------------------------------------------------------------------- 3 | # SETTINGS FOR THE ELIOM PROJECT mixvdomandeliom 4 | #---------------------------------------------------------------------- 5 | 6 | PROJECT_NAME := mixvdomandeliom 7 | 8 | # Source files for the server 9 | SERVER_FILES := $(wildcard *.eliomi *.eliom) 10 | # Source files for the client 11 | CLIENT_FILES := $(wildcard *.eliomi *.eliom) 12 | 13 | # OCamlfind packages for the server 14 | SERVER_PACKAGES := 15 | # OCamlfind packages for the client 16 | CLIENT_PACKAGES := lwt.ppx ocaml-vdom 17 | 18 | # Directory with files to be statically served 19 | LOCAL_STATIC = static 20 | 21 | # The backend for persistent data. Can be dbm or sqlite. 22 | # Make sure you have the following packages installed 23 | # - *dbm* if you use dbm --> opam install dbm. 24 | # - *sqlite3* if you use sqlite --> opam install sqlite3. 25 | PERSISTENT_DATA_BACKEND = sqlite 26 | 27 | # Debug application (yes/no): Debugging info in compilation, 28 | # JavaScript, ocsigenserver 29 | DEBUG := no 30 | 31 | # User to run server with (make run.*) 32 | WWWUSER := www-data 33 | WWWGROUP := www-data 34 | 35 | # Port for running the server (make run.*) 36 | PORT := 80 37 | 38 | # Port for testing (make test.*) 39 | TEST_PORT := 8080 40 | 41 | # Root of installation (must end with /) 42 | PREFIX := /usr/local/ 43 | 44 | # Local folder for make test.* (must end with /) 45 | # Do not add files manually in this directory. 46 | # It is just here to test your installation before installing in / 47 | TEST_PREFIX := local/ 48 | 49 | # The installation tree (relative to $(PREFIX) when 50 | # installing/running or $(TEST_PREFIX) when testing). 51 | # Configuration file $(PROJECT_NAME).conf 52 | ETCDIR := etc/${PROJECT_NAME} 53 | # Project's library $(PROJECT_NAME).cma (cmxs) 54 | LIBDIR := lib/${PROJECT_NAME} 55 | # Command pipe, eg. $ echo reload > $(INSTALL_PREFIX)$(CMDPIPE) 56 | CMDPIPE := var/run/${PROJECT_NAME}-cmd 57 | # Ocsigenserver's logging files 58 | LOGDIR := var/log/${PROJECT_NAME} 59 | # Ocsigenserver's persistent data files 60 | DATADIR := var/data/${PROJECT_NAME} 61 | # Copy of $(LOCAL_STATIC) 62 | STATICDIR := var/www/${PROJECT_NAME}/static 63 | # Project's JavaScript file 64 | ELIOMSTATICDIR := var/www/${PROJECT_NAME}/eliom 65 | -------------------------------------------------------------------------------- /eliom/with-ocaml-vdom/simple/README: -------------------------------------------------------------------------------- 1 | 2 | Instructions 3 | ============ 4 | 5 | This project is (initially) generated by eliom-distillery as the basic 6 | project "mixvdomandeliom". 7 | 8 | Generally, you can compile it and run ocsigenserver on it by 9 | $ make test.byte (or test.opt) 10 | See below for other useful targets for make. 11 | 12 | Generated files 13 | --------------- 14 | 15 | The following files in this directory have been generated by 16 | eliom-distillery: 17 | 18 | - mixvdomandeliom.eliom 19 | This is your initial source file. 20 | 21 | - static/ 22 | The content of this folder is statically served. Put your CSS or 23 | additional JavaScript files here! 24 | 25 | - Makefile.options 26 | Configure your project here! 27 | 28 | - mixvdomandeliom.conf.in 29 | This file is a template for the configuration file for 30 | ocsigenserver. You will rarely have to edit itself - it takes its 31 | variables from the Makefile.options. This way, the installation 32 | rules and the configuration files are synchronized with respect to 33 | the different folders. 34 | 35 | - Makefile 36 | This contains all rules necessary to build, test, and run your 37 | Eliom application. You better don't touch it ;) See below for the 38 | relevant targets. 39 | 40 | - local/ 41 | This directory is the target of the temporary installation of 42 | your application, to test locally before doing a system-wide 43 | installation in /. Do not put anything manually here. 44 | 45 | - README 46 | Not completely describable here. 47 | 48 | 49 | Makefile targets 50 | ---------------- 51 | 52 | Here's some help on how to work with this basic distillery project: 53 | 54 | - Test your application by compiling it and running ocsigenserver locally 55 | $ make test.byte (or test.opt) 56 | 57 | - Compile it only 58 | $ make all (or byte or opt) 59 | 60 | - Deploy your project on your system 61 | $ sudo make install (or install.byte or install.opt) 62 | 63 | - Run the server on the deployed project 64 | $ sudo make run.byte (or run.opt) 65 | 66 | If WWWUSER in the Makefile.options is you, you don't need the 67 | `sudo'. If Eliom isn't installed globally, however, you need to 68 | re-export some environment variables to make this work: 69 | $ sudo PATH=$PATH OCAMLPATH=$OCAMLPATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH make run.byte/run.opt 70 | 71 | - If you need a findlib package in your project, add it to the 72 | variables SERVER_PACKAGES and/or CLIENT_PACKAGES. The configuration 73 | file will be automatically updated. 74 | -------------------------------------------------------------------------------- /eliom/with-ocaml-vdom/simple/README.md: -------------------------------------------------------------------------------- 1 | How to use Eliom services with OCaml Vdom. Tested with Eliom 6.1 and OCaml Vdom 0.1. 2 | It's mainly a proof of concept. Nonetheless, it could be an interesting lighter approach compared to a classic Eliom project. One downside is that you loose the safety given by TyXML. But the client code is simpler. 3 | Note that `Makefile` and `Makefile.options` are different from the default files: 4 | - In `Makefile` you must add `-no-check-prims -jsopt +gen_js_api/ojs_runtime.js` to `JS_OF_ELIOM` 5 | - In `Makefile.options` you must add `ocaml-vdom` to `CLIENT_PACKAGES` 6 | 7 | -------------------------------------------------------------------------------- /eliom/with-ocaml-vdom/simple/mixvdomandeliom.conf.in: -------------------------------------------------------------------------------- 1 | %%% This is the template for your configuration file. The %%VALUES%% below are 2 | %%% taken from the Makefile to generate the actual configuration files. 3 | %%% This comment will disappear. 4 | 5 | 6 | 7 | %%PORT%% 8 | %%% Only set for running, not for testing 9 | %%USERGROUP%% 10 | %%LOGDIR%% 11 | %%DATADIR%% 12 | utf-8 13 | %%% Only set when debugging 14 | %%DEBUGMODE%% 15 | %%CMDPIPE%% 16 | 17 | 18 | 19 | %%% This will include the packages defined as SERVER_PACKAGES in your Makefile: 20 | %%PACKAGES%% 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /eliom/with-ocaml-vdom/simple/mixvdomandeliom.eliom: -------------------------------------------------------------------------------- 1 | 2 | module Service = struct 3 | 4 | let random_value = 5 | Eliom_registration.Ocaml.create 6 | (* having a path is better to avoid broken links if the user doesn't refresh the page after a server restart *) 7 | ~path:(Eliom_service.Path ["rnd"]) 8 | ~meth:(Eliom_service.Get (Eliom_parameter.unit)) 9 | (fun () () -> 10 | let i = Random.int 200 in 11 | if i > 100 then 12 | Lwt.return_ok(Printf.sprintf "Ok: random value %u > 100" i) 13 | else 14 | Lwt.return_error(Printf.sprintf "Error: random value %u <= 100" i) 15 | ) 16 | 17 | end 18 | 19 | [%%client 20 | 21 | type 'msg Vdom.Cmd.t += 22 | | Service of { on_ok: (string -> 'msg); on_error: (string -> 'msg) } 23 | 24 | let create_service ~on_ok ~on_error = Service { on_ok; on_error } 25 | 26 | let run_service ~on_ok ~on_error () = 27 | let%lwt v = Eliom_client.call_ocaml_service ~service:~%Service.random_value () () in 28 | match v with 29 | | Ok v -> on_ok v 30 | | Error v -> on_error v 31 | 32 | let cmd_handler ctx = function 33 | | Service { on_ok; on_error } -> 34 | let _ = run_service 35 | ~on_ok:(fun s -> Lwt.return(Vdom_blit.Cmd.send_msg ctx (on_ok s))) 36 | ~on_error:(fun s -> Lwt.return(Vdom_blit.Cmd.send_msg ctx (on_error s))) 37 | () 38 | in 39 | true 40 | 41 | let () = Vdom_blit.(register (cmd {Vdom_blit.Cmd.f = cmd_handler})) 42 | 43 | type model = { 44 | counter_ok : int ; 45 | counter_error : int ; 46 | message : string ; 47 | } 48 | 49 | type action = 50 | | Call_service 51 | | Service_called_ok of string 52 | | Service_called_error of string 53 | 54 | let update m = function 55 | | Call_service -> ( 56 | Vdom.return m ~c:[create_service ~on_ok:(fun r -> Service_called_ok r) ~on_error:(fun r -> Service_called_error r)] 57 | ) 58 | | Service_called_ok v -> Vdom.return { m with counter_ok = m.counter_ok + 1; message = v } 59 | | Service_called_error v -> Vdom.return { m with counter_error = m.counter_error + 1; message = v } 60 | 61 | let init = Vdom.return { counter_ok = 0; counter_error = 0; message = "No message" } 62 | 63 | let button txt msg = 64 | Vdom.(input [] ~a:[onclick msg; type_button; value txt]) 65 | 66 | let view m = 67 | Vdom.( 68 | div [ 69 | div [ 70 | button "Fetch a random value from server" Call_service 71 | ] ; 72 | div [ 73 | text (Printf.sprintf "%u OK value(s)" m.counter_ok) ; 74 | text " / " ; 75 | text (Printf.sprintf "%u ERROR value(s)" m.counter_error) ; 76 | ] ; 77 | div [ 78 | text m.message 79 | ] ; 80 | ] 81 | ) 82 | 83 | let app = Vdom.app ~init ~view ~update () 84 | 85 | let run () = 86 | Vdom_blit.run app 87 | |> Vdom_blit.dom 88 | |> Js_browser.Element.append_child (Js_browser.Document.body Js_browser.document) 89 | let () = Js_browser.Window.set_onload Js_browser.window run 90 | ] 91 | 92 | module Mixvdomandeliom_app = 93 | Eliom_registration.App ( 94 | struct 95 | let application_name = "mixvdomandeliom" 96 | let global_data_path = None 97 | end) 98 | 99 | let main_service = 100 | Eliom_service.create 101 | ~path:(Eliom_service.Path []) 102 | ~meth:(Eliom_service.Get Eliom_parameter.unit) 103 | () 104 | 105 | let () = 106 | Mixvdomandeliom_app.register 107 | ~service:main_service 108 | (fun () () -> 109 | Lwt.return 110 | (Eliom_tools.F.html 111 | ~title:"mixvdomandeliom" 112 | ~css:[["css";"mixvdomandeliom.css"]] 113 | Eliom_content.Html.F.(body [ 114 | h1 [pcdata "Demo mix Vdom / Eliom"]; 115 | ]))) 116 | -------------------------------------------------------------------------------- /eliom/with-ocaml-vdom/simple/static/css/mixvdomandeliom.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: sans-serif; 3 | } 4 | 5 | h1, div { 6 | text-align: center; 7 | padding-bottom: 2em; 8 | } -------------------------------------------------------------------------------- /jsoo/build-local-csv-file/blob.ml: -------------------------------------------------------------------------------- 1 | open Js_of_ocaml 2 | 3 | class type options = object 4 | method _type : string Js.readonly_prop 5 | end 6 | 7 | let blob_uint8 = Js.Unsafe.global##._Blob -------------------------------------------------------------------------------- /jsoo/build-local-csv-file/blob.mli: -------------------------------------------------------------------------------- 1 | open Js_of_ocaml 2 | 3 | class type options = object 4 | method _type : string Js.readonly_prop 5 | end 6 | 7 | val blob_uint8 : (Typed_array.uint8Array Js.t Js.js_array Js.t -> options Js.t -> File.blob Js.t) Js.constr -------------------------------------------------------------------------------- /jsoo/build-local-csv-file/build.sh: -------------------------------------------------------------------------------- 1 | for f in `find . -name '*.ml*'` ; \ 2 | do ( \ 3 | ocp-indent -i $f; \ 4 | ); \ 5 | done 6 | 7 | ocamlbuild -use-ocamlfind \ 8 | -pkgs lwt_ppx,js_of_ocaml-lwt,js_of_ocaml-ppx,js_of_ocaml-tyxml,tyxml,react,reactiveData \ 9 | main.byte ; 10 | 11 | js_of_ocaml --opt 3 -o www/js/main.js main.byte 12 | -------------------------------------------------------------------------------- /jsoo/build-local-csv-file/main.ml: -------------------------------------------------------------------------------- 1 | 2 | (* 3 | Polyfill for the Encoding Living Standard's API: 4 | https://github.com/inexorabletash/text-encoding 5 | *) 6 | 7 | open Lwt.Infix 8 | open Js_of_ocaml 9 | 10 | module Model = struct 11 | 12 | type t = 13 | (* tiny href * big href *) 14 | Js.js_string Js.t option * Js.js_string Js.t option 15 | 16 | let csv_sep = "," 17 | 18 | let nb_lines_tiny = 3 19 | let nb_cols_tiny = 4 20 | 21 | let nb_lines_big = 400 22 | let nb_cols_big = 20 23 | 24 | let make_data nb_lines nb_cols = 25 | let data = Array.make_matrix nb_lines nb_cols "" in 26 | for i = 0 to (nb_lines - 1) do 27 | for j = 0 to (nb_cols - 1) do 28 | data.(i).(j) <- Printf.sprintf "Cell é è ç à ù € ∀ ◉ %u.%u" i j 29 | done ; 30 | done ; 31 | data 32 | 33 | let data_tiny = make_data nb_lines_tiny nb_cols_tiny 34 | 35 | let data_big = make_data nb_lines_big nb_cols_big 36 | 37 | let empty = (None, None) 38 | 39 | let csv_headers data = 40 | let nb_cols = Array.length data.(0) in 41 | let s = ref "" in 42 | for i = 0 to (nb_cols - 1) do 43 | if i = (nb_cols - 1) then 44 | s := !s ^ (Printf.sprintf "Column %u" i) 45 | else 46 | s := !s ^ (Printf.sprintf "Column %u%s" i csv_sep) 47 | done ; 48 | !s 49 | 50 | let csv_body data = 51 | let nb_cols = Array.length data.(0) in 52 | let s = ref "" in 53 | let column i v = 54 | if i > 0 && i < nb_cols then 55 | s := !s ^ csv_sep ^ v 56 | else 57 | s := !s ^ v 58 | in 59 | let line l = 60 | Array.iteri column l ; 61 | s := !s ^ "\n" 62 | in 63 | Array.iter line data ; 64 | !s 65 | 66 | let csv data = 67 | (csv_headers data) ^ "\n" ^ (csv_body data) 68 | 69 | end 70 | 71 | type rs = Model.t React.signal 72 | type rf = ?step:React.step -> Model.t -> unit 73 | type rp = rs * rf 74 | 75 | module Action = struct 76 | 77 | type t = 78 | | Download_tiny_csv 79 | | Download_big_csv 80 | 81 | end 82 | 83 | module Controller = struct 84 | 85 | open Action 86 | 87 | let encode_href href v = 88 | let blob = Blob.blob_uint8 in 89 | let encoder = TextEncoder.textEncoder in 90 | let url = (Dom_html.window)##._URL in 91 | 92 | let () = 93 | match href with 94 | | None -> () 95 | | Some h -> url##revokeObjectURL h 96 | in 97 | 98 | (* add BOM as first character to force the right encoding 99 | when the url is opened by the spreadsheet software *) 100 | let utf8_bom = new%js Typed_array.uint8Array 3 in 101 | let () = Typed_array.set utf8_bom 0 0xEF in 102 | let () = Typed_array.set utf8_bom 1 0xBB in 103 | let () = Typed_array.set utf8_bom 2 0xBF in 104 | 105 | let opt = object%js 106 | val _type = "text/csv;charset=utf-8" 107 | end in 108 | let e = new%js encoder in 109 | let data = e##encode_uint8 (Js.string v) in 110 | let o = new%js blob (Js.array [| utf8_bom; data |]) opt in 111 | let url = url##createObjectURL o in 112 | url 113 | 114 | let update a ((rs, rf) : rp) = 115 | let href_tiny, href_big = React.S.value rs in 116 | let m = 117 | match a with 118 | | Download_tiny_csv -> 119 | let csv = Model.(csv data_tiny) in 120 | let href_tiny = encode_href href_tiny csv in 121 | (Some href_tiny, href_big) 122 | | Download_big_csv -> 123 | let csv = Model.(csv data_big) in 124 | let href_big = encode_href href_big csv in 125 | (href_tiny, Some href_big) 126 | in 127 | rf m 128 | 129 | end 130 | 131 | module View = struct 132 | 133 | open Action 134 | open Js_of_ocaml_tyxml.Tyxml_js 135 | 136 | let link_tiny_csv (rs, rf) = 137 | let onclick evt = 138 | Controller.update Download_tiny_csv (rs, rf) ; 139 | true (* return true because we must follow the link *) 140 | in 141 | let href = React.S.map (fun (v, _) -> match v with None -> "#" | Some h -> Js.to_string h) rs in 142 | Html.(a ~a:[a_onclick onclick; a_download (Some "tinyfile.csv"); 143 | R.Html.a_href href] [txt "Get CSV from above"]) 144 | 145 | let link_big_csv (rs, rf) = 146 | let onclick evt = 147 | Controller.update Download_big_csv (rs, rf) ; 148 | true (* return true because we must follow the link *) 149 | in 150 | let href = React.S.map (fun (_, v) -> match v with None -> "#" | Some h -> Js.to_string h) rs in 151 | Html.(a ~a:[a_onclick onclick; a_download (Some "bigfile.csv"); 152 | R.Html.a_href href] [txt "Get bigger CSV (not displayed)"]) 153 | 154 | let table (rs, rf) = 155 | let cells data = 156 | let line (acc, i) l = 157 | let f (acc, j) v = 158 | let td = Html.(td [txt v]) in 159 | (td :: acc, j + 1) 160 | in 161 | let (ltd, _) = Array.fold_left f ([], 0) l in 162 | let ltd = List.rev ltd in 163 | (Html.(tr ltd) :: acc, i + 1) 164 | in 165 | let (ltd, _) = Array.fold_left line ([], 0) data in 166 | List.rev ltd 167 | in 168 | Html.(tablex [ Html.tbody (cells Model.data_tiny) ]) 169 | 170 | let view (rs, rf) = 171 | let cells = table (rs, rf) in 172 | Html.( 173 | div [ 174 | p ~a:[a_class ["comments"]] [ 175 | txt "How to build and download a CSV file without any server processing and only from client side data." ; 176 | br () ; 177 | txt "This demo has been tested with Firefox, Chrome and Edge. But please note that a " ; 178 | a ~a:[a_href "https://github.com/inexorabletash/text-encoding"] [ txt "polyfill" ] ; 179 | txt " is necessary for Edge." ; 180 | ] ; 181 | div ~a:[a_class ["cells"]] [ cells ] ; 182 | div ~a:[a_class ["tiny-csv"]] [ link_tiny_csv (rs, rf) ] ; 183 | div ~a:[a_class ["big-csv"]] [ link_big_csv (rs, rf) ] ; 184 | ] 185 | ) 186 | 187 | end 188 | 189 | let main _ = 190 | let doc = Dom_html.document in 191 | let parent = 192 | Js.Opt.get (doc##getElementById(Js.string "main")) 193 | (fun () -> assert false) 194 | in 195 | let m = Model.empty in 196 | let rp = React.S.create m in 197 | Dom.appendChild parent (Js_of_ocaml_tyxml.Tyxml_js.To_dom.of_div (View.view rp)) ; 198 | Lwt.return () 199 | 200 | let _ = Js_of_ocaml_lwt.Lwt_js_events.onload () >>= main -------------------------------------------------------------------------------- /jsoo/build-local-csv-file/textEncoder.ml: -------------------------------------------------------------------------------- 1 | open Js_of_ocaml 2 | 3 | class type t = object 4 | method encode_uint8 : Js.js_string Js.t -> Typed_array.uint8Array Js.t Js.meth 5 | end 6 | 7 | let textEncoder = Js.Unsafe.global##._TextEncoder -------------------------------------------------------------------------------- /jsoo/build-local-csv-file/textEncoder.mli: -------------------------------------------------------------------------------- 1 | open Js_of_ocaml 2 | 3 | class type t = object 4 | method encode_uint8 : Js.js_string Js.t -> Typed_array.uint8Array Js.t Js.meth 5 | end 6 | 7 | val textEncoder : t Js.t Js.constr -------------------------------------------------------------------------------- /jsoo/build-local-csv-file/www/css/style.css: -------------------------------------------------------------------------------- 1 | table { 2 | margin: auto; 3 | color: #333; 4 | font-family: Helvetica, Arial, sans-serif; 5 | border-spacing: 0; 6 | } 7 | 8 | tbody td { 9 | padding: 1em; 10 | border: 1px solid transparent; 11 | transition: all 0.3s; 12 | } 13 | 14 | tbody td { 15 | background: #FAFAFA; 16 | text-align: right; 17 | width: 5.25em; 18 | } 19 | 20 | tbody tr:nth-child(even) td { 21 | background: #F1F1F1; 22 | } 23 | 24 | tbody tr:nth-child(odd) td { 25 | background: #FEFEFE; 26 | } 27 | 28 | tfoot td { 29 | font-weight: bold; 30 | text-align: right; 31 | padding: 1em; 32 | border-top: 1px solid black; 33 | } 34 | 35 | .tiny-csv, .big-csv { 36 | margin-top: 2em; 37 | text-align: center; 38 | } 39 | 40 | .comments { 41 | text-align: center; 42 | margin: 2em; 43 | } -------------------------------------------------------------------------------- /jsoo/build-local-csv-file/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Build local CSV file 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /jsoo/curriculum-vitae/_tags: -------------------------------------------------------------------------------- 1 | true: principal, safe_string, warn_A, warn(-E) 2 | -------------------------------------------------------------------------------- /jsoo/curriculum-vitae/action.ml: -------------------------------------------------------------------------------- 1 | open Types 2 | 3 | type action = 4 | | Update_lang of I18n.lang 5 | | Portfolio_details of int 6 | | Portfolio_summary of int 7 | -------------------------------------------------------------------------------- /jsoo/curriculum-vitae/build.sh: -------------------------------------------------------------------------------- 1 | 2 | ocamlbuild -use-ocamlfind \ 3 | -pkgs js_of_ocaml-lwt,js_of_ocaml-ppx,js_of_ocaml-tyxml,tyxml,react,reactiveData \ 4 | main.byte ; 5 | 6 | js_of_ocaml --opt 3 -o js/main.js main.byte 7 | -------------------------------------------------------------------------------- /jsoo/curriculum-vitae/controller.ml: -------------------------------------------------------------------------------- 1 | open Types 2 | open Action 3 | 4 | let update a ((r, f) : rp) = 5 | let (page, cv) = React.S.value r in 6 | let cv = match a with 7 | | Update_lang lang -> 8 | { cv with CV.lang = lang } 9 | | Portfolio_details _ 10 | | Portfolio_summary _ -> cv 11 | in 12 | let page = match a with 13 | | Portfolio_details n -> 14 | let l = Page.portfolio page in 15 | let l = List.remove_assoc n l in 16 | let l = (n, Page.Details) :: l in 17 | let l = List.stable_sort (fun (i1, _) (i2, _) -> compare i1 i2) l in 18 | { Page.portfolio = l } 19 | | Portfolio_summary n -> 20 | let l = Page.portfolio page in 21 | let l = List.remove_assoc n l in 22 | let l = (n, Page.Summary) :: l in 23 | let l = List.stable_sort (fun (i1, _) (i2, _) -> compare i1 i2) l in 24 | { Page.portfolio = l } 25 | | Update_lang _ -> page 26 | in 27 | f (page, cv) 28 | -------------------------------------------------------------------------------- /jsoo/curriculum-vitae/img/ID-100165338.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slegrand45/examples_ocsigen/e2f5efe57caf7a644795ac6b14f6d6e04168e4be/jsoo/curriculum-vitae/img/ID-100165338.jpg -------------------------------------------------------------------------------- /jsoo/curriculum-vitae/img/ID-100209817.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slegrand45/examples_ocsigen/e2f5efe57caf7a644795ac6b14f6d6e04168e4be/jsoo/curriculum-vitae/img/ID-100209817.jpg -------------------------------------------------------------------------------- /jsoo/curriculum-vitae/img/ID-100371977.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slegrand45/examples_ocsigen/e2f5efe57caf7a644795ac6b14f6d6e04168e4be/jsoo/curriculum-vitae/img/ID-100371977.jpg -------------------------------------------------------------------------------- /jsoo/curriculum-vitae/img/darth-vader.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slegrand45/examples_ocsigen/e2f5efe57caf7a644795ac6b14f6d6e04168e4be/jsoo/curriculum-vitae/img/darth-vader.jpg -------------------------------------------------------------------------------- /jsoo/curriculum-vitae/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Curriculum vitae - Stéphane Legrand 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /jsoo/curriculum-vitae/main.ml: -------------------------------------------------------------------------------- 1 | open Js_of_ocaml 2 | open Lwt.Infix 3 | open Types 4 | 5 | (* https://github.com/ocsigen/js_of_ocaml/blob/master/examples/hyperbolic/hypertree.ml *) 6 | let default_language () = 7 | Js.to_string( 8 | (Js.Optdef.get (Dom_html.window##.navigator##.language) 9 | (fun () -> Js.Optdef.get (Dom_html.window##.navigator##.userLanguage) 10 | (fun () -> Js.string "en")) 11 | )##substring 0 2) 12 | 13 | let main _ = 14 | let doc = Dom_html.document in 15 | let parent = 16 | Js.Opt.get (doc##getElementById(Js.string "main")) 17 | (fun () -> assert false) 18 | in 19 | let lang = 20 | match default_language () with 21 | | "en" -> I18n.En 22 | | _ -> I18n.Fr 23 | in 24 | let m = Model.init lang in 25 | let rp = React.S.create m in 26 | Dom.appendChild parent (Js_of_ocaml_tyxml.Tyxml_js.To_dom.of_div (View.view rp)) ; 27 | Lwt.return () 28 | 29 | let _ = Js_of_ocaml_lwt.Lwt_js_events.onload () >>= main 30 | -------------------------------------------------------------------------------- /jsoo/curriculum-vitae/rl.ml: -------------------------------------------------------------------------------- 1 | let list t = 2 | let open ReactiveData.RList in 3 | make_from 4 | (React.S.value t) 5 | (React.E.map (fun e -> Set e) (React.S.changes t)) -------------------------------------------------------------------------------- /jsoo/curriculum-vitae/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | line-height: 1.5; 3 | font-family: "Roboto",sans-serif; 4 | font-weight: normal; 5 | color: #ffffff; 6 | } 7 | 8 | body { 9 | background-color: black; 10 | } 11 | 12 | header { 13 | height: 200px; 14 | } 15 | 16 | i { 17 | vertical-align: middle; 18 | } 19 | 20 | a:active, a:hover { 21 | outline: 0px none; 22 | } 23 | 24 | a { 25 | text-decoration: none; 26 | background: transparent none repeat scroll 0% 0%; 27 | color: white; 28 | } 29 | 30 | p { 31 | margin: 0; 32 | line-height: inherit; 33 | } 34 | 35 | .mdl-layout__header { 36 | z-index: 0; 37 | background-color: black; 38 | } 39 | 40 | .mdl-layout__content { 41 | width: 100%; 42 | } 43 | 44 | .container { 45 | position: relative; 46 | width: 80%; 47 | margin: auto; 48 | } 49 | 50 | .content { 51 | background-color: black; 52 | box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.26); 53 | border-radius: 2px; 54 | overflow: hidden; 55 | margin-top: -190px; 56 | } 57 | 58 | .center { 59 | text-align: center; 60 | } 61 | 62 | .red-text { 63 | color: red; 64 | } 65 | 66 | .light { 67 | font-weight: 300; 68 | } 69 | 70 | .left-side .block { 71 | margin-bottom: 40px; 72 | } 73 | 74 | .right-side { 75 | padding-left: 5em; 76 | } 77 | 78 | .right-side .block { 79 | margin-bottom: 1.50em; 80 | } 81 | 82 | .left-side .block i, .right-side i { 83 | margin-right: 0.25em; 84 | } 85 | 86 | .right-side i.material-icons:nth-child(2) { 87 | margin-left: 0.25em; 88 | } 89 | 90 | .left-side .block p i,.right-side .block p i { 91 | font-size: 100%; 92 | } 93 | 94 | .left-side .block h6, .right-side .block h5 { 95 | margin-top: 0.25em; 96 | margin-bottom: 0.25em; 97 | } 98 | 99 | .right-side i { 100 | font-size: 34px; 101 | line-height: 40px; 102 | } 103 | 104 | .progress { 105 | height: 4px; 106 | border-radius: 0px; 107 | box-shadow: none; 108 | background: black none repeat scroll 0% 0%; 109 | } 110 | 111 | .progress-bar { 112 | box-shadow: none; 113 | float: left; 114 | width: 0px; 115 | height: 100%; 116 | font-size: 12px; 117 | line-height: 20px; 118 | color: #FFF; 119 | text-align: center; 120 | background-color: #d93117; 121 | transition: width 0.6s ease 0s; 122 | } 123 | 124 | .portfolio { 125 | padding: 0; 126 | } 127 | 128 | .mdl-card { 129 | width: 100%; 130 | color: black; 131 | } 132 | 133 | .mdl-card__actions { 134 | display: flex; 135 | box-sizing:border-box; 136 | align-items: center; 137 | } 138 | 139 | .mdl-mini-footer { 140 | background-color: black; 141 | padding-top: 0; 142 | margin-top: 0; 143 | } 144 | 145 | .left-side .user-img { 146 | display: block; 147 | margin: 0px auto; 148 | width: 50%; 149 | } 150 | 151 | img.circle { 152 | border-radius: 50%; 153 | background-clip: padding-box; 154 | } 155 | 156 | img.responsive-img { 157 | max-width: 100%; 158 | height: auto; 159 | } 160 | 161 | img { 162 | border: 0px none; 163 | } -------------------------------------------------------------------------------- /jsoo/curriculum-vitae/types.ml: -------------------------------------------------------------------------------- 1 | module I18n = struct 2 | type lang = En | Fr 3 | 4 | type t = (lang * string) 5 | 6 | type translation = t list 7 | 8 | type msg = 9 | Contact_title 10 | | Skill_title 11 | | Language_title 12 | | Work_title 13 | | Education_title 14 | | Portfolio_title 15 | 16 | let make ~lang ~s = 17 | (lang, s) 18 | 19 | let msg ~id ~l = 20 | (id, l) 21 | 22 | let translate lang l = 23 | try 24 | List.assoc lang l 25 | with 26 | | Not_found -> "" 27 | 28 | let get_msg id l = 29 | List.assoc id l 30 | end 31 | 32 | module Calendar = struct 33 | type t = { 34 | day : int option ; 35 | month : int option ; 36 | year : int ; 37 | } 38 | 39 | let make ~day ~month ~year = 40 | { day; month; year } 41 | 42 | let translate lang cal = 43 | match cal.day, cal.month with 44 | | None, None -> ( 45 | Printf.sprintf "%04d" cal.year 46 | ) 47 | | Some day, Some month -> ( 48 | match lang with 49 | | I18n.Fr -> Printf.sprintf "%02d/%02d/%04d" 50 | day month cal.year 51 | | I18n.En -> Printf.sprintf "%04d/%02d/%02d" 52 | cal.year month day 53 | ) 54 | | None, Some month -> ( 55 | match lang with 56 | | I18n.Fr -> Printf.sprintf "%02d/%04d" 57 | month cal.year 58 | | I18n.En -> Printf.sprintf "%04d/%02d" 59 | cal.year month 60 | ) 61 | | Some _, None -> ( 62 | assert false 63 | ) 64 | end 65 | 66 | module Date = struct 67 | type t = 68 | Now 69 | | Date of Calendar.t 70 | 71 | let translate_date lang d = 72 | match d with 73 | | None -> "" 74 | | Some v -> ( 75 | match v with 76 | | Now -> ( 77 | match lang with 78 | | I18n.Fr -> "aujourd'hui" 79 | | I18n.En -> "now" 80 | ) 81 | | Date cal -> Calendar.translate lang cal 82 | ) 83 | 84 | let translate_start_end lang date_start date_end = 85 | if date_start = date_end then 86 | Printf.sprintf "%s" (translate_date lang date_start) 87 | else 88 | match lang with 89 | | I18n.Fr -> Printf.sprintf "de %s à %s" 90 | (translate_date lang date_start) (translate_date lang date_end) 91 | | I18n.En -> Printf.sprintf "from %s to %s" 92 | (translate_date lang date_start) (translate_date lang date_end) 93 | end 94 | 95 | module ID = struct 96 | type t = { 97 | firstname : I18n.translation ; 98 | lastname : I18n.translation ; 99 | age : int ; 100 | phone : I18n.translation ; 101 | address : I18n.translation ; 102 | email : string ; 103 | github : string ; 104 | web : string ; 105 | } 106 | 107 | let make ~firstname ~lastname ~age ~phone ~address ~email ~github ~web = 108 | { firstname; lastname; age; phone; address; email; github; web } 109 | 110 | let firstname id = id.firstname 111 | 112 | let lastname id = id.lastname 113 | 114 | let age id = id.age 115 | 116 | let phone id = id.phone 117 | 118 | let address id = id.address 119 | 120 | let email id = id.email 121 | 122 | let github id = id.github 123 | 124 | let web id = id.web 125 | end 126 | 127 | module Skill = struct 128 | type t = { 129 | title : I18n.translation ; 130 | percent : int ; 131 | } 132 | 133 | let make title percent = 134 | { title; percent } 135 | end 136 | 137 | module Experience = struct 138 | type t = { 139 | date_start : Date.t option ; 140 | date_end : Date.t option ; 141 | title : I18n.translation ; 142 | company : I18n.translation option ; 143 | location : I18n.translation option ; 144 | description : I18n.translation ; 145 | } 146 | 147 | let make ~date_start ~date_end ~title ~company ~location ~description = 148 | { date_start; date_end; title; company; location; description } 149 | end 150 | 151 | module Education = struct 152 | type category = Diploma | Certification | Vocational_training 153 | 154 | type t = { 155 | category : category ; 156 | date_start : Date.t option ; 157 | date_end : Date.t option ; 158 | title : I18n.translation ; 159 | school : I18n.translation option ; 160 | description : I18n.translation ; 161 | } 162 | 163 | let translate_category lang cat = 164 | match cat with 165 | | Diploma -> ( 166 | match lang with 167 | | I18n.Fr -> "Diplômantes" 168 | | I18n.En -> "Diploma" 169 | ) 170 | | Certification -> ( 171 | match lang with 172 | | I18n.Fr -> "Certifiantes" 173 | | I18n.En -> "Certification" 174 | ) 175 | | Vocational_training -> ( 176 | match lang with 177 | | I18n.Fr -> "Continues" 178 | | I18n.En -> "Vocational training" 179 | ) 180 | 181 | let make ~category ~date_start ~date_end ~title ~school ~description = 182 | { category; date_start; date_end; title; school; description } 183 | 184 | let diploma edu = 185 | List.filter (fun e -> match e.category with Diploma -> true | _ -> false) edu 186 | 187 | let certification edu = 188 | List.filter (fun e -> match e.category with Certification -> true | _ -> false) edu 189 | 190 | let vocational_training edu = 191 | List.filter (fun e -> match e.category with Vocational_training -> true | _ -> false) edu 192 | end 193 | 194 | module Language = struct 195 | type t = { 196 | title : I18n.translation ; 197 | description : I18n.translation ; 198 | } 199 | 200 | let make ~title ~description = 201 | { title; description } 202 | end 203 | 204 | module Portfolio = struct 205 | type t = { 206 | title : I18n.translation ; 207 | description : I18n.translation ; 208 | image : string ; 209 | } 210 | 211 | let make ~title ~description ~image = 212 | { title; description; image } 213 | end 214 | 215 | module CV = struct 216 | type t = { 217 | lang : I18n.lang ; 218 | title : I18n.translation ; 219 | description : I18n.translation ; 220 | id : ID.t ; 221 | skill : Skill.t list ; 222 | experience : Experience.t list ; 223 | education : Education.t list ; 224 | language : Language.t list ; 225 | portfolio : Portfolio.t list ; 226 | } 227 | 228 | let make ~lang ~title ~description ~id ~skill ~experience ~education ~language ~portfolio = 229 | { lang; title; description; id; skill; experience; education; language; portfolio } 230 | 231 | let lang cv = cv.lang 232 | 233 | let title cv = cv.title 234 | 235 | let description cv = cv.description 236 | 237 | let id cv = cv.id 238 | 239 | let skill cv = cv.skill 240 | 241 | let experience cv = cv.experience 242 | 243 | let education cv = cv.education 244 | 245 | let language cv = cv.language 246 | 247 | let portfolio cv = cv.portfolio 248 | end 249 | 250 | module Page = struct 251 | type status = Summary | Details 252 | 253 | type t = { 254 | portfolio : (int * status) list 255 | } 256 | 257 | let make ~portfolio = 258 | { portfolio } 259 | 260 | let portfolio p = p.portfolio 261 | end 262 | 263 | type t = (Page.t * CV.t) 264 | 265 | type rs = t React.signal 266 | type rf = ?step:React.step -> t -> unit 267 | type rp = rs * rf 268 | -------------------------------------------------------------------------------- /jsoo/lsystem/build.sh: -------------------------------------------------------------------------------- 1 | for f in `find . -name '*.ml*'` ; \ 2 | do ( \ 3 | ocp-indent -i $f; \ 4 | ); \ 5 | done 6 | 7 | ocamlbuild -use-ocamlfind \ 8 | -pkgs lwt_ppx,js_of_ocaml-lwt,js_of_ocaml-ppx,js_of_ocaml-tyxml,tyxml,react,reactiveData \ 9 | lsystem.byte ; 10 | 11 | js_of_ocaml --opt 3 -o www/js/lsystem.js lsystem.byte 12 | -------------------------------------------------------------------------------- /jsoo/lsystem/lsystem.ml: -------------------------------------------------------------------------------- 1 | (* See "The Algorithmic Beauty of Plants" 2 | by Przemyslaw Prusinkiewicz and Aristid Lindenmayer. 3 | http://algorithmicbotany.org/papers/abop/abop.pdf *) 4 | 5 | open Lwt.Infix 6 | open Js_of_ocaml 7 | 8 | module Config = struct 9 | 10 | type t = { 11 | width : float ; 12 | height : float ; 13 | branch_length : float ; 14 | theta : float ; 15 | } 16 | 17 | let init width height = 18 | let branch_length = height *. 0.0175 in 19 | let theta = 22.5 in 20 | { width; height; branch_length; theta } 21 | 22 | end 23 | 24 | module Model = struct 25 | 26 | type alphabet = Plus | Minus | Push | Pop | F 27 | 28 | type turtle = (float * float * float) (* x, y, alpha *) 29 | 30 | let rules char = 31 | match char with 32 | | F -> [F; F; Minus; Push; Minus; F; Plus; F; Plus; F; Pop; Plus; Push; Plus; F; Minus; F; Minus; F; Pop] 33 | | Plus -> [Plus] 34 | | Minus -> [Minus] 35 | | Push -> [Push] 36 | | Pop -> [Pop] 37 | 38 | let apply rules n l = 39 | let replace acc e = 40 | let v = rules e in 41 | List.rev_append v acc 42 | in 43 | let rec f acc n = 44 | if n > 0 then ( 45 | let l = List.rev(List.fold_left replace [] acc) in 46 | f l (n - 1) 47 | ) 48 | else 49 | acc 50 | in 51 | f l n 52 | 53 | let init = [F] 54 | 55 | let empty conf = 56 | let turtle = (conf.Config.width /. 2., conf.Config.height, 90.) in 57 | let stack : turtle Stack.t = Stack.create () in 58 | (turtle, stack, 0, []) 59 | 60 | end 61 | 62 | module View = struct 63 | 64 | let depth_to_class d = 65 | Printf.sprintf "depth%d" d 66 | 67 | let tree conf l = 68 | let open Model in 69 | let open Config in 70 | let pi = acos (- 1.) in 71 | let f ((x, y, alpha), stack, depth, lines) e = 72 | let d = conf.branch_length in 73 | let theta = conf.theta in 74 | match e with 75 | | F -> 76 | (* degree to radian *) 77 | let alpha' = pi *. (alpha /. 180.) in 78 | let x' = x +. (d *. (cos alpha')) in 79 | let y' = abs_float(((abs_float(y -. conf.height)) +. (d *. (sin alpha'))) -. conf.height) in 80 | let line = Js_of_ocaml_tyxml.Tyxml_js.Svg.( 81 | line ~a:[a_class [(depth_to_class depth)]; 82 | a_x1 (x, Some `Px); a_y1 (y, Some `Px); a_x2 (x', Some `Px); a_y2 (y', Some `Px)] [] 83 | ) 84 | in 85 | ((x', y', alpha), stack, depth, line :: lines) 86 | | Plus -> 87 | ((x, y, alpha +. theta), stack, depth, lines) 88 | | Minus -> 89 | ((x, y, alpha -. theta), stack, depth, lines) 90 | | Push -> 91 | let () = Stack.push (x, y, alpha) stack in 92 | ((x, y, alpha), stack, depth + 1, lines) 93 | | Pop -> 94 | let (x', y', alpha') = Stack.pop stack in 95 | ((x', y', alpha'), stack, depth - 1, lines) 96 | in 97 | List.fold_left f (Model.empty conf) l 98 | 99 | let draw conf node l = 100 | let (_, _, _, lines) = tree conf l in 101 | let svg = Js_of_ocaml_tyxml.Tyxml_js.Html5.(svg lines) in 102 | Dom.appendChild node (Js_of_ocaml_tyxml.Tyxml_js.To_dom.of_node svg) 103 | 104 | end 105 | 106 | let start conf node = 107 | let l = 108 | try 109 | Model.apply Model.rules 4 Model.init 110 | with 111 | | e -> Firebug.console##debug e; raise e; 112 | in 113 | View.draw conf node l ; 114 | Lwt.return () 115 | 116 | let main _ = 117 | let doc = Dom_html.document in 118 | Js.Opt.case (doc##getElementById(Js.string "lsystem")) 119 | (fun _ -> assert false) 120 | (fun e -> 121 | let width = float_of_int e##.clientWidth in 122 | let height = float_of_int e##.clientHeight in 123 | let conf = Config.init width height in 124 | start conf e) 125 | 126 | let _ = Js_of_ocaml_lwt.Lwt_js_events.onload () >>= main 127 | -------------------------------------------------------------------------------- /jsoo/lsystem/www/css/style.css: -------------------------------------------------------------------------------- 1 | div#lsystem { 2 | margin: auto; 3 | width: 100%; 4 | height: 95vh; 5 | background-color: black; 6 | } 7 | 8 | svg { 9 | margin: auto; 10 | width: 100%; 11 | height: 100%; 12 | background-color: black; 13 | } 14 | 15 | .depth0 { 16 | stroke: DarkOrange; 17 | fill: DarkOrange; 18 | stroke-width: 0.15%; 19 | } 20 | 21 | .depth1 { 22 | stroke: Orange; 23 | fill: Orange; 24 | stroke-width: 0.13%; 25 | } 26 | 27 | .depth2 { 28 | stroke: GoldenRod; 29 | fill: GoldenRod; 30 | stroke-width: 0.11%; 31 | } 32 | 33 | .depth3 { 34 | stroke: Gold; 35 | fill: Gold; 36 | stroke-width: 0.09%; 37 | } 38 | 39 | .depth4 { 40 | stroke: Yellow; 41 | fill: Yellow; 42 | stroke-width: 0.07%; 43 | } -------------------------------------------------------------------------------- /jsoo/lsystem/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lindenmayer system 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /jsoo/pico-spreadsheet/build.sh: -------------------------------------------------------------------------------- 1 | for f in `find . -name '*.ml*'` ; \ 2 | do ( \ 3 | ocp-indent -i $f; \ 4 | ); \ 5 | done 6 | 7 | ocamlbuild -use-ocamlfind \ 8 | -pkgs lwt_ppx,js_of_ocaml-lwt,js_of_ocaml-ppx,js_of_ocaml-tyxml,tyxml,react,reactiveData \ 9 | spreadsheet.byte ; 10 | 11 | js_of_ocaml --opt 3 -o www/js/spreadsheet.js spreadsheet.byte 12 | -------------------------------------------------------------------------------- /jsoo/pico-spreadsheet/www/css/style.css: -------------------------------------------------------------------------------- 1 | table { 2 | margin: auto; 3 | color: #333; 4 | font-family: Helvetica, Arial, sans-serif; 5 | border-spacing: 0; 6 | } 7 | 8 | tbody td { 9 | padding: 1em; 10 | border: 1px solid transparent; 11 | transition: all 0.3s; 12 | } 13 | 14 | tbody td { 15 | background: #FAFAFA; 16 | text-align: right; 17 | width: 5.25em; 18 | } 19 | 20 | tbody td input { 21 | width: 5em; 22 | } 23 | 24 | tbody tr:nth-child(even) td { 25 | background: #F1F1F1; 26 | } 27 | 28 | tbody tr:nth-child(odd) td { 29 | background: #FEFEFE; 30 | } 31 | 32 | tbody tr td:hover { 33 | background: #666; 34 | color: #FFF; 35 | } 36 | 37 | tfoot td { 38 | font-weight: bold; 39 | text-align: right; 40 | padding: 1em; 41 | border-top: 1px solid black; 42 | } 43 | 44 | svg { 45 | margin: auto; 46 | display: block; 47 | background-color: PaleGreen; 48 | } 49 | 50 | svg circle { 51 | stroke: white; 52 | stroke-width: 3; 53 | fill: #00554d; 54 | } 55 | 56 | svg path { 57 | fill: DarkGreen; 58 | } 59 | 60 | div.comments { 61 | margin: auto; 62 | padding-top: 1em; 63 | padding-bottom: 2em; 64 | font-family: Helvetica, Arial, sans-serif; 65 | font-weight: bold; 66 | text-align: center; 67 | } 68 | -------------------------------------------------------------------------------- /jsoo/pico-spreadsheet/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pico Spreadsheet 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /jsoo/pong/build.sh: -------------------------------------------------------------------------------- 1 | for f in `find . -name '*.ml*'` ; \ 2 | do ( \ 3 | ocp-indent -i $f; \ 4 | ); \ 5 | done 6 | 7 | ocamlbuild -use-ocamlfind \ 8 | -pkgs lwt_ppx,js_of_ocaml-lwt,js_of_ocaml-ppx,js_of_ocaml-tyxml,tyxml,react,reactiveData \ 9 | pong.byte ; 10 | 11 | js_of_ocaml --opt 3 -o www/js/pong.js pong.byte 12 | -------------------------------------------------------------------------------- /jsoo/pong/www/css/style.css: -------------------------------------------------------------------------------- 1 | div#pong { 2 | margin: auto; 3 | width: 660px; 4 | } -------------------------------------------------------------------------------- /jsoo/pong/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pong 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /jsoo/tic-tac-toe/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | 3 | *.byte 4 | -------------------------------------------------------------------------------- /jsoo/tic-tac-toe/action.ml: -------------------------------------------------------------------------------- 1 | open Types 2 | 3 | type action = 4 | | Click_cell of cell -------------------------------------------------------------------------------- /jsoo/tic-tac-toe/build.sh: -------------------------------------------------------------------------------- 1 | 2 | ocamlbuild -use-ocamlfind \ 3 | -pkgs lwt_ppx,js_of_ocaml-lwt,js_of_ocaml-ppx,js_of_ocaml-tyxml,tyxml,react,reactiveData \ 4 | main.byte ; 5 | 6 | js_of_ocaml --opt 3 -o js/main.js main.byte 7 | -------------------------------------------------------------------------------- /jsoo/tic-tac-toe/controller.ml: -------------------------------------------------------------------------------- 1 | open Types 2 | 3 | let update a ((r, f) : rp) = 4 | let m = React.S.value r in 5 | let m = 6 | match a with 7 | | Action.Click_cell cell -> 8 | match m with 9 | | Player_x_to_move (board, valid_moves) -> 10 | if (Model.cell_in_valid_moves_x valid_moves cell) then 11 | let new_game_board = { cells = Model.update_cell board cell Player_x } in 12 | let new_valid_moves = Model.valid_moves_for_player new_game_board (fun e -> Player_o_pos e) in 13 | if (Model.game_won_by_player new_game_board Player_x) then Game_won (new_game_board, Player_x) 14 | else match new_valid_moves with 15 | | [] -> Game_tied new_game_board 16 | | _ -> Player_o_to_move (new_game_board, new_valid_moves) 17 | else m 18 | | Player_o_to_move (board, valid_moves) -> 19 | if (Model.cell_in_valid_moves_o valid_moves cell) then 20 | let new_game_board = { cells = Model.update_cell board cell Player_o } in 21 | let new_valid_moves = Model.valid_moves_for_player new_game_board (fun e -> Player_x_pos e) in 22 | if (Model.game_won_by_player new_game_board Player_o) then Game_won (new_game_board, Player_o) 23 | else match new_valid_moves with 24 | | [] -> Game_tied new_game_board 25 | | _ -> Player_x_to_move (new_game_board, new_valid_moves) 26 | else m 27 | | Game_won _ 28 | | Game_tied _ -> m 29 | in 30 | f m -------------------------------------------------------------------------------- /jsoo/tic-tac-toe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Js_of_ocaml • Tic-Tac-Toe 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /jsoo/tic-tac-toe/main.ml: -------------------------------------------------------------------------------- 1 | open Lwt.Infix 2 | open Types 3 | open Js_of_ocaml 4 | 5 | let main _ = 6 | let doc = Dom_html.document in 7 | let parent = 8 | Js.Opt.get (doc##getElementById(Js.string "main")) 9 | (fun () -> assert false) 10 | in 11 | let m = Model.empty_game in 12 | let rp = React.S.create m in 13 | Dom.appendChild parent (Js_of_ocaml_tyxml.Tyxml_js.To_dom.of_div (View.view rp)) ; 14 | Lwt.return () 15 | 16 | let _ = Js_of_ocaml_lwt.Lwt_js_events.onload () >>= main 17 | -------------------------------------------------------------------------------- /jsoo/tic-tac-toe/model.ml: -------------------------------------------------------------------------------- 1 | open Types 2 | 3 | let valid_moves_for_player board player = 4 | let f e = match e.state with 5 | | Empty -> true 6 | | _ -> false 7 | in 8 | board.cells 9 | |> List.filter f 10 | |> List.map (fun e -> player e.pos) 11 | 12 | let empty_board = 13 | let f_horizontal acc horizontal = 14 | let f_vertical acc vertical = 15 | (horizontal, vertical) :: acc 16 | in 17 | let row = List.rev(List.fold_left f_vertical [] [Top; Center; Bottom]) in 18 | row @ acc 19 | in 20 | let l = List.rev(List.fold_left f_horizontal [] [Left; Middle; Right]) in 21 | { cells = l |> List.map (fun e -> { pos = e; state = Empty }) } 22 | 23 | let empty_game = 24 | Player_x_to_move (empty_board, 25 | valid_moves_for_player empty_board (fun e -> Player_x_pos e)) 26 | 27 | let get_game_board game = 28 | match game with 29 | | Player_x_to_move (board, _) 30 | | Player_o_to_move (board, _) 31 | | Game_won (board, _) 32 | | Game_tied board -> board 33 | 34 | let get_cells_row board row = 35 | let f e = match e.pos with 36 | | (_, vertical) when row = vertical -> true 37 | | _ -> false 38 | in 39 | board.cells |> List.filter f 40 | 41 | let string_of_cell cell = 42 | match cell.state with 43 | | Played Player_x -> "✖" 44 | | Played Player_o -> "⚪" 45 | | Empty -> " " 46 | 47 | let update_cell board cell player = 48 | let f e = 49 | if e = cell then { e with state = Played player } 50 | else e 51 | in 52 | board.cells |> List.map f 53 | 54 | let cell_in_valid_moves_x moves cell = 55 | let f e = 56 | match e with 57 | | Player_x_pos pos -> cell.pos = pos 58 | in 59 | List.exists f moves 60 | 61 | let cell_in_valid_moves_o moves cell = 62 | let f e = 63 | match e with 64 | | Player_o_pos pos -> cell.pos = pos 65 | in 66 | List.exists f moves 67 | 68 | let cells_in_row board row = 69 | let f e = 70 | let (_, vertical) = e.pos in 71 | if vertical = row then 72 | true 73 | else false 74 | in 75 | board.cells |> List.filter f 76 | 77 | let cells_in_col board col = 78 | let f e = 79 | let (horizontal, _) = e.pos in 80 | if horizontal = col then 81 | true 82 | else false 83 | in 84 | board.cells |> List.filter f 85 | 86 | let cells_in_diag board diag = 87 | let l = 88 | match diag with 89 | | Left_top_right_bottom -> [(Left, Top); (Middle, Center); (Right, Bottom)] 90 | | Right_top_left_bottom -> [(Right, Top); (Middle, Center); (Left, Bottom)] 91 | in 92 | let f e = 93 | if (List.mem e.pos l) then 94 | true 95 | else false 96 | in 97 | board.cells |> List.filter f 98 | 99 | let cells_played_by cells player = 100 | let f e = 101 | match e.state with 102 | | Played p when p = player -> true 103 | | _ -> false 104 | in 105 | cells |> List.filter f 106 | 107 | let check_won board player = 108 | let l = [ 109 | cells_in_row board Top; cells_in_row board Center; cells_in_row board Bottom; 110 | cells_in_col board Left; cells_in_col board Middle; cells_in_col board Right; 111 | cells_in_diag board Left_top_right_bottom; cells_in_diag board Right_top_left_bottom; 112 | ] in 113 | try 114 | List.find (fun e -> cells_played_by e player = e) l 115 | with 116 | | Not_found -> [] 117 | 118 | let game_won_by_player board player = 119 | match (check_won board player) with 120 | | [] -> false 121 | | _ -> true 122 | 123 | let cells_game_won board player = 124 | check_won board player 125 | -------------------------------------------------------------------------------- /jsoo/tic-tac-toe/style.css: -------------------------------------------------------------------------------- 1 | table { 2 | border-collapse: collapse; 3 | margin: 0 auto 0 auto; 4 | font-size: 12em; 5 | font-weight: bold; 6 | text-align: center; 7 | } 8 | 9 | td { 10 | border: 5px solid black; 11 | height: 1em; 12 | width: 1em; 13 | margin: 0; 14 | padding: 0; 15 | } 16 | 17 | .info { 18 | text-align: center; 19 | font-size: 9pt; 20 | } 21 | 22 | .cellX { 23 | color: blue; 24 | } 25 | 26 | .cellO { 27 | color: green; 28 | } 29 | 30 | .cellWon { 31 | background-color: yellow; 32 | } -------------------------------------------------------------------------------- /jsoo/tic-tac-toe/types.ml: -------------------------------------------------------------------------------- 1 | type horiz_position = Left | Middle | Right 2 | type vert_position = Top | Center | Bottom 3 | type diag_position = Left_top_right_bottom | Right_top_left_bottom 4 | type cell_position = horiz_position * vert_position 5 | type player = Player_x | Player_o 6 | type cell_state = Played of player | Empty 7 | type cell = { pos : cell_position; state : cell_state } 8 | type game_board = { cells : cell list } 9 | type player_x_pos = Player_x_pos of cell_position 10 | type player_o_pos = Player_o_pos of cell_position 11 | type valid_moves_for_player_x = player_x_pos list 12 | type valid_moves_for_player_o = player_o_pos list 13 | type game_state = 14 | Player_x_to_move of game_board * valid_moves_for_player_x 15 | | Player_o_to_move of game_board * valid_moves_for_player_o 16 | | Game_won of game_board * player 17 | | Game_tied of game_board 18 | 19 | type rs = game_state React.signal 20 | type rf = ?step:React.step -> game_state -> unit 21 | type rp = rs * rf 22 | -------------------------------------------------------------------------------- /jsoo/tic-tac-toe/view.ml: -------------------------------------------------------------------------------- 1 | open Action 2 | open Types 3 | open Js_of_ocaml_tyxml.Tyxml_js 4 | 5 | let cell_row (r, f) board row cells_won = 6 | let cells = Model.get_cells_row board row in 7 | let cell_class cell = 8 | let won = 9 | if (List.mem cell cells_won) then 10 | ["cellWon"] 11 | else [] 12 | in 13 | match cell.state with 14 | | Played Player_x -> won @ ["cellX"] 15 | | Played Player_o -> won @ ["cellO"] 16 | | Empty -> won @ ["cellEmpty"] 17 | in 18 | let f acc e = 19 | let s = Model.string_of_cell e in 20 | let cell_class = cell_class e in 21 | (Html5.(td ~a:[ 22 | a_onclick (fun evt -> (Controller.update (Click_cell e) (r, f)); true) ; 23 | a_class cell_class 24 | ] [txt s])) :: acc 25 | in 26 | let ltd = List.rev(List.fold_left f [] cells) in 27 | Html5.(tr ltd) 28 | 29 | let table_board ((r, f) : rp) = 30 | let cells m = 31 | let board = Model.get_game_board m in 32 | let cells_won = 33 | match m with 34 | | Game_won (b, p) -> Model.cells_game_won b p 35 | | _ -> [] 36 | in 37 | [cell_row (r, f) board Top cells_won ; 38 | cell_row (r, f) board Center cells_won ; 39 | cell_row (r, f) board Bottom cells_won] 40 | in 41 | let rl = ReactiveData.RList.from_signal (React.S.map cells r) in 42 | Html5.(section ~a:[a_class ["main"]] [ 43 | R.Html5.table ~a:[a_class ["board"]] rl 44 | ]) 45 | 46 | let info_header = 47 | Html5.(header ~a:[a_class ["info"]] [ 48 | p [txt "Reload the page (F5) to restart the game"] 49 | ]) 50 | 51 | let info_footer = 52 | Html5.(footer ~a:[a_class ["info"]] [ 53 | p [ 54 | txt "Tic-Tac-Toe - Written by " ; 55 | a ~a:[a_href "https://stephanelegrand.wordpress.com/"] [txt "Stéphane Legrand"] 56 | ] ; 57 | ]) 58 | 59 | let view (r, f) = 60 | Html5.( 61 | div [ 62 | info_header ; 63 | section [table_board (r, f)] ; 64 | info_footer 65 | ]) 66 | -------------------------------------------------------------------------------- /jsoo/todomvc-react/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | 3 | *.byte 4 | -------------------------------------------------------------------------------- /jsoo/todomvc-react/.jshintignore: -------------------------------------------------------------------------------- 1 | js/todomvc.js 2 | -------------------------------------------------------------------------------- /jsoo/todomvc-react/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Compile OCaml source file to OCaml bytecode 4 | ocamlbuild -use-ocamlfind \ 5 | -pkgs lwt_ppx,js_of_ocaml-lwt,js_of_ocaml-ppx,js_of_ocaml-tyxml,tyxml,ppx_deriving,js_of_ocaml-ppx_deriving_json,react,reactiveData \ 6 | todomvc.byte ; 7 | 8 | # Build JS code from the OCaml bytecode 9 | js_of_ocaml --opt 3 -o js/todomvc.js todomvc.byte 10 | -------------------------------------------------------------------------------- /jsoo/todomvc-react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Js_of_ocaml • TodoMVC 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /jsoo/todomvc-react/node_modules/todomvc-app-css/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-font-smoothing: antialiased; 21 | font-smoothing: antialiased; 22 | } 23 | 24 | body { 25 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 26 | line-height: 1.4em; 27 | background: #f5f5f5; 28 | color: #4d4d4d; 29 | min-width: 230px; 30 | max-width: 550px; 31 | margin: 0 auto; 32 | -webkit-font-smoothing: antialiased; 33 | -moz-font-smoothing: antialiased; 34 | font-smoothing: antialiased; 35 | font-weight: 300; 36 | } 37 | 38 | button, 39 | input[type="checkbox"] { 40 | outline: none; 41 | } 42 | 43 | .hidden { 44 | display: none; 45 | } 46 | 47 | .todoapp { 48 | background: #fff; 49 | margin: 130px 0 40px 0; 50 | position: relative; 51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 52 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 53 | } 54 | 55 | .todoapp input::-webkit-input-placeholder { 56 | font-style: italic; 57 | font-weight: 300; 58 | color: #e6e6e6; 59 | } 60 | 61 | .todoapp input::-moz-placeholder { 62 | font-style: italic; 63 | font-weight: 300; 64 | color: #e6e6e6; 65 | } 66 | 67 | .todoapp input::input-placeholder { 68 | font-style: italic; 69 | font-weight: 300; 70 | color: #e6e6e6; 71 | } 72 | 73 | .todoapp h1 { 74 | position: absolute; 75 | top: -155px; 76 | width: 100%; 77 | font-size: 100px; 78 | font-weight: 100; 79 | text-align: center; 80 | color: rgba(175, 47, 47, 0.15); 81 | -webkit-text-rendering: optimizeLegibility; 82 | -moz-text-rendering: optimizeLegibility; 83 | text-rendering: optimizeLegibility; 84 | } 85 | 86 | .new-todo, 87 | .edit { 88 | position: relative; 89 | margin: 0; 90 | width: 100%; 91 | font-size: 24px; 92 | font-family: inherit; 93 | font-weight: inherit; 94 | line-height: 1.4em; 95 | border: 0; 96 | outline: none; 97 | color: inherit; 98 | padding: 6px; 99 | border: 1px solid #999; 100 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 101 | box-sizing: border-box; 102 | -webkit-font-smoothing: antialiased; 103 | -moz-font-smoothing: antialiased; 104 | font-smoothing: antialiased; 105 | } 106 | 107 | .new-todo { 108 | padding: 16px 16px 16px 60px; 109 | border: none; 110 | background: rgba(0, 0, 0, 0.003); 111 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 112 | } 113 | 114 | .main { 115 | position: relative; 116 | z-index: 2; 117 | border-top: 1px solid #e6e6e6; 118 | } 119 | 120 | label[for='toggle-all'] { 121 | display: none; 122 | } 123 | 124 | .toggle-all { 125 | position: absolute; 126 | top: -55px; 127 | left: -12px; 128 | width: 60px; 129 | height: 34px; 130 | text-align: center; 131 | border: none; /* Mobile Safari */ 132 | } 133 | 134 | .toggle-all:before { 135 | content: '❯'; 136 | font-size: 22px; 137 | color: #e6e6e6; 138 | padding: 10px 27px 10px 27px; 139 | } 140 | 141 | .toggle-all:checked:before { 142 | color: #737373; 143 | } 144 | 145 | .todo-list { 146 | margin: 0; 147 | padding: 0; 148 | list-style: none; 149 | } 150 | 151 | .todo-list li { 152 | position: relative; 153 | font-size: 24px; 154 | border-bottom: 1px solid #ededed; 155 | } 156 | 157 | .todo-list li:last-child { 158 | border-bottom: none; 159 | } 160 | 161 | .todo-list li.editing { 162 | border-bottom: none; 163 | padding: 0; 164 | } 165 | 166 | .todo-list li.editing .edit { 167 | display: block; 168 | width: 506px; 169 | padding: 13px 17px 12px 17px; 170 | margin: 0 0 0 43px; 171 | } 172 | 173 | .todo-list li.editing .view { 174 | display: none; 175 | } 176 | 177 | .todo-list li .toggle { 178 | text-align: center; 179 | width: 40px; 180 | /* auto, since non-WebKit browsers doesn't support input styling */ 181 | height: auto; 182 | position: absolute; 183 | top: 0; 184 | bottom: 0; 185 | margin: auto 0; 186 | border: none; /* Mobile Safari */ 187 | -webkit-appearance: none; 188 | appearance: none; 189 | } 190 | 191 | .todo-list li .toggle:after { 192 | content: url('data:image/svg+xml;utf8,'); 193 | } 194 | 195 | .todo-list li .toggle:checked:after { 196 | content: url('data:image/svg+xml;utf8,'); 197 | } 198 | 199 | .todo-list li label { 200 | white-space: pre-line; 201 | word-break: break-all; 202 | padding: 15px 60px 15px 15px; 203 | margin-left: 45px; 204 | display: block; 205 | line-height: 1.2; 206 | transition: color 0.4s; 207 | } 208 | 209 | .todo-list li.completed label { 210 | color: #d9d9d9; 211 | text-decoration: line-through; 212 | } 213 | 214 | .todo-list li .destroy { 215 | display: none; 216 | position: absolute; 217 | top: 0; 218 | right: 10px; 219 | bottom: 0; 220 | width: 40px; 221 | height: 40px; 222 | margin: auto 0; 223 | font-size: 30px; 224 | color: #cc9a9a; 225 | margin-bottom: 11px; 226 | transition: color 0.2s ease-out; 227 | } 228 | 229 | .todo-list li .destroy:hover { 230 | color: #af5b5e; 231 | } 232 | 233 | .todo-list li .destroy:after { 234 | content: '×'; 235 | } 236 | 237 | .todo-list li:hover .destroy { 238 | display: block; 239 | } 240 | 241 | .todo-list li .edit { 242 | display: none; 243 | } 244 | 245 | .todo-list li.editing:last-child { 246 | margin-bottom: -1px; 247 | } 248 | 249 | .footer { 250 | color: #777; 251 | padding: 10px 15px; 252 | height: 20px; 253 | text-align: center; 254 | border-top: 1px solid #e6e6e6; 255 | } 256 | 257 | .footer:before { 258 | content: ''; 259 | position: absolute; 260 | right: 0; 261 | bottom: 0; 262 | left: 0; 263 | height: 50px; 264 | overflow: hidden; 265 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 266 | 0 8px 0 -3px #f6f6f6, 267 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 268 | 0 16px 0 -6px #f6f6f6, 269 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 270 | } 271 | 272 | .todo-count { 273 | float: left; 274 | text-align: left; 275 | } 276 | 277 | .todo-count strong { 278 | font-weight: 300; 279 | } 280 | 281 | .filters { 282 | margin: 0; 283 | padding: 0; 284 | list-style: none; 285 | position: absolute; 286 | right: 0; 287 | left: 0; 288 | } 289 | 290 | .filters li { 291 | display: inline; 292 | } 293 | 294 | .filters li a { 295 | color: inherit; 296 | margin: 3px; 297 | padding: 3px 7px; 298 | text-decoration: none; 299 | border: 1px solid transparent; 300 | border-radius: 3px; 301 | } 302 | 303 | .filters li a.selected, 304 | .filters li a:hover { 305 | border-color: rgba(175, 47, 47, 0.1); 306 | } 307 | 308 | .filters li a.selected { 309 | border-color: rgba(175, 47, 47, 0.2); 310 | } 311 | 312 | .clear-completed, 313 | html .clear-completed:active { 314 | float: right; 315 | position: relative; 316 | line-height: 20px; 317 | text-decoration: none; 318 | cursor: pointer; 319 | position: relative; 320 | } 321 | 322 | .clear-completed:hover { 323 | text-decoration: underline; 324 | } 325 | 326 | .info { 327 | margin: 65px auto 0; 328 | color: #bfbfbf; 329 | font-size: 10px; 330 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 331 | text-align: center; 332 | } 333 | 334 | .info p { 335 | line-height: 1; 336 | } 337 | 338 | .info a { 339 | color: inherit; 340 | text-decoration: none; 341 | font-weight: 400; 342 | } 343 | 344 | .info a:hover { 345 | text-decoration: underline; 346 | } 347 | 348 | /* 349 | Hack to remove background from Mobile Safari. 350 | Can't use it globally since it destroys checkboxes in Firefox 351 | */ 352 | @media screen and (-webkit-min-device-pixel-ratio:0) { 353 | .toggle-all, 354 | .todo-list li .toggle { 355 | background: none; 356 | } 357 | 358 | .todo-list li .toggle { 359 | height: 40px; 360 | } 361 | 362 | .toggle-all { 363 | -webkit-transform: rotate(90deg); 364 | transform: rotate(90deg); 365 | -webkit-appearance: none; 366 | appearance: none; 367 | } 368 | } 369 | 370 | @media (max-width: 430px) { 371 | .footer { 372 | height: 50px; 373 | } 374 | 375 | .filters { 376 | bottom: 10px; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /jsoo/todomvc-react/node_modules/todomvc-app-css/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-app-css", 3 | "version": "2.0.1", 4 | "description": "CSS for TodoMVC apps", 5 | "license": "CC-BY-4.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/tastejs/todomvc-app-css" 9 | }, 10 | "author": { 11 | "name": "Sindre Sorhus", 12 | "email": "sindresorhus@gmail.com", 13 | "url": "sindresorhus.com" 14 | }, 15 | "files": [ 16 | "index.css" 17 | ], 18 | "keywords": [ 19 | "todomvc", 20 | "tastejs", 21 | "app", 22 | "todo", 23 | "template", 24 | "css", 25 | "style", 26 | "stylesheet" 27 | ], 28 | "gitHead": "f1bb1aa9b19888f339055418374a9b3a2d4c6fc5", 29 | "bugs": { 30 | "url": "https://github.com/tastejs/todomvc-app-css/issues" 31 | }, 32 | "homepage": "https://github.com/tastejs/todomvc-app-css", 33 | "_id": "todomvc-app-css@2.0.1", 34 | "scripts": {}, 35 | "_shasum": "f64d50b744a8a83c1151a08055b88f3aa5ccb052", 36 | "_from": "todomvc-app-css@>=2.0.0 <3.0.0", 37 | "_npmVersion": "2.5.1", 38 | "_nodeVersion": "0.12.0", 39 | "_npmUser": { 40 | "name": "sindresorhus", 41 | "email": "sindresorhus@gmail.com" 42 | }, 43 | "maintainers": [ 44 | { 45 | "name": "sindresorhus", 46 | "email": "sindresorhus@gmail.com" 47 | }, 48 | { 49 | "name": "addyosmani", 50 | "email": "addyosmani@gmail.com" 51 | }, 52 | { 53 | "name": "passy", 54 | "email": "phartig@rdrei.net" 55 | }, 56 | { 57 | "name": "stephenplusplus", 58 | "email": "sawchuk@gmail.com" 59 | } 60 | ], 61 | "dist": { 62 | "shasum": "f64d50b744a8a83c1151a08055b88f3aa5ccb052", 63 | "tarball": "http://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.1.tgz" 64 | }, 65 | "directories": {}, 66 | "_resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.1.tgz" 67 | } 68 | -------------------------------------------------------------------------------- /jsoo/todomvc-react/node_modules/todomvc-app-css/readme.md: -------------------------------------------------------------------------------- 1 | # todomvc-app-css 2 | 3 | > CSS for TodoMVC apps 4 | 5 | ![](screenshot.png) 6 | 7 | 8 | ## Install 9 | 10 | 11 | ``` 12 | $ npm install --save todomvc-app-css 13 | ``` 14 | 15 | 16 | ## Getting started 17 | 18 | ```html 19 | 20 | ``` 21 | 22 | See the [TodoMVC app template](https://github.com/tastejs/todomvc-app-template). 23 | 24 | 25 | 26 | ## License 27 | 28 | Creative Commons License
This work by Sindre Sorhus is licensed under a Creative Commons Attribution 4.0 International License. 29 | -------------------------------------------------------------------------------- /jsoo/todomvc-react/node_modules/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /jsoo/todomvc-react/node_modules/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script')); 118 | } 119 | /* jshint ignore:end */ 120 | 121 | function redirect() { 122 | if (location.hostname === 'tastejs.github.io') { 123 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 124 | } 125 | } 126 | 127 | function findRoot() { 128 | var base = location.href.indexOf('examples/'); 129 | return location.href.substr(0, base); 130 | } 131 | 132 | function getFile(file, callback) { 133 | if (!location.host) { 134 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 135 | } 136 | 137 | var xhr = new XMLHttpRequest(); 138 | 139 | xhr.open('GET', findRoot() + file, true); 140 | xhr.send(); 141 | 142 | xhr.onload = function () { 143 | if (xhr.status === 200 && callback) { 144 | callback(xhr.responseText); 145 | } 146 | }; 147 | } 148 | 149 | function Learn(learnJSON, config) { 150 | if (!(this instanceof Learn)) { 151 | return new Learn(learnJSON, config); 152 | } 153 | 154 | var template, framework; 155 | 156 | if (typeof learnJSON !== 'object') { 157 | try { 158 | learnJSON = JSON.parse(learnJSON); 159 | } catch (e) { 160 | return; 161 | } 162 | } 163 | 164 | if (config) { 165 | template = config.template; 166 | framework = config.framework; 167 | } 168 | 169 | if (!template && learnJSON.templates) { 170 | template = learnJSON.templates.todomvc; 171 | } 172 | 173 | if (!framework && document.querySelector('[data-framework]')) { 174 | framework = document.querySelector('[data-framework]').dataset.framework; 175 | } 176 | 177 | this.template = template; 178 | 179 | if (learnJSON.backend) { 180 | this.frameworkJSON = learnJSON.backend; 181 | this.frameworkJSON.issueLabel = framework; 182 | this.append({ 183 | backend: true 184 | }); 185 | } else if (learnJSON[framework]) { 186 | this.frameworkJSON = learnJSON[framework]; 187 | this.frameworkJSON.issueLabel = framework; 188 | this.append(); 189 | } 190 | 191 | this.fetchIssueCount(); 192 | } 193 | 194 | Learn.prototype.append = function (opts) { 195 | var aside = document.createElement('aside'); 196 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 197 | aside.className = 'learn'; 198 | 199 | if (opts && opts.backend) { 200 | // Remove demo link 201 | var sourceLinks = aside.querySelector('.source-links'); 202 | var heading = sourceLinks.firstElementChild; 203 | var sourceLink = sourceLinks.lastElementChild; 204 | // Correct link path 205 | var href = sourceLink.getAttribute('href'); 206 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 207 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 208 | } else { 209 | // Localize demo links 210 | var demoLinks = aside.querySelectorAll('.demo-link'); 211 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 212 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 213 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 214 | } 215 | }); 216 | } 217 | 218 | document.body.className = (document.body.className + ' learn-bar').trim(); 219 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 220 | }; 221 | 222 | Learn.prototype.fetchIssueCount = function () { 223 | var issueLink = document.getElementById('issue-count-link'); 224 | if (issueLink) { 225 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 226 | var xhr = new XMLHttpRequest(); 227 | xhr.open('GET', url, true); 228 | xhr.onload = function (e) { 229 | var parsedResponse = JSON.parse(e.target.responseText); 230 | if (parsedResponse instanceof Array) { 231 | var count = parsedResponse.length 232 | if (count !== 0) { 233 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 234 | document.getElementById('issue-count').style.display = 'inline'; 235 | } 236 | } 237 | }; 238 | xhr.send(); 239 | } 240 | }; 241 | 242 | redirect(); 243 | getFile('learn.json', Learn); 244 | })(); 245 | -------------------------------------------------------------------------------- /jsoo/todomvc-react/node_modules/todomvc-common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-common", 3 | "version": "1.0.1", 4 | "description": "Common TodoMVC utilities used by our apps", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/tastejs/todomvc-common" 9 | }, 10 | "author": { 11 | "name": "TasteJS" 12 | }, 13 | "main": "base.js", 14 | "files": [ 15 | "base.js", 16 | "base.css" 17 | ], 18 | "keywords": [ 19 | "todomvc", 20 | "tastejs", 21 | "util", 22 | "utilities" 23 | ], 24 | "gitHead": "a5e821ea0bd6974a827834eeae0e5e6e05538abd", 25 | "bugs": { 26 | "url": "https://github.com/tastejs/todomvc-common/issues" 27 | }, 28 | "homepage": "https://github.com/tastejs/todomvc-common", 29 | "_id": "todomvc-common@1.0.1", 30 | "scripts": {}, 31 | "_shasum": "547af687c3152bb57eb2d3f7c527a0ef44e3c831", 32 | "_from": "todomvc-common@>=1.0.0 <2.0.0", 33 | "_npmVersion": "1.4.28", 34 | "_npmUser": { 35 | "name": "sindresorhus", 36 | "email": "sindresorhus@gmail.com" 37 | }, 38 | "maintainers": [ 39 | { 40 | "name": "sindresorhus", 41 | "email": "sindresorhus@gmail.com" 42 | }, 43 | { 44 | "name": "addyosmani", 45 | "email": "addyosmani@gmail.com" 46 | }, 47 | { 48 | "name": "passy", 49 | "email": "phartig@rdrei.net" 50 | }, 51 | { 52 | "name": "stephenplusplus", 53 | "email": "sawchuk@gmail.com" 54 | } 55 | ], 56 | "dist": { 57 | "shasum": "547af687c3152bb57eb2d3f7c527a0ef44e3c831", 58 | "tarball": "http://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.1.tgz" 59 | }, 60 | "directories": {}, 61 | "_resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.1.tgz" 62 | } 63 | -------------------------------------------------------------------------------- /jsoo/todomvc-react/node_modules/todomvc-common/readme.md: -------------------------------------------------------------------------------- 1 | # todomvc-common 2 | 3 | > Common TodoMVC utilities used by our apps 4 | 5 | 6 | ## Install 7 | 8 | ``` 9 | $ npm install --save todomvc-common 10 | ``` 11 | 12 | 13 | ## License 14 | 15 | MIT © [TasteJS](http://tastejs.com) 16 | -------------------------------------------------------------------------------- /jsoo/todomvc-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "todomvc-app-css": "^2.0.0", 5 | "todomvc-common": "^1.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jsoo/todomvc-react/readme.md: -------------------------------------------------------------------------------- 1 | # Js_of_ocaml TodoMVC Example 2 | 3 | > Js_of_ocaml is a compiler of OCaml bytecode to Javascript. 4 | 5 | ## Resources 6 | 7 | - [Website](http://ocsigen.org/js_of_ocaml/) 8 | - [GitHub](https://github.com/ocsigen/js_of_ocaml) 9 | - [Try Js_of_ocaml](http://try.ocamlpro.com/js_of_ocaml/) 10 | 11 | ## Support 12 | 13 | Js_of_ocaml is part of [Ocsigen project](http://ocsigen.org/). 14 | 15 | - [Mailing list](https://sympa.inria.fr/sympa/subscribe/ocsigen) 16 | - IRC : #ocsigen on irc.freenode.net 17 | 18 | 19 | ## Implementation 20 | 21 | Open `index.html` in your browser to try the application. 22 | 23 | If you want to build the application on your own: 24 | 25 | 1. Install Js_of_ocaml and the required dependencies. An easy way is to use [opam](https://opam.ocaml.org/). After having installed `opam`, follow these steps: 26 | 27 | - If you use `opam` for the first time, you have to initialize it: 28 | 29 | ```sh 30 | > opam init 31 | > eval `opam config env` 32 | ``` 33 | 34 | This will create a `.opam` directory in your home. 35 | 36 | - You should use a recent version of OCaml compiler. To check the current version used by `opam`: 37 | 38 | ```sh 39 | > opam switch 40 | 4.07.0 ocaml-base-compiler.4.07.0 4.07.0 41 | → 4.07.1+flambda ocaml-variants.4.07.1+flambda 4.07.1+flambda 42 | default ocaml-system.4.06.1 default 43 | ``` 44 | 45 | The `default` one is the version installed at a system level (ie in /usr/local/bin/ for instance). The `→` symbol shows the current compiler used by `opam`. If you need to install it, use the command `opam switch 4.07.1+flambda` for instance. This won't remove the system compiler as `opam` will install the files in your `.opam` directory. 46 | 47 | ```sh 48 | > opam switch 4.07.1+flambda 49 | > eval `opam config env` 50 | ``` 51 | 52 | You can use again `opam switch` to check that the current compiler is now OCaml 4.07.1+flambda. 53 | 54 | - Let's now install Js_of_ocaml and all the required dependencies. As Js_of_ocaml is a part of a larger project named Eliom, we will simply install this package: 55 | 56 | ```sh 57 | > opam install eliom 58 | ``` 59 | 60 | - The final and important step is to be sure to have the latest version of Eliom and its dependencies. So ask to `opam` to upgrade the packages if needed: 61 | 62 | ```sh 63 | > opam update 64 | > opam upgrade 65 | ``` 66 | 67 | Congratulations, you now have all the required packages! We can now build the application. 68 | 69 | 2. Compile the `todomvc.ml` file to OCaml bytecode with the `ocamlbuild` command: 70 | 71 | ```sh 72 | > ocamlbuild -use-ocamlfind \ 73 | -pkgs lwt_ppx,js_of_ocaml-lwt,js_of_ocaml.ppx,js_of_ocaml.tyxml,tyxml,ppx_deriving,js_of_ocaml-ppx.deriving,js_of_ocaml.deriving,react,reactiveData \ 74 | todomvc.byte ; 75 | ``` 76 | 77 | The command options are: 78 | - `-use-ocamlfind` and `-pkgs ...` to use the necessary `ocamlfind` packages. 79 | 80 | 3. Build the Javascript file from the `todomvc.byte` file with the `js_of_ocaml` command: 81 | 82 | ```sh 83 | > js_of_ocaml --opt 3 -o js/todomvc.js todomvc.byte 84 | ``` 85 | 86 | The command options are: 87 | - `--opt 3` to set optimization profile. 88 | - `-o js/todomvc.js` to set output file name. 89 | 90 | Please note that for the second and third step, you can also use the `build.sh` script: 91 | 92 | ```sh 93 | > /bin/sh build.sh 94 | ``` 95 | 96 | ## Credit 97 | 98 | Created by [Stéphane Legrand](https://github.com/slegrand45). 99 | 100 | Various code improvements from [Gabriel Radanne](https://github.com/Drup). 101 | 102 | Based on Elm implementation by [Evan Czaplicki](https://github.com/evancz). 103 | --------------------------------------------------------------------------------