Gestire l'inserimento dei codici OTP sul frontend con JavaScript

Quando dobbiamo inserire un codice OTP per effettuare l'autenticazione online solitamente ci troviamo di fronte ad uno o più campi di input dove inserire il codice ricevuto. In questo articolo vedremo come gestire l'interazione utente con questi campi in JavaScript.

Il problema dei campi multipli

La feature che sicuramente ci impegna di più lato frontend è quella in cui ci sono campi multipli, ossia quando il codice OTP viene inserito un carattere alla volta (dunque un carattere per campo) oppure incollato nella sua interezza e automaticamente ripartito su tutti i campi.

Usare un campo singolo è senza ombra di dubbio la soluzione più pratica e veloce, ma come sappiamo i requisiti di un progetto spesso privilegiano soluzioni più elaborate.

Quello che dobbiamo fare è sostanzialmente questo:

  1. Gestire gli eventi focus e paste su ciascun campo abilitando l'autofocus sul campo successivo.
  2. Sull'evento paste verificare se il valore del campo ha una lunghezza pari alla lunghezza complessiva del codice OTP. In questo caso, dobbiamo separare il valore nei singoli caratteri e popolare automaticamente tutti i campi.
  3. Validare sempre il formato dell'input utente.

La struttura HTML

Partiamo da questa struttura HTML di base.

<form action="#" class="otp-code-form">
            <div class="form-group">
                <input name="code[]" type="text" class="form-input">
                <input name="code[]" type="text" class="form-input">
                <input name="code[]" type="text" class="form-input">
                <input name="code[]" type="text" class="form-input">
                <input name="code[]" type="text" class="form-input">
                <input name="code[]" type="text" class="form-input">
            </div>        
</form>

In questo esempio il codice OTP è composto da 6 cifre, quindi abbiamo 6 campi di input. L'attributo name impostato con un riferimento ad un array non ha effetto sul codice JavaScript ma viene qui impostato come suggerimento per l'implementazione lato server.

Gli elementi input sono tutti figli dello stesso genitore nele DOM. Questo è importante, poiché li dobbiamo considerare come inseriti in un array, esattamente come le cifre del codice (JavaScript gestisce le stringhe come array di caratteri).

Il gestore di eventi

Usiamo un'unica funzione come gestore di eventi che accetta come unico parametro un riferimento all'elemento input nel DOM.

Tramite questo riferimento, abbiamo accesso alla proprietà value che riceve il valore inserito dall'utente nel campo.

Dobbiamo fare due verifiche su questo valore:

  1. È un singolo numero? Allora se esiste un elemento input che segue quello corrente nel DOM, quest'ultimo deve ricevere il focus.
  2. È composto di 6 cifre? Allora dobbiamo trasformarlo in un array di caratteri ed effettuare un ciclo su di esso andando a inserire ciascun carattere come valore del campo corrispondente.
function handleValue(input = null) {
    if (!input) {
      return false;
    }
    const value = input.value.trim();
    if (/^\d$/.test(value)) {
      const next = input.nextElementSibling;
      if (next) {
        next.focus();
      }
      return true;
    }
    if (/^\d{6}$/.test(value)) {
      const parts = value.split('');
      const inputs = input.parentNode.querySelectorAll('input');
      for (let i = 0; i < parts.length; i++) {
        inputs[i].value = parts[i];
      }
      return true;
    }
  }

Quindi andiamo a legare questa funzione agli eventi associati ai campi di input.

function handleEvents(inputSelector = '') {
    const inputs = document.querySelectorAll(inputSelector);

    for (const input of inputs) {
      input.addEventListener(
        'input',
        function () {
          handleValue(this);
        },
        false,
      );
      input.addEventListener(
        'paste',
        function () {
          handleValue(this);
        },
        false,
      );
    }
  }

Inizializzazione del codice

Useremo l'evento DOMContentLoaded dell'elemento document per inizializzare il nostro codice quando il DOM è stato interamente costruito.

document.addEventListener('DOMContentLoaded', () => {
    handleEvents('.form-input');
  });

Demo

JavaScript OTP Code

Conclusione

I codici OTP pongono nuove sfide a chi si occupa di sviluppo frontend soprattutto se si pensa che una user experience fluida e semplice spesso incide parecchio sulla buona opinione che gli utenti hanno su siti e applicativi web.