Home > Software engineering >  Default the user's cursor to the first invalid input field on form submit
Default the user's cursor to the first invalid input field on form submit

Time:10-16

As part of a learning project, I am trying to create a form that will default the user's cursor to the first invalid input field upon submit. The script must use vanilla javascript. I have tried numerous different suggestions (see list below) but none will actually do what I need it to do. It seems like it has something to do with the focus() and the aria-required="true" (which I've added) but it still doesn't work.

I have tried the suggestions from here: Trigger 'oninvalid' event on input field
https://blog.openreplay.com/form-validation-using-javascript-s-constraint-validation-api
https://community.hubspot.com/t5/APIs-Integrations/How-to-set-the-focus-to-first-invalid-form-field/m-p/229637

Here is the pen that I created to test this:

https://codepen.io/JMGable/pen/wvjOxGm

/* Uses the document.querySelector() method to select the input fields and the form */
const usernameEl = document.querySelector('#username');
const emailEl = document.querySelector('#email');
const passwordEl = document.querySelector('#password');
const confirmPasswordEl = document.querySelector('#confirm-password');
const firstnameEl = document.querySelector('#firstname');
const lastnameEl = document.querySelector('#lastname');
const phoneEl = document.querySelector('#phone');

const form = document.querySelector('#signup');
 /* */




// This validates the username field. 
const checkUsername = () => {

// This makes it false by default until validated with specific parameters
    let valid = false;
  // This sets the min and max lengths for the field
    const min = 8,
        max = 20;
// This trims whitespace before and after
    const username = usernameEl.value.trim();
// This checks if the username field is blank, if so, it shows a message
    if (!isRequired(username)) {
        showError(usernameEl, 'Username cannot be blank.');
        
 // This checks to see if the username length is between the min/max parameters, if not, it will show a message       
    } else if (!isBetween(username.length, min, max)) {
        showError(usernameEl, `Username must be between ${min} and ${max} characters.`)

//The function returns true if the field passes the required checks.
    } else {
        showSuccess(usernameEl);
        valid = true;
    }
    // Now the username is considered valid
    return valid;
};

/* 
This uses the isRequired() and isEmailValid() functions for checking the email.The checkEmail() function returns true if the email is valid and uses the showError() and showSuccess() functions to provide feedback messages.
 */
const checkEmail = () => {
    let valid = false;
    const email = emailEl.value.trim();
    if (!isRequired(email)) {
        showError(emailEl, 'Email cannot be blank.');
   // This checks the isEmailValid regex to validate the email     
    } else if (!isEmailValid(email)) {
        // If the check fails the parameters, its considered invalid and shows an error message
        showError(emailEl, 'Email is not valid.')
    } else {
// If the checkEmail() passes all the parameters, its considered valid    
        showSuccess(emailEl);
        valid = true;
    }
    return valid;
};

// This uses the checkPassword() function to check the password field to see if it matches the required format
const checkPassword = () => {
// This makes it false by default until validated with specific parameters
    let valid = false;
    // This trims whitespace before and after
    const password = passwordEl.value.trim();
// This checks to see if the password field is empty
    if (!isRequired(password)) {
        showError(passwordEl, 'Password cannot be blank.');
    // This uses the isPasswordSecure regex to validate the password 
    } else if (!isPasswordSecure(password)) {
        showError(passwordEl, 'Password must has at least 8 characters that include at least 1 lowercase character, 1 uppercase characters, 1 number, and 1 special character in (!@#$%^&*)');
    } else {
        // If the checkPassword() passes all the parameters, its considered valid  
        showSuccess(passwordEl);
        valid = true;
    }

    return valid;
};

//This uses the checkConfirmPassword() function to check if the passwords match.
const checkConfirmPassword = () => {
    let valid = false;
    // check confirm password
    const confirmPassword = confirmPasswordEl.value.trim();
    const password = passwordEl.value.trim();

    if (!isRequired(confirmPassword)) {
        showError(confirmPasswordEl, 'Please enter the password again');
    } else if (password !== confirmPassword) {
        showError(confirmPasswordEl, 'The password does not match');
    } else {
        showSuccess(confirmPasswordEl);
        valid = true;
    }

    return valid;
};

