by Brandon Richey

Site in Phoenix and Elm

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


Creating Our Phoenix Site

 $ mix phoenix.new elm_articles 

answer ‘Y’ to the question about fetching/installing dependencies

Adding Brunch-Elm

brunch-elm package (npm repo) will allow us to work on Elm alongside our Phoenix application:

 $ npm install --save-dev elm-brunch 

Open up brunch-config.js and add the configuration for elmBrunch. Here is the example of the changed brunch-config file below:

  plugins: {
    babel: {
      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',
],

Your file package.json in the root of the project must contain:

  "dependencies": {
     "elm-brunch": "^0.5.0",
     "phoenix": "file:deps/phoenix",
     "phoenix_html": "file:deps/phoenix_html"
  },

open up web/static/js/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 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.

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 1", 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 1", 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 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
    }


We got up to the point where we were simulating the requests with Elm but weren’t actually making the necessary HTTP calls out. Let’s fix that!

Because getting HTTP support in place is significantly different from a standard Javascript implementation and requires a lot of new concepts, this article is going to be focused entirely on making an HTTP GET request to an API endpoint that returns a list of articles only. The next tutorial will focus on using the other verbs and building an Article form and the rest, but we’ll keep this one pretty short and sweet because there’s a lot going on here.

Adding In Some New Libraries

Let’s start off by adding in the library support that will give us the functionality we need. Open up web/elm/ArticleList.elm and we’ll start making modifications. We need a couple of things here:

  1. The ability to create HTTP requests (provided by Json)
  2. The ability to perform those HTTP requests (provided by Http)
  3. The ability to decode the responses for those HTTP requests (Provided by Task)
  4. The ability to debug messages if things are going south (Provided by Debug)

So, our imports will look like this:

import Http
import Task
import Json.Decode as Json exposing ((:=))
import Debug

The third line here is a little trickier. The Json library includes constructors such as int, string, etc, and in my mind those make the code more complicated, so we’re going to force specifying the Json module for those constructors (instead of “something” := int, we’d have “something” := Json.int). We’re also including one thing exposed into our current namespace, the := operator, which decodes a Json key into a Json value that we specify if it has that field (more on that later).

Updating Our Msg Type To Support HTTP

Now we need to update our messages (the Msg union type) to include a few more different types of messages. We already have our NoOp (No Operation) and Fetch calls, but we also need to add messages for when the fetch succeeds and for when the fetch fails.

If the fetch is successful, we want it to return a List of Articles (Article.Model). If the fetch is unsuccessful, it needs to have a constructor that takes in an Http.Error type (since that is what will be returned in those cases).

type Msg
  = NoOp
  | Fetch
  | FetchSucceed (List Article.Model)
  | FetchFail Http.Error

So, to recap:

  1. We need to specify a success Msg. That success msg must specify the type of the data that gets decoded out. Since our server is returning back Json structures that match our Article Model (and since it is an index call, it will return a list of articles. Thus, List Article.Model).
  2. We need to specify a failure Msg. That failure msg must accept Http.Error in its constructor.

And then we need to update our update function to be able to handle these new types:

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    NoOp ->
      (model, Cmd.none)
    Fetch ->
      (model, fetchArticles)
    FetchSucceed articleList ->
      (Model articleList, Cmd.none)
    FetchFail error ->
      case error of
        Http.UnexpectedPayload errorMessage ->
          Debug.log errorMessage
          (model, Cmd.none)
        _ ->
          (model, Cmd.none)

First, we need to modify our Fetch call to instead call a “fetchArticles” function. We haven’t written this function yet, but it’s the function responsible for handling the Command (Cmd) from our app. A Command is a managed effect in Elm, which is a nice way of saying that it is a Data representation of a side effect for something that we know we need to do. We change the Fetch handler to return out the current model (since we haven’t actually done the fetch via HTTP yet) and a Cmd letting Elm know that we’ll be modifying the state of our application as a result of this specified command.

Next, our FetchSucceed accepts an “articleList” argument. It just returns out a constructor of our Model, which has a built-in constructor that takes in one argument: a List of Articles. This call has no further Cmds we need to worry about, so for the Cmd we return out Cmd.none.

