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!!