これまでの復習
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
今回は内部実装変更が主だったので、見た目はそれほど変わらないです。
おわりに
5回に渡って連載してきたElm 0.18で作るTodoアプリですが、今回が最終回です。
elmに初めて触り、tutorialなどを参考に作成してみました。
AddボタンがTodoList側にあるところ(できればTodoCreator側にボタン設置したい)など設計上気になるところはありますが、JavaScriptに触らずにここまで作れるのはすごいなと思いました。
ElmにはJavaScriptと連携させることもできるようですが、そこまで手が回らなかったので、徐々に勉強していきたいと思います。