Writing A Full Site in Phoenix and Elm

Last Updated

June 29th, 2016

Current Versions

Elixir: v1.2.6, Phoenix: v1.1.6, Elm: v0.17

Introduction

Elixir is a very nice functional language, and with Phoenix, we can use it to build very nice backends to our web applications. The trouble is, however, that as more and more sites become JavaScript-heavy, we lose our nice functional language to go work in those other languages. What are our options in the scenario that we want to stick with functional programming both in the backend but also in the front-end to our sites as well?

One of the options that we can choose is Elm, a language that has a lot of the front-end development circles, functional development circles, and Elixir circles pretty excited! It’s fast, it’s clean, and the buy-in for the general architecture that Elm dictates leaves essentially no room for runtime errors in the front-end of your site, leaving you with an incredibly fast and incredibly safe experience for your users.

If you’d like to get a better handle on Elm by itself, or if you’d like to read a little more on integration of Elm with Phoenix, please check out these other posts I’ve written on those subjects:

Creating Our Phoenix Site

We’ll start off by creating our initial Phoenix site just to power the Elm front-end and provide the backend API layer. Run the following command:

$ mix phoenix.new elm_articles

And answer ‘Y’ to the question about fetching/installing dependencies. We’ll do more in Phoenix later, but for right now that will suffice.

Adding Brunch-Elm

We’ll start off by adding the brunch-elm package via NPM which will allow us to work on Elm alongside our Phoenix application:

$ npm install --save-dev elm-brunch

Now we need to configure elm-brunch. Open up brunch-config.js and add the configuration for elmBrunch. We’re assuming right now that we’re just going to have the single main Elm application, and we’re going to toss it in to the javascript side with the rest of our JS code. Here is the example of the changed brunch-config file below:

// Configure your plugins
plugins: {
babel: {
// Do not use ES6 compiler in vendor code
ignore: [/web\/static\/vendor/]
},
elmBrunch: {
elmFolder: 'web/elm',
mainModules: ['Main.elm'],
outputFolder: '../static/js',
}
},

And you’ll also need to update the watched key so that brunch knows to look for changes to Elm files inside of web/elm:

watched: [
'web/static',
'test/static',
'web/elm',
],

Finally, open up app.js, and add:

import Elm from './main';
const elmDiv = document.querySelector('#elm-target');
if (elmDiv) {
Elm.Main.embed(elmDiv);
}

Then run:

$ mkdir web/elm
$ cd web/elm
$ elm package install elm-lang/html

Creating Our Elm Site

Now let’s start creating the actual code base for our initial foray into Elm. Run the following commands:

$ touch web/elm/Main.elm

Then in web/elm/Main.elm, add the following:

module Main exposing (..)
import Html exposing (text, Html)
main : Html a
main =
text "Hello Foo"

And change our page layout so that the Elm app can actually point to something. In web/templates/page/index.html.eex:

<div id="elm-target"></div>

Now when the page reloads you should see everything showing up correctly in your browser! We’ll use Phoenix to handle the backend to this site and the general layout, but everything else that we write that is related to the front-end will specifically be in Elm!

Our first Phoenix/Elm app!

Now let’s create a Components directory which will be responsible for storing our separate components from Main, and we’ll create an ArticleList component specifically.

$ mkdir web/elm/Components
$ touch web/elm/Components/ArticleList.elm

And then we need to configure Elm so that it knows to look in this new Components directory. Open up elm-package.json and look for the “source-directories” key, and change it so it looks like the following:

"source-directories": [
".",
"./Components"
],

Now we can build out a little sample component to keep this project moving. Open up web/elm/Components/ArticleList.elm:

module Components.ArticleList exposing (view)
import Html exposing (Html, text, ul, li, div, h2)
import Html.Attributes exposing (class)
view : Html a
view =
div [ class "article-list" ] [
h2 [] [ text "Article List" ],
ul []
[ li [] [ text "Article 1" ]
, li [] [ text "Article 2" ]
, li [] [ text "Article 3" ] ] ]

And then we’ll head back to web/elm/Main.elm and embed the ArticleList inside of our Main component:

module Main exposing (..)
import Html exposing (Html, text, div)
import Html.Attributes exposing (class)
import Components.ArticleList as ArticleList
main : Html a
main =
div [ class "elm-app" ] [ ArticleList.view ]