Finally, our FetchFail accepts the Http.Error message, which we then pattern match on. In the case of an Http.UnexpectedPayload, we capture the errorMessage that it returns and print it out to the web developer console via Debug.log (Debug.log takes a string that you wish to write out), and then return out our unmodified model with no Cmds. This lets us know if our data is set up in a wacky way or returning out something we’re not expecting to handle.

We now have all of the framework code in place that we need, so let’s move on to actually writing out the HTTP calls that do the actual work!

Adding In HTTP Functionality

We’ll start by writing our fetchArticles function that we mentioned earlier as the Cmd handler for actually performing the HTTP call.

-- HTTP calls
fetchArticles : Cmd Msg
fetchArticles =
  let
    url = "/api/articles"
  in
    Task.perform FetchFail FetchSucceed (Http.get decodeArticleFetch url)

fetchArticles returns a Cmd constructor that accepts a Msg type. We then specify the url that we want to fetch from inside of our let block, and then we call out to Task.perform.

Task.perform takes a few arguments. The first is what happens when the Task fails, the second is what happens when the Task succeeds, and finally we specify what the task itself is. Http.get is another helper function that performs the actual HTTP GET request; it takes in the decode function that we’ll be passing the data to and the url that we want to perform the fetch from.

Now, we need to tackle our decoding functions. We’ll start by analyzing the structure of the data that we’ll be sending back to the client:

{ data :
    [ { title: "Article Title", url: "Article URL", posted_by: "Author", posted_on: "Timestamp" }
    , { title: "Article Title", url: "Article URL", posted_by: "Author", posted_on: "Timestamp" }
    , { title: "Article Title", url: "Article URL", posted_by: "Author", posted_on: "Timestamp" }
    ]
}

It’s easier to tackle these functions in stages. If we were to parse this out ourselves, we’d:

  1. Write something to pull the data out of the “data” key
  2. Write something to pull the list of data out of the above response
  3. Write something to decode each item of the above into an Article

So let’s do precisely that:

-- Fetch the articles out of the "data" key
decodeArticleFetch : Json.Decoder (List Article.Model)
decodeArticleFetch =
  Json.at ["data"] decodeArticleList

Json.at is a simple function that says “At the specified key (or structure of keys), run the data extracted against the provided decoder. This specifies a “decodeArticleList” decoder that will take the data and turn it into an Elm list.

-- Then decode the "data" key into a List of Article.Models
decodeArticleList : Json.Decoder (List Article.Model)
decodeArticleList =
  Json.list decodeArticleData

Json.list is another helper function; this time, it says “apply the Json.list decoder to turn the passed-in data structure into an Elm list of decoders”. This time, we’re saying it is a List of Decoders that can parse the “Article” data structure, so let’s write that function next:

-- Finally, build the decoder for each individual Article.Model
decodeArticleData : Json.Decoder Article.Model
decodeArticleData =
  Json.object4 Article.Model
    ("title" := Json.string)
    ("url" := Json.string)
    ("posted_by" := Json.string)
    ("posted_on" := Json.string)

Here, we’re at the level of each individual item. Since we’re pulling 4 keys out, we need to use the Json.object4function to pull 4 keys out of the structure and convert it into an Article.Model type. We’re expecting to see title, url, posted_by, and posted_on in the structure, so we pass those to the Article.Model constructor. The :=operator here says “Look for the key specified on the left side and use the decoder on the right side to parse the data back out if it exists”.


Our Elm application is neat but barely functional right now. One thing we’ll need to really let our application shine a bit more is add a cursory sense of navigation. We can jump in by starting off refactoring our view function to be a little more extensible.

Refactoring the View Function

So right now, view just looks like this:

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

That means our view can ONLY ever display the Article List, but that’s clearly not what we’re going to want long-term. Let’s refactor this out by moving the Html.App.map call into its own function:

articleListView : Model -> Html Msg
articleListView model =
  Html.App.map ArticleListMsg (ArticleList.view model.articleListModel)

