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.
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’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.