7.5

Creating List Posts Page

Let’s move the code for fetching and displaying all posts to a new module. Create a new directory called Page inside post-app and add a file named ListPosts.elm to it.

Add the following code to ListPosts.elm.

module Page.ListPosts exposing (Model, Msg, init, update, view)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Http
import Json.Decode as Decode
import Post exposing (Post, PostId, postsDecoder)
import RemoteData exposing (WebData)


type alias Model =
    { posts : WebData (List Post)
    }


type Msg
    = FetchPosts
    | PostsReceived (WebData (List Post))


init : () -> ( Model, Cmd Msg )
init _ =
    ( { posts = RemoteData.Loading }, fetchPosts )


fetchPosts : Cmd Msg
fetchPosts =
    Http.get
        { url = "http://localhost:5019/posts/"
        , expect =
            postsDecoder
                |> Http.expectJson (RemoteData.fromResult >> PostsReceived)
        }


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        FetchPosts ->
            ( { model | posts = RemoteData.Loading }, fetchPosts )

        PostsReceived response ->
            ( { model | posts = response }, Cmd.none )



-- VIEWS


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick FetchPosts ]
            [ text "Refresh posts" ]
        , viewPosts model.posts
        ]


viewPosts : WebData (List Post) -> Html Msg
viewPosts posts =
    case posts of
        RemoteData.NotAsked ->
            text ""

        RemoteData.Loading ->
            h3 [] [ text "Loading..." ]

        RemoteData.Success actualPosts ->
            div []
                [ h3 [] [ text "Posts" ]
                , table []
                    ([ viewTableHeader ] ++ List.map viewPost actualPosts)
                ]

        RemoteData.Failure httpError ->
            viewFetchError (buildErrorMessage httpError)


viewTableHeader : Html Msg
viewTableHeader =
    tr []
        [ th []
            [ text "ID" ]
        , th []
            [ text "Title" ]
        , th []
            [ text "Author" ]
        ]


viewPost : Post -> Html Msg
viewPost post =
    tr []
        [ td []
            [ text (Post.idToString post.id) ]
        , td []
            [ text post.title ]
        , td []
            [ a [ href post.authorUrl ] [ text post.authorName ] ]
        ]


viewFetchError : String -> Html Msg
viewFetchError errorMessage =
    let
        errorHeading =
            "Couldn't fetch posts at this time."
    in
    div []
        [ h3 [] [ text errorHeading ]
        , text ("Error: " ++ errorMessage)
        ]


buildErrorMessage : Http.Error -> String
buildErrorMessage httpError =
    case httpError of
        Http.BadUrl message ->
            message

        Http.Timeout ->
            "Server is taking too long to respond. Please try again later."

        Http.NetworkError ->
            "Unable to reach server."

        Http.BadStatus statusCode ->
            "Request failed with status code: " ++ String.fromInt statusCode

        Http.BadBody message ->
            message

Creating the Main Module

We moved almost all of the code from DecodingJson.elm to either ListPosts.elm or Post.elm. The only thing remaining is the main function. Where should we put it? It too should have its own module. Create a new file called Main.elm in the post-app directory and add the following code to it.

module Main exposing (main)

import Browser
import Page.ListPosts as ListPosts


main : Program () ListPosts.Model ListPosts.Msg
main =
    Browser.element
        { init = ListPosts.init
        , view = ListPosts.view
        , update = ListPosts.update
        , subscriptions = \_ -> Sub.none
        }

Right now the Main module relies heavily on the ListPosts page. In the next section, we’ll give it its own Model, Msg, init, view, and update functions so that it’s not tightly coupled to any specific module. Going forward, the primary responsibility of Main will be to route users to the correct page.

Before moving on, let’s make sure everything is working. Run json-server from the beginning-elm directory in terminal using the following command.

$ json-server --watch server/db.json -p 5019 --delay 1000

Now run elm reactor from the beginning-elm directory in a different terminal window and go to this URL in your browser: http://localhost:8000/post-app/Main.elm. You should see a list of posts.

Back to top

New chapters are coming soon!

Sign up for the Elm Programming newsletter to get notified!

* indicates required
Close