Validate Forms in TypeScript

5 min read

Table of Contents:

Title Image for the Article

Introduction

Check out the GIT Repo here .

The script is setup to grab all forms on the page and apply the validation to them. It is built in a class with a self-executing function so it will be difficult for it to have name collisions with other libraries you may have installed. The validators we use are going to check against the HTML attributes to ensure the user inputs their information correctly. Are validator will use the "min", "max", and "required" attributes (to include their "type")

Requirements

In this project we are using Snowpack to help us in the development environment. Since we are using TypeScript , a browser cannot natively support it. I like to use Snowpack due to its ease of use. See another project using Snowpack here .

If you do not have the ability to run NPM packages in your development environment, you can still follow along with the tutorial in JavaScript just be removing the TypeScript items (typing, and variable assertions).

In your project directory you will need to run:

npm init 
npm install --save-dev snowpack
npx snowpack init

That is all we should need for now!

Index.html

Now we will setup an index page just to test our code. This page will be a simple form to test out our code to ensure it works.


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Form Validator</title>
    <link rel="stylesheet" type="text/css" href="./src/styles.css">
</head>
<body>
    <form id="contactForm" action="/" method="GET">
        <div class="form-block">
            <label for="firstname">First Name:</label>
            <input id="firstname" type="text" name="firstname" placeholder="First Name" min="3" max="5" required/>
        </div>
        <div class="form-block">
            <label for="lastname">Last Name:</label>
            <input id="lastname" type="text" name="lastname" placeholder="Last Name" min="3" required/>
        </div>
        <div class="form-block">
            <label for="emailname">Email:</label>
            <input id="emailname" type="email" name="emailname" placeholder="Email" required/>
        </div>
        <div class="form-block">
            <input type="submit" value="Submit" />
        </div>
    </form>
    <script src="./src/app.js" defer></script>
</body>
</html>

The form is setup as a GET request so we can see if the form is submitting (by looking at the URL). When a get request submits information it tacks it onto the URL. This should never be used for any sensitive information in a production environment!

Styles.css

I added a "styles" css file to give some basic style to the form.

CSS Logo
.form-block {
    margin: 1rem 1rem;
    display: block;
    border-radius: 1rem;
}

.form-block label {
    display: block;
    font-weight: bold;
}

The TypeScript

All of the code will be wrapped in a class. The reason for this is we do not want any name collisions. All the names will stay local to the project. The code has been kept as generic as possible to grab all forms with all inputs inside of it. You can change the code to fit your needs

The Constructor will contain the basic information to setup our form object.


class FormValidator {

    public form : HTMLFormElement;
    public inputs : NodeListOf<HTMLInputElement>;

