TODOリストを作る部 その2

nekoroll.hatenablog.com

の続き
ellie-app.com

やること

  • [x] ListでTODOリストを保持する
  • [x] ListHTMLに吐き出す
  • [x] inputに入力した値をbuttonを押すことでリストに追加
  • [x] TODO横に削除ボタンを作成、押したら消せるように
  • [ ] TODOをダブルクリックでinput化して更新できるように
  • [ ] localstorageに保存できるようにする
  • [ ] 見た目オシャンティーにする

やったこと

空のTODOは追加できないようにする

ERROR型の定義

type ErrorMessage
    = NO_ERROR
    | TODO_IS_EMPTY
  • NO_ERROR
    エラーなし
  • TODO_IS_EMPTY
    追加しようとしているTODOが空のエラー

エラーのパターンを制御して出し分けたくて型作ってVariantを定義した

Modelへ追加

type alias Model =
    { todoList : List Todo
    , inputText : String
    , errorMessage : ErrorMessage
    }

定義したErrorMessage型でエラーを定義
後々viewで表示するために使う

init

initialModel : Model
initialModel =
    { todoList = []
    , inputText = ""
    , errorMessage = NO_ERROR
    }

初期状態ではエラーは無いので、エラーなしを示す NO_ERRORを設定

update関数のAdd処理

update : Msg -> Model -> Model
update msg model =
    case msg of
        Add ->
            case String.isEmpty model.inputText of
                True ->
                    { model | errorMessage = TODO_IS_EMPTY }

                False ->
                    { model
                        | todoList =
                            { index = getNextTodoIndex model.todoList
                            , todo = model.inputText
                            }
                                :: model.todoList
                        , inputText = ""
                        , errorMessage = NO_ERROR
                    }
  1. String.isEmptyを使って追加対象のTODOが空文字かどうかチェック
  2. True(空文字の場合)
    errorMessageに空を示すTODO_IS_EMPTY``Variantを設定
  3. False(空文字ではない場合) 元々のTODO処理にerrorMessage = NO_ERRORを足して
    エラーメッセージが無い状態にするようにした

showErrorMessage関数

showErrorMessage : ErrorMessage -> Html Msg
showErrorMessage errorType =
    case errorType of
        NO_ERROR ->
            div [] []

        TODO_IS_EMPTY ->
            div [] [ text "空文字のTODOは作成できません" ]

ErrorMessageを引数に受けて、エラーのHTMLを返す関数
NO_ERRORなら空のdiv
TODO_IS_EMPTYなら空文字エラーのメッセージを持ったdiv
を返すように実装した
これでエラーパターンを増やしたり減らした時に検知できるはず

view

view : Model -> Html Msg
view model =
    div []
        [ h1 [] [ text "TODO LIST" ]
        , showErrorMessage model.errorMessage

showErrorMessagemodel.errorMessageを渡して呼ぶ
エラーの追加はupdate関数のAdd
表示はshowErrorMessageという形で分けた

指定したTODOを削除できるようにする

Todoエイリアスを作ってindexを足した

type alias Todo =
    { id : Int
    , todo : String
    }

TODOの削除にあたってidが必要になったので
todoidはセットのレコードだなと考えてTodoエイリアスを作った
name,bioUserエイリアスにするやり方を参考にした
型エイリアス · An Introduction to Elm

削除処理を実装

filterByIndex : List Todo -> Int -> List Todo
filterByIndex todoList id =
    List.filter (\todo -> todo.id /= id) todoList

todoListidを引数に受けてtodoList内から渡したidと一致するものを
削除したtodoListを返すようにした
ここもっといい作りある気がしてモヤモヤしている

Msgupdateに削除処理を追加

type Msg
    = Add
    | UpdateTextField String
    | Delete Int

update
Delete id ->
            { model | todoList = filterByIndex model.todoList id }

Modelidを指定して削除する想定なのでIntを受けるようにした
updateは先程作った削除処理を呼び出しているだけ

ここでidを振る処理がないな、と気づく

TODOに一意なidを振る処理を実装

getNextTodoId : List Todo -> Int
getNextTodoId todoList =
    case List.head todoList of
        Just todo ->
            todo.id + 1

        Nothing ->
            0

TODOリストを渡すと次のidを返してくれるだけの処理
(削除すると連番ではなくなるけど、一意であればOKなので問題ない)
単なるcounterを用意しても良かったけどMaybe型を使ってみたかった

TODO追加時にidを振るように

{ model
| todoList =
    Todo (getNextTodoId model.todoList) model.inputText :: model.todoList
    , inputText = ""
    , errorMessage = NO_ERROR
}

ここもガイドを参考にした
https://guide.elm-lang.jp/types/type_aliases.html#レコードコンストラクタ
エイリアスを作るとレコードコンストラクタが生まれるので、楽にレコードが作れた
Todoの第一引数はidなので、一意なidを振るgetNextTodoIdを呼ぶ
→それぞれTodoの引数扱いになって怒られたので()で囲んだ
第二引数はtodoなので、入力されたTODOを入れた
::でのList追加は変わらず

ここで`TODO表示処理使いづらいな…と思い改造する

元のTODO一覧表示を改造した

showTodoList : List String -> Html msg
showTodoList todoList =
    todoList
        |> List.map (\todo -> li [] [ text todo ])
        |> ul []

view
, showTodoList model.todoList

元々ul liの形をまとめて作るようにしていたけど
必要だったのは「1つのTodoHTMLにする」で
ul liの形までするのはやりすぎだった
List化やul[]内の要素にするかは他の責務にすることにした

showTodo : Todo -> Html Msg
showTodo todo =
    li [] [ text todo.todo ]

急にシンプルでわかりやすい関数になった
これでulではなくdiv内の要素にしたり
liではなくdivにしたりと変更しやすくなった

, ul [] (List.map showTodo model.todoList)

呼び出す側でulで囲んでList.mapでList化するようにした

TODO表示処理に削除ボタン追加

showTodo : Todo -> Html Msg
showTodo todo =
    li []
        [ text (todo.todo ++ " ")
        , button [ onClick (Delete todo.id) ] [ text "delete" ]
        ]

ちょっとダサかったので(todo.todo ++ " ")ボタンとのスペース追加
削除処理はidが必要なのでtodo.idを渡すようにした
ここにボタン足すだけで全部のTODOにボタンが追加されるので楽だった
いい修正をした気がする

これで削除ができるようになった やったね

まとめ

  • リスト操作が学べた
    • 削除
    • List.mapの基礎的な使い方
  • counterサンプルのIntを絞るやり方が活きた
    • 型大事だなってなった、ありがとうございます
  • とりあえず関数化するようになった カリー化?高階関数?が今後の課題
    ナマステ
  • エイリアスの便利さに気づいた
    • レコードコンストラクタが便利
    • レコードを型として扱えるのが(・∀・)イイ!!
      javaとかでも型作れば出来るけど、サクッと書けて気持ちいい

今後の事やおまけ

  • カリー化、高階関数わかるともっときれいに書けそう
    filterByIndexを作ったけど、微妙に使いづらい関数な気が
  • 削除方法他にもあった気がする
    id指定じゃなくてheadtail組み合わせても行けると思う
    id指定だと全部捜査するから遅い気がする
  • index使う処理に慣れすぎてまだまだList操作に慣れない

忘備録的な学習メモ的なブログになっちゃってるけど
後々はもっと誰かの助けになるようなブログにしたい
100日後にはもっと成長できてるといいな…
f:id:nekorollykk:20200827012718p:plain