Learn JS: FSFS – Section 10 – Live Form Validation


Courses

Updated Jul 5th, 2021

Chapter 112 – Live Validation (Part 1)

Server-side validation is a necessity because a user can disable front-end validation. This is just to enhance user experience.

Create a “frontend-js” folder and “modules” subfolder. Create a “registrationForm.js” file in the “modules” subfolder and import it into the “main.js” file.

if (document.querySelector("#registration-form")) {
  new RegistrationForm()
}

Bring in a red validation message for each of the three fields. In the “registrationForm.js” file create a “this.allFields” property and set to document.querySelectorAll(“#registration-form .form-control”).

Create an “insertValidationElements” method and have:

this.allFields.forEach(function(el) {el.insertAdjacentHTML("afterend", '<div class="alert alert-danger small liveValidateMessage"></div>')})

Note that nested quotes need to be a different type (singles inside of double or vice-versa but no doubles inside of doubles).

For certain fields you need to give the user a chance to finish typing. You also may want to delay to begin a request to the network, for example, to see if a username has already been taken. But there are some situations where you want to show an error message immediately. These include cases like a field containing special characters (non-alpha-numeric characters), too many characters, etc.

Set a “this.username” property set to the username field and in the events area add a “keyup” event listener that, via an arrow function’s block, triggers a “this.isDifferent” method and pass it “this.username” and “this.usernameHandler.”

Create the “isDifferent” method and give it two parameters, (el, handler). In the method’s body, see if the field’s value has changed since the last “keyup.”

if (el.previousValue != el.value) {handler.call(this)}
el.previousValue = el.value

Note that in the code above if we call the handler function using handler() the “this” keyword will be pointing towards the global object which is not what we want. By calling the handler function using handler.call(this) the “this” keyword will still be pointing towards our overall object.

Add a “this.username.previousValue” property to the constructor and set to an empty string. Note: the reason we need to compare a field’s previous value to it’s current value is for keys like left, right, caps lock, shift, etc.

Create a “usernameHandler” method that calls a “this.usernameImmediately()” method and using the “setTimeout” method calls a “this.usernameAfterDelay()” method after 800ms. We will also need to reset the timer using clearTimeout().

usernameHandler() {
  this.username.errors = false
  this.usernameImmediately()
  clearTimeout(this.username.timer)
  this.username.timer = setTimeout(() =>   this.usernameAfterDelay(), 800)
}

Chapter 113 – Live Validation (Part 2)

Create a “usernameImmediately” method to check if it’s not blank and not alpha-numeric using a regular expression. Note: We are skipping the use of the validator package because it’s 34kb and since this is front-end JavaScript, our users would have to download this.

if (this.username.value != "" && !/^([a-zA-Z0-9]+)$/.test(this.username.value)) {
  this.showValidationError(this.username, "Username Error")
}

Create the “showValidationError” method to have two parameters, (el, message).

showValidationError() {
  el.nextElementSibling.innerHTML = message
  el.nextElementSibling.classList.add("liveValidateMessage--visible")
  el.errors = true
} 

Note the “this.username.errors” property and logic is needed to restart the validation check and remove an error if the reason for the error was removed. Add a if check to the “usernameImmediately” method to check if there are no errors (!this.username.errors), and if so, run a “this.hideValidationError(this.username)” method with argument.

Create “hideValidationError” method with one parameter (el) that just removes the “liveValidateMessage–visible” class.

In “usernameImmediately” method, below the alpha-numeric if check, also check if the username is greater than 30 characters and, if so, call a “this.showValidationError()” method and pass it two arguments, (this.username, “Username cannot exceed 30 characters.”)

if (this.username.value.length > 30) {
this.showValidationError(this.username, "Username cannot exceed 30 characters.")
}

Also create a “usernameAfterDelay” method to show an error if there’s less than three characters. This will be very similar to the code above.

Chapter 114 Live Validation (Part 3)

Import the “axios” package and inside of the “usernameAfterDelay” method and after the initial if check, set up a new if statement to check if there are no errors and, if so, check if the username is unique by sending an “axios.post()” request to the “/doesUsernameExist” route. Add a .then() and .catch() and pass both of arrow functions. in the “axios.post()” parentheses, as the second argument, send a data object {username: this.username.value}. Give the function in the .then() a parameter of response and within the function block have an if/else. Set “response.data” as the condition and if so, show an error message and set “this.username.isUnique” to false. In the else block set “this.username.isUnique” to true. In the .catch() log a message to the console.

In the “router.js” file create a route for “/doesUsernameExist” and have it run a “userController.doesUsernameExist” function. Create the “exports.doesUsernameExist” function and have it leverage the existing “User.findByUsername()” function created earlier in the course by passing it “(req.body.username).” Add a .then() and .catch() within functions within after the “findByUsername” function call. In the .then() function have res.json(true) and within catch function res.json(false).

At this point we are done with the username field and can begin working on the email field.

Set “this.email” and “this.email.previousValue” properties in the constructor. In the events area duplicate the username lines swap username with email.

Duplicate the “usernameHandler” method to be a new “emailHandler” method and inside delete the call for the “usernameImmediately” method. When it comes to email we don’t need to run any validation checks immediately.

Create a new method “emailAfterDelay” that checks for email format using regex and if it passes then fires off an “axios.post()” request to “/doesEmailExist” route. Send {email: this.email.value} as the data object. In the “router.js” file create a new route for “doesEmailExist” that runs a function named “userController.doesEmailExist.”

emailAfterDelay() {
  if (!/^\S+@\S+$/.test(this.email.value)) {
  this.showValidationError(this.email, "Custom Err Msg")
  }
  if (this.email.errors) {
  axios.post(a, b).then(() => {}).catch(() => {})
  }
}

Inside the .then() function do an if check with the condition being (response.data) and if this is true send an error and set “this.email.isUnique” to false. If it’s true then set “this.email.isUnique” to true and call “this.hideValidationError(this.email).” In the catch function just log to the console.

Chapter 115 – Live Validation (Part 4)

Create the “exports.doesEmailExist” function in the “userController.js” file. In the “User.js” file, we will create a “User.doesEmailExist” function that talks to the database and returns a promise with a value of either true or false. So Here is how we set up:

// in userController.js

exports.doesEmailExist = async function(req, res) {
  let emailBool = await User.doesEmailExist(req.body.email)
  res.json(emailBool)
}

// in User.js

User.doesEmailExist = function(email) {
  return new Promise(async function(resolve, reject)=> {
    if (typeof(email) != "string") {
      resolve(false) 
      return
    }
    let user = await usersCollection.findOne({email: email})
    if (user) {
    resolve(true)
    } else {
    resolve(false)
    }
  })
}

This finishes the validation for the email field. Now we can work on validating the password by creating properties of “this.password” and “this.passwordPreviousValue” to the constructor.

Duplicate and update the “keyup” listener in the events area. Duplicate and update the “usernameHandler” method to create a “passwordHandler” method.

Create a “passwordImmediately” method that uses an if statement to check whether “this.password.length” is more than 50 characters. If so, then call “this.showValidationError()” with necessary arguments. If it passes the if check then call “this.hideValidationError()” with necessary arguments.

Create a “passwordAfterDelay” method that has an if statement checking if “this.password.value.length” is less than 6 and, if so, then call “this.showValidationError()” with necessary arguments. If it passes the if check then call “this.hideValidationError()” with necessary arguments.

To bring it all back together we need to manage the clicking of the submit button. We want to prevent the form from submitting unless we are perfectly happy with all three values typed into the fields.

In the constructor, create a property that selects the form called “this.form.” Also create, and set to false, properties called “this.username.IsUnique” and “this.email.IsUnique.” In the events area add a listener for the form submitting that runs a “this.formSubmitHandler” method when the event fires.

Create a “formSubmitHandler” method that prevents the default, calls all our validation checks, and only if there are no errors, submits the form.

formSubmitHandler() {
  this.usernameImmediately()
  this.usernameAfterDelay()
  this.emailAfterDelay()
  this.passwordImmediately()
  this.passwordAfterDelay()
  
  if (
      this.username.isUnique &&
      !this.username.errors && 
      this.email.isUnique && 
      !this.email.errors && 
      !this.password.errors 
  ) {
    this.form.submit()
  }
}

Boom. We’re done besides one final detail to fix a bug.

The issue is when someone puts a special character and immediately hits the tab key, the message is bypassed. You essentially just listen for a “blur” event for each field in the events area. Just duplicate the three “keyup” events and update the listener from “keyup” to “blur.” Note that “blur” runs when you exit off of or when a field loses focus.

Head to Section 11 – CSRF Protection