|
| 1 | +--- |
| 2 | +title: 'TodoMVC: a reactive version' |
| 3 | +layout: default |
| 4 | +author: Stéphane Legrand |
| 5 | +--- |
| 6 | + |
| 7 | +[TodoMVC](http://todomvc.com/) 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 this project is to give a possibility to make a fair comparison between several frameworks by coding the same application. A js_of_ocaml (JSOO) version using the React module is now available. Check out the [source code](https://github.com/slegrand45/examples_ocsigen/tree/master/jsoo/todomvc-react) and the [demo](http://slegrand45.github.io/examples_ocsigen.site/jsoo/todomvc-react/). Comments and pull requests are welcome! |
| 8 | + |
| 9 | +#### MVC |
| 10 | + |
| 11 | +[MVC](https://en.wikipedia.org/wiki/Model-view-controller) stands for Model-View-Controller. It's a commonly used software architecture for implementing user interfaces. The application is divided in three components: |
| 12 | + |
| 13 | +- the Model manages the data, logic and rules of the application ; |
| 14 | +- the Controller manages events from the view and accordingly updates the model ; |
| 15 | +- the View generates an output presentation (a web page for instance) based on the model data. |
| 16 | + |
| 17 | +For the Todo application, we have three corresponding modules. [Model](https://github.com/slegrand45/examples_ocsigen/blob/d6766d404a449d0b1d36ad3cd916b0c444390a19/jsoo/todomvc-react/todomvc.ml#L38) contains mainly the tasks list and the new task field value. It uses the deriving feature of JSOO to convert the data to JSON and vice versa in order to be able to save/restore the application state. Otherwise this module is written with basic OCaml code. [Controller](https://github.com/slegrand45/examples_ocsigen/blob/d6766d404a449d0b1d36ad3cd916b0c444390a19/jsoo/todomvc-react/todomvc.ml#L111) produces new models according to the actions it receives. Whenever a new model is built, the module also sets it as the new reactive signal value. We will detail this point later. [View](https://github.com/slegrand45/examples_ocsigen/blob/d6766d404a449d0b1d36ad3cd916b0c444390a19/jsoo/todomvc-react/todomvc.ml#L175) builds the HTML to display the page. It takes the dynamic data from the model. The HTML contains also the events management code needed to emit the corresponding actions. |
| 18 | + |
| 19 | +Besides these three MVC modules, the application also uses three helpers. [Storage](https://github.com/slegrand45/examples_ocsigen/blob/d6766d404a449d0b1d36ad3cd916b0c444390a19/jsoo/todomvc-react/todomvc.ml#L14) contains the functions to read/write a string value in the browser local storage. This module is used to save/restore the application data in JSON format. [Action](https://github.com/slegrand45/examples_ocsigen/blob/d6766d404a449d0b1d36ad3cd916b0c444390a19/jsoo/todomvc-react/todomvc.ml#L94) contains all the actions available from the user interface. [ReactList](https://github.com/slegrand45/examples_ocsigen/blob/d6766d404a449d0b1d36ad3cd916b0c444390a19/jsoo/todomvc-react/todomvc.ml#L3-11) contains only one function to ease the creation of a reactive list thanks to the [ReactiveData library](https://github.com/hhugo/reactiveData). |
| 20 | + |
| 21 | +#### React |
| 22 | + |
| 23 | +[React](http://erratique.ch/software/react) is an OCaml module for [functional reactive programming](https://en.wikipedia.org/wiki/Functional_reactive_programming) (FRP). In this TodoMVC example, React gives a way to automatically refreshes the view whenever a new model is built by the controller. To achieve this goal, the application uses a reactive signal which carries the varying model values over time. At the beginning, the model value may be the [empty model](https://github.com/slegrand45/examples_ocsigen/blob/d6766d404a449d0b1d36ad3cd916b0c444390a19/jsoo/todomvc-react/todomvc.ml#L60-L65). But at any specific point in time, the reactive signal contains a model value. When this value is modified by the controller (a new model has been generated), the view automatically refreshes its reactive parts. |
| 24 | + |
| 25 | +#### Mixing the two |
| 26 | + |
| 27 | +The following figure shows what happens when the user interacts with the application (add a new task, click on a checkbox to select a specific task...): |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | +1. the view sends the action to the controller ; |
| 32 | +2. the controller gets the current model from the reactive signal and build a new model accordingly to the action ; |
| 33 | +3. the controller sets this new model as the new reactive signal value ; |
| 34 | +4. the view detects that a new model is available (the view reacts to the new signal value) and updates itself with the new model data. |
| 35 | + |
| 36 | +Let's now see how this is implemented. Please note that i will only detail the reactive features. |
| 37 | + |
| 38 | +### Initialization |
| 39 | + |
| 40 | +The [main function](https://github.com/slegrand45/examples_ocsigen/blob/d6766d404a449d0b1d36ad3cd916b0c444390a19/jsoo/todomvc-react/todomvc.ml#L393) creates the reactive signal with an initial model (possibly empty). The `m` value is of type `Model.t`: |
| 41 | + |
| 42 | +{% highlight ocaml %} |
| 43 | +let rp = React.S.create m in |
| 44 | +{% endhighlight %} |
| 45 | + |
| 46 | +`React.S.create` returns a tuple. The first value is a primitive signal. The second value is a function which will be used later by the controller [to set a new model as the new signal value](https://github.com/slegrand45/examples_ocsigen/blob/d6766d404a449d0b1d36ad3cd916b0c444390a19/jsoo/todomvc-react/todomvc.ml#L170). |
| 47 | + |
| 48 | +### Reactive attribute |
| 49 | + |
| 50 | +This first example explains how the CSS style of a HTML node becomes reactive ([source code](https://github.com/slegrand45/examples_ocsigen/blob/d6766d404a449d0b1d36ad3cd916b0c444390a19/jsoo/todomvc-react/todomvc.ml#L267-L299)). In the Todo application, the tasks 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. But must contain `visibility: visible;` if the number of tasks is greater than zero. So the style attribute of this `<section>` node must change regarding the model content: |
| 51 | + |
| 52 | +{% highlight ocaml %} |
| 53 | +R.Html5.a_style (React.S.map css_visibility r) |
| 54 | +{% endhighlight %} |
| 55 | + |
| 56 | +We use the `Tyxml_js` module to safely build the HTML code. The first thing to note is that we use its `R.Html5` submodule instead of the `Html5` one. `R.Html5` is simply the Reactive counterpart of `Html5`. As it's a reactive attribute, the `a_style` function expects a reactive signal as its argument. Here we use `React.S.map` which have the signature `('a -> 'b) -> 'a React.signal -> 'b React.signal`. This map function takes as its first argument a function named `css_visibility`: |
| 57 | + |
| 58 | +{% highlight ocaml %} |
| 59 | +let css_visibility m = |
| 60 | + let tasks = m.Model.tasks in |
| 61 | + match tasks with |
| 62 | + | [] -> "visibility: hidden;" |
| 63 | + | _ -> "visibility: visible;" |
| 64 | +{% endhighlight %} |
| 65 | + |
| 66 | +As you can see, this function takes a model `m` as its argument. In fact, thanks to `React.S.map`, it takes the signal value as its argument. And then the function returns the right style regarding if the tasks list is empty or not. |
| 67 | + |
| 68 | +The second argument to `React.S.map` is the value named `r`. This is the primitive signal, the first value returned by the `React.S.create` function. |
| 69 | + |
| 70 | +So each time the signal value will be updated by the controller, the `css_visibility` function will be automatically called with the new signal value (a new model) as its argument and the style attribute will be automatically modified. |
| 71 | + |
| 72 | +### Reactive list |
| 73 | + |
| 74 | +Having reactive attributes would not be enough to build a user interface. We also need to be able to have a reactive list of child nodes. This is for instance the case to display the tasks list ([the source code section is the same than for the first example](https://github.com/slegrand45/examples_ocsigen/blob/d6766d404a449d0b1d36ad3cd916b0c444390a19/jsoo/todomvc-react/todomvc.ml#L267-L299)). It's a list of `<li>` contained in a `<ul>`. So we have a reactive node: |
| 75 | + |
| 76 | +{% highlight ocaml %} |
| 77 | +R.Html5.ul ~a:[a_class ["todo-list"]] rl |
| 78 | +{% endhighlight %} |
| 79 | + |
| 80 | +As before, we use the `R.Html5` module. But this time it's not on an attribute, it's on the `<ul>` element. The `rl` value contains the node's children: |
| 81 | + |
| 82 | +{% highlight ocaml %} |
| 83 | +let rl = ReactList.list (React.S.map visible_tasks r) |
| 84 | +{% endhighlight %} |
| 85 | + |
| 86 | +We create the reactive list with the helper module [ReactList](https://github.com/slegrand45/examples_ocsigen/blob/d6766d404a449d0b1d36ad3cd916b0c444390a19/jsoo/todomvc-react/todomvc.ml#L3). And as for the previous example, we use `React.S.map` to build a reactive signal. The `r` value is again the primitive signal. The `visible_task` function generates the `<li>` elements from the tasks list, filtered by the current selected visibility: |
| 87 | + |
| 88 | +{% highlight ocaml %} |
| 89 | +let visible_tasks m = |
| 90 | + let visibility = m.Model.visibility in |
| 91 | + let is_visible todo = |
| 92 | + match visibility with |
| 93 | + | Model.Completed -> todo.Model.completed |
| 94 | + | Active -> not todo.completed |
| 95 | + | All -> true |
| 96 | + in |
| 97 | + let tasks = List.filter is_visible m.Model.tasks in |
| 98 | + List.rev(List.fold_left (todo_item (r, f)) [] tasks) |
| 99 | +{% endhighlight %} |
| 100 | + |
| 101 | +Following the same principle than for the reactive attribute, each time the signal value will be updated by the controller, the `<li>` nodes will be automatically refreshed. |
| 102 | + |
| 103 | +### Signal typing |
| 104 | + |
| 105 | +You may have noticed that the code [includes these types](https://github.com/slegrand45/examples_ocsigen/blob/d6766d404a449d0b1d36ad3cd916b0c444390a19/jsoo/todomvc-react/todomvc.ml#L89-L91): |
| 106 | + |
| 107 | +{% highlight ocaml %} |
| 108 | +type rs = Model.t React.signal |
| 109 | +type rf = ?step:React.step -> Model.t -> unit |
| 110 | +type rp = rs * rf |
| 111 | +{% endhighlight %} |
| 112 | + |
| 113 | +They are used to specify the type of arguments in some functions. For instance, in the `update` function from Controller module: |
| 114 | + |
| 115 | +{% highlight ocaml %} |
| 116 | +let update a ((r, f) : rp) = |
| 117 | +{% endhighlight %} |
| 118 | + |
| 119 | +It seems that this explicit typing is required, or else the compiler complains. |
| 120 | + |
| 121 | +### Comparison with Elm implementation |
| 122 | + |
| 123 | +[Elm](http://elm-lang.org/) is a functional programming language dedicated to web frontend application designed by Evan Czaplicki. This example is based on the [Elm TodoMVC implementation](https://github.com/evancz/elm-todomvc). It follows the same structure used in [all Elm programs](https://github.com/evancz/elm-architecture-tutorial/): a model, an update function and a view. And, like Elm, it also uses the Functional Reactive Programming (FRP) style thanks to the React library and the reactive modules Tyxml_js.R and ReactiveData. |
| 124 | + |
| 125 | +One major difference is obviously the language. But Elm is nonetheless quite understandable for an OCaml software developer, and even more for any Haskell programmer. |
| 126 | + |
| 127 | +### Conclusion |
| 128 | + |
| 129 | +The addition of OCaml + js_of_ocaml + Functional Reactive Programming provides a killer features combination to build rich client for a web application. OCaml static typing associated with the ability to check the HTML validity at compile time thanks to Tyxml also increase reliability. |
0 commit comments