Controlli del modulo più efficaci

Con un nuovo evento e API di elementi personalizzati, partecipare ai moduli è diventato molto più facile.

Arthur Evans

Molti sviluppatori creano controlli dei moduli personalizzati per fornire controlli che non sono integrati nel browser o per personalizzare aspetto e design che vanno oltre le possibilità offerte dai controlli integrati dei moduli.

Tuttavia, può essere difficile replicare le funzionalità dei controlli integrati del modulo HTML. Considera alcune delle funzionalità che un elemento <input> ottiene automaticamente quando lo aggiungi a un modulo:

  • L'input viene aggiunto automaticamente all'elenco dei controlli del modulo.
  • Il valore dell'input viene inviato automaticamente con il modulo.
  • L'input partecipa alla convalida del modulo. Puoi definire lo stile dell'input utilizzando le pseudoclassi :valid e :invalid.
  • L'input viene avvisato quando il modulo viene reimpostato, quando il modulo viene ricaricato o quando il browser prova a compilare automaticamente le voci dei moduli.

I controlli dei moduli personalizzati in genere dispongono di alcune di queste funzionalità. Gli sviluppatori possono aggirare alcune limitazioni di JavaScript, ad esempio aggiungendo un elemento <input> nascosto a un modulo per partecipare all'invio. Tuttavia, non è possibile replicare altre funzionalità solo in JavaScript.

Due nuove funzionalità web semplificano la creazione di controlli dei moduli personalizzati e rimuovono i limiti dei controlli personalizzati attuali:

  • L'evento formdata consente a un oggetto JavaScript arbitrario di partecipare all'invio del modulo, quindi puoi aggiungere dati del modulo senza utilizzare un <input> nascosto.
  • L'API degli elementi personalizzati associati ai moduli consente agli elementi personalizzati di agire in modo più simile ai controlli integrati del modulo.

Queste due funzionalità possono essere utilizzate per creare nuovi tipi di controlli più efficaci.

API basata sugli eventi

L'evento formdata è un'API di basso livello che consente a qualsiasi codice JavaScript di partecipare all'invio del modulo. Il meccanismo funziona nel seguente modo:

  1. Aggiungi un listener di eventi formdata al modulo con cui vuoi interagire.
  2. Quando un utente fa clic sul pulsante Invia, il modulo attiva un evento formdata, che include un oggetto FormData che include tutti i dati inviati.
  3. Ogni listener formdata ha la possibilità di aggiungere o modificare i dati prima che il modulo venga inviato.

Di seguito è riportato un esempio di invio di un singolo valore in un listener di eventi formdata:

const form = document.querySelector('form');
// FormData event is sent on <form> submission, before transmission.
// The event has a formData property
form.addEventListener('formdata', ({formData}) => {
  // https://developer.mozilla.org/docs/Web/API/FormData
  formData.append('my-input', myInputValue);
});

Fai una prova usando il nostro esempio su Glitch. Eseguila su Chrome 77 o versioni successive per vedere l'API in azione.

Compatibilità del browser

Supporto dei browser

  • Chrome: 5.
  • Edge: 12.
  • Firefox: 4.
  • Safari: 5.

Origine

Elementi personalizzati associati ai moduli

Puoi utilizzare l'API basata sugli eventi con qualsiasi tipo di componente, ma ti consente solo di interagire con il processo di invio.

I controlli dei moduli standardizzati partecipano a molte parti del ciclo di vita del modulo oltre all'invio. Gli elementi personalizzati associati ai moduli mirano a colmare il divario tra widget personalizzati e controlli integrati. Gli elementi personalizzati associati al modulo corrispondono a molte delle funzionalità degli elementi del modulo standardizzati:

  • Quando inserisci un elemento personalizzato associato al modulo in un elemento <form>, questo viene associato automaticamente al modulo, come un controllo fornito dal browser.
  • L'elemento può essere etichettato utilizzando un elemento <label>.
  • L'elemento può impostare un valore che viene inviato automaticamente con il modulo.
  • L'elemento può impostare un flag che indica se ha o meno un input valido. Se i dati inseriti in uno dei controlli del modulo non sono validi, il modulo non può essere inviato.
  • L'elemento può fornire callback per varie parti del ciclo di vita del modulo, ad esempio quando il modulo viene disattivato o reimpostato allo stato predefinito.
  • L'elemento supporta le pseudoclassi CSS standard per i controlli del modulo, come :disabled e :invalid.

Sono tante funzionalità. In questo articolo verranno illustrati tutti i concetti fondamentali per integrare l'elemento personalizzato in un modulo.

Definizione di un elemento personalizzato associato al modulo

