LoginSignup
13
4

More than 3 years have passed since last update.

[Elm]SelectListの つ か い か た

Last updated at Posted at 2018-12-13

最近、Bibliopola(Storybook)とかelm-local-packagesといった開発ツールを作ってます

その前段としてSelectListというパッケージを作って、使ってます
これが結構便利でよく使ってるので使い方をステップバイステップで書いていこうと思います

SelectListを使ってくれてた人に聞いたらselectedMapForListをお願いされたのでそれやります

SelectListってなに

そもそもSelectListって何かっていうとListのうち1つの要素を選択した状態を表現する型です

[ 1, 2, 3, 4 ]こういうリストがあって
[ 1, 2, "3", 4 ]3を選択している
というのをElmで表現する場合どうするか

type SelectList a =
    SelectList (List a) a (List a)

こういう風になります

真ん中のaが選択している値ですね

以降では[ 1, 2, "3", 4 ]のように選択している要素を""で囲んで表現したいと思います

どういうときに使いたくなるのか

UI作っていると複数要素があるうちの1つを選択しているってことはそれなりにあります
タブとか、セレクトボックスとか。タブは複数あって、そのうちの1つが表示されているのでSelectListで表現できます。セレクトボックスも複数の選択肢のうち1つを選んでいるのでSelectListで表現できます。

さて、選択状態があるリストを表現するのに使えるよってのがSelectListなのですが、実はただのListでもSelectListの恩恵を得られます

それがselectedMapForListです

何はともあれ型を見ましょう。ドキュメントなんて飾りです。型 ウソツカナイ

selectedMapForList : (SelectList a -> b) -> List a -> List b
map                : (           a -> b) -> List a -> List b

ついでにList.mapの型も並べてみました
こうして並べてみると一目瞭然ですね。selectedMapForListはList.mapと同じようにListに対して使えそうです

個別の要素に関数を適用したいんだけど…

例えばview関数を要素に適用したいとするとこうなるはずです

List.map view [ 1, 2, 3, 4 ]
== [ view 1, view 2, view 3, view 4 ]

決してSelect List を引数に取りたいわけではないですよね。selectedMapForListの引数の関数はSelect List を引数に取る関数なのでなんか変です

こんな感じになります

selectedMapForList view [ 1, 2, 3, 4 ]
== [ view [ "1", 2, 3, 4 ]
   , view [ 1, "2", 3, 4 ]
   , view [ 1, 2, "3", 4 ]
   , view [ 1, 2, 3, "4" ]
   ]

選択した要素をずらしながら渡します。SelectList.selectedを使えば選択状態の要素を取り出せるので、selectedMapForListがList.mapの上位互換だということが分かると思います

List.indexedMapと比較

まず型です。

selectedMapForList : (SelectList a -> b) -> List a -> List b
indexedMap         : (    Int -> a -> b) -> List a -> List b

Intが増えてますね。要素のindexが渡されてます

実はindexもSelectListから取れちゃいます。SelectList.indexです

つまりindexedMapの上位互換であるということも分かりましたね

実際に使っているところ見てみよう

exmaplesのコードを使います

複数のカウンターがあるアプリです。
機能を絞っているので数字をクリックすると増える動作だけ実装されています

type alias Model =
    { nums : List Int
    }

モデルです。Listですね

type Msg
    = ClickNumber (SelectList Int)

Msgです。諸事情によりSelectListがわたってきていますがListでもいいです(後でまた言及します)

view : Model -> Html Msg
view model =
    div [] <| SelectList.selectedMapForList renderRow model.nums


renderRow : SelectList Int -> Html Msg
renderRow selected =
    div
        [ Events.onClick (ClickNumber selected) ]
        [ text <| String.fromInt <| SelectList.selected selected ]

viewです。renderRowはSelectList Intを取ります。
selectedで取り出して表示していますね。onClickではSelectListをそのままMsgに包んでupdateに送っています。ということでupdateを見てみましょう

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ClickNumber selectList ->
            ( { model
                | nums =
                    SelectList.toList <|
                        SelectList.updateSelected ((+) 1) selectList
              }
            , Cmd.none
            )

やってきたselectListupdateSelectedで選択している要素を1増やしてtoListでListに戻した後numsを更新しています。
クリックされた数字がselectListで選択されているのでこれでちゃんと更新されます。

これはupdate内に処理を書いたんですがちょっとview内に書くように変えてみましょう

type Msg
    = SetNums (List Int)

renderRow : SelectList Int -> Html Msg
renderRow selected =
    div
        [ Events.onClick (SelectList.updateSelected ((+) 1) selected |> SelectList.toList |> SetNums) ]
        [ text <| String.fromInt <| SelectList.selected selected ]

Msgを更新後のnumsを受け取るように変更しましょう。updateは更新するだけで自明なので略です。
renderRowでSelectListを使って更新後のListを作っています。

このように、個別要素を表示するview内から直接全体を更新する処理を書けます

indexedMapを使う場合はindexを受け取るので、これを使ってupdateにどのindexの要素がクリックされたか教える感じになりますElmではindexベースの更新処理はちょっと煩雑なのでそれが解消されるだけでも結構便利ですね

更なる力を・・・

実はSelectListには更なる力がありますが、今回はもう終わりです。軽く紹介だけします
SelectListの更なる力とは隣の要素が見えたり、隣と入れ替えたりすることです
こっちのexampleでその力を使っているので興味があればみてみてください

終わり

フィードバック歓迎です

13
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
4