And now our view function should look like this:

view : Model -> Html Msg
view model =
  div [ class "elm-app" ]
    [ articleListView model ]

Also, it may not make sense for us to be so tightly coupling the articleView to our main application view. What if we want to navigate around and display different components? Right now we really do not have a good way to handle this, so let’s write one more function, pageView. pageView will accept a model as an argument and we’ll tweak the model later to also include a “currentView” member. Let’s write our pageView function first and see if that gives us a good path forward in writing the model changes and union types necessary for base navigation.

pageView : Model -> Html Msg
pageView model =
  articleListView model

And again, we’ll return back to our view function and continue refactoring.

view : Model -> Html Msg
view model =
  div [ class "elm-app" ]
    [ pageView model ]

Modifying our Model for Navigation

Our navigation is sort of useless without a way to actually change it. Let’s design out how we think we’ll want this to work:

  1. Our model should store what pageView is currently displaying
  2. There should be an update call to change what pageView displays
  3. We know what each value is that pageView will look for

Based on these, let’s create a new union type, Page. This will start off with two values: Root and ArticleList. For Root, we’ll need to create a welcomeView that will function as the welcome page the user sees when they first visit our site.

type Page
  = RootView
  | ArticleListView

Next, we’ll modify our model to track the current view a user is seeing:

type alias Model =
  { articleListModel : ArticleList.Model
  , currentView : Page
  }

initialModel : Model
initialModel = { articleListModel = ArticleList.initialModel , currentView = RootView }

Then we’ll need to update our Msg union type to allow for a new type of Msg that changes the model to display something new.

type Msg
  = ArticleListMsg ArticleList.Msg
  | UpdateView Page

Finally, we need to change the update function to be able to react to this new 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 )
    UpdateView page ->
      ({ model | currentView = page }, Cmd.none)

Changing Our Views

Now we have everything we need to be able to actually do navigation, but we’ll need to do a little legwork to actually allow for the navigation to happen.

First, we’ll modify our pageView function to actually look at model.currentView and display something different depending on that model’s value:

pageView : Model -> Html Msg
pageView model =
  case model.currentView of
    RootView ->
      welcomeView
    ArticleListView ->
      articleListView model

We don’t have our welcomeView function written yet, so this will bomb out. Let’s write that very quickly:

welcomeView : Html Msg
welcomeView =
  h2 [] [ text "Welcome to Elm Articles!" ]

Note: Make sure you add h2 to the list of functions exposed by the Html import, or this will fail. Alternatively, change the Html import statement to be:

import Html exposing (..)

Upon doing this and refreshing the page, you should see the following:

Adding a Header for Navigation

We need to provide some sort of navigation header as well to let the user get around. You’ll start to see some logic that is looking eerily like routing logic, which is intentional. Instead of jumping right into elm-navigation or hop or one of the other routing packages out there, I felt it’d make a lot more sense for us to tackle things from a very low level and work our way up to more complicated route state management!

Time for us to write a navigation header:

header : Html Msg
header =
  div []
  [ h1 [] [ text "Elm Articles" ]
  , ul []
    [ li [] [ a [ href "#", onClick (UpdateView RootView) ] [ text "Home" ] ]
    , li [] [ a [ href "#articles", onClick (UpdateView ArticleListView) ] [ text "Articles" ] ]
    ]
  ]

You’ll need a few more imports for this function above to work:

import Html.Attributes exposing (class, href)
import Html.Events exposing (onClick)

There’s not much to explain in our header function. We display a list (just a standard unordered list), and each of those contain an anchor tag. The href value is set to ‘#’ for the root route, and we have an onClick handler set up to call our update function with the appropriate Msg. Recall that our UpdateView Msg is a constructor that takes in the Page union type as its only argument, so that’s why our onClick looks like this:

onClick (UpdateView RootView)

If you didn’t wrap the UpdateView constructor call in parantheses, you’d get an error like the following:

Function `onClick` is expecting 1 argument, but was given 2.
77|                                     onClick UpdateView ArticleListView
                                                           ^^^^^^^^^^^^^^^
