9. Forms

Forms are a gateway into the database and, therefore, into the most sensitive area of the web application. The information we receive must pass a thorough quality control: age must be a number, not an email address; a photo must not be a compressed zip file. And so we could go on with a long list of specifications that we won't forgive the visitor for, not even by a single character.

We're not looking to punish, but to guide. With clear, well explained messages, we'll indicate the best way to fill in each field with the appropriate format. And until the requirements are met, we'll ignore any information that reaches the database. We'll even prevent the button from being pressed to submit from the frontend. We are the last ones responsible for anything that could happen.

Validating from the frontend (JavaScript) doesn't prevent information from reaching the server in the wrong format. We must always check from the backend and return a 400 status code otherwise.

However, you should be aware that a form goes through 4 states:

Form flow

  1. Rest: The user hasn't entered any information yet. The fields are empty, pristine, waiting to be filled in.
  2. Validation: Data is entered. The fields must meet certain guidelines. Otherwise, the user is shown how to correct it.
  3. Submission: The fields meet the requirements described. It's sent to the server or service (such as an API).
  4. Server response: The user is informed whether it arrived successfully or failed.

In this lesson we'll focus on validation with the aim of improving the user experience. Let's look at some commonly used examples.

If you'd like to submit the form asynchronously, you can take a look at the AJAX lesson.

Input or Textarea

Required

As simple as adding the required attribute to an input.

<form>
  <label for="name">Name: </label>
  <input type="text" name="name" id="name" required autocomplete="off">
  <button>Submit</button>
</form>

To customize the message, such as removing it once the field is filled in, we'll need to work with the input event (typing inside the field) and the invalid event (the field's validation fails), which we'll use to show the desired message with setCustomValidity().

const miInput = document.querySelector('input');

// Removes the validation as you type
miInput.addEventListener('input', () => {
    // Removes the message as you type
    miInput.setCustomValidity('');
    // Checks whether it should be validated
    miInput.checkValidity();
});

// Shows the validation message
miInput.addEventListener('invalid', () => {
    miInput.setCustomValidity('If it\'s not too much trouble... could you tell me your name?');
});

It's advisable to use autocomplete="off" to prevent browsers' autocomplete from covering up the validations.

Character limit

We include the pattern attribute on the input to validate, with a regular expression pattern.

<form>
    <label>
        <input type="text" id="marca" required pattern="^[a-zA-Z0-9]{4,}$" autocomplete="off">
    </label>
    <input type="submit" id="submit" value="Submit">
</form>

While the JavaScript would be the same as the previous example.

// Variables
const inputMarca = document.querySelector('#marca');
const mensajeErrorMarcaCorto = "Too short. Give me a name with 4 or more characters.";

// Events
inputMarca.addEventListener('input', () => {
    // Removes the message as you type
    inputMarca.setCustomValidity('');
    // Checks whether it should be validated
    inputMarca.checkValidity();
});

inputMarca.addEventListener('invalid', () => {
    inputMarca.setCustomValidity(mensajeErrorMarcaCorto);
});

Valid characters

In this case we'll check 2 things:

  • It must be filled in.
  • The allowed characters are alphanumeric, including spaces.

We include the pattern attribute on the input to validate, with a regular expression pattern.

<form>
    <label for="name">Name: </label>
    <input type="text" name="name" id="name" required pattern="^[a-zA-Z0-9 ]*$" autocomplete="off">
    <button>Submit</button>
</form>
const miInput = document.querySelector('input');

// Removes the validation as you type
miInput.addEventListener('input', () => {
    // Removes the message as you type
    miInput.setCustomValidity('');
    // Checks whether it should be validated
    miInput.checkValidity();
});

// Shows the validation message
miInput.addEventListener('invalid', () => {
    if(miInput.value === '') {
        // Empty field
        miInput.setCustomValidity('If it\'s not too much trouble... could you tell me your name?');
    } else {
        // Pattern
        miInput.setCustomValidity('You must enter alphanumeric characters');
    }
});

Removing unnecessary spaces

To prevent fields from being sent with spaces at the beginning and end of a text, we can use the trim() function.

trim('   Time After time   ')

//'Time After time'

If we use it with the event for when focus is lost (blur), we can fix it quickly.

// Removes the spaces at the beginning and end
miInput.addEventListener('blur', () => {
    miInput.value = miInput.value.trim();
});

Email

We have an input of type email available, which will save us from using complex patterns.

<input type="email" required>
<form>
    <label for="email">Email: </label>
    <input type="email" name="email" id="email" required autocomplete="off">
    <button>Submit</button>
</form>
const miInput = document.querySelector('input');

// Removes the validation as you type
miInput.addEventListener('input', () => {
    // Removes the message as you type
    miInput.setCustomValidity('');
    // Checks whether it should be validated
    miInput.checkValidity();
});

// Shows the validation message
miInput.addEventListener('invalid', () => {
    miInput.setCustomValidity('This doesn\'t look like an email to me');
});

Numbers

This time we use the number type.

<input type="number" required>
<form>
    <label for="email">Email: </label>
    <input type="email" name="email" id="email" required autocomplete="off">
    <button>Submit</button>
</form>
const miInput = document.querySelector('input');

// Removes the validation as you type
miInput.addEventListener('input', () => {
    // Removes the message as you type
    miInput.setCustomValidity('');
    // Checks whether it should be validated
    miInput.checkValidity();
});

// Shows the validation message
miInput.addEventListener('invalid', () => {
    miInput.setCustomValidity('This is not a number');
});

