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

これまでの復習

[Elm 0.18で作るTodoアプリ(1)](https://qiita.com/tomluck/items/872787cda6682834a3a1)「固定値のTodoをTodoリストへ追加できるようにする」
[Elm 0.18で作るTodoアプリ(2)](https://qiita.com/tomluck/items/ec95df77fb54c9d54275)「テキストボックスへ入力したTodoをTodoリストへ追加できるようにする」
[Elm 0.18で作るTodoアプリ(3)](https://qiita.com/tomluck/items/d0cf014b80449e919c3c)「Todoのチェック用にチェックボックスを設置する」

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

# 今回のテーマ
TodoList側の機能拡張をする。具体的には以下の機能を追加していきます。
　機能1. 一つのTodoを削除できるようにする
　機能2. 完了したTodo数を表示できるようにする
　機能3. 完了したTodoを削除して、未完了のリストを表示できるようにする

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

# 実装

## Todo.elmの修正(機能1への対応)

- モデルの修正

```diff
 type alias Model = 
     { done : Bool
     , item : String
+    , del  : Bool
     }
 
-new : Bool -> String -> Model
-new b s = 
-    { done = b
+new : Bool -> String -> Bool -> Model
+new do s de = 
+    { done = do
     , item = s
+    , del = de
     }
     
 type Msg
     = NoOp
     | ToggleDone String
+    | OnDelete String
```

削除状態を管理するための属性(del)と削除メッセージ(onDelete)を追加します。

- updateの修正

```diff
update : Msg -> Model -> Model
update message model =
    case message of
        NoOp ->
            model

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

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

onDeleteのケースを追加し、引数でTodo相当の文字列をもらい、該当のTodoのみ状態を変更させるようにしています。

- viewの定義

```diff
view : Model -> Html Msg
view model = 
    li [] 
        [ 
            checkbox (ToggleDone model.item) model
        ]

-checkbox : msg -> Model -> Html msg
+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 -> 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" ]
+        ]
```

msgの型修正とviewDeleteButton関数の追加をしています。
ボタン押下時にonDeleteメッセージを呼び出します。

一つのTodoを管理しているTodo.elmでは自身の状態を変更するだけです。
リストからTodoを削除するのはTodoList側で処理します。

## TodoList.elmの変更(機能2、3への対応)

- モデルの変更

```diff
 initialModel : Model
 initialModel = 
     { todoList = 
-        [ Todo.new False "task1"
-        , Todo.new False "task2"
-        , Todo.new True "task3"
-        , Todo.new False "task4"
+        [ Todo.new False "task1" False
+        , Todo.new False "task2" False
+        , Todo.new True "task3" False
+        , Todo.new False "task4" False
         ]
     }
 
 type Msg
     = NoOp
     | AddNew
+    | DeleteFinished
     | TodoMsg Todo.Msg
```

Todo.elmのモデル変更による修正と、機能3.の対応のためにメッセージ(DeleteFinished)を追加しています。

- updateの変更

```diff
 update : Msg -> TodoModel -> Model -> ( Model, Cmd Msg )
 update message todo model =
     case message of
         NoOp ->
             model ! []
         AddNew -> 
             { model | todoList = model.todoList ++ [todo] } ! []
 
+        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 = updatedTodoList } ! []
+                { model | 
+                    todoList = List.filter itemIsNotDeleted updatedTodoList } ! []
```

Listの標準関数を使ってListの要素からフィルタリングしたものだけを取り出しています。

DeleteFinishedではtodoModelのdoneがFalseのものだけをフィルタリングしています。

TodoMsgではlet文でTodoへ更新の委譲をした後のリストを作成し、in文で更新後のリストに対してtodoModelのdelがFalseのものだけをフィルタリングしています。

- viewの変更

```diff
 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
```

viewCounter関数ではTodoの（完了数 /　総数）という表示をしています。
deleteFinishedButton関数はボタン押下でDeleteFinishedメッセージを呼び出しています。

## Main.elmの変更(機能1への対応)

- ファイル全体
 
```diff
 module Main exposing (..)
 
 import Html exposing (Html, program)
 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) model.todoList
+                    TodoList.update subMsg (Todo.new False model.todoCreator.inputStr False) 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) 
         ]
```

Todo.elmのモデルの変更に追従した修正のみです。

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

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

![FF_elm_study4.png](https://qiita-image-store.s3.amazonaws.com/0/129853/ebc4004e-f625-f982-4b22-27c9bb28719b.png)

Finished TaskでTodoの数が表示され、DeleteFinishedボタンや×ボタンがあり、押下するとTodoが削除されることが分かると思います。

# おわりに
主にTodoListで3つの機能を追加してみました。
ただし現在の実装だとTodoモデルのitem属性でupdateするTodoを決めているため、同じitem属性(Todo名)の要素の場合、一緒にupdateされてしまうという問題があります。

次回(おそらく最終回)は一意にupdateするTodoを決められるように機能拡張をし、少し見栄えを整えたいと思います。
次回：[Elm 0.18で作るTodoアプリ(5)](https://qiita.com/tomluck/items/c3069bb66e6239217179)