Maybe you forgot some parentheses? Or a comma?

Next, return to our view function and add the header to our div:

view : Model -> Html Msg
view model =
  div [ class "elm-app" ]
    [ header, pageView model ]

Now return to our page, and we should see a handy little navigation bar up at the top and the links are clickable, and even change our displayed view!

Firing Off Events When Switching Routes

One thing that we’ll probably want as part of our application is a way to load up data or fire off particular events per each time certain routes are loaded up. The good news is that with how simple our routing mechanism is right now, that becomes a very trivially easy thing to do! Let’s take a look at our update function (specifically, the UpdateView branch) and modify it to listen for particular page loads:

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 )
    UpdateView page ->
      case page of
        ArticleListView ->
          ({ model | currentView = page }, Cmd.map ArticleListMsg ArticleList.fetchArticles)
        _ ->
          ({ model | currentView = page }, Cmd.none)

In UpdateView page, we pattern match off of the supplied Page we’re transitioning to. If we’re switching to the ArticleListView, we’ll want to fire off the fetchArticles task defined inside of ArticleList.elm. Since doing this would mean our function would return an ArticleList.Msg instead of our Main Msg type, we need to use Cmd.map to transform the return Msg type of ArticleList.fetchArticles from ArticleList.Msg into ArticleListMsg (defined inside of our Main application’s Msg union type). That way we know that any resulting Msgs from ArticleList.fetchArticle need to be handled by our parent update statement (and converted into ArticleListMsg). We use Cmd.map because we’re saying that every resulting Cmd Msg needs to get passed to the constructor for ArticleListMsg.

Now, when you navigate to the Article List, it will fetch and display the Articles from our endpoint automatically! I’m going to remove the “Fetch Articles” button from the ArticleList view, since we don’t really need it anymore!

Adding a Show Link to the Article List

One thing that may not be immediately clear is what if one of our child components, like ArticleList, want to trigger navigation to a different component, such as an ArticleShow component. You can’t do this directly on the ArticleList, so you’ll instead want to bubble up the event to the parent and let that handle routing the way we were already handling routing.

Remember that we’re essentially adding a means of navigation to the ArticleList component that we’re expecting the parent to be able to handle, so we’ll create a new union type called SubPage in ArticleList.elm:

type SubPage
  = ListView
  | ShowView Article.Model

And we’ll also need to update our ArticleList Msg to handle the new Msg of changing our navigation, so we’ll update the Msg union type:

type Msg
  = NoOp
  | Fetch
  | FetchSucceed (List Article.Model)
  | FetchFail Http.Error
  | RouteToNewPage SubPage

And since we added a new Msg type, we need to add a handler for our update function that takes in this RouteToNewPage Msg. However, we don’t actually care what it does; it’s the parent’s job to handle those details, so we’ll add a dummy case to our update function:

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    NoOp ->
      (model, Cmd.none)
    Fetch ->
      (model, fetchArticles)
    FetchSucceed articleList ->
      (Model articleList, Cmd.none)
    FetchFail error ->
      case error of
        Http.UnexpectedPayload errorMessage ->
          Debug.log errorMessage
          (model, Cmd.none)
        _ ->
          (model, Cmd.none)
    _ ->
      (model, Cmd.none)

Now that we have a way of handling our RouteToNewPage Msg, we need a way to dispatch it. To do this we’ll need to add a new link to each Article that we display in the list. We should get into a habit of recognizing reusable code and breaking it apart into separate functions wherever we can. Let’s take a look at our articleLink function that we’ll write:

articleLink : Article.Model -> Html Msg
articleLink article =
  a
    [ href ("#article/" ++ article.title ++ "/show")
    , onClick (RouteToNewPage (ShowView article))
    ]
    [ text " (Show)" ]

We arbitrarily set the href attribute on our link, but our onClick is where the action is. On our onClick event, we fire off the RouteToNewPage Msg and we pass the ShowView SubPage, and pass the article that’s getting passed in to this function call into the ShowView constructor. Next, we’ll need to modify our renderArticle function to include our new articleLinkcall.

