Elm Blog GitHub Series
Part 3 - Add multiple pages
Draft

Add interaction while improving the architecture.
If you're just arriving, read
the introduction
,
part 1
and
part 2
first.

Step 1 - Add The Elm Architecture skeleton

Now that we know how to get something in Elm on a web page hosted on GitHub, let's follow
The Elm Architecture
.
In it's simplest form it includes:
a model,
a view, and
an update function.
The model represents our data.
The view defines how we want the model to be displayed. In this case we will output HTML.
The update function explains what should happen when a new message is received requesting some action or change. Example: requesting to view a blog post.
Add the following to your
Main.elm
file:
main =
    Browser.sandbox
        { init = home
        , update = update
        , view = view
        }


-- Model


type alias Model = { ... }


home = Nothing



-- Update


type Msg = Reset | ...


update msg model =
    case msg of
        Reset ->
            ...



-- View


view model =
    div []
        [ h1 [] [ text "Elm explorer blog" ]
        , text "Here's what I learned while exploring Elm..."
        ]
This won't compile yet. We'll fill in the "..." bits soon.

Step 2 - Define the model

Now we'll define our model. To keep it simple let's just have a title and some content.
type alias Model =
    { title : String
    , content : String
    }
Let's create a function that returns the model we want for our home page.
home =
    { title = "Elm explorer blog"
    , content = "Here's what I learned while exploring Elm..."
    }

Step 3 - Define the interactions

Next we'll define the interactions we want in our blog. To continue with the simplicity theme, we'll define each of the pages messages as distinct messages:
type Msg
    = ShowHomePage
    | ShowWhatIMadeWithElmPost
This means we can do two things in our application: show the home page and show the "What You Made with Elm" blog post.

Step 4 - Define the
update
function

The
update
function defines how to interact with the view and the outside world.
We'll define how to handle our interactions:
update msg model =
  case msg of
      ShowHomePage ->
          ...
This won't compile for a couple of reasons. The first is obvious: we haven't defined what happens when we want to show the home page.
Let's fill that in:
update msg model =
  case msg of
      ShowHomePage ->
          home
We've already defined
home
so it seems like this should work. Let's see what the compiler tells us...
elm make src/Main.elm --output=elm.js
Output from the compiler:
-- MISSING PATTERNS ----------------------------------------------- src/Main.elm

This `case` does not have branches for all possibilities:

43|>    case msg of
44|>        ShowHomePage ->
45|>            home

Missing possibilities include:

    ShowWhatIMadeWithElmPost

I would have to crash if I saw one of those. Add branches for them!

Hint: If you want to write the code for each branch later, use `Debug.todo` as a
placeholder. Read <https://elm-lang.org/0.19.0/missing-patterns> for more
guidance on this workflow.
Whoa! That's really useful. It tells us we forgot to handle one of the interactions. That means cases
must be exhaustive
when used this way. It even provides a link explaining why and how to resolve it.
Let's fix it by adding the other interaction to our case expression:
-- Add the whatIMadeWithElmPost record
whatIMadeWithElmPost =
    { title = "What I Made With Elm"
    , content = "Here's the blog I made in Elm: ..."
    }

-- Other code here


update msg model =
    case msg of
        ShowHomePage ->
            home

        ShowWhatIMadeWithElmPost ->
            whatIMadeWithElmPost
Here's what the whole program looks like right now:
module Main exposing (main)

import Browser
import Html exposing (..)


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


type alias Model =
    { title : String
    , content : String
    }


home =
    { title = "Elm explorer blog"
    , content = "Here's what I learned while exploring Elm..."
    }


whatIMadeWithElmPost =
    { title = "What I Made With Elm"
    , content = "Here's the blog I made in Elm: ..."
    }



-- Update


type Msg
    = ShowHomePage
    | ShowWhatIMadeWithElmPost


update msg model =
    case msg of
        ShowHomePage ->
            home

        ShowWhatIMadeWithElmPost ->
            whatIMadeWithElmPost


view model =
    div []
        [ h1 [] [ text "Elm explorer blog" ]
        , text "Here's what I learned while exploring Elm..."
        ]
Woohoo! It compiles!!
What happens on the page? Wait, it still just shows the same thing. Let's improve that.

Step 5 - Use the model in the
view
function

As you can see we've moved
viewBlogPost
to the bottom and renamed it to just
view
. Now we'll set the title and content based on the model instead of hard-coding it.
view model =
    div []
        [ h1 [] [ text model.title ]
        , text model.content
        ]
However, there's still no interaction. Not for long!

Step 6 - Add buttons

Add a
div
with two
button
elements to the
view
function. The
onClick
event mimics HTML and expects us to give it a
Msg
to define what it should do.
view model =
    div []
        [ h1 [] [ text model.title ]
        , text model.content
        , div []
            [ button [ onClick ShowHomePage ] [ text "Home" ]
            , button [ onClick ShowWhatIMadeWithElmPost ] [ text "What I Made with Elm" ]
            ]
        ]
We'll need to import
Html.Events
at the top before it will compile.
import Html.Events exposing (onClick)

Step 7 - Review the whole program

Here's your blog post app in all it's glory:
module Main exposing (main)

import Browser
import Html exposing (..)
import Html.Events exposing (onClick)


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


type alias Model =
    { title : String
    , content : String
    }


home =
    { title = "Elm explorer blog"
    , content = "Here's what I learned while exploring Elm..."
    }


whatIMadeWithElmPost =
    { title = "What I Made With Elm"
    , content = "Here's the blog I made in Elm: ..."
    }



-- Update


type Msg
    = ShowHomePage
    | ShowWhatIMadeWithElmPost


update msg model =
    case msg of
        ShowHomePage ->
            home

        ShowWhatIMadeWithElmPost ->
            whatIMadeWithElmPost


view model =
    div []
        [ h1 [] [ text model.title ]
        , text model.content
        , div []
            [ button [ onClick ShowHomePage ] [ text "Home" ]
            , button [ onClick ShowWhatIMadeWithElmPost ] [ text "What I Made with Elm" ]
            ]
        ]
This is a naïve approach to adding multiple pages meant for learning.
Once you've built some small things in Elm and need to organize bigger applications with multiple pages (single-page applications - also known as SPAs), I recommend checking out Richard Feldman's
elm-spa-example
. I use this architecture at work all the time with great success.
If you're looking to build a production blog with Elm, check out these tools:
Alex Korban's
Elmstatic
- An Elm-to-HTML static site generator.
Dillon Kearns'
elm-pages
- This is what I used to build this blog!
These tools require some advanced understanding of Elm.

Summary

We covered a lot in this post:
case
expressions
Model using records
Things we didn't discuss, but secretly used anyway:
Pure functions
Union types
Declarative programming

Next steps

Explore some more on your own.
Here are a few ideas:
Improve the UI
with CSS
Create a full-screen app, controlling the page title, with
Browser.document
Write a new blog post (add interaction
Msg
):
ShowElmMuchLovePost
If you liked the series, share it!
If you have any feedback, I would love to hear from you:
dev@jaredmsmith.com
.
Sharing is caring:
Tweet
about this article.
© 2018 Jared M. Smith