elm

elmでwebアプリ作成 7 〜 webAPIで取ってきたデータを表示

基本的にelm tutorialの真似をして進めています。表示するデータが少し違う程度です。

今回の内容はこの辺りです。

前回は施設一覧を表示することができるようになったので、はじめに作ったjsonモックサーバーからデータを取ってきて表示して見ましょう。

まずは以下の3つのパッケージをインストールします。

elm-package install elm-lang/http # httpリクエストを送るためのパッケージ
elm-package install NoRedInk/elm-decode-pipeline # jsonをデコードするのに使用
elm-package install krisajenkins/remotedata # 外部リソースを扱うために使用

大まかに以下のような動作をするものを作ります。

  1. initが呼び出された時に施設一覧データを取得
  2. モデルを更新
  3. 表示

必要なものは、httpリクエストを送ってデータを取得する機能、取得したjsonデータをFacility型にデコードする機能、データ取得結果をトリガーとしてモデルを更新する機能です。

メッセージがプログラムに送信されるとアップデートが呼ばれます。OnFetchFacilitysはウェブから取得したデータを運ぶので、その内容に従ってモデルがアップデートされます。

参考図

src/Msgs.elmの編集

webAPIでデータを取ってきたら、それをトリガーにしてメッセージを送り、モデルをアップデートします。そのため、メッセージを一つ定義しておきます。また、モデルをWebData型にします。

module Msgs exposing (..)

import Models exposing (Facility)
import RemoteData exposing (WebData)

type Msg
    = OnFetchFacilities (WebData (List Facility))

src/Models.elmの編集

モデルをWebData型にします。initの時点ではまだリソースを取得していないため、initialModelはロード中にしておきます。また、Facilityのopeningを、文字列のタプルではなく、レコードにしました。元のdb.jsonの方も、"opening": ["9:00", "22:30"]という形式から、"opening": {"open": "9:00", "close": "22:30"}という形式に変更しておきます。ここには開館時間と閉館時間が入るため配列は不適切であり、また、jsonをelmのデータ型にデコードする時にも、こちらの方が処理がわかりやすいためです。

module Models exposing (..)

import RemoteData exposing (WebData)

type alias Model =
    { facilities: WebData (List Facility) }

initialModel : Model
initialModel =
    { facilities = RemoteData.Loading
    }

type alias FacilityId =
    String

type alias Facility =
    { id: FacilityId
    , name: String
    , opening: Opening
    , address: String
    , postcode: String
    , web_site: String
    , description: String
    }

type alias Opening =
    { open: String
    , close: String
    }

src/Commands.elmの作成・編集

外部からリソースを取ってくるhttp通信は副作用なので、コマンドを使用します。このコマンド(fetchFacilities)を、init時に呼ぶことになります。Json.Decodeを使ってjsonデコーダーを定義します。ここで、先ほどのopeningの型が重要になります。このフィールドはjsonオブジェクトが入れ子になっているため、openingDecoderというのを別途定義して、このデコーダーを使用します。

module Commands exposing (..)

import Http
import Json.Decode as Decode
import Json.Decode.Pipeline exposing (decode, required)
import Msgs exposing (Msg)
import Models exposing (FacilityId, Facility, Opening)
import RemoteData

fetchFacilities : Cmd Msg
fetchFacilities =
    Http.get fetchFacilitiesUrl facilitiesDecoder
        |> RemoteData.sendRequest
        |> Cmd.map Msgs.OnFetchFacilities

fetchFacilitiesUrl : String
fetchFacilitiesUrl =
    "http://localhost:4000/facilities"

facilitiesDecoder : Decode.Decoder (List Facility)
facilitiesDecoder =
    Decode.list facilityDecoder

facilityDecoder : Decode.Decoder Facility
facilityDecoder =
    decode Facility
        |> required "id" Decode.string
        |> required "name" Decode.string
        |> required "opening" openingDecoder
        |> required "address" Decode.string
        |> required "postcode" Decode.string
        |> required "web_site" Decode.string
        |> required "description" Decode.string

openingDecoder : Decode.Decoder Opening
openingDecoder =
    decode Opening
        |> required "open" Decode.string
        |> required "close" Decode.string

src/Main.elmの編集

init時に先ほどのコマンドを呼ぶようにします。

module Main exposing (..)

import Commands exposing (fetchFacilities)
import Html exposing (program)
import Models exposing (Model, initialModel)
import Msgs exposing (Msg)
import Update exposing (update)
import View exposing (view)


init : (Model, Cmd Msg)
init =
    ( initialModel, fetchFacilities )

subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.none

main : Program Never Model Msg
main =
    program
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }

src/Update.elmの編集

OnfetchFacilitiesは外部リソースを取得した時に発行されるメッセージなので、取得した施設一覧データでモデルを更新します。

module Update exposing (..)

import Models exposing (Model)
import Msgs exposing (Msg(..))

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        Msgs.OnFetchFacilities response ->
            ( { model | facilities = response }, Cmd.none)

src/Facilities/List.elmの編集

WebDataは、未リクエスト、ロード中、成功、失敗の4つのパターンがあるので、それに応じてパターン分岐します。また、openinigの型を変更したのに合わせて、表示する時の値の取得方法も少し変わります(56行目、61行目)。

module Facilities.List exposing (..)

import Html exposing (..)
import Models exposing (Facility)
import Msgs exposing (Msg)
import RemoteData exposing (WebData)



view : WebData (List Facility) -> Html Msg
view response =
    div []
        [ nav
        , maybeList response
        ]

nav : Html Msg
nav =
    div []
        [ div [] [ text "施設一覧" ] ]

maybeList : WebData (List Facility) -> Html Msg
maybeList response =
    case response of
        RemoteData.NotAsked ->
            text ""
        RemoteData.Loading ->
            text "Loading..."
        RemoteData.Success facilities ->
            list facilities
        RemoteData.Failure error ->
            text (toString error)

list : List Facility -> Html Msg
list facilities =
    div []
        [ table []
            [ thead []
                [ tr []
                    [ th [] [ text "Id" ]
                    , th [] [ text "Name" ]
                    , th [] [ text "Opening" ]
                    , th [] [ text "Address" ]
                    , th [] [ text "Postal code" ]
                    , th [] [ text "URL" ]
                    , th [] [ text "description" ]
                    ]
                ]
            , tbody [] (List.map facilityRow facilities)
            ]
        ]

facilityRow : Facility -> Html Msg
facilityRow facility =
    let
        openingTime = facility.opening
    in
        tr []
            [ td [] [ text facility.id ]
            , td [] [ text facility.name ]
            , td [] [ text (openingTime.open ++ " - " ++ openingTime.close) ]
            , td [] [ text facility.address ]
            , td [] [ text facility.postcode ]
            , td [] [ text facility.web_site ]
            , td [] [ text facility.description ]
            ]

ブラウザで確認すると、以下のようなつまらない一覧ページが表示されます。

次は、各施設の個別ページを作って行きます。
elmでwebアプリ作成8 〜 施設名をリンクにする

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です