renderArticle : Article.Model -> Html Msg
renderArticle article =
  li [ ] [
    div [] [ Article.view article, articleLink article ]
  ]

Adding an Article Show Component

Now that we have a way to dispatch events that will move us to displaying an article, we should build a handy little ArticleShow component. Create a new file, ./Components/ArticleShow.elm, and we’ll populate it with the following:

module Components.ArticleShow exposing (..)
import Components.Article as Article
import Html exposing (..)
import Html.Attributes exposing (href)
type Msg = NoOp
view : Article.Model -> Html Msg
view model =
  div []
    [ h3 [] [ text model.title ]
    , a [ href model.url ] [ text ("URL: " ++ model.url) ]
    , br [] []
    , span [] [ text ("Posted by: " ++ model.postedBy ++ " On: " ++ model.postedOn) ]
    ]

This isn’t a very complicated component, overall. The biggest gotcha here is that we have to create a dummy Msg that just has a NoOp value, since this show component doesn’t actually do anything!

Modifying Our Main Component to Display the Show Component

Open up Main.elm and we need to start making modifications. First, we need to import our new ArticleShow component:

import Components.ArticleShow as ArticleShow

And we’ll need to update our Page union type to include a view for ArticleShow:

type Page
  = RootView
  | ArticleListView
  | ArticleShowView Article.Model

Note that the constructor for our ArticleShowView Page requires a passed-in Article.

We also need to update the Msg type to include messages from ArticleShow:

type Msg
  = ArticleListMsg ArticleList.Msg
  | UpdateView Page
  | ArticleShowMsg ArticleShow.Msg

Next, we need to tackle the changes to the update function, and it’s a doozy.

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    ArticleListMsg articleMsg ->
      case articleMsg of
        ArticleList.RouteToNewPage page ->
          case page of
            ArticleList.ShowView article ->
              ({ model | currentView = (ArticleShowView article) }, Cmd.none)
            _ ->
              (model, Cmd.none)
        _ ->
          let (updatedModel, cmd) = ArticleList.update articleMsg model.articleListModel
          in ( { model | articleListModel = updatedModel }, Cmd.map ArticleListMsg cmd )
    UpdateView page ->
      case page of
        ArticleListView ->
          ({ model | currentView = page }, Cmd.map ArticleListMsg ArticleList.fetchArticles)
        _ ->
          ({ model | currentView = page }, Cmd.none)
    ArticleShowMsg articleMsg ->
      (model, Cmd.none)

So, the biggest change here is that we need to catch any ArticleListMsgs and we pattern match off of the articleMsg argument itself. In the case of it being a RouteToNewPage from the ArticleList component, we change our currentView to dispatch to our ArticleShowView with the expected article. Otherwise, we return out the default dummy response. This block:

case articleMsg of
    ArticleList.RouteToNewPage page ->
      case page of
        ArticleList.ShowView article ->
          ({ model | currentView = (ArticleShowView article) }, Cmd.none)
        _ ->
          (model, Cmd.none)

If we do not get a RouteToNewPage Msg, then we’ll just handle it the same way we used to handle these events.

        _ ->
          let (updatedModel, cmd) = ArticleList.update articleMsg model.articleListModel
          in ( { model | articleListModel = updatedModel }, Cmd.map ArticleListMsg cmd )

Since we updated our Msg to include ArticleShowMsg, we need to handle that, so we’ll also handle the ArticleShowMsg case with a dummy handler.

ArticleShowMsg articleMsg ->
      (model, Cmd.none)

Next, we need to change our pageView function to deal with the new ArticleShowView.

pageView : Model -> Html Msg
pageView model =
  case model.currentView of
    RootView ->
      welcomeView
    ArticleListView ->
      articleListView model
    ArticleShowView article ->
      articleShowView article

Finally, we add an articleShowView function that takes in an article (Article.Model). It needs to map any Msgs dispatched from the ArticleShow component into the ArticleShowMsg Msg so that our main component knows how to deal with that:

articleShowView : Article.Model -> Html Msg
articleShowView article =
  Html.App.map ArticleShowMsg (ArticleShow.view article)