This Lesson is for Members

Subscribe today and get access to all lessons! Plus direct HD download for offline use, enhances transcripts, member comment forums, and iTunes "podcast" RSS feed. Level up your skills now!

Unlock This Lesson

Already subscribed? Sign In


    Model a Field Requiring Validation in an Elm Form

    Enrico BuonannoEnrico Buonanno

    As is often the case in a statically typed programming language like Elm, the best solutions are achieved by taking advantage of the type system to accurately model the problem at hand.

    In this lesson, we define a type that can represent the various states and the data for a validatable field. We then let the compiler guide us and work through to a satisfactory solution.



    Become a Member to view code

    You must be a Member to view code

    Access all courses and lessons, track your progress, gain confidence and expertise.

    Become a Member
    and unlock code for this lesson


    Instructor: 00:00 One approach to my try would be to say, well, since I'm using result to represent the result of my validation functions, let me put a result on the model. For example, result string string, it should be OK if the email has been validated correctly or an error, and I would be able to render that invaluable.

    00:21 Now, let me try compiling this and see what breaks. In the initial model, this now would need to be a result. Here we have the first problem, because if I say, "OK" with an empty string, then this contradicts a validation logic, since this is a mandatory field.

    00:35 If I pass it an error, so for example, if I say, "isEmail," empty string, this will return an error, but then it will mean that as soon as the user allows my form, he already receives validation errors.

    00:52 This is not the only problem, because if we go here, where the user has changed his input in the email field, we would now need to validate the field. If his input at the moment was not a valid email, we'd be effectively discard in the user's input.

    01:06 For all these reasons, result is not good enough type to represent the state of a field of my form. Let me go to my validation model and define the new type. I'll call this field and I'll say that a field can be valid and contain a value of type A, so we need A as a type argument, or it can be invalid in which case we have a string and error message.

    01:36 We also need a second parameter, so another string which is the user's current input. Just for clarity, let me add the comment to clarify what is each string is. Then, we also need another state which are called not validated with the string.

    02:00 Now, let me open these files side by side. Instead of a result, here I will have a field, so email would be a field of string. The same for message also field of string. When I first load the model, the value is not validated with an empty string, same for message.

    02:23 Now, let me go to the update function. Whenever the user changes the input of the email field, we will have not validated E and the same for the message. Let's go down to the view, where we want to display the label. I want to say, and I want to have a function that somehow extracts the error message from this field.

    02:54 Let me add this function to my library, extract error which takes a field of A, and returns a string. You can just switch in the value. If it's invalid with an error and the value, then we return an error. What about the other cases? We don't have an error in the other cases, so rather than returning a string, let's return a Maybe String. If it's invalid, we return just error. In all other cases, we return nothing.

    03:44 Now, extract the error we return a Maybe, and I'll say Maybe with default empty string. This will show the error if it's invalid, and otherwise, it will be an empty string, so the label won't be visible.

    04:06 I'm going to have the same code for other validations. Let me just extract this into a function. We call this error label. This will take a field of A and returns an HTML of A. Let me place the code to the just extracted. We get a field and here is my field, so we extract the error from the field, and then we show that or an empty string.

    04:56 Here, I'll put error label for, and next of the message, I'll put an error label for model.message. Now for the value, is now a field, so I somehow need to extract the current input.

    05:21 I'll have a function similar to this one that are called display value that takes the field of A, and returns a string. Let's switch on the state of the field. If it's invalid with an error and the val, and it's just the val, if it's not validated with the val that it also val.

    06:03 If it's a valid val, then you would like to return val, but the problem is that val can be any type A not just the string. Additionally, let's also add the function here that will render A as a string. No need to call this render and here it would be rendered val.

    06:30 Let me call this "S" for string. Let me call this A, since it's a value of type A. Here the value would be model email, but let me feed this to display value with identity as a function, and parentheses here. Let me do the same with the message.

    07:09 I also have a type of here Maybe, and let's see what breaks, so here we always need to return HTML message. Now on the submit response, when we clear these fields we need to say, "Not a validated empty string."

    07:34 Now comes the interesting part, so the actual validation called. Here, we were feeding the strings to these validation functions, and we were using results and using result Math2 to combine these results together. Now, a message are of few types, so that we'll over the contain validation errors, so the validation has to be done previously.

    07:56 I'll remove these, and assume I can combine these with validation Math2, which we need to define later. These are now of type field string, and field string. This is also part of the problem of again comments, is that they don't cause compile errors, so let me remove this.

    08:22 However, let's keep in mind that these fields could be in the not validated state, so somehow in we need to force the validation on the model. Let me write the function and call it validate model that takes the model and returns an updated model.

    08:48 This will return the model, and I'll set the email field to be and pass in that to a validate function yet to be defined that validates isEmail. Similarly, the message field on the model will be set to model.mesage. This will also go through validate taking as a validator is not empty.

    09:29 The idea is that, when the user submits the form, we take the model and we pass it this validate model of function. This will ensure that any fields whether in the not validated state or validated, so that they go either into the valid or the invalid state, and we pass this to submit if valid.

    09:52 What we need to do now is to implement these functions validate and Math2. Let me start with the validate function. Let's look the signature. The first argument as you can see is a validator. Let's call this validator string A. I can fix the first parameter to string, because we're validating values that come from input fields.

    10:20 The second argument is a field A, and return value would be a field A. Let me call these arguments, validator, and field. I'm going to switch on the field. If it's not validated with the value, then we'll apply the validation, so I say, validator value.

    10:53 Then, I'll switch on this value which is of type result. If this isn't OK with an A, then I will produce a valid A. If it's an error with an error message, then I will produce an invalid with error as error message and value as a current input value.

    11:28 If the field is in the valid or invalid case, we'll then, validation has already been performed, and I can just return the field as it is. Next, I'm going to implement the Math2 function for validation.

    11:51 If we look at the usage of Math2 over here, we can see that this takes a binary function, so we'll take an A, and a B, and return a C. The second argument would be a field of A, and a field of B, and return value would be a field of C.

    12:22 Let's call the function F, and let's call the first parameter FA and second one FB. Now, I can switch on FA. If this is not validated with the string, then we do nothing. We return not validated with the same string. If it's invalid with error and S, we likewise return invalid error and S.

    12:59 In the valid case, we have an A, and we can supply this A to the F function. In this case, I can still switch on FB. This is not validated as above, similarly stay, but not validated F. Finally, if it's a valid that wraps a value B, then I can apply it to arguments A and B to the F function and wrap this into a valid.

    13:42 This type annotation is wrong, because this is actually a field of command message that will remove this annotation as well, because it's a bit over qual. Because submission result is now a field, then it can be a valid with the command in which case we proceed to submit.

    14:01 In the other case, we do not submit, so we'll return the unchanged model, and no command to perform. As you have a little compilation error, where I'm constructing my error labels, and this is because this is a string, and I need to feed this to the text function.

    14:21 Now, my application compiles. You can see that the initial state email message are not validated. If I try to submit, they go to invalid, and we see the error messages appear. Let me refresh the page to go back to the initial state, and let me type a valid email, and click submit.

    14:46 You can see that the field email now has gone to valid with email of input and message is invalid, because it's required. Let me now open network tab to see, if a valid message is submitted, and indeed we see that it is.