DEV Community

Cover image for Form validation using javascript
Adam Nagy
Adam Nagy

Posted on • Updated on

Form validation using javascript

Working with forms is an every day task for almost every web developer. No matter what site you'll create it will use forms. Validating the form data on the client side is a nice-to-have feature when it comes to user experience. In this tutorial we'll create a simple form validation using javascript.

While client-side form validation gives a nice user experience, it can be tricked and bypassed really easily. To prevent malicious use, you should always validate form data on the server side

Video Tutorial

If you would watch a detailed step-by-step video instead you can check out the video I made covering this project on my Youtube Channel:

HTML

Let's start with the HTML markup. We'll have a container div, that we'll use to position and style our form. Inside that, not surprisingly, we'll create a form, we also set an id for it, and set the action to / since we don't really want to submit this form.

We'll create four input fields, for the username, email, password, and password confirmation. For styling and control purposes we'll wrap these input tags into divs with the class input control. Each of these input controls will contain a label, an input, and a div with the class error. Every input should have an id and name attribute. The label's should have a matching for property with the corresponding input tag's name attribute. For the input type we will use text for the username and email, and use password for the password and the password confirmation. The div with the error class will hold the error messages for the specific input field. It will be empty for now, we will modify it from javascript.

Lastly, we have to add a button to "submit" our form. In this example we won't really submit the form just simulate it. For the submit button I'll use a button with a type of submit.

<div class="container">
        <form id="form" action="/">
            <h1>Registration</h1>
            <div class="input-control">
                <label for="username">Username</label>
                <input id="username" name="username" type="text">
                <div class="error"></div>
            </div>
            <div class="input-control">
                <label for="email">Email</label>
                <input id="email" name="email" type="text">
                <div class="error"></div>
            </div>
            <div class="input-control">
                <label for="password">Password</label>
                <input id="password"name="password" type="password">
                <div class="error"></div>
            </div>
            <div class="input-control">
                <label for="password2">Password again</label>
                <input id="password2"name="password2" type="password">
                <div class="error"></div>
            </div>
            <button type="submit">Sign Up</button>
        </form>
    </div>
Enter fullscreen mode Exit fullscreen mode

That is the HTML markup that we need for our form. Let's style it a bit with CSS.

CSS

We'll give a simple clean spacious design for this tutorial. I'll set a linear gradient as the background and I'll use a custom google font, that you can install from here.

body {
    background: linear-gradient(to right, #0f2027, #203a43, #2c5364);
    font-family: 'Poppins', sans-serif;
}
Enter fullscreen mode Exit fullscreen mode

We'll give a fix width to our form, and center it with margins, also I'll give it a top margin to move it down a bit vertically. To have more space we apply 20px of padding. We'll set a fixed font size, a light background color and also set a border radius to have rounded corners.

#form {
    width: 300px;
    margin: 20vh auto 0 auto;
    padding: 20px;
    background-color: whitesmoke;
    border-radius: 4px;
    font-size: 12px;
}
Enter fullscreen mode Exit fullscreen mode

For the form title, we'll use a dark text color, and center it horizontally using text-align: center. The submit button should stand out so we'll use a blue background color, and white text color. We also remove the browser default borders and give it a little border-radius. We'll give it a little spacing with paddings and margins, and make it full-width by applying 100% width.

#form h1 {
    color: #0f2027;
    text-align: center;
}

#form button {
    padding: 10px;
    margin-top: 10px;
    width: 100%;
    color: white;
    background-color: rgb(41, 57, 194);
    border: none;
    border-radius: 4px;
}
Enter fullscreen mode Exit fullscreen mode

To have the inputs stacked below each other we'll use flexbox. To do that we'll set display: flex; and flex-direction: column. For the inputs we'll set a grey border, with a little border-radius. We'll set the display property to block, and make them full-width, by applying width 100%. We'll also set a little padding, so it'll be more spacious. I'll also remove the outline when the input is in focus, by setting outline: 0.

