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

Lucamug
9 min readJan 2, 2018

--

Part 3 — Spinner, Floating Labels, Checkboxes, Date Picker, Autocomplete

This is part 3 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 13 — Adding a spinner while the app is waiting for an answer

CodeDemo

Time to add a spinner after the form is submitted and before the server answer. Being a Single Page Application we need to give some feedback to the user while the app is waiting for a server to answer.

Let’s add a formState value in the modal of the type

type FormState = Editing | Fetching

Then in the update function

SubmitForm ->
case validate model of
...
[] ->
( { model | errors = []
, response = Nothing
, formState = Fetching
}
, Http.send Response (postRequest model)
)
errors ->
( { model | errors = errors, showErrors = True }
, Cmd.none
)
Response (Ok response) ->
( { model | response = Just response
, formState = Editing
}
, Cmd.none
)
Response (Err error) ->
( { model | response = Just (toString error)
, formState = Editing
}
, Cmd.none
)

then on the view, if the formState is Fetching, we add a section with class form-cover that we can use from CSS to add a spinner

viewForm : Model -> Html Msg
viewForm model =
div [ class "form-container" ]
[ div
[ onEnter SubmitForm
]
[ node "style" [] [ text "" ]
, viewInput model Email "text" "Email"
, viewInput model Password "password" "Password"
, button [ onClick SubmitForm ] [ text "Submit" ]
]
, if model.formState == Fetching then
div [ class "form-cover" ] []
else
text ""
]

In the CSS:

