TodoMVC is a project which offers the same Todo application implemented using MV* concepts in most of the popular JavaScript MV* frameworks. One of the aims of TodoMVC is to enable a fair comparison between several frameworks, by providing implementations of the same application. A js_of_ocaml (JSOO) version is now available:
Our version is powered by the React
module for functional
reactive programming (FRP).
In this post, we outline the architecture of our implementation, with a particular emphasis on how it applies MVC and FRP concepts.
MVC, which stands for Model-View-Controller, is a software architecture very commonly used for implementing user interfaces. MVC divides an application into three components:
the Model manages the data, logic and rules of the application;
the Controller manages events from the view, and accordingly updates the model;
the View generates an output presentation (a web page for instance) based on the model data.
For the Todo application, we have three corresponding OCaml
modules. Model
mainly contains the task list and the new
task field value. It uses Deriving_Json
to convert
the data to JSON and vice versa, in order to be able to save and
restore the application state. This module is otherwise written with
basic OCaml code. Controller
produces new models
according to the actions it receives. Whenever a new model is built,
the model becomes the new reactive signal value. We will elaborate on
this point later. View
builds the HTML to display the
page. It receives as input the dynamic data from the model. The HTML
also contains the event management code needed to emit the
corresponding actions.
Besides these three MVC modules, the application uses three
helpers. Storage
contains the functions to read and write a string value in the browser
local storage. This module is used to save and restore the application
data in JSON
format. Action
contains all the actions available from the user
interface. ReactList
contains a single function to ease the creation of a reactive list via
the ReactiveData
library.
React is an OCaml module for functional reactive programming
(FRP). In our TodoMVC example, React
provides a way to
automatically refresh the view whenever a new model is built by the
controller. To achieve this goal, the application uses a reactive
signal which carries the model values (which vary over time). The
model value may initially be equal to the empty model. When
this value is modified by the controller, i.e., after a new model
has been generated, the view automatically refreshes its reactive
parts.
The following figure shows what happens when the user interacts with the application, e.g., by adding a new task, or by clicking on a checkbox to select a specific task:
the view sends the action to the controller;
the controller gets the current model from the reactive signal, and builds a new model accordingly to the action;
the controller sets this new model as the new reactive signal value;
the view reacts to the newly-available model (new signal value) and updates itself with the corresponding data.
We proceed to describe our implementation of the above scheme, with an emphasis on the reactive features.
The main function creates the reactive signal with an initial
(possibly empty) model. The m
value is of type Model.t
:
React.S.create
returns a tuple, the first part of which is a
primitive signal; the second part is a function used by the controller
to set a new model as the new signal value.
We first explain how the CSS style of a HTML node becomes
reactive. In the Todo application, the task list is
displayed in a <section>
HTML tag. The CSS style of this HTML node
must contain visibility: hidden;
if the tasks list is empty, and
visibility: visible;
otherwise (i.e., if the number of tasks is
greater than zero). The style attribute of this <section>
node must
therefore change according to the model content:
We use the Tyxml_js
module to safely build the HTML
code. The first thing to note is that we use the reactive R.Html5
submodule, not the plain Html5
submodule. The a_style
function
implements a reactive attribute; it expects a reactive signal as its
argument. Here we use React.S.map
, which has the signature ('a ->
'b) -> 'a React.signal -> 'b React.signal
. The first argument to
map
is the css_visibility
function:
As you can see, css_visibility
receives a model m
as its
argument. When wrapped by React.S.map
as above, css_visibility
operates on signals. The function returns the right style, depending
on whether the list of tasks is empty or not.
The second argument to React.S.map
is the value named r
, which is
the primitive signal. r
is the first value returned by the
React.S.create
function.
Each time the signal value gets updated by the controller, the
css_visibility
function is automatically called with the new signal
value (a new model) as its argument, and the style attribute is
automatically modified.
Reactive attributes alone would not suffice to build a user
interface. We also need a reactive list of child nodes. Such a list is
for example needed to display the task list. (The source code section
is the same as for the first
example.) It
is a list of <li>
nodes contained in a <ul>
node. We accordingly
have a reactive node, as follows:
As before, we use the R.Html5
module. This time we do not use
R.Html5
to build an attribute, but rather a (<ul>
) node. rl
contains the node’s children:
We create the reactive list via the helper module
ReactList. As for the previous example, we use
React.S.map
to build a reactive signal, r
being again the
primitive signal. The visible_tasks
function generates the <li>
elements from the task list, filtered by the current selected
visibility:
Following the same principle as for the reactive attribute, each time
the signal value gets updated by the controller, the <li>
nodes are
automatically refreshed.
You may have noticed that the code includes the following types:
These types are used whenever type annotations are required, e.g.,
for the update
function from the Controller
module:
Elm is a functional programming language dedicated to frontend web application development. Elm was designed by Evan Czaplicki. The language should feel familiar to OCaml programmers.
Our TodoMVC example is based on the Elm
implementation, which follows
the structure used in all Elm
programs: a
model, an update function, and a view. Like Elm, our example uses the
functional reactive programming style, enabled in our case by the
React
library and the reactive modules Tyxml_js.R
and
ReactiveData
.
The combination of OCaml, js_of_ocaml, and functional reactive programming provides a killer feature combination for building rich web clients. Additionally, OCaml static typing can provide compile-time HTML validity checking (via TyXML), thus increasing reliability.