Client Side Form Validations Using TypeScript For Beginners

Validating a data entry form before submitting it to the server is one of the most common task in web applications. In ASP.NET Core you can easily do that with the help of validation attributes and tag helpers. However, at times you need to take charge of the client side validation process and roll out your own strategy to validate the data being submitted to the server. For example, you might be building a SPA and want to validate data using HTML5 features. To that end this article discusses how HTML5 form validation features can be used in TypeScript and ASP.NET Core.

Before we actually write any code, let's quickly introduce ourselves with the sample form we are going to build. Take a look at the following figure.

The form consists of two textboxes and a button. Both these input form fields use HTML5 attributes such as readonly, minlength, and maxlength to validate the input. Whenever there is any validation error a custom error message is displayed in front of the data entry field. A bulleted list of all the error messages in the form is displayed at the bottom of the page.

Now that you know what we are going to build, let's take a glance at the HTML markup that makes this form.

<form id="form1" asp-controller="Home" asp-action="Index2">
    <label for="name">Name : </label>
    <br />
    <input id="name" name="name" type="text" 
minlength="3" maxlength="10" 
pattern="^[A-Za-z]+$" required />
    <span id="msgName"></span>
    <br />
    <label for="age">Age : </label>
    <br />
    <input id="age" name="age" type="number" 
min="18" max="100" required />
    <span id="msgAge"></span>
    <br /><br />
    <button id="submit" type="submit">Submit</button>
</form>
<br />
<ul id="errorList"></ul>

Notice the code shown in bold letters. The name textbox has minlength, maxlength, pattern, and required attributes. The minlength and maxlength attributes that the length of name is between 3 and 10 characters. The pattern attribute uses a regular expression to allow only uppercase and lowercase alphabets. The msgName <span> element is used to display a custom error message in case these validations fail.

The age number textbox has min, max and required attributes. The min and max attributes ensure that the age is between 18 and 100 years. The msgAge <span> element is used to display an error message in case these validations fail.

The <button> element is a submit button and submits the form to the Index2 action of Home controller.

The errorList bulleted list is used to display the validation summary.

Create a new ASP.NET Core MVC application and add the above markup in its Index.cshtml. The Index() and Index2() actions required by this form are shown below:

public IActionResult Index()
{
    return View();
}

[HttpPost]
public IActionResult Index2()
{
    ViewData["Message"] = "Form data received 
on the server";
    return View("Index");
}

The Index() simply sets a message in ViewData indicating that the form data has been received on the server.