.form-cover {
background-color: #dddddddd;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
@keyframes spinner {
to {transform: rotate(360deg);}
}
.form-cover:before {
content: '';
box-sizing: border-box;
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin-top: -10px;
margin-left: -10px;
border-radius: 50%;
border-top: 2px solid orange;
border-right: 2px solid transparent;
animation: spinner .6s linear infinite;
}

☞ Example 14 — Adding ”Floating Labels”

CodeDemo

Floating Labels for Email and Password

Floating Labels are labels in the input fields that occupy the placeholder space until the user focus on that field. Then it moves out of the field. Something described in the Material Design. You can find an example of this functionality at elm-mdl.

To implement this is very simple at this point.

  • We remove the placeholder attribute from the <input> element as we don’t need it anymore
  • In the viewInput we add a new section just after the input field
div
[ classList
[ ( "placeholder", True )
, ( "upperPosition", hasFocus || content /= "" )
]
]

The class upperPosition is the one that will move the label above. It is not active only when the field has no focus and it is empty.

This is all about the code. For the styles:

.placeholder {
position: absolute;
top: 15px;
left: 10px;
pointer-events: none;
font-size: 18px;
transition: all 0.2s;
color: #bbb;
}
.placeholder.upperPosition {
top: -17px;
left: 0px;
font-size: 15px;
color: #888;
}

Not very elegant but it should work.

☞ Example 15 — Adding Checkboxes

CodeDemo

Time to add something else. Let’s try checkboxes. Usually checkbox could have any pair of name/value and the pair is sent over submission if the checkbox is checked. We ignore this behaviour and we just keep the status in our model, as usual.

To store the data I choose a Dict but other forms of data structure are also fine.

Let’s suppose we have a list of fruits and we want the user to select 0, one or more of them.

We define the alias for Fruit

type alias Fruit =
String

Then in the model we add

fruits : Dict.Dict Fruit Bool

Keys will be the name of the fruit as String, the value is just a boolean for checked and unchecked.

We initialise the value with a list of fruit and False values:

fruits =
Dict.fromList
[ ( "Apple", False )
, ( "Banana", False )
, ( "Orange", False )
, ( "Mango", False )
, ( "Pear", False )
, ( "Strawberry", False )
]

We add a new message to handle the clicks on the checkboxes

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

In the update we simply toggle the boolean of the corresponding key

ToggleFruit fruit ->
( { model | fruits = toggle fruit model.fruits }, Cmd.none )

Dict doesn’t have a toggle function, so we create an helper for that:

toggle :
comparable
-> Dict.Dict comparable Bool
-> Dict.Dict comparable Bool
toggle key dict =
Dict.update key
(\oldValue ->
case oldValue of
Just value ->
Just <| not value
Nothing ->
Nothing
)
dict

Last thing, we need to update the view:

div []
(List.map
(\fruit ->
let
value =
Dict.get fruit model.fruits
in
label [ class "checkbox" ]
[ input
[ type_ "checkbox"
, checked <| Maybe.withDefault False value
, onClick <| ToggleFruit fruit
]
[]
, text <| " " ++ fruit ++ " is "
, text <| toString <| value
]
)
(Dict.keys
model.fruits
)
)

Here we iterate all the item of the Dict and for each element we create an <input> element of the type checkbox. In case the value of the element is True we also add the checked parameter. Moreover, in case of click we toggle the value of the element.

The checkbox could move into an helper the same we did for the input text and input password but for the moment we will leave it in the view.

We can confirm now, using the History Explorer, that the model is updating accordingly on the checkboxes that are checked.

☞ Example 16 — Encoding Checkboxes

CodeDemo

This should be straigforward at this point. First we create a filter that take the fruit Dict and return a list of Fruit that have the value set to True (it means that they have been checked in the form):

filteredFruits : Dict.Dict Fruit Bool -> List Fruit
filteredFruits fruits =
Dict.keys
(Dict.filter (\key value -> value) fruits)

We can now add the fruit to the encoder in the post request

postRequest : Model -> Http.Request String
postRequest model =
let
body =
Encode.object
[ ( "email", Encode.string model.email )
, ( "password", Encode.string model.password )
, ( "fruits"
, Encode.list <|
List.map (\key -> Encode.string key)
(filteredFruits model.fruits)
)
]
|> Http.jsonBody
in
Http.request
{ method = "POST"
, headers = []
, url = Utils.urlMirrorService
, body = body
, expect = Http.expectString
, timeout = Nothing
, withCredentials = False
}

Our encoded json will be something like this now

"json": {
"email": "foo",
"fruits": [
"Apple",
"Banana",
"Mango"
],
"password": "bar"
}

☞ Example 17 — Adding maximum number of checkable fruits

CodeDemo

To create this validation let’s first create a parameter and an helper:

maxFruitSelectable : Int
maxFruitSelectable =
3
fruitsQuantityHaveReachedTheLimit : Dict.Dict comparable Bool ->
Bool
fruitsQuantityHaveReachedTheLimit fruits =
List.length (filteredFruits fruits) >= maxFruitSelectable

To check if we reached the limit we can just measure the length of the List of filtered Fruits (see Example 16 about filteredFruits)

Then in the section of the view that display the checkbox we make them disabled if the limit of selectable fruit is already reached

(\fruit ->
let
value =
Dict.get fruit model.fruits
isDisabled =
fruitsQuantityHaveReachedTheLimit model.fruits && not
(Maybe.withDefault False value)
in
label
[ classList
[ ( "checkbox", True )
, ( "disabled", isDisabled )
]
]
[ input
[ type_ "checkbox"
, checked <| Maybe.withDefault False value
, disabled isDisabled
, onClick <| ToggleFruit fruit
]
[]
, text <| " " ++ fruit
]
)

We both add a class disabled, to visually disable the check box with css and we also add the attribute disabled to the <input> element so that users cannot click on them.

Now all the check boxes will be disabled after the number os checked fruit reached 3. Is still possible to change the selection but is necessary to remove some fruit first.

☞ Example 18 — Adding svg fruit icons

CodeDemo

Well, this is not at all about forms so feel free to skip at Example 19.

We got bored at watching to a text-only form that it was natural to add some fruit icon to make it more colorful.

This is how the svg icons are implemented:

svgLemmon : Html.Html msg
svgLemmon =
Svg.svg [ SA.viewBox "0 0 55 55" ]
[ Svg.path [ SA.fill "#f4c44e", SA.d "M55 31H13a21 21 0 1 0
42 0z" ] []
, Svg.path [ SA.fill "#f9ea80", SA.d "M51 31H17a17 17 0 1 0
34 0z" ] []
, Svg.path [ SA.fill "#f9da49", SA.d "M33 31h2v17h-2z" ] []
, Svg.path [ SA.fill "#f9da49", SA.d "M34.7 30.3l12 12-1.4
1.4-12-12z" ] []
, Svg.path [ SA.fill "#f9da49", SA.d "M33.3 30.3l1.4 1.4-12
12-1.4-1.4z" ] []
, Svg.path [ SA.fill "#f9d70b", SA.d "M48 11.3l.4-.4a4 4 0 0
0 0-5.7 4 4 0 0 0-5.7 0l-.2.3c-9.1-6.2-23.1-
3.8-33 6s-12.3 24-6.1 33l-.3.3c-1.5 1.6-1.5 4.1
0 5.7s4.1 1.5 5.7 0l.4-.4a23.4 23.4 0 0 0 19.6
1.2A21 21 0 0 1 13 31h36.2c2.4-7 2.1-14.1-1.2-
19.7zM32.9 9c-.5 1.2-1.6 1.6-2.8 1.3.6.1 0 0-.2
0a6 6 0 0 0-.4 0h-.8c-1.1.2-2.3-.5-2.5-1.7-.2-
1.1.6-2.3 1.8-2.5 1.2-.2 2.4-.1 3.6.2 1.1.2 1.6
1.7 1.3 2.7z" ] []
]

Where SA is coming from

import Svg.Attributes as SA

☞ Example 19 — Adding Date Picker

CodeDemo

Let’s add a Data Picker widget. We can leverage this library: http://package.elm-lang.org/packages/elm-community/elm-datepicker/latest

After installation, we need to import with

import Date
import DatePicker

In the init we setup some configuration:

init : ( Model, Cmd Msg )
init =
let
isDisabled date =
Date.dayOfWeek date
|> flip List.member [ Date.Sat, Date.Sun ]
( datePicker, datePickerFx ) =
DatePicker.init
in
( { errors = []
, email = ""
, password = ""
, fruits =
Dict.fromList
[ ( "Apple", False )
, ( "Banana", False )
, ( "Orange", False )
, ( "Pear", False )
, ( "Strawberry", False )
, ( "Cherry", False )
, ( "Grapes", False )
, ( "Watermelon", False )
, ( "Pineapple", False )
]
, response = Nothing
, focus = Nothing
, showErrors = False
, showPassword = False
, formState = Editing
, date = Nothing
, datePicker = datePicker
, defaultSettings = DatePicker.defaultSettings
}
, Cmd.map ToDatePicker datePickerFx
)

In the configuration above, for example, we are disabling Saturday and Sundays from the calendar.

Then we add a new message

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

That we handle this way:

ToDatePicker msg ->
let
( newDatePicker, _, mDate ) =
DatePicker.update settings msg model.datePicker
date =
case mDate of
DatePicker.Changed date ->
date
_ ->
model.date
in
( { model
| date = date
, datePicker = newDatePicker
}
, Cmd.none
)

Now we prepare a little helper to convert the date into a string. This will become a bit handy also later when we add the date into the Json request

formatDate : Date.Date -> String
formatDate d =
toString (Date.month d) ++ " " ++ toString (Date.day d) ++ ", "
++ toString (Date.year d)

Then we add it to the view using Html.map, so that the messages will be properly transformed as explained here:

DatePicker.view model.date settings model.datePicker
|> Html.map ToDatePicker

Done!

☞ Example 20 — Adding HTML5 Date Picker

CodeDemo

Out of curiosity let’s add and an <input> element with type = date so that we can appreciate the difference between the Elm date picker and the Default Browser data picker. This is just simple view stuff.

☞ Example 21— Adding Autocomplete Drop Down Menu

CodeDemo

Let’s add another widget to the form, an Autocomplete input field. In this case we use the thebritican/elm-autocomplete package. For a better implementation of this package, read the post Autocomplete widget in Elm.

Part 1Part 2 ⬅ __END_OF_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