Forms

Now we will make a rudimentary form. It has a field for your name, a field for your password, and a field to verify that password. We will also do some very simple validation to check if the passwords match.

I included the full program below. Click the blue “Edit” button to mess with it in the online editor. Try introducing a typo to see some error messages. Try misspelling a record field like password or a function like placeholder. Click the blue button now!

Edit

  1. import Browser
  2. import Html exposing (..)
  3. import Html.Attributes exposing (..)
  4. import Html.Events exposing (onInput)
  5. -- MAIN
  6. main =
  7. Browser.sandbox { init = init, update = update, view = view }
  8. -- MODEL
  9. type alias Model =
  10. { name : String
  11. , password : String
  12. , passwordAgain : String
  13. }
  14. init : Model
  15. init =
  16. Model "" "" ""
  17. -- UPDATE
  18. type Msg
  19. = Name String
  20. | Password String
  21. | PasswordAgain String
  22. update : Msg -> Model -> Model
  23. update msg model =
  24. case msg of
  25. Name name ->
  26. { model | name = name }
  27. Password password ->
  28. { model | password = password }
  29. PasswordAgain password ->
  30. { model | passwordAgain = password }
  31. -- VIEW
  32. view : Model -> Html Msg
  33. view model =
  34. div []
  35. [ viewInput "text" "Name" model.name Name
  36. , viewInput "password" "Password" model.password Password
  37. , viewInput "password" "Re-enter Password" model.passwordAgain PasswordAgain
  38. , viewValidation model
  39. ]
  40. viewInput : String -> String -> String -> (String -> msg) -> Html msg
  41. viewInput t p v toMsg =
  42. input [ type_ t, placeholder p, value v, onInput toMsg ] []
  43. viewValidation : Model -> Html msg
  44. viewValidation model =
  45. if model.password == model.passwordAgain then
  46. div [ style "color" "green" ] [ text "OK" ]
  47. else
  48. div [ style "color" "red" ] [ text "Passwords do not match!" ]

This is pretty similar to our text field example but with more fields.

Model

I always start out by guessing at the Model. We know there are going to be three text fields, so let’s just go with that:

  1. type alias Model =
  2. { name : String
  3. , password : String
  4. , passwordAgain : String
  5. }

I usually try to start with a minimal model, maybe with just one field. I then attempt to write the view and update functions. That often reveals that I need to add more to my Model. Building the model gradually like this means I can have a working program through the development process. It may not have all the features yet, but it is getting there!

Update

Sometimes you have a pretty good idea of what the basic update code will look like. We know we need to be able to change our three fields, so we need messages for each case.

  1. type Msg
  2. = Name String
  3. | Password String
  4. | PasswordAgain String

This means our update needs a case for all three variations:

  1. update : Msg -> Model -> Model
  2. update msg model =
  3. case msg of
  4. Name name ->
  5. { model | name = name }
  6. Password password ->
  7. { model | password = password }
  8. PasswordAgain password ->
  9. { model | passwordAgain = password }

Each case uses the record update syntax to make sure the appropriate field is transformed. This is similar to the previous example, except with more cases.

We get a little bit fancier than normal in our view though.

View

This view function is using helper functions to make things a bit more organized:

  1. view : Model -> Html Msg
  2. view model =
  3. div []
  4. [ viewInput "text" "Name" model.name Name
  5. , viewInput "password" "Password" model.password Password
  6. , viewInput "password" "Re-enter Password" model.passwordAgain PasswordAgain
  7. , viewValidation model
  8. ]

In previous examples we were using input and div directly. Why did we stop?

The neat thing about HTML in Elm is that input and div are just normal functions. They take (1) a list of attributes and (2) a list of child nodes. Since we are using normal Elm functions, we have the full power of Elm to help us build our views! We can refactor repetitive code out into customized helper functions. That is exactly what we are doing here!

So our view function has three calls to viewInput:

  1. viewInput : String -> String -> String -> (String -> msg) -> Html msg
  2. viewInput t p v toMsg =
  3. input [ type_ t, placeholder p, value v, onInput toMsg ] []

This means that writing viewInput "text" "Name" "Bill" Name in Elm would turn into an HTML value like <input type="text" placeholder="Name" value="Bill"> when shown on screen.

The fourth entry is more interesting. It is a call to viewValidation:

  1. viewValidation : Model -> Html msg
  2. viewValidation model =
  3. if model.password == model.passwordAgain then
  4. div [ style "color" "green" ] [ text "OK" ]
  5. else
  6. div [ style "color" "red" ] [ text "Passwords do not match!" ]

This function first compares the two passwords. If they match, you get green text and a positive message. If they do not match, you get red text and a helpful message.

These helper functions begin to show the benefits of having our HTML library be normal Elm code. We could put all that code into our view, but making helper functions is totally normal in Elm, even in view code. “Is this getting hard to understand? Maybe I can break out a helper function!”

Exercises: Go look at this example in the online editor here. Try to add the following features to the viewValidation helper function:

  • Check that the password is longer than 8 characters.
  • Make sure the password contains upper case, lower case, and numeric characters.

Use the functions from the String module for these exercises!

Warning: We need to learn a lot more before we start sending HTTP requests. Keep reading all the way to the section on HTTP before trying it yourself. It will be significantly easier with proper guidance!

Note: It seems like efforts to make generic validation libraries have not been too successful. I think the problem is that the checks are usually best captured by normal Elm functions. Take some args, give back a Bool or Maybe. E.g. Why use a library to check if two strings are equal? So as far as we know, the simplest code comes from writing the logic for your particular scenario without any special extras. So definitely give that a shot before deciding you need something more complex!