|
|
|
import Html exposing (..) |
|
import Html.Attributes exposing (..) |
|
import Html.Events exposing (..) |
|
import Http |
|
import Task |
|
import Json.Decode as Decoder exposing(Decoder, field) |
|
|
|
-- Every elm program starts with main. We'll use the `App.program` function |
|
-- to kick things off. It needs some functions mapped. |
|
main = |
|
Html.program |
|
{ init = init |
|
, view = view |
|
, update = update |
|
, subscriptions = subscriptions |
|
} |
|
|
|
|
|
|
|
-- MODEL |
|
|
|
-- Describe a Person - a name and the craft they're on. |
|
|
|
-- Here's what the data looks like: |
|
-- |
|
-- { |
|
-- "message": "success", |
|
-- "number": 3, |
|
-- "people": [ |
|
-- { |
|
-- "craft": "ISS", |
|
-- "name": "Anatoly Ivanishin" |
|
-- }, |
|
-- . . . |
|
-- ] |
|
-- } |
|
-- |
|
-- So, each item under "people" has a name and a craft. That's the PersonRecord we're defining: |
|
|
|
type alias PersonRecord = |
|
{ name: String |
|
, craft: String |
|
} |
|
|
|
-- Describe our model - the representation of the state of the app |
|
-- In this app, it's a list of PersonRecords and an error message we can display if something goes wrong. |
|
type alias Model = |
|
{ people : List PersonRecord |
|
, errorMessage : String |
|
} |
|
|
|
|
|
-- This function returns an initialized model with an empty list. This is |
|
-- what our app will start with, and any other time we need to reset |
|
-- the model. |
|
initialModel: Model |
|
initialModel = |
|
{ people = [], errorMessage = "" } |
|
|
|
|
|
-- init is what runs when the app starts - it's mapped by Html.program. It returns a model and the function that kicks it all off. |
|
init : (Model, Cmd Msg) |
|
init = |
|
( initialModel |
|
, getPeopleInSpace |
|
) |
|
|
|
|
|
|
|
-- UPDATE |
|
|
|
-- Normally we'd have messages for button clicks and other events. |
|
-- But we can have messages to represent other events, like successful API results. |
|
-- In this app the only thing we do is fetch records from the API. |
|
-- So the Msg is the result from our API call. |
|
-- The successful request is going to return a list of PersonRecords |
|
-- A failure? It'll return an HTTP error. We'll pattern match these values |
|
-- in the update function. |
|
type Msg |
|
= GotResultsFromJson (Result Http.Error (List PersonRecord)) |
|
|
|
-- update is fired when one of those events fires. Did the request succeed or not? |
|
-- if it succeeds, create a new model with the data we got back. |
|
-- If it fails? Just display the default model. |
|
update : Msg -> Model -> (Model, Cmd Msg) |
|
update msg model = |
|
case msg of |
|
GotResultsFromJson (Ok data) -> |
|
( {model | people = data}, Cmd.none) |
|
GotResultsFromJson (Err message) -> |
|
( {model | errorMessage = ("Can't get data because " ++ toString message)}, Cmd.none) |
|
|
|
|
|
-- VIEW |
|
|
|
-- The display of the data. Show a div with an h1 and an unordered list. |
|
-- We use List.map to iterate over the list of PersonRecords, generating an array of |
|
-- list items. We put the name and the craft in the text of each listitem |
|
-- Weird to read at first, but a lot nicer than a ForEach loop. |
|
view : Model -> Html Msg |
|
view model = |
|
let |
|
content = |
|
(List.map (\p -> li [] [text (p.name ++ " - " ++ p.craft) ]) model.people) |
|
in |
|
div [] |
|
[ div [] [text model.errorMessage ] |
|
, h1 [] [text "Who's in space"] |
|
, ul [] content |
|
] |
|
|
|
|
|
|
|
-- SUBSCRIPTIONS |
|
|
|
-- We need to define subscriptions for this app because we are using the Html.program function to kick |
|
-- off the app, and it requires that we provide it some subscriptions. But we don't need any subscriptions |
|
-- so we just state that. One of the drawbacks to programming to an interface sometimes. |
|
subscriptions : Model -> Sub Msg |
|
subscriptions model = |
|
Sub.none |
|
|
|
|
|
|
|
-- HTTP |
|
|
|
-- To get data from the API, we set the URL variable, and use a Task to make the call. |
|
-- A Task will execute and then emit a message that our Update will respond to. |
|
-- We perform the task, passing the messages it will return, followed by the function |
|
-- the task should call. |
|
|
|
-- The Http.get function needs to know how to decode the results it gets back, so |
|
-- we have to pass a JSON decoder in, along with the URL. This is not JS - Elm is staticly |
|
-- typed, so we have to map data types to Elm data types. That's what a decoder does. |
|
getPeopleInSpace : Cmd Msg |
|
getPeopleInSpace = |
|
let |
|
url = |
|
"http://localhost:4242/api/astros.json" |
|
in |
|
decodePeopleInSpace |
|
|> Http.get(url) |
|
|> Http.send GotResultsFromJson |
|
|
|
|
|
-- Here's the decoder. It needs to return a List of PersonRecords |
|
-- First we define the decoder for the PersonRecord - the name and craft. |
|
-- Once we have that we can use `at` to fetch all the data from the "people" field and |
|
-- decode it to a list. |
|
decodePeopleInSpace : Decoder (List PersonRecord) |
|
decodePeopleInSpace = |
|
let |
|
decoder = Decoder.map2 PersonRecord |
|
(field "name" Decoder.string) |
|
(field "craft" Decoder.string) |
|
in |
|
Decoder.at ["people"] (Decoder.list decoder) |
|
|
|
-- That's the end of our program! |
|
|