I like to mark any components I build in elm with a class name referencing whatever class it is. It makes it easier to track individual components and style them as well.

Now, when the page refreshes, you should see the Article List h2 at the top and a list of the three sample Articles we started with. This is a pretty good start and a good way to visualize how to break your Elm apart into separate components and modules to make your code easier to work with, but what we have in ArticleList.elm is still pretty clunky. Let’s refactor the display of Articles to instead be a separate function. In web/elm/Components/ArticleList.elm:

module Components.ArticleList exposing (view)
import Html exposing (Html, text, ul, li, div, h2)
import Html.Attributes exposing (class)
renderArticles : List (Html a)
renderArticles =
[ li [] [ text "Article 1" ]
, li [] [ text "Article 2" ]
, li [] [ text "Article 3" ] ]
view : Html a
view =
div [ class "article-list" ] [
h2 [] [ text "Article List" ]
, ul [] renderArticles ]

This will make it easier to refactor article rendering when we have the Article component in place, and makes good sense regardless. Speaking of our Article component, let’s build that next. Create and open web/elm/Components/Article.elm:

module Article exposing (view, Model)
import Html exposing (Html, span, strong, em, a, text)
import Html.Attributes exposing (class, href)
type alias Model =
{ title : String, url : String, postedBy : String, postedOn: String }
view : Model -> Html a
view model =
span [ class "article" ]
[a [ href model.url ] [ strong [ ] [ text model.title ] ]
, span [ ] [ text (" Posted by: " ++ model.postedBy) ]
, em [ ] [ text (" (posted on: " ++ model.postedOn ++ ")") ]
]

This one is a little more complicated. We’re setting up the internal representation of a model to use when we’re building Articles in the future. We’re starting off by saying that an Article contains the title, url, postedBy, and postedOn attributes, giving us some information about each Article. Finally, we define a view function that takes in a model and returns out some Html. The rest of the code here is just an Elm template, so we don’t need to spend too much time there, but the type alias at the top is pretty interesting, so let’s explain it a little more.

type alias Model =
{ title : String, url : String, postedBy : String, postedOn: String }

All we’re telling Elm here is that whenever we see Model referenced in this code, what we really mean is a Record type with four keys: title, url, postedBy, postedOn and that they’re all of the String type. So in the type definition for our view function, we see that it takes in a Model and returns out HTML appropriate for that model!

At the top of our Article component, we expose view and Model to any importers so that they can work with the data structure defined here!

Now, let’s return to web/elm/Components/ArticleList.elm and refactor it to use our new spiffy Article component:

module Components.ArticleList exposing (view)
import Html exposing (Html, text, ul, li, div, h2)
import Html.Attributes exposing (class)
import List
import Article
articles : List Article.Model
articles =
[ { title = "Article 1", url = "http://google.com", postedBy = "Author", postedOn = "06/20/16" }
, { title = "Article 2", url = "http://google.com", postedBy = "Author 2", postedOn = "06/20/16" }
, { title = "Article 3", url = "http://google.com", postedBy = "Author 3", postedOn = "06/20/16" }
]
renderArticle : Article.Model -> Html a
renderArticle article =
li [ ] [ Article.view article ]
renderArticles : List (Html a)
renderArticles =
List.map renderArticle articles
view : Html a
view =
div [ class "article-list" ]
[ h2 [] [ text "Article List" ]
, ul [] renderArticles ]

So we start off by importing two new modules into our ArticleList, List and Article. List is built-in to Elm and Article is our new Article component that we just built. We need List for the List.map function call.

We then implement an articles function that just returns three dummy Articles independent of any source. Our articles function’s type definition is defined as a List of Article.Model types. We then define two render functions: one which renders a single article (renderArticle) and one that calls renderArticle for each article, which it pulls from our dummy data source.

renderArticle wraps the exposed Article.view function (which, remember, takes in a type of Article.Model) and returns the Html for that article. Then, renderArticles maps over the list of stubbed articles and calls renderArticle on each of them.

Finally, our view function is slightly modified by having the unordered list just call renderArticles instead of specifying its list of children (since renderArticles, based on looking at the type definition, returns a List of Html elements).

Making ArticleList.elm More Idiomatic