.input-control {
    display: flex;
    flex-direction: column;
}

.input-control input {
    border: 2px solid #f0f0f0;
    border-radius: 4px;
    display: block;
    font-size: 12px;
    padding: 10px;
    width: 100%;
}

.input-control input:focus {
    outline: 0;
}
Enter fullscreen mode Exit fullscreen mode

We'll use two classes ("success" and "error") to give visual feedback to the user on whether the input's value is valid or not. We'll apply these classes from javascript to the input-control div which contains the specific input field. When the success class is present we will set a green border color, otherwise if error is present we'll use a red border color instead. For the error div we'll use a smaller font-size and a red color to show the error messages.

.input-control.success input {
    border-color: #09c372;
}

.input-control.error input {
    border-color: #ff3860;
}

.input-control .error {
    color: #ff3860;
    font-size: 9px;
    height: 13px;
}
Enter fullscreen mode Exit fullscreen mode

Let's do the validation in javascript next!

Javascript

The first thing we have to do is to save references for the form, and the input fields. As we gave id for every input and the form we can easily to do by using getElementById.

const form = document.getElementById('form');
const username = document.getElementById('username');
const email = document.getElementById('email');
const password = document.getElementById('password');
const password2 = document.getElementById('password2');
Enter fullscreen mode Exit fullscreen mode

To prevent the form for automatically submit we have to attach and event listener to our form's submit event. In this event handler function we have to call preventDefault() function to prevent the form from submitting automatically. Instead of submitting we'll call the validateInputs function, which will validate the inputs and if we want to we can submit the form in there after every check passes, but we won't do that in this tutorial. We'll create this validateInputs shortly.

form.addEventListener('submit', e => {
    e.preventDefault();

    validateInputs();
});
Enter fullscreen mode Exit fullscreen mode

We'll also create two helper functions: setError,Β setSuccess. We'll use these helper functions to set the error or success states of the input controls. Let's start with the setError one. It receives two parameters: element, and message. The element will be the input element that is in the specific input-control. So first we have to get the input control parent div. We'll save it into the inputControl variable, and get the input control div by using the parent property of the input element. Next we have to gather the error div, and save it into a variable. We can do that by querying the input control with the error class.
Now we have to set the error div's innerText to be the message that we got in parameters, and remove the success class from the input control (if it exists) and add the error class.

const setError = (element, message) => {
    const inputControl = element.parentElement;
    const errorDisplay = inputControl.querySelector('.error');

    errorDisplay.innerText = message;
    inputControl.classList.add('error');
    inputControl.classList.remove('success')
}
Enter fullscreen mode Exit fullscreen mode

The setSuccess method will be really similar. The first difference is that it won't receive a message as a parameter. We have to clear the error display by setting its innerText to an empty string. Lastly we have to reverse the class application. We'll add the success class to the inputControl and remove the error class (if present).

const setSuccess = element => {
    const inputControl = element.parentElement;
    const errorDisplay = inputControl.querySelector('.error');

    errorDisplay.innerText = '';
    inputControl.classList.add('success');
    inputControl.classList.remove('error');
};
Enter fullscreen mode Exit fullscreen mode

We will create one last helper function to validate emails. This is an optional step, if you don't want to use regular expressions, feel free to just set the input type of the email field to email. The isValidEmail function will take a string as a parameter and use this weird looking regular expression to check whether it is a valid email or not. We'll use String.test() function to test the string against the regex. We'll also convert the email to a string and make it lowercase.

const isValidEmail = email => {
    const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(String(email).toLowerCase());
}
Enter fullscreen mode Exit fullscreen mode

Now we should create the validator validateInputs function. First we will get the value of all the input fields. We can do that by getting the value property's value of the input field references. We'll call the String.trim() function to remove the trailing empty spaces (if any) from the start and end of the values.
Then we can start validating inputs. We'll use if, else statements to do the validation. For the username we will check whether if it is empty or not, by comparing the value with an empty string. If it empty, we'll call the setError function and provide the username element to it, with our error message. Otherwise we'll call the setSuccess method with the username element. Now we have to do this for the other input fields, but the approach will be the same.

