I’m rather new to Elm, and while I am I’m absolutely loving it, this was definitely a pain point for me.
The standard way to conditionally include an HTML element, as far as I can tell is
div []
[ element1
, if condition then
element2
else
text ""
, element3
]
Personally I don’t like this. the “do nothing”text "" is not expressive, and, when doing this for attributes, is not even trivial (classList [] is the best solution I know). I’ve created tooling to make this, IMHO, nicer. It’s still a WIP but I’m using it in my current project right now. I’m curious if there’s already different common approaches to this that people think are nicer.
There are some nice solutions in this reddit thread - it pertains to attributes but the same rules apply for element lists.
I think text "" is an alright solution provided it’s wrapped in something like this to make it more expressive:
module Main exposing (main)
import Html exposing (Html, div, text)
htmlIf : Html msg -> Bool -> Html msg
htmlIf el cond =
if cond then
el
else
text ""
main : Html msg
main =
div []
[ div [] [ text "Always here" ]
, htmlIf (div [] [ text "Conditionally drawn" ]) True
, div [] [ text "Always here" ]
]
empty : Html msg
empty =
Html.text ""
viewJust : (a -> Html msg) -> Maybe a -> Html msg
viewJust fn maybe =
case maybe of
Just a ->
fn a
Nothing ->
empty
viewIf : (() -> Html msg) -> Bool -> Html msg
viewIf fn shouldDisplay =
if shouldDisplay then
fn ()
else
empty
These usually end up in a module like Html.Styled.Extra if I’m using elm-css (which is almost always) or Html.Extra if I’m not.
I’m a big fan of pulling conditionals out of markup. It’s a bit more verbose but is much more readable.
expandable : Model -> Html Msg
expandable model =
if model.isExpanded then
expandedView model.items
else
collapsedView
expandedView : List Item -> Html Msg
expandedView items =
div [ class "expanded" ] <| List.map itemView items
collapsedView : Html a
collapsedView =
div [ class "collapsed" ] []
I use a couple of helper methods like the other examples, but which operate on List instead of Html.
concatMaybe : Maybe (List a) -> List a -> List a
concatMaybe maybe list =
case maybe of
Just toConcat ->
list ++ toConcat
Nothing ->
list
add : a -> List a -> List a
add value list =
value :: list
addMaybe : Maybe a -> List a -> List a
addMaybe maybe list =
case maybe of
Just toAdd ->
toAdd :: list
Nothing ->
list
addIf : Bool -> a -> List a -> List a
addIf doAdd value list =
if doAdd then
value :: list
else
list
addIfMatches : (a -> Bool) -> a -> List a -> List a
addIfMatches predicate value list =
if predicate value then
value :: list
else
list
This is nice because it looks basically the same as if you had written out a list - just imagine replacing |> with , and you nearly have a list literal.
Downside is you have to build things backwards because :: prepends, or remember to use List.reverse. Doesn’t matter for attributes because their order doesn’t make a difference.
I have found text "" to be the most straight-forward an clear approach in most cases. So that is what we use.
Dealing with element lists (for the purpose of returning []) usually leads to code that is harder to work with and understand.
Html.Attributes.property "" Encode.null does actually set a property on the DOM object though. Html.Attributes.classList [] has absolutely no effect af far as I can tell, that’s why I prefer to use that.
Yes it does. And that property is "". So unless one wants to define a unnamed property of a node it should be fine. In practice that’s a very edge case. Thanks for pointing that out, it maybe useful for folks to learn.