    constructor(form : HTMLFormElement) 
    {
        this.form = form;
        this.inputs = this.form.querySelectorAll('input');
        this.setupFormSubmission();
        this.setUpValidators();
    }

...

We are getting each form individually from the HTML document to encase it in its own environment. We then run functions to setup the form submission to ensure the form only submits valid content off the validation rules we gave it. The validator setup runs the code to actually validate the information.

setupFormSubmission()

We are going to stop the form from submitting with


e.preventDefault();

Without that line of code, the form would submit the information before we could check it. Even if validators through errors it would not stop the code right now.

We are going to set custom data attributes on our inputs to see if they return if they are valid or not.

setupFormSubmission() : void
{
    this.form.addEventListener('submit', e => {
        e.preventDefault();
        let canSubmit = true;
        this.inputs.forEach(input => {
            if(input.getAttribute('data-valid') === 'false')
            {
                canSubmit = false;
            }
        });

        if(canSubmit) this.form.submit();
    });
}

setUpValidators()

We want to add logic that only checks the form once it has been manipulated. We will use the blur EventListener. The blur action fires once a form input has been entered and left. This will ensure we do not fire a bunch of errors at the user prior to them even submitting the form. Additional methods that will be discussed later on used in this class.

It is a good idea to break up your code for method/functions to only do one thing. While that does not always work out, it is a good practice.

setUpValidators() : void
{
    if(this.inputs.length === 0) return;
    this.inputs.forEach(input => {
        input.addEventListener('blur', () => {
            let errorList : HTMLElement[] = [];
            this.addValidationRulesToInput(input, errorList)
            this.generateErrorList(input, errorList);
        });
    });
}

addValidationRulesToInput()

Now we are going to setup the validation rules for each type of form. The method above will add this method to all of our inputs. We will grab the attribute of "type" to see what kind of input value it is. If it does not match what we want it will simply ignore any logic given to it.


addValidationRulesToInput(input : HTMLInputElement, errorList : HTMLElement[]) : void
{
    const inputType = input.getAttribute('type')!.toLowerCase();
    switch (inputType) {
        case 'text':
            this.minLengthRule(input, errorList);
            this.maxLengthRule(input, errorList);
            this.requiredRule(input, errorList);
            break;
        case 'email':
            this.minLengthRule(input, errorList);
            this.maxLengthRule(input, errorList);
            this.emailRule(input, errorList);
            this.requiredRule(input, errorList);
            break;
        default:
            break;
    }
}

The Rules

These methods can all share a section to themselves. They fundamentally do the same thing just for different actions. They check values against attributes set in the HTML. If they find something wrong, they will add a value to the errorList variable.

The errorList has something special about it!

As you will see we do not return anything with these methods. We just modify the errorList. In JavaScript/TypeScript, and most programming languages, an array is a reference type. That means when it is passed as a parameter it just lets the function know the location of it rather than the value. That means whatever it sends over will be modified as opposed to like a number or string.

minLengthRule(input : HTMLInputElement, errorList : HTMLElement[])
{
    if(!input.getAttribute('min')) return;
    if(input.value.length < parseInt(input.getAttribute('min')!))
    {
        errorList.push(this.createErrorLiElement('Min Length is ' + input.getAttribute('min')));
    }
}

maxLengthRule(input: HTMLInputElement, errorList : HTMLElement[])
{
    if(!input.getAttribute('max')) return;
    if(input.value.length > parseInt(input.getAttribute('max')!))
    {
        errorList.push(this.createErrorLiElement('Max Length is ' + input.getAttribute('max')));
    }
}

requiredRule(input : HTMLInputElement, errorList : HTMLElement[])
{
    if(!input.hasAttribute('required')) return;
    if(input.value.trim().length === 0)
    {
        errorList.push(this.createErrorLiElement('Required Field'));
    }
}

emailRule(input : HTMLInputElement, errorList : HTMLElement[])
{
    const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$/;
    if(!emailRegex.exec(input.value))
    {
        errorList.push(this.createErrorLiElement('Invalid Email.'));
    }
}

generateErrorList()

Now we will create a function that looks to see if an errorList has a length over 0 (which means an error exists). It will generate an unordered list and attach it to the parent element of our input. I surrounded all of the inputs with div elements to ensure the errors are attached to their respective inputs.

generateErrorList(input : HTMLInputElement, errorList : HTMLElement[])
{
    if(errorList.length > 0)
    {
        const previousErrors = input.parentElement!.querySelector('.error_class_container');
        if(previousErrors) previousErrors.remove();
        const errorListUl = document.createElement('ul');
        errorListUl.classList.add('error_class_container');
        input.setAttribute('data-valid', 'false');
        errorList.forEach(error => {
            errorListUl.appendChild(error);
        });
        input.parentElement!.appendChild(errorListUl);
        errorList = [];
    } else {
        errorList = this.removeErrorList(input);
    }
}

removeErrorList(input : HTMLInputElement)
{
    input.setAttribute('data-valid', 'true');
    const previousErrors = input.parentElement!.querySelector('.error_class_container');
    if(previousErrors) previousErrors.remove();
    return [];
}

The removeErrorList function is also attached to this part to show how it removes the list if the information is valid.

createErrorLiElement()

This is a simple method that will create error list items for us. We created this method, so we do not have to type the same code over and over again in the validation rules.

createErrorLiElement(message : string) : HTMLLIElement
{
    const errorLi = document.createElement('li');
    errorLi.innerHTML = message;
    return errorLi;
}

The Final function

The last function will ensure our code is ran on each form in the DOM. We have a self-calling function that runs the function as soon as the file is fully loaded.

(function() {
    const forms = document.querySelectorAll('form');
    forms.forEach(form => {
        new FormValidator(form);
    })
})();

Conclusion

Now take this code play around with it, add more rules to fit your website's needs. Stay tuned for more tutorials and projects on GoodEveningTech!

Full TypeScript Code

class FormValidator {

    public form : HTMLFormElement;
    public inputs : NodeListOf<HTMLInputElement>;

