LoginSignup
2
1

More than 3 years have passed since last update.

ElmでCRUDを作る - 其のC ②

Last updated at Posted at 2020-02-07

この記事は前の記事からの続きになってます。
追記した箇所だけ説明していくスタイルにしているので、サンプルコードで分からないところがあったら過去記事も参照してみてください。

ElmでCRUDを作る - 其のR

ElmでCRUDを作る - 其のC ①

ElmでCRUDを作る - 其のC ② (この記事)

ElmでCRUDを作る - 其のD

今回作ったもの

前回に引き続いてTODOリストです。
今回は前回のものを改良して、任意の文字列をリストに追加できるようにします。

完成品

完成品こちらです。ここ(Try Elm!)で貼り付けて動作を試せます。

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


-- MAIN

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


-- MODEL

type alias Model =
  { inputText : String
  , list : List String
  }

init : Model
init = Model "" []


-- UPDATE

type Msg
  = SetText String
  | AddItem

update : Msg -> Model -> Model
update msg model =
  case msg of
    SetText inputText ->
      { model | inputText = inputText }

    AddItem ->
      { model | list = model.list ++ [model.inputText] }


-- VIEW

view : Model -> Html Msg
view model =
  div []
    [ input [ onInput SetText ] []
    , button [ onClick AddItem ] [ text "Add" ]
    , makeList model.list
    ]


-- ユーザー定義関数

makeList : List String -> Html Msg
makeList list =
  ul [] ( List.map makeListItem list )

makeListItem : String -> Html Msg
makeListItem itemText =
  li [] [ text itemText ]

解説

モジュールの読み込み

import Html.Attributes exposing (..)

前回に加えてHtml.Attributesモジュールを追加しました。
これはタグの属性を記述するための関数を集めたモジュールです。

メイン関数

前回と同じなので割愛。

モデルの定義と初期化

type alias Model =
  { inputText : String
  , list : List String
  }

init : Model
init = Model "" []

モデルを定義します。
ちなみに { 〜 } という形で波括弧で囲って定義されるものをレコードと言って、またその要素をフィールドといいます。(⇒ レコード )
今回はモデルに inputText : String というフィールドを新たに追加します。
このフィールドはテキストボックスの入力値を保持するために使用します。

更新処理の定義

type Msg
  = SetText String
  | AddItem

今回はMsgに SetText String というバリアントを新しく追加しました。
これは先ほどモデルに定義したinputTextフィールドにテキストボックスの入力値を設定するための処理として利用します。

AddItemにも変更があります。
前回はAddItemは1つの値を持っていましたが、今回は値を持っていません。
なぜ持っていないかは後述します。

update : Msg -> Model -> Model
update msg model =
  case msg of
    SetText inputText ->
      { model | inputText = inputText }

    AddItem ->
      { model | list = model.list ++ [model.inputText] }

先ほどメッセージに追加したSetTextの処理内容を定義します。
この処理はSetTextの値として渡ってきたinputTextの値を新しいモデルのinputTextフィールドに設定して生成します。
SetTextのinputTextとモデルのinputTextは名前が同じですが別物です。

AddItemにも変更があります。
前回はAddItemの持っている値をリストに追加していましたが、今回はその代わりにモデルのinputTextフィールドの値を使います。
なので処理内容としては、元のモデルのlistフィールドのリストにinputTextフィールドの値を追加したものを新しいモデルとして生成します。

直接テキストボックスの値を渡してリストに追加したいところですが、入力を行うテキストボックスとクリック動作を受け取るボタンは部品としてお互い独立しているので直接値の受け渡しはできません。
なので、テキストボックスのへの入力という操作によって入力値をモデルに反映させたのちに、ボタンのクリックという操作によってモデルの持っているテキストボックスの入力値をリストに追加するという流れで反映させます。

ページ描画処理の定義

