LoginSignup
0
0

More than 5 years have passed since last update.

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

Posted at

これまでの復習

Elm 0.18で作るTodoアプリ(1)「固定値のTodoをTodoリストへ追加できるようにする」
Elm 0.18で作るTodoアプリ(2)「テキストボックスへ入力したTodoをTodoリストへ追加できるようにする」
Elm 0.18で作るTodoアプリ(3)「Todoのチェック用にチェックボックスを設置する」
Elm 0.18で作るTodoアプリ(4)「TodoList側の機能拡張をする。」

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

今回のテーマ

(最終回)一意にupdateするTodoを決められるように機能拡張する。

ファイル構成

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

実装

Todo.elmの修正

  • モデルの修正
 type alias Model = 
-    { done : Bool
+    { itemID : Int
+    , done : Bool
     , item : String
     , del  : Bool
     }

-new : Bool -> String -> Bool -> Model
-new do s de = 
-    { done = do
+new : Int -> Bool -> String -> Bool -> Model
+new itemID do s de = 
+    { itemID = itemID
+    , done = do
     , item = s
     , del = de
     }

 type Msg
     = NoOp
-    | ToggleDone String
-    | OnDelete String
+    | ToggleDone Int
+    | OnDelete Int

Todoを一意に識別するためにitemID属性を追加します。
それに伴いnew関数の引数やメッセージも修正します。

  • updateの修正
update : Msg -> Model -> Model
update message model =
    case message of
         NoOp ->
             model

-        ToggleDone s -> 
-            if s == model.item then
+        ToggleDone itemID -> 
+            if itemID == model.itemID then
                 { model | done = not model.done }
             else
                 model

-        OnDelete s -> 
-            if s == model.item then
+        OnDelete itemID -> 
+            if itemID == model.itemID then
                 { model | del = True }
             else
                 model

ToggleDoneとonDeleteの引数を文字列からitemIDへ変更し、itemID単位で該当するTodoのみ状態を変更させるようにしています。

  • viewの定義
view : Model -> Html Msg
view model = 
    li [] 
        [ 
-            checkbox (ToggleDone model.item) model
+            checkbox (ToggleDone model.itemID) model
        ]

 checkbox : Msg -> Model -> Html Msg
 checkbox msg model = 
     label []
         [ input [ type_ "checkbox", checked model.done, onClick msg ] []
         , viewItem model
         , viewDeleteButton model
         ]

 viewItem : Model -> Html Msg
 viewItem model =
     if model.done == False then
         text model.item
    else
        s [] 
             [ span [ style [ ("color", "gray") ] ]
                     [ text model.item ]
             ]

 viewDeleteButton : Model -> Html Msg
 viewDeleteButton model =
     span []
-        [ button [ onClick (OnDelete model.item) ] [ text "x" ]
+        [ button [ onClick (OnDelete model.itemID) ] [ text "x" ]
         ]

メッセージの定義変更に合わせて引数をitemIDへ変更しています。

TodoList.elmの変更

  • ファイル全体

 module TodoList exposing (..)

 import Html exposing (..)
 import Html.Attributes exposing (class)
 import Html.Events exposing (onClick)
 import Todo

 type alias Model = 
     { todoList : List TodoModel
+    , nextID : Int
     }

 initialModel : Model
 initialModel = 
     { todoList = 
-        [ Todo.new False "task1" False
-        , Todo.new False "task2" False
-        , Todo.new True "task3" False
-        , Todo.new False "task4" False
+        [ Todo.new 1 False "task1" False
+        , Todo.new 2 False "task2" False
+        , Todo.new 3 True  "task3" False
+        , Todo.new 4 False "task4" False
         ]
+    , nextID = 5
     }

 type Msg
     = NoOp
     | AddNew
     | DeleteFinished
     | TodoMsg Todo.Msg

 -- update
-update : Msg -> TodoModel -> Model -> ( Model, Cmd Msg )
-update message todo model =
+update : Msg -> String -> Model -> ( Model, Cmd Msg )
+update message item model =
     case message of
         NoOp ->
             model ! []

         AddNew -> 
-            { model | todoList = model.todoList ++ [todo] } ! []
+            let
+                todo = 
+                    Todo.new model.nextID False item False
+            in
+                { model | 
+                    todoList = model.todoList ++ [todo] 
+                    , nextID = model.nextID + 1
+                } ! []

         DeleteFinished -> 
             let
                 itemIsNotFinished todoModel = not todoModel.done
             in
                 { model | 
                     todoList =  List.filter itemIsNotFinished model.todoList } ! []

         TodoMsg subMsg ->
             let
                 itemIsNotDeleted todoModel = 
                     not todoModel.del

                 updatedTodoList = 
                     List.map (Todo.update subMsg) model.todoList
             in
                 { model | 
                     todoList = List.filter itemIsNotDeleted updatedTodoList } ! []

 -- view
 view : Model -> Html Msg
 view model = 
     div []
         [
         div [ class "p2" ]
             [ addButton
             , viewCounter model
             , deleteFinishedButton
             , viewList model
             ]
         ]

 addButton : Html Msg
 addButton = 
     div [] 
         [ button [ onClick AddNew ] [ text "Add" ] ]

 deleteFinishedButton : Html Msg
 deleteFinishedButton = 
     div [] 
         [ button [ onClick DeleteFinished ] [ text "Delete Finished" ] ]

 viewCounter : Model -> Html Msg
 viewCounter model = 
     div []
         [ text ("Finished Task: " 
             ++ toString ( countDoneItems model )
             ++ "/" 
             ++ toString ( List.length model.todoList ) ) 
         ]

 countDoneItems : Model -> Int
 countDoneItems model = 
     List.filter itemIsDone model.todoList
     |> List.length

 itemIsDone : TodoModel -> Bool
 itemIsDone todoModel = todoModel.done 

 viewList : Model -> Html Msg
 viewList model = 
     (ul [] 
         (List.map Todo.view model.todoList))
         |> Html.map TodoMsg

モデルにnextID属性(次のTodoのID)を追加しています。
その他の修正はTodo.elmのモデル変更に伴うものです。

updateの引数の型を一部変更しました。
TodoModel型をString型へ変更したことで、AddNewのコードの見通しが悪くなったため、let-in文で書き直しました。nextIDのインクリメントの処理を追加しています。

Main.elmの変更

  • ファイル全体

 module Main exposing (..)

-import Html exposing (Html, program)
+import Html exposing (..)
 import TodoCreator
 import TodoList
-import Todo

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

 -- model
 type alias Model = 
     { todoCreator : TodoCreator.Model
     , todoList : TodoList.Model
     }

 initialModel : Model
 initialModel = 
     { todoCreator = TodoCreator.initialModel
     , todoList = TodoList.initialModel
     }

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

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

 -- update
 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 (Todo.new False model.todoCreator.inputStr False) model.todoList
+                    TodoList.update subMsg model.todoCreator.inputStr model.todoList
             in
                 ( { model | todoList = updatedTodoListModel }, Cmd.map TodoListMsg todoListCmd )

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

 -- view
 view : Model -> Html Msg
 view model =
-    Html.div []
-        [ Html.map TodoCreatorMsg (TodoCreator.view model.todoCreator)
-        , Html.map TodoListMsg (TodoList.view model.todoList) 
-        ]
+    body [] 
+        [ h1 [] [ text "ToDo list" ]
+        , div [] 
+            [ map TodoCreatorMsg (TodoCreator.view model.todoCreator)
+            , map TodoListMsg (TodoList.view model.todoList) 
+            ]
+    ]

TodoListのメッセージの引数の型がTodoModel型からString型へ変更されたため、TodoList.updateの引数はtodoCreatorのinputStrをそのまま渡すことができるようになりました。
その結果、Todoをimportせずに済むようになり、Todoの変更に影響されなくなりました。

viewの変更は主に見栄えの変更のみです。

実行結果

前回と同様にmakeして、作成したindex.htmlをブラウザで見ます。

$ elm-make Main.elm --output index.html --debug

FF_elm_study5.png

今回は内部実装変更が主だったので、見た目はそれほど変わらないです。

おわりに

5回に渡って連載してきたElm 0.18で作るTodoアプリですが、今回が最終回です。

elmに初めて触り、tutorialなどを参考に作成してみました。
AddボタンがTodoList側にあるところ(できればTodoCreator側にボタン設置したい)など設計上気になるところはありますが、JavaScriptに触らずにここまで作れるのはすごいなと思いました。

ElmにはJavaScriptと連携させることもできるようですが、そこまで手が回らなかったので、徐々に勉強していきたいと思います。

0
0
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
0
0