ElmでViewの共通化をしたいとき、別にViewを頑張ってつくらなくてもAttributeを"Mixin"として使い回せばいいことがわりとあります。
たとえば
module Mixin exposing (..)
import Html exposing (Attribute)
import Html.Attributes as Attributes
bigButton : Attribute msg
bigButton =
Attributes.class "button button_big"
のような関数を定義して使いまわすイメージです。
この例は簡単なので別にこれでもいいんですが、場合によっては複数の Attribute をセットにして使いまわしたいことがしばしばあります。
module Mixin exposing (..)
import Html exposing (Attribute)
import Html.Attributes as Attributes
submitButton : Bool -> List (Attribute msg)
submitButton sending =
[ Attributes.class "button"
, Attributes.class "button_submit"
, Attributes.disabled sending
]
このようにリストにするだけなのですが、これをViewで呼ぼうとするとどうなるでしょうか?
module Main exposing (..)
import Html exposing (Html)
import Html.Attributes as Attributes
import Mixin
submitButton : Model -> Html msg
submitButton model =
Html.button
(Mixin.submitButton model.sending)
[]
これを見る限りは特に問題なさそうですね!
じゃあ、Mixin.bigButton
と Mixin.submitButton
を同時に使いたい時はどうなるでしょうか?
bigSubmitButton : Model -> Html msg
bigSubmitButton model =
Html.button
( Mixin.bigButton :: Mixin.submitButton model.sending)
[]
ちょっと嫌な予感がしてきましたね?
じゃあ、ピンク色の大きな送信ボタンを作ろうと思ったらどうなるでしょうか?
ピンク色のボタンにするための Mixin が以下の型をしているとします。
pinkButton : List (Attribute msg)
こうなりますね?
bigPinkSubmitButton : Model -> Html msg
bigPinkSubmitButton model =
Html.button
( Mixin.bigButton :: Mixin.submitButton model.sending ++ Mixin.pinkButton)
[]
わーお。
それじゃあ今度は、model.broken
が True
のときのみ class="broken"
を付けてボタンがぶっ壊れるようにしてみましょう!
bigPinkHotSubmitButton : Model -> Html msg
bigPinkHotSubmitButton model =
Html.button
(Mixin.bigButton
:: Mixin.submitButton model.sending
++ Mixin.pinkButton
++ (if model.broken then
[ Attributes.class "broken"
]
else
[]
)
)
[]
うぅううううううああああああああ!!!!!
もういやだぁあああああああああ!!!!!!!!!!
おうちにかえるぅうううううううう!!!!!!!!!!!
追記 2019/8/11
最近は elm-mixin というライブラリを作ったので、そちらをメインに使っています。
Attributes.batch で解決する
たとえばこれを以下のように書けるとしたらいかがでしょうか?
module Mixin exposing (..)
...
...
submitButton : Bool -> Attribute msg
submitButton sending =
Attributes.batch
[ Attributes.class "button"
, Attributes.class "button_submit"
, Attributes.disabled sending
]
pinkButton : Attribute msg
pinkButton =
Attributes.batch
[ ...
, ...
]
module Main exposing (..)
...
...
bigPinkHotSubmitButton : Model -> Html msg
bigPinkHotSubmitButton model =
Html.button
[ Mixin.bigButton
, Mixin.submitButton model.sending
, Mixin.pinkButton
, if model.broken then
Attributes.class "broken"
else
Attributes.none
]
[]
めちゃくちゃ可読性が向上してませんか?
これを実現する上でポイントとなっているのが次の2つの関数です。
Html.Attributes.batch : List (Attribute msg) -> Attribute msg
Html.Attributes.none : Attribute msg
ちょうど Cmd
とか Sub
とかの Cmd.batch
Cmd.none
と似たイメージですね!
arowM/html
さて、便利さはわかりましたが、残念ながらこれらの関数は elm/html
にも elm-community/html-extra
にも含まれていません。
これらを使うには arowM/html に 乗り換える 必要があります。
詳細は省きますが、elm/html
に 加えて 他のライブラリを読み込む形では Html.Attributes.batch
のようなものは実現できません。
そこで、arowM/html
は elm/html
を 置き換える という戦略をとっています。
もちろん互換性は保ってありますので、今まで書いてきた elm/html
に依存したコードは arowM/html
に乗り換えてもそのまま同じように動きます。
それに加えて、上記の batch
と none
が使えるようになるのです。
arowM/html への置き換え方
elm/html
にさよならして arowM/html
に置き換えるのはとても簡単です。
-
elm.json
の dependencies にあるelm/html
をarowM/html
に変える - elm-stuff ディレクトリを削除する
- コンパイルしなおす
- コンパイラが「こいつらを
elm.json
の "indirect" に追加してね」と教えてくれるので指示にしたがう
これだけでコンパイルが通るようになり、batch
や none
の力が手に入ります!