Are there any common patters for dealing with conditionally including markup?

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" ]
        ]

View on Ellie

This is more or less the approach I’m taking currently.

Instead of replacing your element with a dummy element, you can leave it out of the child list.

view condition =
    div []
        ([ element1 ]
            ++ (if condition then
                    [ element2 ]

                else
                    []
               )
            ++ [ element3 ]
        )

For elements, it seems it’s considered that text "" should be enough, although there is an open issue. [1]

For attributes there was this idea of a batch function [2]:
Html.Attributes.batch : List (Attribute msg) -> Attribute msg

But the idea was dismissed by Evan himself later. [3]

[1] https://github.com/elm/html/issues/53
[2] https://groups.google.com/d/topic/elm-dev/6hmLG0iNm90/discussion
[3] https://github.com/elm/html/issues/103#issuecomment-313605792

normally I define some simple helpers:

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 something close to what @brian posted, the only difference is the function name.

empty : Html msg
empty =
    Html.text ""


when : Bool -> Html msg -> Html msg
when shouldRender view =
    if shouldRender then
        view

    else
        empty

I’d prefer when because it seems nice when you do pipes:


div [ class "d-flex flex-column flex-fill p-3" ]
    [ errorView
        |> when (RemoteData.isFailure form.submissionStatus)
    , myCoolView
        |> when somethingElse
    ]

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

Then build lists of things with pipelines.

let
       attributes =
            [ Css.tile ]
                |> Build.concatMaybe maybeClickAttributes
                |> Build.concatMaybe (getLinkAttributes (config.linkTo data))
                |> Build.addIf shouldTransistion Css.transition

       children =
           []
                |> Build.add (Text.heading "Sponsor Information")
                |> Build.add (Text.paragraph sponsorDetails)
                |> Build.addMaybe (Maybe.map viewContactInfo sponsorContactInfo)
                |> Build.addMaybe (Maybe.map viewAdminInfo sponsorAdminInfo)
                |> Build.addIf showMoreLink (Element.link (Route.SponsorInfo sponsorId) "More Info")
                |> List.reverse
in
Html.div attributes children

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.

This was mentioned in the thread I linked before, and the order of attributes does matter.
https://groups.google.com/d/msg/elm-dev/6hmLG0iNm90/_PIUjr0SCwAJ

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.

For empty view we define:

empty : Html msg
empty = Html.text ""

For empty attribute we define:

empty : Html.Attribute msg
empty = Html.Attributes.property "" Encode.null

Both are harmless and very usefull.

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.

I think it does if no class is already defined, see:

oh wow, you’re right. I thought I had tested that but I guess not :confused:

In that case, text "" would also have an effect on the DOM - it actually creates the empty text node!

div [] [ text "", text "Hello, World!", text ""]

produces

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.

My world is falling apart!