Per trasformare un elemento personalizzato in un elemento personalizzato associato al modulo sono necessari alcuni passaggi aggiuntivi:

  • Aggiungi una proprietà formAssociated statica alla classe dell'elemento personalizzato. Questo indica al browser di trattare l'elemento come un controllo del modulo.
  • Richiama il metodo attachInternals() sull'elemento per avere accesso a metodi e proprietà aggiuntivi per i controlli del modulo, come setFormValue() e setValidity().
  • Aggiungi le proprietà e i metodi comuni supportati dai controlli del modulo, come name, value e validity.

Ecco come questi elementi rientrano in una definizione di elemento personalizzato di base:

// Form-associated custom elements must be autonomous custom elements--
// meaning they must extend HTMLElement, not one of its subclasses.
class MyCounter extends HTMLElement {

  // Identify the element as a form-associated custom element
  static formAssociated = true;

  constructor() {
    super();
    // Get access to the internal form control APIs
    this.internals_ = this.attachInternals();
    // internal value for this control
    this.value_ = 0;
  }

  // Form controls usually expose a "value" property
  get value() { return this.value_; }
  set value(v) { this.value_ = v; }

  // The following properties and methods aren't strictly required,
  // but browser-level form controls provide them. Providing them helps
  // ensure consistency with browser-provided controls.
  get form() { return this.internals_.form; }
  get name() { return this.getAttribute('name'); }
  get type() { return this.localName; }
  get validity() {return this.internals_.validity; }
  get validationMessage() {return this.internals_.validationMessage; }
  get willValidate() {return this.internals_.willValidate; }

  checkValidity() { return this.internals_.checkValidity(); }
  reportValidity() {return this.internals_.reportValidity(); }

  
}
customElements.define('my-counter', MyCounter);

Una volta effettuata la registrazione, puoi utilizzare questo elemento ovunque utilizzerai un controllo modulo fornito dal browser:

<form>
  <label>Number of bunnies: <my-counter></my-counter></label>
  <button type="submit">Submit</button>
</form>

Impostazione di un valore

Il metodo attachInternals() restituisce un oggetto ElementInternals che fornisce l'accesso alle API di controllo modulo. Il più fondamentale è il metodo setFormValue(), che imposta il valore corrente del controllo.

Il metodo setFormValue() può assumere uno di tre tipi di valori:

  • Un valore stringa.
  • Un oggetto File.
  • Un oggetto FormData. Puoi utilizzare un oggetto FormData per trasmettere più valori (ad esempio, un controllo di immissione della carta di credito potrebbe trasmettere un numero di carta, una data di scadenza e un codice di verifica).

Per impostare un valore semplice:

this.internals_.setFormValue(this.value_);

Per impostare più valori, puoi procedere in questo modo:

// Use the control's name as the base name for submitted data
const n = this.getAttribute('name');
const entries = new FormData();
entries.append(n + '-first-name', this.firstName_);
entries.append(n + '-last-name', this.lastName_);
this.internals_.setFormValue(entries);

Convalida degli input

Il tuo controllo può anche partecipare alla convalida del modulo chiamando il setValidity() sull'oggetto internals.

// Assume this is called whenever the internal value is updated
onUpdateValue() {
  if (!this.matches(':disabled') && this.hasAttribute('required') &&
      this.value_ < 0) {
    this.internals_.setValidity({customError: true}, 'Value cannot be negative.');
  }
  else {
    this.internals_.setValidity({});
  }
  this.internals.setFormValue(this.value_);
}

Puoi definire lo stile di un elemento personalizzato associato a un modulo con :valid e :invalid pseudoclassi, proprio come un controllo modulo integrato.

Callback del ciclo di vita

Un'API degli elementi personalizzati associata ai moduli include una serie di callback aggiuntivi del ciclo di vita da collegare al ciclo di vita del modulo. I callback sono facoltativi: implementa un callback solo se l'elemento deve fare qualcosa in quel momento del ciclo di vita.

void formAssociatedCallback(form)

Richiamato quando il browser associa l'elemento a un elemento del modulo o annulla l'associazione dell'elemento da un elemento del modulo.

void formDisabledCallback(disabled)

Viene richiamato dopo la modifica dello stato disabled dell'elemento, perché l'attributo disabled di questo elemento è stato aggiunto o rimosso. o perché lo stato disabled è cambiato su un <fieldset> che è un predecessore di questo elemento. Il parametro disabled rappresenta il nuovo stato disattivato dell'elemento. Ad esempio, l'elemento potrebbe disabilitare elementi nel suo DOM shadow quando è disabilitato.

void formResetCallback()