view : Model -> Html Msg
view model =
  div []
    [ input [ onInput SetText ] []
    , button [ onClick AddItem ] [ text "Add" ]
    , makeList model.list
    ]

前回に加えて input [ onInput SetText ] [] という部分を追加しました。inputはテキストボックスを作成する関数です。これでリストに加える文字列を入力するテキストボックスを作ります。
onInput SetText は入力時(onInput)にSetTextバリアントを更新処理に渡すということを表しています。
ただ、SetTextは1つ値を持っているはずなのにその値が記述されていません。
なぜかというと、SetTextの値にはテキストボックスの入力値が入るからです。(当然といえば当然ですが)
このようにinput関数では自らの入力値をバリアントに渡すことができるようになっています。

ユーザー定義関数

ここは前回と変わってないので割愛です。

全体の流れ

今回は、少し処理が複雑になったので一連の処理の流れを追ってみます。

まずはinit関数が実行されモデルを初期化します。

init : Model
init = Model "" []

次にview関数が実行され、モデルの初期状態を元にページが描画されます。

view : Model -> Html Msg
view model =
  div []
    [ input [ onInput SetText ] []
    , button [ onClick AddItem ] [ text "Add" ]
    , makeList model.list
    ]

この時点では、モデルの状態は inputText = "", list = [] という状態なので、テキストボックスの中身は空、リストにも何も表示されない、という状態でページが描画されます。

そして次に、ユーザーがテキストボックスに文字列を入力します。
例えば、「テスト」と入力したとします。

そうすると、この入力の操作をトリガーとしてupdate関数に SetText "テスト" というメッセージが送られます。
そして、update関数のSetTextの処理が実行されます。

update : Msg -> Model -> Model
update msg model =
  case msg of
    SetText inputText ->
      { model | inputText = inputText }

SetTextの処理によって、"テスト"という文字列がinputTextフィールドに設定された新しいモデルが生成されます。
つまり、現在のモデルの状態は inputText = "テスト", list = [] という状態です。

そして再びview関数が実行されます。

view : Model -> Html Msg
view model =
  div []
    [ input [ onInput SetText ] []
    , button [ onClick AddItem ] [ text "Add" ]
    , makeList model.list
    ]

ただ、画面の描画に関係あるmodel.listの状態は変わっていないので、画面の更新は行われません。
(どうやら画面の更新がかかるのはview関数の中で使っている値(この例ではmodel.list)が更新された時だけのようです。)

次に、ユーザーが"Add"ボタンがクリックします。
すると、クリックしたことをトリガーとしてupdate関数に AddItem というメッセージが送られます。

update : Msg -> Model -> Model
update msg model =
  case msg of
    AddItem ->
      { model | list = model.list ++ [model.inputText] }

AddItemの処理によって、モデルのinputTextフィールドに入っていた"テスト"という文字列がlistフィールドのリストに追加された状態の新しいモデルが生成されます。
つまり、現在のモデルの状態は inputText = "テスト", list = ["テスト"] という状態になります。

そしてさらに再びview関数が実行されます。

view : Model -> Html Msg
view model =
  div []
    [ input [ onInput SetText ] []
    , button [ onClick AddItem ] [ text "Add" ]
    , makeList model.list
    ]

model.listの値が変わったことによって、初めて画面の更新が行われます。
画面には"テスト"というアイテムがリストに表示された状態で描画されます。

と、ここまでが一連の流れです。

あと、"Add"ボタンは連続でクリックすれば同じ文字列がいくつもリストに追加されていきます。(まぁ当たり前ですが)

まとめ

更新処理も2つになってちょっと複雑さが増してきました。
処理の流れの基本は、ユーザーの操作⇒モデルの更新⇒ページの再描画、という流れになっています。
なんとなくこの辺が分かってくるとどういう風にプログラムを描いていけばいいかがだんだん見えてくる気がします。

次回は、D(Delete)を実装します。U(Update)は無いので次で終わりです。

ElmでCRUDを作る - 其のD

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1