Now run the application (we haven't written any client side code yet) and click on the Submit button. 

Upon clicking the button, the browser triggers the validations. The textboxes causing error are highlighted and an error message is displayed in a popup.

Notice that at this stage the browser displays its own error message (and this message can vary based on the browser you use.). Also note that the browser triggers the validations only when <button> type is submit.

Now we want to customize the error messages. We also want to add field level error messages and a form level error list as shown in the first figure.

Create a folder named TypeScript under wwwroot and add a TypeScript file named FormValidation.ts to it.

Visual Studio will prompt you to install Microsoft.TypeScript.MSBuid NuGet package. Install the package as suggested

Now add a type alias at the top of the .ts file as shown below:

type ValidationMessages = Partial<Record<'valueMissing' | 
'tooShort' | 'tooLong' | 'rangeUnderflow' | 
'rangeOverflow' | 'typeMismatch' | 'patternMismatch', 
string>>;

Here, we create a type alias called ValidationMessages using two TypeScript utility types - Partial and Record. The Record type allows us to create a dictionary with fixed set of keys and their values of a specific type. In this example we define seven fixed keys such as valueMissing and patternMismatch. These keys are taken from the HTML5's ValidityState properties. We do this to simplify our validation logic as discussed later. The values of these keys are going to be string custom validation messages.

The Record type expects that all the keys are assigned some value when you use it. However, in our case we might not need all the keys. For example, for validating the name textbox we need only valueMissing, tooShort, tooLong, and patternMismatch keys. To overcome this we wrap the Record inside Partial type. The Partial type allows optional properties. You can read more about Partial and Record utility types in TypeScript's official documentation here.

Then add a TypeScript class called FormValidator as shown below:

class FormValidator {

}

We are going to write a couple of methods inside FormValidator class. The first method handles field level form validations and is called setValidationMessages(). This method is shown below:

public setValidationMessages(ctrlID: string, msgEleID: string,
    messages: ValidationMessages) {

    let element:any = document.querySelector("#" + ctrlID);

    element.addEventListener("input", (evt) => {

        let flag: boolean;

        if (element.validity.valueMissing) {
            if (typeof messages.valueMissing !== "undefined") {
                element.setCustomValidity(messages.valueMissing);
                flag = true;
            }
        }

        if (element.validity.tooShort) {
            if (typeof messages.tooShort !== "undefined") {
                element.setCustomValidity(messages.tooShort);
                flag = true;
            }
        }

        if (element.validity.tooLong) {
            if (typeof messages.tooLong !== "undefined") {
                element.setCustomValidity(messages.tooLong);
                flag = true;
            }
        }

        if (element.validity.rangeUnderflow) {
            if (typeof messages.rangeUnderflow !== "undefined") {
                element.setCustomValidity(messages.rangeUnderflow);
                flag = true;
            }
        }

        if (element.validity.rangeOverflow) {
            if (typeof messages.rangeOverflow !== "undefined") {
                element.setCustomValidity(messages.rangeOverflow);
                flag = true;
            }
        }


        if (element.validity.patternMismatch) {
            if (typeof messages.patternMismatch !== "undefined") {
                element.setCustomValidity(messages.patternMismatch);
                flag = true;
            }
        }

        if (element.validity.typeMismatch) {
            if (typeof messages.typeMismatch !== "undefined") {
                element.setCustomValidity(messages.typeMismatch);
                flag = true;
            }
        }

        if (flag) {
            document.querySelector("#" + msgEleID).
innerHTML = element.validationMessage;
        }
        else {
            element.setCustomValidity("");
            document.querySelector("#" + msgEleID).innerHTML = "";
        }
    });
}

The setValidationMessages() method accepts three parameters. The ctrlID parameter is the ID of the textbox being validated. The msgEleID parameter is the ID of the <span> element that displays field level error message for the ctrlID textbox. The messages parameter contains key-value pairs of validating errors and their custom error messages. This type of this parameter is ValidationMessages.

Inside, we pick the required input textbox using querySelector() method. We then wire input event handler for that textbox using addEventListener() method. The input event is raised when value of the underlying element is changed. This event handler gives us opportunity to customize the browser's default validation error messages as well as display our own field level error.

The input event handler contains a series of checks on input element's validity properties. For example, valueMissing property returns true if the textbox has required attribute set and is kept empty. To set a custom error message we use input element's setCustomValidity() method. The setCustomValidity() method accepts a validation message string to be displayed for the concerned error. We get this custom message from ValidationMessages values. We also set a boolean flag variable for later use.

Once all the error checking is complete we check the status of flag variable. If flag is true we display the element's validation error message in the respective <span> element. The element's custom message can be obtained using its validationMessage property.

It is also important to notify the browser when there are no validation errors. We do that by calling setCustomValidity() method with empty string. The <span> element is also cleared accordingly.

At this stage we can use the FormValidator class to see our custom error messages in action. To do so, make sure to compile the project so that FormValidation.js file gets outputted.

Now go to Index.cshtml view file and add a <script> reference to the FormValidation.js file just before the </body> tag.

<script src="~/FormValidation.js"></script>

Then add a <script> block as shown below:

window.onload = function () {

    let obj = new FormValidator();

    obj.setValidationMessages("name", "msgName", {
        valueMissing: "Name must be specified",
        tooShort: "Name is too short",
        tooLong: "Name is too long",
        patternMismatch:"Only alphabets allowed"
    });


    obj.setValidationMessages("age", "msgAge", {
        valueMissing: "Age must be specified",
        rangeUnderflow: "Age must be minimum of 18 years",
        rangeOverflow: " Age must be less than 100 years"
    });
}

Here, we added some code to the window's onload event handler. The code creates an object of FormValidator class. It then calls the setValidationMessages() method - once for name textbox and then for age number box. Notice how we specify the error keys and their custom messages.

You can also add some CSS styling to the error messages:

<style>
     span, ul {
        color: red;
        font-weight: bold;
        margin-left:5px;
    }
</style>

Now run the application and try entering some invalid values in both the textboxes. You should see our custom error messages as shown below:

You must have noticed that unless you hit the Submit button browser's error message popup isn't displayed. Click the Submit button just to confirm that the popup also reflects your custom error messages.

So far so good. Now let's add support for validation summary.

Open the FormValidator class again and add another method called showValidationSummary(). This method is discussed below.

showValidationSummary(formID:string, listID:string) {

let form :any = document.querySelector("#" + formID);
let errList = document.querySelector("#" + listID);
errList.innerHTML = "";

if (!form.checkValidity()) {

    for (let i = 0; i < form.elements.length; i++){

        let element = form.elements[i];

        if (!element.checkValidity()) {

            errList.insertAdjacentHTML('beforeend', 
"<li>" + element.validationMessage + "</li>");

        }
    };
    form.reportValidity();
}

The showValidationSummary() method accepts two parameters - ID of the form element that houses the input textboxes and ID of the bulleted list element that displays the list of errors.

Inside, we check the form's checkValidity() method. The checkValidity() returns false if there are one or more validation errors in the form, else it returns true. The checkValidity() method is available to input elements also in case you wish to test their validity in the code.

The code then iterates through the form.elements array. We then invoke checkValidity() on each form element one-by-one. If an element contains invalid value we add its validationMessage to the bulleted list using insertAdjacentHTML() method. Finally, we call reportValidity() method to trigger browser's inbuilt error message popup. If you are using Submit button on the form you don't need to call reportValidity() explicitly. But at times you use a push button (type = button) instead of a submit button. If so, browser won't display the error message popup.

Now we need to call showValidationSummary() from the Index view. So, add the following call to the window.onload event handler:

window.onload = function () {

    let obj = new FormValidator();

    obj.setValidationMessages("name", "msgName", {
        valueMissing: "Name must be specified",
        tooShort: "Name is too short",
        tooLong: "Name is too long",
        patternMismatch:"Only alphabets allowed"
    });

    obj.setValidationMessages("age", "msgAge", {
        valueMissing: "Age must be specified",
        rangeUnderflow: "Age must be minimum of 18 years",
        rangeOverflow: " Age must be less than 100 years"
    });

    let submitBtn = document.querySelector("#submit");

    submitBtn.onclick = function () {
        obj.showValidationSummary("form1", "errorList");
    }
}

We wire onclick handler and invoke showValidationSummary() method.

Run the application again and check whether it shows the validation summary.

Notice how field level errors, validation summary, and browser's error popup is displayed when you click the Submit button. Once you enter valid values you will get the success message stored in ViewData.

To know more about HTML5 form validation go here.

That's it for now! Keep coding!!


Bipin Joshi is an independent software consultant, trainer, author, and meditation teacher. He has been programming, meditating, and teaching for 25+ years. He conducts instructor-led online training courses in ASP.NET family of technologies for individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get connected : Facebook  Twitter  LinkedIn  YouTube

Posted On : 08 February 2021


Tags : ASP.NET ASP.NET Core MVC .NET Framework C# Visual Studio