これまでの復習
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
Finished TaskでTodoの数が表示され、DeleteFinishedボタンや×ボタンがあり、押下するとTodoが削除されることが分かると思います。
おわりに
主にTodoListで3つの機能を追加してみました。
ただし現在の実装だとTodoモデルのitem属性でupdateするTodoを決めているため、同じitem属性(Todo名)の要素の場合、一緒にupdateされてしまうという問題があります。
次回(おそらく最終回)は一意にupdateするTodoを決められるように機能拡張をし、少し見栄えを整えたいと思います。
次回:Elm 0.18で作るTodoアプリ(5)