    constructor(form : HTMLFormElement) 
    {
        this.form = form;
        this.inputs = this.form.querySelectorAll('input');
        this.setupFormSubmission();
        this.setUpValidators();
    }
    setupFormSubmission() : void
    {
        this.form.addEventListener('submit', e => {
            e.preventDefault();
            let canSubmit = true;
            this.inputs.forEach(input => {
                if(input.getAttribute('data-valid') === 'false')
                {
                    canSubmit = false;
                }
            });

            if(canSubmit) this.form.submit();
        });
    }

    setUpValidators() : void
    {
        if(this.inputs.length === 0) return;
        this.inputs.forEach(input => {
            input.addEventListener('blur', () => {
                let errorList : HTMLElement[] = [];
                this.addValidationRulesToInput(input, errorList)
                this.generateErrorList(input, errorList);
            });
        });
    }

    addValidationRulesToInput(input : HTMLInputElement, errorList : HTMLElement[]) : void
    {
        const inputType = input.getAttribute('type')!.toLowerCase();
        switch (inputType) {
            case 'text':
                this.minLengthRule(input, errorList);
                this.maxLengthRule(input, errorList);
                this.requiredRule(input, errorList);
                break;
            case 'email':
                this.minLengthRule(input, errorList);
                this.maxLengthRule(input, errorList);
                this.emailRule(input, errorList);
                this.requiredRule(input, errorList);
                break;
            default:
                break;
        }
    }

    minLengthRule(input : HTMLInputElement, errorList : HTMLElement[])
    {
        if(!input.getAttribute('min')) return;
        if(input.value.length < parseInt(input.getAttribute('min')!))
        {
            errorList.push(this.createErrorLiElement('Min Length is ' + input.getAttribute('min')));
        }
    }

    maxLengthRule(input: HTMLInputElement, errorList : HTMLElement[])
    {
        if(!input.getAttribute('max')) return;
        if(input.value.length > parseInt(input.getAttribute('max')!))
        {
            errorList.push(this.createErrorLiElement('Max Length is ' + input.getAttribute('max')));
        }
    }

    requiredRule(input : HTMLInputElement, errorList : HTMLElement[])
    {
        if(!input.hasAttribute('required')) return;
        if(input.value.trim().length === 0)
        {
            errorList.push(this.createErrorLiElement('Required Field'));
        }
    }

    emailRule(input : HTMLInputElement, errorList : HTMLElement[])
    {
        const emailRegex = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$/;
        if(!emailRegex.exec(input.value))
        {
            errorList.push(this.createErrorLiElement('Invalid Email.'));
        }
    }

    generateErrorList(input : HTMLInputElement, errorList : HTMLElement[])
    {
        if(errorList.length > 0)
        {
            const previousErrors = input.parentElement!.querySelector('.error_class_container');
            if(previousErrors) previousErrors.remove();
            const errorListUl = document.createElement('ul');
            errorListUl.classList.add('error_class_container');
            input.setAttribute('data-valid', 'false');
            errorList.forEach(error => {
                errorListUl.appendChild(error);
            });
            input.parentElement!.appendChild(errorListUl);
            errorList = [];
        } else {
            errorList = this.removeErrorList(input);
        }
    }

    removeErrorList(input : HTMLInputElement)
    {
        input.setAttribute('data-valid', 'true');
        const previousErrors = input.parentElement!.querySelector('.error_class_container');
        if(previousErrors) previousErrors.remove();
        return [];
    }

    createErrorLiElement(message : string) : HTMLLIElement
    {
        const errorLi = document.createElement('li');
        errorLi.innerHTML = message;
        return errorLi;
    }
}
(function() {
    const forms = document.querySelectorAll('form');
    forms.forEach(form => {
        new FormValidator(form);
    })
})();

Recent Articles

Push Weight with Python

Create a simple terminal application that can let you know how much weight you need to put on the ba...

Validate Forms in TypeScript

Relying on HTML to validate your forms is just one step in ensuring visitors enter their information...

Automation with NodeJS

NodeJS can help you automate simple tasks. Being a web developer you can achieve the same goals as o...

Automation with Python

Python can take boring tasks away from you so you can continue to do other things. It is a scripting...

Speed With Cache in WordPress

Most WordPress sites use Apache as their server. Apache has a lot of tools built into it to speed up...

Social Media