// This validates the firstname field. 
const checkFirstname = () => {
// This makes it false by default until validated with specific parameters
    let valid = false;
// This sets the min and max lengths for the field
    const min = 2,
          max = 30;
// This trims whitespace before and after
    const firstname = firstnameEl.value.trim();
// This checks if the firstname field is blank, if so, it shows a message
    if (!isRequired(firstname)) {
        showError(firstnameEl, 'Firstname cannot be blank.');
 // This checks to see if the firstname length is between the min/max parameters, if not, it will show a message       
    } else if (!isBetween(firstname.length, min, max)) {
        showError(firstnameEl, `firstname must be between ${min} and ${max} characters.`)

//The function returns true if the field passes the required checks.
    } else {
        showSuccess(firstnameEl);
        valid = true;
    }
    // Now the firstname is considered valid
    return valid;
};

// This validates the lastname field. 
const checkLastname = () => {
// This makes it false by default until validated with specific parameters
    let valid = false;
// This sets the min and max lengths for the field
    const min = 2,
          max = 30;
// This trims whitespace before and after
    const lastname = lastnameEl.value.trim();
// This checks if the lastname field is blank, if so, it shows a message
    if (!isRequired(lastname)) {
        showError(lastnameEl, 'Lastname cannot be blank.');
 // This checks to see if the lastname length is between the min/max parameters, if not, it will show a message       
    } else if (!isBetween(lastname.length, min, max)) {
        showError(lastnameEl, `Lastname must be between ${min} and ${max} characters.`)

//The function returns true if the field passes the required checks.
    } else {
        showSuccess(lastnameEl);
        valid = true;
    }
    // Now the lastname is considered valid
    return valid;
};


// This validates the phone field. 
const checkPhone = () => {
// This makes it false by default until validated with specific parameters
    let valid = false;
// This sets the min and max lengths for the field
    const min = 14,
          max = 30;
// This trims whitespace before and after
    const phone = phoneEl.value.trim();
// This checks if the phone field is blank, if so, it shows a message
    if (!isRequired(phone)) {
        showError(phoneEl, 'Phone cannot be blank.');


 // This checks to see if the phone length is between the min/max parameters, if not, it will show a message       
    } else if (!isBetween(phone.length, min, max)) {
        showError(phoneEl, `Phone must be between ${min} and ${max} characters.`)

//The function returns true if the field passes the required checks.
    } else {
        showSuccess(phoneEl);
        valid = true;
    }
    // Now the phone is considered valid
    return valid;
};



// REGULAR EXPRESSIONS FOR CHECKING SPECIFIC VALIDATIONS

// This regular expression is used to check if the email is valid
const isEmailValid = (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(email);
};

// This regex is used to check if the password is strong, and matches a specific pattern
const isPasswordSecure = (password) => {
    const re = new RegExp("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})");
    return re.test(password);
};


// GENERIC UTILITIES FOR CHECKING VALIDATIONS


// The following isRequired() function returns true if the input argument is empty
const isRequired = value => value === '' ? false : true;
// The following isBetween() function returns false if the length arg is not between the min and max args.
const isBetween = (length, min, max) => length < min || length > max ? false : true;


/* If the input field is invalid, the showError() function highlights the border of the input field and displays an error message */
const showError = (input, message) => {

    // This gets the parent element of the input field, it's the div element that contains the formField class
    const formField = input.parentElement;

    //This removes the success class and adds the error class to the form-field element
    formField.classList.remove('success');
    formField.classList.add('error');
  
    /* This selects the small element inside the formField element then sets the error message to it's textContent property */
    const error = formField.querySelector('small');
    error.textContent = message;
};

/* If the input field is valid, the showSuccess() function removes the error class and adds the success class*/