Checkbox

It's quite common to force a user to accept a checkbox before letting them continue (call it an agreement or legal extortion). Whatever the case, it doesn't differ much from what we've seen before. We'll use the required attribute.

<form>
    <label>
        <input type="checkbox" required autocomplete="off"> I agree to sell my soul
    </label>
    <button>Submit</button>
</form>
const miInput = document.querySelector('input');

// Removes the validation as you type
miInput.addEventListener('input', () => {
    // Removes the message as you type
    miInput.setCustomValidity('');
    // Checks whether it should be validated
    miInput.checkValidity();
});

// Shows the validation message
miInput.addEventListener('invalid', () => {
    miInput.setCustomValidity('If you don\'t accept, you can\'t continue');
});

File

Let's look at a classic example: validating that a field is an image.

<input type="file" id="foto">

In the following example we'll validate that the file field, or file type, meets the following requirements:

  • Extensions: jpg, jpeg and png.
  • Doesn't exceed 2 Mb.
const oneMegaBytesInBytes = 10 ** 6;
const pesoLimite = oneMegaBytesInBytes * 2; // 2 megabytes
const extensionesPermitidas =  ['jpg','jpeg','png'];
const miInput = document.querySelector('#foto');

function validarImagen () {
    // Resets the message
    miInput.setCustomValidity('');

    // We destructure to get the name and the size
    const { name: archivoNombre, size: archivoPeso } = this.files[0];


    // We get the extension
    const fileExtension = archivoNombre.split(".").pop();

    // We check if it has a valid extension
    if (!extensionesPermitidas.includes(fileExtension)){
        miInput.setCustomValidity('Invalid format, only jpg and png are allowed');
    }

    // We check the size
    if(archivoPeso > pesoLimite) {
        miInput.setCustomValidity('Too large');
    }
}

miInput.addEventListener("input", validarImagen);

Submission

If all the fields are correct, it will be submitted, however if there's a need to submit it without pressing the button, we'll use the DOM form's submit() function.

<form id="mi-formulario">
    <input type="text" value="">
</form>
const miFormulario = document.querySelector('#mi-formulario');

miFormulario.submit();

Example without using native validations

Starting from code where all its fields are of type text, we'll do validations using only JavaScript's tools. It could be called the "old" way.

<form id="formulario">
    <p>
        <label>
            Name
            <input type="text" id="nombre" value="" autocomplete="off">
        </label>
    </p>
    <p>
        <label>
            Age
            <input id="edad" type="text" value="" autocomplete="off">
        </label>
    </p>
    <p>
        <label>
            Message
            <textarea cols="30" id="mensaje" name="" rows="10" autocomplete="off"></textarea>
        </label>
    </p>
    <p>
        <input type="submit" id="enviar" value="Submit"/>
    </p>
</form>
<!-- Error messages -->
<ul id="errores"></ul>

We're going to validate its fields without relying on the automatic validations that HTML provides us.

// Variables
const formulario = document.querySelector('#formulario');
const nombre = document.querySelector('#nombre');
const edad = document.querySelector('#edad');
const mensaje = document.querySelector('#mensaje');
const enviar = document.querySelector('#enviar');
const errores = document.querySelector('#errores');
let mensajesErrores = [];

// Functions
function validar (evento) {
    // Prevent the form from being submitted
    evento.preventDefault();

    // Empties the mensajesErrores array before filling it again
    mensajesErrores = [];

    // VALIDATIONS

    // Name is required

    if (nombre.value.trim().length === 0) {
        mensajesErrores = mensajesErrores.concat('Name is a required field');
    }

    // Name valid characters

    if (!/^[a-zA-Z0-9]*$/.exec(nombre.value.trim())) {
        mensajesErrores = mensajesErrores.concat('Name does not have valid characters');
    }

    // Age must be a number

    if (isNaN(edad.value.trim())) {
        mensajesErrores = mensajesErrores.concat('Age must be a number');
    }

    // We check that the message has a minimum of 10 characters

    if (mensaje.value.trim().length < 10) {
        mensajesErrores = mensajesErrores.concat('Message too short');
    }

    // SEND OR SHOW MESSAGES
    if (mensajesErrores.length === 0) {
        // We submit the form if there are no errors
        formulario.submit();
    } else {
        // I show the errors
        errores.textContent = '';
        mensajesErrores.forEach(function (mensaje) {
            const miLi = document.createElement('li');
            miLi.textContent = mensaje;
            errores.appendChild(miLi);
        });
    }
}

// Events
formulario.addEventListener('submit', validar);

// Start
Activity 1

Create a form to enter a secret place called: FBI (Federation of Irritable Shorties).

Add the appropriate fields and validate them.

  • Name: Required, text only.
  • Password: Required.
Activity 2

Create a form to publish a second-hand product.

Add the appropriate fields and validate them.

  • Name: Required, text only.
  • Email: Required, correct format.
  • Price: Number only.
  • Accept policies: Required.

Nightmare level 👹

Add another field: Image. It must be an image that doesn't exceed 5Mb.

This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.

Desafíos de programación atemporales y multiparadigmáticos

Desafíos de programación atemporales y multiparadigmáticos

Te encuentras ante un librillo de actividades, divididas en 2 niveles de dificultad. Te enfrentarás a los casos más comunes que te puedes encontrar en pruebas técnicas o aprender conceptos elementales de programación.

Buy the book

Will you buy me a coffee?

This is how I keep writing without ads or paywalls.

Comments

There are no comments yet.