const validateInputs = () => {
    const usernameValue = username.value.trim();
    const emailValue = email.value.trim();
    const passwordValue = password.value.trim();
    const password2Value = password2.value.trim();

    if(usernameValue === '') {
        setError(username, 'Username is required');
    } else {
        setSuccess(username);
    }
};
Enter fullscreen mode Exit fullscreen mode

For the email we'll check if it is provided or not, and set an error if it is empty. If it is not empty we'll check whether it is a valid email address, and if not we'll set an error, otherwise we set success for the field.

if(emailValue === '') {
        setError(email, 'Email is required');
    } else if (!isValidEmail(emailValue)) {
        setError(email, 'Provide a valid email address');
    } else {
        setSuccess(email);
    }
}
Enter fullscreen mode Exit fullscreen mode

For the password we'll check whether it is empty or not, and if it is not empty we'll check if it is longer than 7 characters. If not, well set an error, otherwise we'll set it as success.

if(passwordValue === '') {
        setError(password, 'Password is required');
    } else if (passwordValue.length < 8 ) {
        setError(password, 'Password must be at least 8 character.')
    } else {
        setSuccess(password);
    }
}
Enter fullscreen mode Exit fullscreen mode

For the password confirmation we'll check if it is empty, and we should also check if the password confirmation's value is equal to the password's value.

