In this tutorial, we will create a form validator for a simple login page using HTML, SASS, and Vanilla JavaScript. Basically, we use JS to create classes that will validate the login variables based on our needs then we will store information in the browser for authentication.
I do not use Bootstrap, jQuery, or any other frameworks. This method can be used on local browsers, web apps, and even on your server since it is all front-end code.
View This On YouTube
Folder Structure
- index.html
- dashboard.html
- init.js
- /sass
- /js
- /css
Sass
I have my environment set up to publish a style.css and style.min.css into my css folder automatically. Any changes I make to my sass/style.scss file will automatically go into that folder and minify itself.
Login Page
For the login page, we will use the index.html since it is the first page our visitors will access when they go to the website. This does not need to be how you set it up, but it will prevent us from having to create a JS redirect if they are not logged in.
<!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>Login</title>
<link rel="stylesheet" href="/css/style.min.css">
<script defer src="/js/login.js"></script>
</head>
<body class="login">
<div class="container">
<h2 class="text-center">Login</h2>
<br>
<form action="/dashboard.html" class="loginForm">
<div class="input-group">
<label for="username" class="label">Username</label>
<input type="text" id="username" class="input">
<span class="error-message"></span>
</div>
<br>
<div class="input-group">
<label for="password" class="label">Password</label>
<input type="password" id="password" class="input">
<span class="error-message"></span>
</div>
<br>
<button class="button" type="submit">Login</button>
</form>
</div>
</body>
</html>
This is a basic page with an HTML form on it. Notice that the body has a class="login" and the form directs to dashboard.html. You can have this either hard-coded in your form as I did, or you can add the redirect to your JS file.
Login JavaScript
In your js folder, add a file named login.js. This will control the login functionality for this page and is referenced in the script tags of the HTML.
Login Class
We will first create a new class that we can call for this script. All functions will go inside of this class so they are easier to use later. Notice the naming convention I am using for my JS class.
class Login {
// everything will go here
}
Constructor
Our constructor is needed to help "prefill" some variables we will need through the class. We will send a form and the required fields to our script when we make a call to the class so we need to make sure they are being passed and set before the functions are run. Add this between the brackets in your class above.
constructor(form, fields) {
this.form = form;
this.fields = fields;
this.validateonSubmit();
}
Validate on Submit Function
In the constructor, we call a function called ValidateonSubmit(). This function will handle the default abilities of the script and make calls to the other functions when they are needed. This function can be added directly below the constructor.
validateonSubmit() {
let self = this; // setup calls to the "this" values of the class described in the constructor
// add a "submit" event listener to the form
this.form.addEventListener("submit", (e) => {
// remove default functionality
e.preventDefault();
var error = 0;
// loop through the fields and check them against a function for validation
self.fields.forEach((field) => {
const input = document.querySelector(`#${field}`);
if (self.validateFields(input) == false) {
// if a field does not validate, auto-increment our error integer
error++;
}
});
// if everything validates, error will be 0 and can continue
if (error == 0) {
//do login api here or in this case, just submit the form and set a localStorage item
localStorage.setItem("auth", 1);
this.form.submit();
}
});
}
Validation Function
In the previous function, we call a class named validateFields(), this function also takes the field variable to pass to itself. The field is based on where in the defined array and the form the script currently is.
validateFields(field) {
// remove any whitespace and check to see if the field is blank, if so return false
if (field.value.trim() === "") {
// set the status based on the field, the field label, and if it is an error message
this.setStatus(
field,
`${field.previousElementSibling.innerText} cannot be blank`,
"error"
);
return false;
} else {
// if the field is not blank, check to see if it is password
if (field.type == "password") {
// if it is a password, check to see if it meets our minimum character requirement
if (field.value.length < 8) {
// set the status based on the field, the field label, and if it is an error message
this.setStatus(
field,
`${field.previousElementSibling.innerText} must be at least 8 characters`,
"error"
);
return false;
} else {
// set the status based on the field without text and return a success message
this.setStatus(field, null, "success");
return true;
}
} else {
// set the status based on the field without text and return a success message
this.setStatus(field, null, "success");
return true;
}
}
}
Status Function
Our status function called in the previous function is used to modify the css of the input fields and display the error messages. The code is relatively simple and works for all fields we have on the form.
setStatus(field, message, status) {
// create variable to hold message
const errorMessage = field.parentElement.querySelector(".error-message");
// if success, remove messages and error classes
if (status == "success") {
if (errorMessage) {
errorMessage.innerText = "";
}
field.classList.remove("input-error");
}
// if error, add messages and add error classes
if (status == "error") {
errorMessage.innerText = message;
field.classList.add("input-error");
}
}
Calling Our Login Class
For ease of use, I added the Login class call to the bottom of the file where we created the class. Typically, you would add this to an app.js or ini.js file, but it serves its purpose here as well.
// create a variable for the login form
const form = document.querySelector(".loginForm");
// if the form exists, run the class
if (form) {
// setup the fields we want to validate, we only have two but you can add others
const fields = ["username", "password"];
// run the class
const validator = new Login(form, fields);
}
Dashboard Page
This page is just for demo purposes. It only shows how a successful redirect happens and how we log out of the application. Notice the body does not have a class and there are two scripts called in the head.
<!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>Dashboard</title>
<link rel="stylesheet" href="/css/style.min.css">
<script defer src="/js/auth.js"></script>
<script defer src="/init.js"></script>
</head>
<body>
<div class="container">
<h1 class="text-center">Welcome to the Dashboard</h1>
<p class="text-center"><a href="#" class="logout">Log Out</a></p>
</div>
</body>
</html>
Auth Class
This simple class essentially just makes sure the localStorage item exists and creates a function to log out.
class Auth {
// setup the class and hide the body by default
constructor() {
document.querySelector("body").style.display = "none";
const auth = localStorage.getItem("auth");
this.validateAuth(auth);
}
// check to see if the localStorage item passed to the function is valid and set
validateAuth(auth) {
if (auth != 1) {
window.location.replace("/");
} else {
document.querySelector("body").style.display = "block";
}
}
// will remove the localStorage item and redirect to login screen
logOut() {
localStorage.removeItem("auth");
window.location.replace("/");
}
}
Call Auth Class on Every Page
In order for our auth class to work for every page that needs to have auth, we just add it in our init.js file. This will call our Auth class with every page load so we are sure that the pages we want to protect are protected without having to rewrite the function every time. Add the following to your init.js file.
const auth = new Auth();
document.querySelector(".logout").addEventListener("click", (e) => {
auth.logOut();
});
SASS
I will not go over every detail about the SASS file as it is not very important for this tutorial, but you can read through it if you want to make any changes.
$primary: rgb(0, 132, 255);
$error: red;
@import url("https://fonts.googleapis.com/css2?family=Open+Sans&display=swap");
* {
box-sizing: border-box;
}
body {
font-family: "Open Sans", sans-serif;
background-color: $primary;
font-size: 16px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
padding: 0;
margin: 0;
}
.container {
max-width: 98vw;
margin: 0 auto;
padding: 1rem;
background-color: #fff;
}
.text-center {
text-align: center;
}
.login {
.container {
max-width: 460px;
margin: 3rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 0.25rem;
background-color: #fff;
}
}
.input {
appearance: none;
display: block;
width: 100%;
color: #333;
border: 1px solid rbg(180, 180, 180);
background-color: white;
padding: 1rem;
border-radius: 0.25rem;
&.input-error {
border: 1px solid $error;
&:focus {
border: 1px solid $error;
}
}
&:focus {
outline: none;
border: 1px solid $primary;
background-clip: padding-box;
}
}
.error-message {
font-size: 0.85rem;
color: $error;
}
.button {
background-color: $primary;
padding: 1rem;
border: none;
color: #fff;
font-weight: bold;
display: block;
width: 100%;
text-align: center;
cursor: pointer;
font-size: 1rem;
&:hover {
filter: brightness(110%);
}
}
Conclusion
This is phase one of a few more tutorials I have coming up. Stay tuned to learn more about building a vanilla JavaScript application with login, authentication, and internal, secured pages. In my next tutorial + video, I will be building a simple JSON API using PHP and MySQL to tie this into. If you want to watch the video when it premiers, don't forget to subscribe to Dev Drawer on YouTube.