Controlli del modulo più efficaci

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

Arthur Evans

Molti sviluppatori creano controlli per i moduli personalizzati, sia per fornire controlli che non sono integrati nel browser, sia per personalizzare l'aspetto e il design oltre il possibile con i controlli dei moduli integrati.

Tuttavia, può essere difficile replicare le funzionalità dei controlli integrati dei moduli HTML. Considera alcune delle funzionalità che un elemento <input> riceve 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 viene ricaricato o quando il browser prova a compilare automaticamente le voci del modulo.

I controlli dei moduli personalizzati hanno in genere poche di queste funzionalità. Gli sviluppatori possono aggirare alcune delle limitazioni di JavaScript, ad esempio l'aggiunta di un <input> nascosto a un modulo per partecipare all'invio del modulo. Tuttavia, altre funzionalità non possono essere replicate solo in JavaScript.

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

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

Queste due funzionalità possono essere utilizzate per creare nuovi tipi di controlli che funzionano meglio.

API basata su 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 contenente tutti i dati inviati.
  3. Ogni listener formdata ha la possibilità di aggiungere o modificare i dati prima dell'invio del modulo.

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. Assicurati di eseguirla su Chrome 77 o versioni successive per vedere l'API in azione.

Compatibilità del browser

Supporto dei browser

  • 5
  • 12
  • 4
  • 5

Fonte

Elementi personalizzati associati al modulo

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

I controlli standardizzati dei moduli, oltre all'invio, partecipano in molte parti del ciclo di vita del modulo. 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 dei moduli standardizzati:

  • Quando inserisci un elemento personalizzato associato a un modulo in un elemento <form>, questo viene automaticamente associato 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 un input valido. Se uno dei controlli del modulo presenta dati non 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 viene ripristinato lo stato predefinito.
  • L'elemento supporta le pseudoclassi CSS standard per i controlli del modulo, come :disabled e :invalid.

Sono tantissime funzionalità. Questo articolo non li tratterà tutti, ma descriverà gli aspetti di base necessari per integrare l'elemento personalizzato in un modulo.

Definizione di un elemento personalizzato associato a un modulo

Per trasformare un elemento personalizzato in un elemento personalizzato associato a un 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 modulo.
  • Richiama il metodo attachInternals() sull'elemento per ottenere l'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 si inseriscono in una definizione di base di elementi personalizzati:

// 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 registrato, puoi utilizzare questo elemento ovunque useresti 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 dei moduli. Il più semplice è il metodo setFormValue(), che imposta il valore corrente del controllo.

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

  • Un valore stringa.
  • Un oggetto File.
  • Un oggetto FormData. Puoi utilizzare un oggetto FormData per trasmettere più valori (ad esempio, un controllo dell'inserimento di una carta di credito potrebbe trasmettere il numero di una carta, la 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 controllo può anche partecipare alla convalida del modulo chiamando il metodo 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 un elemento personalizzato associato a un modulo con le pseudoclassi :valid e :invalid, proprio come un controllo modulo integrato.

Callback del ciclo di vita

Un'API per elementi personalizzati associati a un modulo include un insieme 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 dissocia l'elemento da un elemento del modulo.

void formDisabledCallback(disabled)

Richiamato dopo la modifica dello stato disabled dell'elemento, perché l'attributo disabled dell'elemento è stato aggiunto o rimosso oppure perché lo stato disabled è cambiato in un <fieldset> che è un predecessore dell'elemento. Il parametro disabled rappresenta il nuovo stato disattivato dell'elemento. Ad esempio, l'elemento può disattivare gli elementi nel DOM shadow quando viene disattivato.

void formResetCallback()

Chiamata dopo la reimpostazione del modulo. L'elemento dovrebbe reimpostarsi su uno stato predefinito. Per gli elementi <input>, di solito è necessario impostare la proprietà value in modo che corrisponda all'attributo value impostato nel markup (o, nel caso di una casella di controllo, impostare la proprietà checked in modo che corrisponda all'attributo checked).

void formStateRestoreCallback(state, mode)

Chiamata in una delle due circostanze:

  • 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, vedi Ripristinare lo stato del modulo.

Ripristino dello stato del modulo

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

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

this.internals_.setFormValue(value, state);

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

Il parametro state è utile quando non puoi 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 della tabella inviabile corrisponde al colore selezionato in una forma canonica, come "#7fff00". Tuttavia, per ripristinare il controllo a uno stato specifico, devi sapere anche in quale modalità si trovava, quindi lo state potrebbe essere "palette/#7fff00".

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

Il codice deve 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 un input numerico), il valore probabilmente è sufficiente per ripristinare il controllo allo stato precedente. Se ometti state durante la chiamata a setFormValue(), il valore viene passato a formStateRestoreCallback().

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

Esempio pratico

L'esempio seguente riunisce molte delle funzionalità degli elementi personalizzati associati al modulo. Assicurati di eseguirla su Chrome 77 o versioni successive per vedere l'API in azione.

Rilevamento delle funzionalità

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 disponibili polyfill per nessuna delle due funzionalità. In entrambi i casi, puoi ricorrere all'aggiunta di un elemento del modulo nascosto per propagare il valore del controllo al modulo. Molte delle funzionalità più avanzate degli elementi personalizzati associati al modulo saranno probabilmente difficili o impossibili da realizzare con il polyfill.

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 creare controlli per i moduli personalizzati.

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

L'API per gli elementi personalizzati associati al modulo offre un nuovo insieme di funzionalità per creare controlli per i moduli personalizzati che funzionano come controlli dei moduli integrati.

Immagine hero di Oudom Pravat su Unsplash.