Adding Interactivity with Elm!

html_css_js.jpg

A couple weeks ago, we learned how we could serve HTML code from a Haskell server using Servant. Doing this without a system like Reflex left us with a quandry. We still want interactivity on our web pages. But we'd like to do this in a functional way, without writing a lot of Javascript.

Reflex FRP is only one of several options for writing frontend code in a functional language. A while back we considered how to do this with Elm. In this final part of our series, we'll combine Servant with Elm to produce an interactive page.

There are, of course, other options both for frontend and backend when making web apps. Take a look at our Production Checklist to learn more!

A Basic Counter

For a more in depth look at Elm, you should explore our full series on the topic. But for now, let's go over a quick and simple application that we could put in a browser. This app will have a "model" and we will pass "messages" using UI components.

The model will just be an integer counter. And we'll pass "increment" and "decrement" messages by clicking a couple of buttons. We start an Elm application by defining our model and message types. We'll use an alias for the integer model.

type alias Model = Int
type Msg = Increment | Decrement

Now we need to specify how each message type updates our model. An increment message will increase it, and a decrement message will decrease it.

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

Next we generate the HTML elements for our page with a view function. This takes our Model and returns Html to display. We'll have two buttons to send the increment and decrement messages. Then we'll also display the current count.

import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)

view : Model -> Html Msg
view model =
  div []
    [ button [onClick Decrement] [text "-"]
    , div [] [text (String.fromInt model) ]
    , button [onClick Increment] [text "+"]
    ]

Finally, we generate our final application as a "main" expression. We use Browser.sandbox, and pass an initial model value, as well as our update and view expressions.

import Browser

main = Browser.sandbox { init = 0, update = update, view = view}

Compiling Our Elm

Supposing we've written this code in an Elm project as Main.elm, it's now quite easy to compile it into a full HTML file. We run this command:

elm make src/Main.elm

This produces a file index.html that contains our full Elm application. The file requires a lot of boilerplate to get Elm working, so it's over 5000 lines long! But if we return that from our server instead of a blaze-generated HTML file, our app will work!

Referencing Elm

We could make manual modifications to this HTML file to do other things like adding our own CSS. But often times it's easier to compile the Elm into a Javascript file we can include with our other assets. To do this, we just have to tweak our command by outputting to a .js file:

elm make src/Main.elm --output elm.js

This output file now contains all 5000 lines of our compiled Elm. Now we can make our own HTML file that looks like this:

<html>
  <head>...</head>
  <body>
    <div id="elm"></div>
    <script src="/static/elm.js"/>
    <script>
      var app = Elm.Main.init({node: document.getElementById("elm")});
    </script>
  </body>
</html>

The first script includes our compiled app. The second, shorter script does the work of embedding the app in the preceding div. In this way, we can embed a smaller Elm application in along with other HTML components if we want to. It's much easier to swap out our other assets without having to re-compile our whole frontend!

Of course, for this to work, we have to use the techniques from our earlier article. Our Servant server must serve its static content from a particular directory. This will allow us to include elm.js and any other scripts we make. And then it has to serve our HTML page for the desired endpoints.

Conclusion

You should now have enough tools at your disposal to make a simple web app using only functional tools! Our Real World Haskell Series will give you a good tutorial on some other parts of the stack. If you need any other tools, take a look at our Production Checklist. You can also take a look at all the code for this brief series on Github.

Starting next week, we're going to transition a bit. We've explored the idea of Haskell and machine learning before on this blog. Next time, we'll start looking at some ideas in game AI and agent development. This will set the stage for a combination of Open AI Gym and Haskell code.

Previous
Previous

Open AI Primer: Frozen Lake!

Next
Next

Servant Testing Helpers!