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.