ArticleList is working, and that’s great, but it’s completely divorced from any sort of meaningful user interaction. If we want it to be able to really react appropriately to any sort of interaction or events, we need to refactor it to be more idiomatic to the Elm architecture. We’ll need to define an initial model, an initializer, a view function, an update function, and a Msg union type. Our Model definition as it exists right now is fine, though, thankfully. Open up web/elm/Components/ArticleList.elm and let’s add to it:

We’ll start off by changing our module definition at the top to just expose (..) instead of view.

module Components.ArticleList exposing (..)

Then we’ll start changing our imports since this component is clearly doing a lot more.

import Html exposing (Html, text, ul, li, div, h2, button)
import Html.Attributes exposing (class)
import Html.Events exposing (onClick)
import List
import Article

We need to add in the expose call for button in Html, and add in the onClick import from Html.Events. Next, we need to modify the Model type definition to be a record with an articles key:

type alias Model =
{ articles: List Article.Model }

And we’ll need to modify our old articles function to work with our new model definition:

articles : Model
articles =
{ articles =
[ { title = "Article 1", url = "http://google.com", postedBy = "Author", postedOn = "06/20/16" }
, { title = "Article 2", url = "http://google.com", postedBy = "Author 2", postedOn = "06/20/16" }
, { title = "Article 3", url = "http://google.com", postedBy = "Author 3", postedOn = "06/20/16" } ] }

And then our Msg union type:

type Msg
= NoOp
| Fetch

I like to set up NoOp messages for every Msg union type I write, since it’s good to keep track of what to do if we receive an event that doesn’t do anything. Next, our update function:

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
NoOp ->
(model, Cmd.none)
Fetch ->
(articles, Cmd.none)

Finally, let’s implement our initialModel function:

initialModel : Model
initialModel =
{ articles = [] }

This will let our ArticleList start off with a list of no articles. Finally, let’s change our ArticleList component to have a button that will trigger that Fetch message that we defined in our Msg union type:

view : Model -> Html Msg
view model =
div [ class "article-list" ]
[ h2 [] [ text "Article List" ]
, button [ onClick Fetch, class "btn btn-primary" ] [ text "Fetch Articles" ]
, ul [] (renderArticles model) ]

And finally, we need to modify the renderArticles function to accept a model and render the list of articles based on the articles key from the model:

renderArticles : Model -> List (Html a)
renderArticles model =
List.map renderArticle model.articles

Making Main.elm More Idiomatic

Some of you probably noticed that Main.elm was lacking a lot of the standard code that you tend to find in larger Elm applications, and none of it was really engineered around Elm’s standard architecture, which makes embedding apps that have any sense of user interaction significantly harder! Plus, our previous component that is now more idiomatic doesn’t work due to our substantial refactoring!

To do this, we’ll need to start restructuring the main application to use the Elm App structure. Again, we’ll need to define an initial model, an initializer, a view function, an update function, and a subscriptions function. We’ll start by defining our Model to have a concept of some of its child components and their models. Open up web/elm/Main.elm and let’s refactor it significantly:

type alias Model =
{ articleListModel: ArticleList.Model }

This is saying that our model will contain models for our child components as well. Next, let’s define what our initial model state should look like:

initialModel : Model
initialModel =
{ articleListModel = ArticleList.initialModel }

So we’re saying that any of the data relating to the articleListModel should be mapped, at the start, to the initialModel function. Next, we’ll write our initializer for our module:

init : (Model, Cmd Msg)
init =
( initialModel, Cmd.none )

We’re saying that our initializer returns the initialModel and a do-nothing Command. Next, let’s tackle the update functions and definitions:

type Msg
= ArticleListMsg ArticleList.Msg

Here, we’re saying that any messages broadcast from our ArticleList component can be wrapped in the parent component (Main.elm) as a Msg of type ArticleListMsg. Next, we’ll tackle the update function:

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
ArticleListMsg articleMsg ->
let (updatedModel, cmd) = ArticleList.update articleMsg model.articleListModel
in ( { model | articleListModel = updatedModel }, Cmd.map ArticleListMsg cmd )

This is a little more complicated, but here we’re basically just pattern matching on the type of Msg being dispatched. If we see that it’s an ArticleListMsg (a Msg dispatched from the ArticleList component), we’ll call to the update function defined in our ArticleList component, and then we’ll return out the updated part of the model (ArticleListMsg messages can only update the articleListModel portion of our model), and we’ll map any additional Msgs from the ArticleList component as the ArticleListMsg type.