if(password2Value === '') {
        setError(password2, 'Please confirm your password');
    } else if (password2Value !== passwordValue) {
        setError(password2, "Passwords doesn't match");
    } else {
        setSuccess(password2);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we have every input validated, if we wanted to we could submit our form now to a specific endpoint.

Good job now you have a working form validation Javascript. Please note that you always have to validate the form inputs on the server-side as client-side validation can be easily bypassed. There are way more advanced form validation methods and libraries that we use in modern web development, but this project is a really good way to start and learn the fundamentals.

Where you can learn more from me?

I create education content covering web-development on several platforms, feel free to πŸ‘€ check them out.

I also create a newsletter where I share the week's or 2 week's educational content that I created. No bullπŸ’© just educational content.

πŸ”— Links:

Discussion (18)

Collapse
rkallan profile image
RRKallan • Edited on

Your solution and example is very static and not dynamic.

to make it dynamic i would suggest to use after submitting the form
event.currentTarget.elements

with event.currentTarget.elements you will get all the formElements as object

and based on data attribute validation (for example) i would set the validation type. Maybe better is to create a js array of object for your form. On each item you can set the validation type. This is what you can use for validation for the field and form

If you want to have a example let me know

Collapse
javascriptacademy profile image
Adam Nagy Author

Thanks @rkallan for writing this comment!
Yes, the solution is very static, intentionally. This tutorial’s target audience are the beginners, and I think they get the hang of how it works more easil if the validation is static and not dynamic. Probably would worth to create and advanced form validation tutorial too πŸ€”

Collapse
rkallan profile image
RRKallan

I understand your explanation and I can agree about the static validation as a start

But to get the values of your form elements I would suggest to use event.currentTarget.elements

The 2 functions setError and setSucces I would created as 1 function

const setValidationClassName = (element, isValid = true, message = β€˜β€™) => {
    const inputControl = element.parentElement;
    const errorDisplay = inputControl.querySelector('.error');
    const validationClassNames = [β€˜error’, β€˜succes’];

    errorDisplay.innerText = message;
    inputControl.classList.replace(validationClassNames[!isValid], validationClassNames[isValid]);
}
Enter fullscreen mode Exit fullscreen mode

The if statements for validation I would created functions. And avoiding if elseif else statements
And return value would be a object

return {
    isValid: true,
    message: β€˜β€™,
};
Enter fullscreen mode Exit fullscreen mode
Thread Thread
anil027 profile image
Anil Kumar Venugopal

Hi RRKallan,Thanks for explaining this, can you provide real time working example on the same !!

Thread Thread
rkallan profile image
RRKallan

@anil027

<div class="container">
    <form id="form" action="/" method="post" novalidate>
        <h1>Registration</h1>
        <div class="input-control">
            <label for="username">Username</label>
            <input id="username" name="username" type="text" required data-validation="username" />
            <div class="error"></div>
        </div>
        <div class="input-control">
            <label for="email">Email</label>
            <input id="email" name="email" type="email" required data-validation="email" />
            <div class="error"></div>
        </div>
        <div class="input-control">
            <label for="password">Password</label>
            <input id="password" name="password" type="password" required data-validation="password" />
            <div class="error"></div>
        </div>
        <div class="input-control">
            <label for="password2">Password again</label>
            <input id="password2" name="password2" type="password" required data-validation="passwordConfirm" />
            <div class="error"></div>
        </div>
        <button type="submit">Sign Up</button>
    </form>
</div>
Enter fullscreen mode Exit fullscreen mode
const getType = (value) => {
    const type = Object.prototype.toString.call(value).slice(1, -1).split(" ");

    return type && type[1].toLowerCase();
};

const validations = {
    isEmpty: ({ value }) => {
        const valueIsType = getType(value);

        if (valueIsType === "string") return !value.trim().length;
        if (valueIsType === "array") return !value.length;
        if (valueIsType === "object") return !Object.keys(value).length;
        if (valueIsType === "number") return Number.isNaN(value);
        if (valueIsType === "boolean") return false;

        return true;
    },
    email: ({ value }) => {
        const isEmpty = validations.isEmpty({ value });
        const returnValue = {
            isValid: false,
            message: null,
        };

        if (isEmpty === true) {
            returnValue.message = "Email is required";
            return returnValue;
        }

        const pattern =
            /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        const isValid = pattern.test(String(value).toLowerCase());

        if (isValid === false) {
            returnValue.message = "Provide a valid email address";
        }
        returnValue.isValid = isValid;

        return returnValue;
    },
    username: ({ value }) => {
        const isEmpty = validations.isEmpty({ value });
        const returnValue = {
            isValid: !isEmpty,
            message: null,
        };

        if (isEmpty === true) {
            returnValue.message = "Username is required";
        }
        return returnValue;
    },
    password: ({ value }) => {
        const isEmpty = validations.isEmpty({ value });
        const returnValue = {
            isValid: false,
            message: null,
        };

        if (isEmpty === true) {
            returnValue.message = "Password is required";
            return returnValue;
        }

        if (value.length < 8) {
            returnValue.message = "Password must be at least 8 character.";
            return returnValue;
        }

        returnValue.isValid = true;
        return returnValue;
    },
    passwordConfirm: ({ value, valueToMatch }) => {
        const isEmpty = validations.isEmpty({ value });
        const valueToMatchIsEmpty = validations.isEmpty(valueToMatch);
        const returnValue = {
            isValid: false,
            message: null,
        };

        if (valueToMatchIsEmpty === true) {
            return returnValue;
        }

        if (isEmpty === true) {
            returnValue.message = "Please confirm your password";
            return returnValue;
        }

        if (value !== valueToMatch) {
            returnValue.message = "Passwords doesn't match";
            return returnValue;
        }

        returnValue.isValid = true;
        return returnValue;
    },
};

const setInputControlClassName = ({ element, isValid = true, message = null }) => {
    const inputControl = element.parentElement || {};
    inputControl.classList.toggle("error", !isValid);
    inputControl.classList.toggle("succes", isValid);

    const errorDisplay = inputControl.querySelector(".error") || {};
    errorDisplay.innerText = message || "";
};

const onSubmitForm = (event) => {
    event.preventDefault();

    const formObject = event.currentTarget;
    const formElements = formObject.elements;

    Array.prototype.slice.call(formElements).forEach((element) => {
        const { type, required, dataset } = element;

        if (["submit", "reset", "button"].includes(type) || !required) return;

        const validationType = dataset.validation;
        const value = element.value?.trim();
        const valueToMatch = validationType === "passwordConfirm" ? formElements.password?.value?.trim() : null;
        const { isValid, message } = validations[validationType]({ value, valueToMatch });

        setInputControlClassName({ element, isValid, message });
    });
};

const formElement = document.querySelector("#form");
formElement.addEventListener("submit", onSubmitForm);
Enter fullscreen mode Exit fullscreen mode
Thread Thread
anil027 profile image
Anil Kumar Venugopal

Thank you !!

Collapse
pauladeleke profile image
PaulAdeleke

Not sure we need such as your extravagant example. I think the OP was trying to teach it at its basic level.
I would rather applaud him for being basic and simple.

Collapse
rkallan profile image
RRKallan

The OP replayed and explained it why he used the static way. I can agree with the arguments

Collapse
jonrandy profile image
Jon Randy

You're probably better off using the in-built HTML field validation

Collapse
javascriptacademy profile image
Adam Nagy Author

That can work for simple use cases, but most of the time there are Specific validation conditions. For example the password should contain lowercase, uppercase letters numbers and should not contain special characters (like ?*%). This example can surely be done with built in HTML valodators, but the aim is to get an idea how you can do more advanced validations.

Collapse
psndoc23 profile image
psnDoc23

Thanks, Adam. This is exactly what I was looking for. A simple JavaScript validation that teaches me the basics of how to do this in a static way with JS, not an elaborate program with all the bells and whistles. And thanks for pointing out the backend validation too, I may have let that slip by without thinking all the way through how people can evade the front end pretty easily.

Collapse
jonrandy profile image
Jon Randy

Your password example can be done with built-in validation

Thread Thread
javascriptacademy profile image
Adam Nagy Author

Okay lets get into a specific use case I faced a few years earlier. On a registration site we had to validate if an email address is a real existing address. To do that we had to call an API to validate the email address. There’s no way to do that eith built in html.

Thread Thread
jonrandy profile image
Jon Randy

All the in-built stuff can be called with JS. You just need to augment it a little for cases like this - not re-invent the wheel

Thread Thread
rkallan profile image
RRKallan

When a user use inspect element and change the pattern or and the type the build in validation would result in a false isValid

Thread Thread
jonrandy profile image
Jon Randy • Edited on

Similarly, any JS-only validation can be bypassed (breakpoints, changing values etc.)

Far too many sites these days forget this. I've lost count of the number of forms I've 'fooled' into letting me continue as all the validation is done client-side. I've even seen 'server-side' validation fail as the result of a server-side check whose result was being checked on the client-side - something like this:

const formIsValid = validateFormServerSide()

// make a breakpoint here, change formIsValid to true ...
// Voila! 'server-side' validation bypassed

if (formIsValid) {
  // Do stuff
}
Enter fullscreen mode Exit fullscreen mode
Thread Thread
rkallan profile image
RRKallan

True submitting a form needs also to be validate on server side.

Collapse
ashleyjsheridan profile image
Ashley Sheridan

Please don't ever just remove the focus styles from form elements like this:

.input-control input:focus {
    outline: 0;
}
Enter fullscreen mode Exit fullscreen mode

Some people rely on the visual indicator of focused elements, especially those who use a keyboard to navigate the form.

One thing I noticed in your HTML is that you're not using the native form element types where they exist. For example, the email field should be of type email. Not only does this present the user with an appropriate keyboard (device dependent) but you can hook into the default validation provided by the browser, and then remove the regular expression checking from the code.

In-fact, by using the HTML form validation attributes (type="", required, pattern, etc), you can make your Javascript validation far more generic. That way, you can target all input elements as you need, and the validation will still work across the entire form regardless of whatever form elements are added or removed in the future.