const showSuccess = (input) => {
    // This gets the parent element of the input field, the div that holds the formField
    const formField = input.parentElement;

    // This removes the error class and adds the success class
    formField.classList.remove('error');
    formField.classList.add('success');

/* This selects the small element inside the formField element then hides 
   the error message of the textContent property */
      const error = formField.querySelector('small');
    error.textContent = '';
}




/* This attaches the submit event listener to the form by using the addEventListener() method */
form.addEventListener('submit', function (e) {

   

/* By default, the form will submit when the submit button is clicked. To prevent the form from submitting, the e.preventDefault() method is called within the event listener.*/
    e.preventDefault();

  // This modifies the submit event handler and the functions that validate the input fields in the submit event handler
    // This validates the username, password and confirm password fields
    let isUsernameValid = checkUsername(),
        isEmailValid = checkEmail(),
        isPasswordValid = checkPassword(),
        isFirstnameValid = checkFirstname();
        isLastnameValid = checkLastname();
        isPhoneValid = checkPhone();
// This validates the required fields meet all the qualifications and the form can be submitted
    let isFormValid = 
        isUsernameValid &&
        isEmailValid &&
        isPasswordValid &&
        isConfirmPasswordValid  &&
        isFirstnameValid &&
        isLastnameValid  &&
        isPhoneValid;

    // submit to the server if the form is valid
    if (isFormValid) {

    }
});





    // Format phone number while typing
// document.getElementById("phone").addEventListener("input", function (e) {

// var x = e.target.value.replace(/\D/g, "").match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
// e.target.value = !x[2]
// ? x[1]
// : "("   x[1]   ") "   x[2]   (x[3] ? "-"   x[3] : "");
// });

CodePudding user response:

Basically, input.focus() on the showError function. If you want it to happen only for the first input, then validate them by order. Also set a helper flag to first that tells if this is the first invalid input.

var first;

/* If the input field is invalid, the showError() function highlights the border of the input field and displays an error message */
const showError = (input, message) => {

  // This gets the parent element of the input field, it's the div element that contains the formField class
  const formField = input.parentElement;

  //This removes the success class and adds the error class to the form-field element
  formField.classList.remove('success');
  formField.classList.add('error');

  /* This selects the small element inside the formField element then sets the error message to it's textContent property */
  const error = formField.querySelector('small');
  error.textContent = message;

  if (first) {
    input.focus();
    first = false;
  }
};

form.addEventListener('submit', function(ev) {
  ev.preventDefault();
  first = true;
  showError(input, 'hello world')
  showError(input2, 'hello world')
})
<form id="form">
  <div >
    <input id="input">
    <small style="color:red"></small>
  </div>
  <div >
    <input id="input2">
    <small style="color:red"></small>
  </div>
  <button>submit</button>
</form>

CodePudding user response:

First of all, you forgot to add small to the phone input.
You can make a separate validation function, but that's probably overengineered.

const validateInputs = () => {
  const validationFunctionOnInputId = [
    ["username", checkUsername],
    ["email", checkEmail],
    ["password", checkPassword],
    ["firstname", checkFirstname],
    ["lastname", checkLastname],
    ["phone", checkPhone],
  ];

  const invalidInputIds = validationFunctionOnInputId
    .map(([inputId, validationFunction]) => ({ inputId, isValueValid: validationFunction() }))
    .filter(({ isValueValid }) => !isValueValid)
    .map(({ inputId }) => inputId);
  const isFormInvalid = !!invalidInputIds.length;

  return {
    isFormInvalid: isFormInvalid,
    idOfFirstInvalidInput: isFormInvalid ? invalidInputIds[0] : null,
  }
}

/* This attaches the submit event listener to the form by using the addEventListener() method */
form.addEventListener('submit', function (e) {
  /* By default, the form will submit when the submit button is clicked. To prevent the form from submitting, the e.preventDefault() method is called within the event listener.*/
  e.preventDefault();

  const { isFormInvalid, idOfFirstInvalidInput } = validateInputs();
  // submit to the server if the form is valid
  if (isFormInvalid) {
    console.log("form is invalid");
    document.getElementById(idOfFirstInvalidInput).focus();
    return;
  }

  console.log("form is valid");
});
  • Related