Next, we’ll need to write a subscription handler, which right now will just be a little bit of boilerplate code.

subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none

Right now, we have no subscriptions, so we’re not going to worry about explaining out subscriptions until we have any! Now, it’s time to move on to tackling the view function. We’ll be using Html.App, which will map messages from children into messages that our parent app will include, so toss the following line up at the top:

import Html.App

And then modify the view function to use this new call:

view : Model -> Html Msg
view model =
div [ class "elm-app" ]
[ Html.App.map ArticleListMsg (ArticleList.view model.articleListModel) ]

As our view function in the ArticleList component is defined as view model, we need to provide both. In addition, we need to tell the ArticleList component that the model will be affected by updates mapped from this parent component.

Finally, we can tackle our main function:

main : Program Never
main =
Html.App.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}

Now, when we start up our app, we should see a nice styled blue button that says “Fetch Articles”. Upon clicking that button, you should have our sample list of articles. Hooray! User interaction and proper component compartmentalization!

Our final result (for now)

The Code So Far

web/elm/Components/ArticleList.elm:

module Components.ArticleList exposing (..)
import Html exposing (Html, text, ul, li, div, h2, button)
import Html.Attributes exposing (class)
import Html.Events exposing (onClick)
import List
import Article
type alias Model =
{ articles: List Article.Model }
type Msg
= NoOp
| Fetch
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
NoOp ->
(model, Cmd.none)
Fetch ->
(articles, Cmd.none)
articles : Model
articles =
{ articles =
[ { title = "Article 1", url = "http://google.com", postedBy = "Author", postedOn = "06/20/16" }
, { title = "Article 2", url = "http://google.com", postedBy = "Author 2", postedOn = "06/20/16" }
, { title = "Article 3", url = "http://google.com", postedBy = "Author 3", postedOn = "06/20/16" } ] }
renderArticle : Article.Model -> Html a
renderArticle article =
li [ ] [ Article.view article ]
renderArticles : Model -> List (Html a)
renderArticles articles =
List.map renderArticle articles.articles
initialModel : Model
initialModel =
{ articles = [] }
view : Model -> Html Msg
view model =
div [ class "article-list" ]
[ h2 [] [ text "Article List" ]
, button [ onClick Fetch, class "btn btn-primary" ] [ text "Fetch Articles" ]
, ul [] (renderArticles model) ]

web/elm/Components/Article.elm:

module Article exposing (view, Model)
import Html exposing (Html, span, strong, em, a, text)
import Html.Attributes exposing (class, href)
type alias Model =
{ title : String, url : String, postedBy : String, postedOn: String }
view : Model -> Html a
view model =
span [ class "article" ] [
a [ href model.url ] [ strong [ ] [ text model.title ] ]
, span [ ] [ text (" Posted by: " ++ model.postedBy) ]
, em [ ] [ text (" (posted on: " ++ model.postedOn ++ ")")
]
]

web/elm/Main.elm:

module Main exposing (..)
import Html exposing (Html, text, div)
import Html.App
import Html.Attributes exposing (class)
import Components.ArticleList as ArticleList
-- MODEL
type alias Model =
{ articleListModel : ArticleList.Model }
initialModel : Model
initialModel =
{ articleListModel = ArticleList.initialModel }
init : (Model, Cmd Msg)
init =
( initialModel, Cmd.none )
-- UPDATE
type Msg
= ArticleListMsg ArticleList.Msg
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
ArticleListMsg articleMsg ->
let (updatedModel, cmd) = ArticleList.update articleMsg model.articleListModel
in ( { model | articleListModel = updatedModel }, Cmd.map ArticleListMsg cmd )
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- VIEW
view : Model -> Html Msg
view model =
div [ class "elm-app" ]
[ Html.App.map ArticleListMsg (ArticleList.view model.articleListModel) ]
-- MAIN
main : Program Never
main =
Html.App.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}

Next Steps

That was quite a lot to get started, so we’ll pause here and continue on in the next article in this series, where we’ll fetch the data from our API, set up a couple of mock endpoints, and slowly start to stitch together a working site and SPA!

If you’d like to check out the finished source code for this tutorial, the code is available on Github at https://github.com/Diamond/elm_articles.

Next Article In This Series