プルダウンメニューの実装について。先日の記事でも触れましたが、あれについてもうちょっと書けそうなネタがあったので書いてみます。
つくったもの
こういうものです。
実装
このように使えるように書きました。
let filePulldown = { id = "File" , label = "File" , checked = False , children = PullDown.Children [ { id = "File/New" , label = "New" , children = PullDown.empty , checked = False } , { id = "File/Open" , label = "Open" , children = PullDown.empty , checked = False } , { id = "File/Save" , label = "Save" , children = PullDown.empty , checked = False } ] } in PullDown.view [ filePullDown ] Pulldown.rootLevel
入れ子構造になっていて、childrenに入れ続ければいくらでもネストした要素を表示できるようにしたかったです。
ネストした構造を再帰的に表示
view : List PullDown -> Int -> Html Msg view pullDowns level = let classes = classList [ ( "app-pulldown--level-" ++ String.fromInt level, True ) , ( "app-pulldown__children", level > 1 ) ] in ul [ classes ] <| List.map (viewPullDown level) pullDowns viewPullDown : Int -> PullDown -> Html Msg viewPullDown level pullDown = case pullDown.children of Children [] -> if pullDown.checked then li [ classList [ ( "app-pulldown--checked", pullDown.checked ) ] ] [ text pullDown.label , span [ class "material-icons", Html.Attributes.style "font-size" "16px" ] [ text "check" ] ] else li [ classList [ ( "app-pulldown--checked", pullDown.checked ) ], onClick (OnClick pullDown.id) ] [ text pullDown.label ] Children children -> if level > 1 then li [] [ div [ class "app-pulldown__label" ] [ text pullDown.label ] , span [ class "material-icons" ] [ text "arrow_right" ] , view children (level + 1) ] else li [] [ div [ class "app-pulldown__label" ] [ text pullDown.label ] , view children (level + 1) ]
viewPullDown
関数が何をしてるかというと、パターンマッチでchildrenを見て以下の処理にしています。
- 空配列のとき:テキスト要素を返す
- 配列に中身があるとき:childrenを渡してviewを呼び出す
雑感
実務ではこういう深さが決まってないネストしたものを末端まで処理していくようなやつを書く機会に遭遇してなかった(あってもライブラリを使うとかしてた)ので、楽しかったです。