Elm

Elm 0.18で作るTodoアプリ(2)

前回までの復習
Elm 0.18で作るTodoアプリ(1)

前回をベースにTodoアプリを拡張していきます。

今回のテーマ

テキストボックスへ入力したTodoをTodoリストへ追加できるようにする

ファイル構成

  • Main.elm:大元のプログラム。基本的には配下のモジュールへ委譲するのみ。
  • TodoList.elm:Todoのリスト管理を行う
  • TodoCreator.elm:(New!)テキストボックスを管理する。

実装

TodoCreator.elmの新規作成

  • moduleの宣言とimport関連
module TodoCreator exposing (..)

import Html exposing (..)
import Html.Events exposing (onClick, onInput)
import Html.Attributes exposing (value, type_)
  • モデルの定義

テキストエリアの内容を格納するItem型のinputStrを保持するものをModelとしています。
モデルの更新通知用のメッセージをUpdateInputと定義しています。

-- model
type alias Item = 
    String

type alias Model = 
    { inputStr : Item
    }

initialModel : Model
initialModel = 
    { inputStr = ""
    }

type Msg
    = NoOp
    | UpdateInput String

  • updateの定義

UpdateInputの引数でs(文字列)をもらい、それをinputStrへ追加することにしました。

-- update
update : Msg -> Model -> ( Model, Cmd Msg )
update message model =
    case message of
        NoOp ->
            model ! []

        UpdateInput s ->
            { model | inputStr = s } ! []

  • viewの定義

todoInputの定義で文字入力があったらinputStrへ格納するようにしています。

-- view
view : Model -> Html Msg
view model = 
    div []
        [ todoInput model
        ]

todoInput : Model -> Html Msg
todoInput model = 
    form []
        [ input [ onInput UpdateInput, value model.inputStr ] []
        ]

Main.elmの変更

TodoCreator.elmを追加したことに伴い、処理を委譲するところを変更しています。
あと、全体的にModelの定義をAppModelからModelへリネームしています。

  • module、importとmain関数の変更箇所
 module Main exposing (..)

 import Html exposing (Html, program)
+import TodoCreator
 import TodoList

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

TodoCreatorのimportを追加します。

  • modelの変更箇所
 -- model
