Forms in Elm — Validation, Tutorial and Examples — Part 2

Lucamug
6 min readJan 2, 2018

--

Part 2 — Removing <form>, on-the-fly validation, Focus detection, Show/Hide the password

This is part 2 of a 3 parts series. Have a look at part 1 for an introduction.

Parts

  • Part 1 — Examples 1~6: x-www-form-urlencoded and json encoding, validation
  • Part 2 — Examples 7~12: Removing <form>, on-the-fly validation, Focus detection, Show/Hide the password
  • Part 3 — Examples 13~21: Spinner, Floating Labels, Checkboxes, Date Picker, Autocomplete

☞ Example 7 — Bringing back the onEnter behaviour

CodeDemo

1. Create a function to detect the press of Enter button:

onEnter : msg -> Attribute msg
onEnter msg =
keyCode
|> Decode.andThen
(\key ->
if key == 13 then
Decode.succeed msg
else
Decode.fail "Not enter"
)
|> on "keyup"

2. Add onEnter SubmitForm to the div that wrap the form

☞ Example 8 — Added validation while typing and disabled Submit button

CodeDemoEllie

The last small thing that we can add is the validation running while changing the text in the form.

Please note that this is considered a bad practice. You don’t want to get the error “email not valid” while you are typing the email but you haven’t finished yet.

For more sophisticated validation have a look at etaque/elm-form or ericgj/elm-validation.

But just to keep the things simple:

1. Add the helper setErrors

setErrors : Model -> Model
setErrors model =
case validate model of
[] ->
{ model | errors = [] }
errors ->
{ model | errors = errors }

2. Change SetField in the update function, from

SetField field value ->
( setField field value model, Cmd.none )

to

SetField field value ->
( model
|> setField field value
|> setErrors
, Cmd.none
)

Note that setErrors come after setField because it needs to work with the newest model.

☞ Example 9 — Created the helper “viewInput” that generalise the creation of input fields

CodeDemo

We create a new helper that can be use for every input fields in the form. The helper is:

viewInput : Model -> FormField -> String -> String -> Html Msg
viewInput model formField inputType inputName =
label
[]
[ text inputName
, input
[ type_ inputType
, placeholder inputName
, onInput <| SetField formField
, value <|
case formField of
Email ->
model.email
Password ->
model.password
]
[]
, viewFormErrors formField model.errors
]

It has four parameters:

  • model is the model
  • formField is the FormField type ( = Email | Password )
  • inputType is the type attribute in the <input> element. In our case either text or password
  • inputName is the String version of the name of the field

Some of this stuff could go in some kind of configuration structure. We should also pass the value among the parameters rather than extrapolating it from the model (model.email and model.password).But it is too early to implement such optimisation.

So now the viewForm will only call this helper with

 ...
, viewInput model Email “text” “Email”
, viewInput model Password “password” “Password”
...

☞ Example 10 — Added “showErrors” functionality that show error only after the first submit

CodeDemo

Users would not want to be annoyed with forms error while they are filling the form for the first time. So let’s add this simple functionality. I added a showErrors boolean to the model set to False at the beginning and set to True only on submitting the form in the update function.

Then we use this value inside viewFormErrors:

viewFormErrors : Model -> FormField -> List Error -> Html msg
viewFormErrors model field errors =
if model.showErrors then
errors
|> List.filter (\( fieldError, _ ) -> fieldError ==
field)
|> List.map (\( _, error ) -> li [] [ text error ])
|> ul [ class "formErrors" ]
else
text ""

☞ Example 11 — Adding focus detection so that focus is evident also during history playback

CodeDemo

I always enjoyed replaying the History using the History Browser. It has a lot of potential. So I would like to be Elm in charge of as many things as possible. So let’s make Elm aware when the input fields receive focus. It can be handy also later to create some interaction.

Let’s add a new entry in the model, focus, to store a Maybe FormField.

In the list of messages we need to add two extra messages:

