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!
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 ArticleListmain : 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 Articlearticles : 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 articlesview : 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!
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 Articletype alias Model =
{ articles: List Article.Model }type Msg
= NoOp
| Fetchupdate : 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.articlesinitialModel : 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-- MODELtype alias Model =
{ articleListModel : ArticleList.Model }initialModel : Model
initialModel =
{ articleListModel = ArticleList.initialModel }init : (Model, Cmd Msg)
init =
( initialModel, Cmd.none )-- UPDATEtype Msg
= ArticleListMsg ArticleList.Msgupdate : 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-- VIEWview : Model -> Html Msg
view model =
div [ class "elm-app" ]
[ Html.App.map ArticleListMsg (ArticleList.view model.articleListModel) ]-- MAINmain : 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
Check out my new book!
Hey everyone! If you liked what you read here and want to learn more with me, check out my new book on Elixir and Phoenix web development:
I’m really excited to finally be bringing this project to the world! It’s written in the same style as my other tutorials where we will be building the scaffold of a full project from start to finish, even covering some of the trickier topics like file uploads, Twitter/Google OAuth logins, and APIs!