gaaamiiのブログ

間違ったことを書いている時があります。コメントやTwitter、ブコメなどでご指摘ください

プルダウンメニューを再帰的に表示する

プルダウンメニューの実装について。先日の記事でも触れましたが、あれについてもうちょっと書けそうなネタがあったので書いてみます。

つくったもの

こういうものです。

実装

このように使えるように書きました。

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を呼び出す

雑感

実務ではこういう深さが決まってないネストしたものを末端まで処理していくようなやつを書く機会に遭遇してなかった(あってもライブラリを使うとかしてた)ので、楽しかったです。