Richiamato dopo la reimpostazione del modulo. L'elemento dovrebbe reimpostarsi su un qualche tipo di stato predefinito. Per gli elementi <input>, di solito comporta l'impostazione della proprietà value in modo che corrisponda all'attributo value impostato nel markup (o, nel caso di una casella di controllo, l'impostazione della proprietà checked in modo che corrisponda all'attributo checked.

void formStateRestoreCallback(state, mode)

Chiamata in una delle due circostanze seguenti:

  • Quando il browser ripristina lo stato dell'elemento (ad esempio, dopo una navigazione o al riavvio del browser). In questo caso, l'argomento mode è "restore".
  • Quando le funzionalità di assistenza all'inserimento del browser, come la compilazione automatica dei moduli, impostano un valore. In questo caso, l'argomento mode è "autocomplete".

Il tipo del primo argomento dipende da come è stato chiamato il metodo setFormValue(). Per maggiori dettagli, consulta Ripristinare lo stato del modulo.

Ripristino dello stato del modulo in corso...

In alcuni casi, ad esempio quando si torna a una pagina o si riavvia il browser, il browser potrebbe tentare di ripristinare il modulo allo stato in cui l'utente lo aveva lasciato.

Per un elemento personalizzato associato al modulo, lo stato ripristinato proviene dai valori che passi al metodo setFormValue(). Puoi chiamare il metodo con un singolo parametro di valore, come mostrato negli esempi precedenti, o con due parametri:

this.internals_.setFormValue(value, state);

value rappresenta il valore inviabile del controllo. Il parametro facoltativo state è una rappresentazione interna dello stato del controllo, che può includere dati che non vengono inviati al server. Il parametro state accetta gli stessi tipi del parametro value e può essere una stringa, un oggetto File o FormData.

Il parametro state è utile quando non è possibile ripristinare lo stato di un controllo in base al solo valore. Ad esempio, supponi di creare un selettore colori con più modalità: una tavolozza o una ruota dei colori RGB. Il valore inviabile sarebbe il colore selezionato in una forma canonica, come "#7fff00". Tuttavia, per ripristinare il controllo a uno stato specifico, devi anche sapere in quale modalità si trovava, quindi lo stato potrebbe essere simile a "palette/#7fff00".

this.internals_.setFormValue(this.value_,
    this.mode_ + '/' + this.value_);

Il codice dovrebbe ripristinare lo stato in base al valore dello stato archiviato.

formStateRestoreCallback(state, mode) {
  if (mode == 'restore') {
    // expects a state parameter in the form 'controlMode/value'
    [controlMode, value] = state.split('/');
    this.mode_ = controlMode;
    this.value_ = value;
  }
  // Chrome currently doesn't handle autofill for form-associated
  // custom elements. In the autofill case, you might need to handle
  // a raw value.
}

Nel caso di un controllo più semplice, ad esempio l'inserimento di un numero, è probabile che il valore sia sufficiente per ripristinare lo stato precedente del controllo. Se ometti state quando chiami setFormValue(), il valore viene passato a formStateRestoreCallback().

formStateRestoreCallback(state, mode) {
  // Simple case, restore the saved value
  this.value_ = state;
}

Esempio funzionante

L'esempio seguente riunisce molte delle funzionalità degli elementi personalizzati associati ai moduli. Eseguila su Chrome 77 o versioni successive per vedere l'API in azione.

Rilevamento delle caratteristiche

Puoi utilizzare il rilevamento delle funzionalità per determinare se l'evento formdata e gli elementi personalizzati associati al modulo sono disponibili. Al momento non sono stati rilasciati polyfill per nessuna delle due funzionalità. In entrambi i casi, puoi ricorrere all'aggiunta di un elemento di modulo nascosto per propagare il valore del controllo al modulo. Il polyfill di molte delle funzionalità più avanzate degli elementi personalizzati associati ai moduli sarà probabilmente difficile o impossibile.

if ('FormDataEvent' in window) {
  // formdata event is supported
}

if ('ElementInternals' in window &&
    'setFormValue' in window.ElementInternals.prototype) {
  // Form-associated custom elements are supported
}

Conclusione

L'evento formdata e gli elementi personalizzati associati al modulo forniscono nuovi strumenti per la creazione di controlli personalizzati per i moduli.

L'evento formdata non offre nuove funzionalità, ma offre un'interfaccia per aggiungere i dati del modulo alla procedura di invio, senza dover creare un elemento <input> nascosto.

L'API degli elementi personalizzati associata ai moduli offre un nuovo insieme di funzionalità per creare controlli del modulo personalizzati che funzionino come i controlli integrati del modulo.

Immagine hero di Oudom Pravat su Unsplash.