I gave a talk at ElixirConf 2015 on combining the Phoenix web framework with the Elm programming language. This is the tutorial that was referred to in that talk. The tutorial walks through the creation of a very basic seat saving application, like one you'd use when booking a flight for example. The application will do just enough to demonstrate the mechanisms for getting the two technologies talking to each other. There is an accompanying repo for this tutorial. Each of the numbered steps has an associated commit so that you can just look at the diffs if you'd rather not read through the whole thing.

Thanks to Anthony Verez (@netantho) and Mark Provan (@markprovan) for some corrections in this post. :)

Adding an Update

We mentioned in part 3 that Elm has a Model - Update - View architecture. We've looked at the View and the Model, so let's turn our attention now to the Update. The best way to get a handle on what the update function will need to do is by taking a look at its type annotation.

update : Msg -> Model -> Model

The update function will take two arguments, one of type Msg and one of type Model, and return a value of type Model. In actual fact it will look for a matching message handler in the update function for the message (Msg) sent to it. If it finds one it will call it, which will transform the current Model (or state) of the application into a brand new Model.

It is important to note here that the update function does not change the current Model. It creates a whole new Model based on the current Model. The Model in Elm is immutable.

But what is a message? Well it is a Union Type that lets us group a bunch of other types together. The end result is that we can then pattern match on those types in order to react to different "messages".

Let's look at an example,

-- UPDATE type Msg = Increment | Decrement update : Msg -> Model -> Model update msg model = case msg of Increment -> model + 1 Decrement -> model - 1

Let's assume that our Model is an Int that initializes to 0. We have two possible Msg types, Increment and Decrement . When the update function is called it is passed a Msg and the current Model. It will then pattern match on the given Msg's type to find a function to call. If the Msg type is Increment then a new Model will be produced by adding 1 to the current Model. If it is Decrement then 1 will be subtracted from the current Model instead.

From this we can see that the purpose of the update function, for now anyway, is to step the Model from one state to the next.

Adding an update function

Let's update our Elm application so that we can toggle a Seat from available to occupied and vice versa.

Add the following to your web/elm/SeatSaver.elm file. It doesn't matter where you put it, but I typically stick the Update section between the Model and View sections. -- UPDATE type Msg = Toggle Seat update : Msg -> Model -> Model update msg model = case msg of Toggle seatToToggle -> let updateSeat seatFromModel = if seatFromModel . seatNo == seatToToggle . seatNo then { seatFromModel | occupied = not seatFromModel . occupied } else seatFromModel in List . map updateSeat model OK, there's a lot going on here, so let's take it line by line. First of all we define a Msg called Toggle. The Toggle Msg will take an argument of type Seat. That is why we have Toggle Seat . We are not declaring two Msgs here, otherwise there would have been a | between them. In our update function we have a case statement that just has one matcher currently for our Toggle Msg. The message handler will use List.map (in the in block at the bottom) to call the updateSeat function for each seat in the model (remember our model is a List of Seat). The updateSeat function is defined in the let block. The let block enables us to define functions that can be used within the local scope. The updateSeat function checks to see if the seat passed into it seatFromModel has a seatNo that matches the seatNo of the seatToToggle passed into the Msg. If it matches, the function returns a new seat record with the occupied boolean flipped to the opposite value. If it doesn't match it just returns a new seat record with the same values as the existing seatFromModel . Phew! The upshot of this is that, when the update function is called with the Toggle message and a seat, it will return a new List with the given seat's occupied boolean flipped.

Introducing Html.App

We now have our update function, but we're not using it anywhere. We could at this point start looking at Elm Signals and Mailboxes, at folding and mapping and merging, but let's not. Elm handily provides a wrapper around all of the necessary wiring required to have Msgs routed around our application into the Update. This wrapper is called Html.App.

Let's import it into our web/elm/SeatSaver.elm file. import Html.App as Html The as Html part allows us to create an alias so that calls to Html.App. can be done as Html. . Don't worry, this doesn't overwrite any existing Html module functions and there are no name clashes between these two modules. This is the idiomatic way to import the Html.App module. We need to change our main function to use the beginnerProgram function from the Html.App module. This takes as an argument a record with our init, update and view functions, does all the necessary wiring under the covers and returns Program Never values. Program in Elm is a way to package up an Elm program. You can find out more in the Never part just means that this operation can never fail. You can read in Elm is a way to package up an Elm program. You can find out more in the Elm docs . Thepart just means that this operation can never fail. You can read more on Never in the Elm docs. main : Program Never main = Html . beginnerProgram { model = init , update = update , view = view }

Clicking on a seat

We now have our beginnerProgram set up, but it doesn't yet do anything.

Let's change our view so that we can click on a seat in the browser and have that update the model using our Toggle action. seatItem : Seat -> Html seatItem seat = li [ class "seat available" , onClick ( Toggle seat ) ] [ text ( toString seat . seatNo ) ] We've added an onClick function to our attributes, which takes the Msg to be sent, and the current seat, as its argument. Toggle Msg needs to have the clicked Seat as an argument. However the onClick function can only accept one argument. So how do we get the Seat in there? We can do this in the same way as we would in maths, by denoting precedence of function execution with parentheses. When we write onClick (Toggle seat) what we are doing is creating a OurMsg needs to have the clicked Seat as an argument. However thefunction can only accept one argument. So how do we get the Seat in there? We can do this in the same way as we would in maths, by denoting precedence of function execution with parentheses. When we writewhat we are doing is creating a partial function that binds the clicked seat to the Toggle function call. In other words, we create a Toggle function where we have already provided the Seat argument. This technique is known as currying We need to import the onClick event for this to work. import Html.Events exposing ( onClick ) We also need to change the type annotation of this function now. Before we added the onClick event we were passing out plain HTML strings. Now we are passing out HTML that can result in Messages being called. If you try to compile the file as it currently stands, you will get the following error: As before the error messages are very helpful. They are telling us that both of our View functions state that they will return values of type Html String when in fact they are now returning values of Html Msg . Change the type annotations as follows to enable the file to compile again: view : Model -> Html Msg seatItem : Seat -> Html Msg When we click on a seat we create a Toggle Msg with the seat that was clicked as an argument. 'beginnerProgram will handle things from here, picking the Msg up and routing it through the update` function. This in turn will toggle the occupied flag of that seat. Changing the occupied flag is all well and good, but we can't actually tell currently if that has happened or not. So that we get an indication that something has happened, let's change the style of the seat based on its occupied status. seatItem : Seat -> Html Msg seatItem seat = let occupiedClass = if seat . occupied then "occupied" else "available" in li [ class ( "seat " ++ occupiedClass ) , onClick ( Toggle seat ) ] [ text ( toString seat . seatNo ) ] We're using a let block again to define a local function. occupiedClass will return "occupied" if the seat is occupied or "available" if it is not. We then use the ++ function to concatenate the result of calling occupiedClass with the existing class string. Now, if you go to your browser, you should be able to click on the seats and see them turn from gray to green and back again!

Summary

This has been a rather long and complex section of the tutorial, but we finally have something that we can interact with. This may seem like a lot of effort to set up something relatively simple, but the pay-offs come further down the line as we add more complexity.

We'll take a brief detour in Part 7 to look at Signals. After that we can start to bring in Phoenix to bring our application to life.