LoginSignup
1
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-10-13

これまでの復習

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

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

今回のテーマ

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

ファイル構成

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

実装

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

  • モデルの修正
 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の修正
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の定義
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への対応)

  • モデルの変更
 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の変更
 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の変更
 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への対応)

  • ファイル全体
 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

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

おわりに

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

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

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