最近、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
)
やってきたselectList
をupdateSelected
で選択している要素を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でその力を使っているので興味があればみてみてください
終わり
フィードバック歓迎です