Conditionally add or remove CSS (or HTML element) in middle of list

The simplest example would be an error message below a text input, which of course should only be visible if the input is invalid.

view = div []
    [ input [type_ "text", ...] []
    , span [ css [ color (rgb 255 0 0) ] ] [ text "Your input is invalid." ]
    , button [ onClick Validate ] [ text "Validate input" ]
    ]

Now, the problem for adding a css class (e. g. display: hidden) or removing the span entirely is the same: How do I insert an element conditionally into the middle of the list, without muddying the syntax too much?

The two variations I know are

  • with filter and using Maybe (which means adding Just before every constant entry),
  • using list concatenation, ++, and a conditional, if shouldBeAdded then [ display hidden ] else [] (which takes up a lot of space when it is broken down by elm-format).

Of these two, I think the one with filter could be the nicer one. Especially if you just define a helper function for filtering out Maybes.

But maybe I’m missing a more elegant way?

I usually just code those inline:

view = div []
    [ input [type_ "text", ...] []
    , if isInputValid model then
          span [ css [ color (rgb 255 0 0) ] ] [ text "Your input is invalid." ]
      else
          text ""
    , button [ onClick Validate ] [ text "Validate input" ]
    ]

But you could make it more elegant with a function:

ifValid : (model -> bool) -> model -> (model -> Html msg) -> Html msg
ifValid predicate model renderer =
    if predicate model then
        renderer model
    else
        Html.text ""

Then your code becomes:

view =
    div []
        [ input [ type_ "text", ... ] []
        , ifValid isInputValid Model <|
            \_ ->
                span [ css [ color (rgb 255 0 0) ] ]
                    [ text "Your input is invalid." ]
        ]

Or you could take the span ... out-of-line, or remove the delaying of computing it by passing it directly to ifValid instead of inside a lambda, but none of these really get simpler than just doing it inline as in my first example above.

One approach is to include the valid/invalid flag directly in your model

type Model = {

..
..
..
 inputValid : bool
..}

This will be set each time you go through the ‘update’ function

case msg of 
 ..
..
  Validate s -> if isValidInput s then { m | inputValid = true} else {m | inputValid = false}

… then, your view function simplifies to:

view : Model -> Html Msg
view m = div []
    [ input [type_ "text", ...] []
          span [ onOffStyle m] [ text "Your input is invalid." ]     
        , button [ onClick Validate ] [ text "Validate input" ]
    ]

… the on/off bit is handled by the style

onOffStyle : Model -> Attribute a
onOffStyle m =
  case m.inputValid of
            True ->
                style  "display"  "none" 

             False ->
                style "display" "block" 

I like this approach because it clearly separates updating from viewing. I get confused if my view function contains branches and internal functions. My instinct is always to add an extra field to the model to keep my View function clean and linear.

I was hesitant to add a dummy node, but all in all an empty text node doesn’t seem that bad.

For a touch more elegance, I might add a custom empty = text "" to be a bit more semantically explicit.

Thanks for your solution!

I’m hesitant to add fields to my model, if they are already implicitly contained, because that makes it impossible to forget to sync them up. My validation consists in simply checking whether the input is empty, so that is trivially done in the view function and does not necessitate extra model entries.

But thank you for your answer! I agree that sometimes it makes sense to compress complex validation into a field in the model.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.