type Msg
= NoOp
| SubmitForm
| SetField FormField String
| OnFocus FormField
| OnBlur FormField
| Response (Result Http.Error String)

and in the update:

OnFocus formField ->
( { model | focus = Just formField }, Cmd.none )
OnBlur formField ->
( { model | focus = Nothing }, Cmd.none )

Finally inside the view of the input element we add these two lines:

     , onFocus <| OnFocus formField
, onBlur <| OnBlur formField

It was pretty simple. Now we can add a class focus to the <label> element when the inner input field has focus. We add a small helper inside viewInput where we create the value hasFocus so the entire view will be

viewInput : Model -> FormField -> String -> String -> Html Msg
viewInput model formField inputType inputName =
let
hasFocus =
case model.focus of
Just focusedField ->
focusedField == formField
Nothing ->
False
in
label
[]
[ text inputName
, input
[ type_ inputType
, classList
[ ( "focus", hasFocus ) ]
, placeholder inputName
, onInput <| SetField formField
, onFocus <| OnFocus formField
, onBlur <| OnBlur formField
, value <|
case formField of
Email ->
model.email
Password ->
model.password
]
[]
, viewFormErrors model formField model.errors
]

At voilà, now we can add styles to the input field that will be replayed during the history playback adding this double definition to the style definition:

input:focus, input.focus    {
outline: #7ab4ff solid 2px;
outline-offset: 0px;
}

☞ Example 12 — Adding the icon to hide and show the password

CodeDemo

Small “Eye” icon to show the Password

I think, once we setup the initial boilerplate, it is quite fun to add features in Elm. Compared to developing in other systems, you don’t need to think ahead much when start coding. You can simply add features and refactor the code anytime is necessary.

The difference it comes for the simplicity of refactoring in Elm.

So, let’s add now the feature that would allow users to double check the password that they just typed offering a toggle button to show and hide it.

Let’s add showPassword, an extra boolean value to the model and an extra message in the list:

type Msg
= NoOp
| SubmitForm
| SetField FormField String
| Response (Result Http.Error String)
| OnFocus FormField
| OnBlur FormField
| ToggleShowPasssword

that will redirect to the simple toggle section in the update:

ToggleShowPasssword ->
( { model | showPassword = not model.showPassword }, Cmd.none )

When toggling between show and hide, we change the type of the input box between password and text. To do so, we add this condition in the view:

if formField == Password && model.showPassword then
type_ "text"
else
type_ inputType

then we add the clickable button:

div [ class "iconInsideField", onClick ToggleShowPasssword ]
[ if model.showPassword then
svgHide "orange"
else
svgShow "orange"
]

This is all it needs!

The svgHide and svgShow are two svg icons. They are embedded in the code like:

svgShow : String -> Html msg
svgShow color =
Svg.svg [ Svg.Attributes.viewBox "0 0 512 512",
Svg.Attributes.height "32", Svg.Attributes.width "32" ]
[ Svg.path
[ Svg.Attributes.fill
color
, Svg.Attributes.d
"M256 192a64 64 0 1 0 0 128 64 64 0 0 0 0-128zm250
49l-89-89c-89-89-233-89-322 0L6 241c-8 8-8 22 0
30l89 89a227 227 0 0 0 322 0l89-89c8-8 8-22 0-
30zM256 363a107 107 0 1 1 0-214 107 107 0 0 1 0
214z"
]
[]
]

Part 1 ⬅ __END_OF_PART_2__ ➡ Part 3

Parts

  • Part 1 — Examples 1~6: x-www-form-urlencoded and json encoding, validation
  • Part 2 — Examples 7~12: Removing <form>, on-the-fly validation, Focus detection, Show/Hide the password
  • Part 3 — Examples 13~21: Spinner, Floating Labels, Checkboxes, Date Picker, Autocomplete

Let me know if you have any suggestion or comment.

Thank you for reading.

--

--

No responses yet