-type alias AppModel =
-    { todoListModel : TodoList.Model
+type alias Model = 
+    { todoCreator : TodoCreator.Model
+    , todoList : TodoList.Model
     }

-initialModel : AppModel
+initialModel : Model
 initialModel = 
-    { todoListModel = TodoList.initialModel
+    { todoCreator = TodoCreator.initialModel
+    , todoList = TodoList.initialModel
     }

-init : ( AppModel, Cmd Msg)
+init : ( Model, Cmd Msg)
 init = 
     ( initialModel, Cmd.none)

 type Msg
-    = TodoListMsg TodoList.Msg
+    = TodoCreatorMsg TodoCreator.Msg
+    | TodoListMsg TodoList.Msg

ここは単純にTodoCreator関連の追加とリネームだけなので、特に説明はないです。

  • update(とsubscription)の処理の変更箇所
 -- update
-update : Msg -> AppModel -> ( AppModel, Cmd Msg )
+update : Msg -> Model -> ( Model, Cmd Msg )
 update message model = 
     case message of
+        TodoCreatorMsg subMsg ->
+            let
+                ( updatedCreator, todoCreatorCmd ) =
+                    TodoCreator.update subMsg model.todoCreator
+            in
+                ( { model | todoCreator = updatedCreator }, Cmd.map TodoCreatorMsg todoCreatorCmd )
+
         TodoListMsg subMsg ->
             let
                 ( updatedTodoListModel, todoListCmd ) =
-                    TodoList.update subMsg (TodoList.ToDo 0 "dummy") model.todoListModel
+                    TodoList.update subMsg (TodoList.ToDo False model.todoCreator.inputStr) model.todoList
             in
-                ( { model | todoListModel = updatedTodoListModel }, Cmd.map TodoListMsg todoListCmd )
+                ( { model | todoList = updatedTodoListModel }, Cmd.map TodoListMsg todoListCmd )

 -- subscription
-subscriptions : AppModel -> Sub Msg
+subscriptions : Model -> Sub Msg
 subscriptions model =
     Sub.none

updateはTodoListと同様にTodoCreatorへ処理を委譲することを記述します。
case文のもう一方であるTodoListMsgは委譲する引数をdummyのものからtodoCreatorのinputStrを渡すように変更しています。これにより、TodoCreatorで入力された内容をTodoList側へ渡すことが可能になります。

  • viewの変更箇所
 -- view
-view : AppModel -> Html Msg
+view : Model -> Html Msg
 view model =
     Html.div []
-        [ Html.map TodoListMsg (TodoList.view model.todoListModel) 
+        [ Html.map TodoCreatorMsg (TodoCreator.view model.todoCreator)
+        , Html.map TodoListMsg (TodoList.view model.todoList) 
         ]

TodoCreatorのviewも追加します。

TodoList.elmの変更

  • modelの変更箇所
 -- model
 type alias ToDo = 
-    { count : Int
+    { done : Bool
     , item : String
     }

type alias Model = 
    { todoList : List ToDo
    }

initialModel : Model
initialModel = 
    { todoList = 
-        [ ToDo 1 "item1" 
-        , ToDo 2 "item2"
+        [ ToDo False "item1" 
+        , ToDo False "item2"
+        , ToDo False "item3"
         ]
    }

type Msg
    = NoOp
-   | AddNew ToDo
+   | AddNew

まず、ToDoの定義でcount変数は使われていなかったので、Todoらしく実行済みか否かというBool型の変数doneに変更しました。

initialModelはToDoの変数変更に伴う修正がメインです。

MsgはAddNewの引数を削除しました。これについては後述します。

  • updateの変更箇所
 -- update
 update : Msg -> ToDo -> Model -> ( Model, Cmd Msg )
 update message todo model =
         NoOp ->
             model ! []

-        AddNew todo -> 
-            ( { model | todoList = model.todoList ++ [todo] }, Cmd.none )
+        AddNew -> 
+            { model | todoList = model.todoList ++ [todo] } ! []

AddNewについて、model側での引数を削除したので、update側でも削除します。
また、「( {}, Cmd.none)」という表現と「() ! []」は同等の処理になるらしく、すっきりして見えるので書き換えました。
引数ですが、AddNewの引数のtodoを使うのではなく、updateの引数のtodoを使うようにしました。
このようにすることで、親(Main.elm)から受け取った情報を使うことができるようになりました。

  • viewの変更箇所
 -- view
 view : Model -> Html Msg
 view model = 
     div []
-        [ viewList model.todoList
+        [
+        div [ class "p2" ]
+            [ addButton
+            , viewItems model.todoList
+            ]
         ]

-viewList : List ToDo -> Html Msg
-viewList models = 
-    div [ class "p2" ]
-    [ viewItems models
-    , updateButton models
-    ]

 viewItems : List ToDo -> Html Msg
 viewItems models = 
     ul [] (List.map viewItem models)

 viewItem : ToDo -> Html Msg
 viewItem model = 
     li [] [
         text model.item
     ]

-updateButton : List ToDo -> Html Msg
-updateButton models = 
-    div [] 
-        [ button [ onClick (AddNew (ToDo 4 "item4")) ] [ text "Click" ] ]
-    ]
+addButton : Html Msg
+addButton = 
+    div [] 
+        [ button [ onClick AddNew ] [ text "Add" ] ]

viewListを関数として定義する意味があまりなさそうだったのでview関数に統合してしまいました。
updateButton関数の代わりにaddButton関数を定義しました。
型を見てもらえば分かりますが、引数を持たず、HtmlとMsgを返す関数としています。
AddNewの定義で引数がなくなったので呼び出し側も引数がなくなりました。

実行結果

前回と同様にmakeして、作成したindex.htmlをブラウザで見ます。
テキストエリアに文字を入力しAddボタンを押下するとTodoが追加されることが分かると思います。
FF_elm_study2.png

おわりに

TodoCreator追加に伴った変更を行ってきました。
今回の例のように、配下のモジュールが追加されたら、親の各要素で配下へ委譲するという処理を追加すると連携ができることが分かったと思います。

次回はTodoのチェック用にチェックボックスの設置する拡張を行っていきたいと思います。
次回:Elm 0.18で作るTodoアプリ(3)