Con un nuovo evento e le API degli elementi personalizzati, partecipare ai moduli è diventato molto più semplice.
Molti sviluppatori creano controlli dei moduli personalizzati per fornire controlli non integrati nel browser o per personalizzare l'aspetto oltre a quanto sia possibile con i controlli dei moduli integrati.
Tuttavia, può essere difficile replicare le funzionalità dei controlli dei moduli HTML integrati. 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 applicare uno stile all'input utilizzando le pseudoclassi
:valid
e:invalid
. - L'input viene visualizzato quando il modulo viene reimpostato, ricaricato o quando il browser tenta di compilare automaticamente le voci del modulo.
I controlli dei moduli personalizzati in genere hanno poche di queste funzionalità. Gli sviluppatori possono aggirare alcune delle limitazioni di JavaScript, ad esempio aggiungendo 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 dei moduli personalizzati e rimuovono le limitazioni dei controlli personalizzati attuali:
- L'evento
formdata
consente a un oggetto JavaScript arbitrario di partecipare all'invio del modulo, in modo da poter aggiungere i dati del modulo senza utilizzare un<input>
nascosto. - L'API Elementi personalizzati associati al modulo consente agli elementi personalizzati di comportarsi più come i controlli dei moduli integrati.
Queste due funzionalità possono essere utilizzate per creare nuovi tipi di controlli più efficaci.
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:
- Aggiungi un gestore eventi
formdata
al modulo con cui vuoi interagire. - Quando un utente fa clic sul pulsante Invia, il modulo attiva un evento
formdata
, che include un oggettoFormData
contenente tutti i dati inviati. - Ogni ascoltatore di
formdata
ha la possibilità di aggiungere o modificare i dati prima dell'invio del modulo.
Ecco un esempio di invio di un singolo valore in un gestore 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);
});
Prova questa funzionalità utilizzando il nostro esempio su Glitch. Assicurati di eseguirlo su Chrome 77 o versioni successive per vedere l'API in azione.
Compatibilità del browser
Elementi personalizzati associati al modulo
Puoi utilizzare l'API basata su eventi con qualsiasi tipo di componente, ma ti consente di interagire solo con la procedura di invio.
I controlli dei moduli standardizzati partecipano a molte parti del ciclo di vita del modulo oltre all'invio. L'obiettivo degli elementi personalizzati associati ai moduli è colmare il divario tra i widget personalizzati e i controlli integrati. Gli elementi personalizzati associati al modulo corrispondono a molte delle funzionalità degli elementi di modulo standardizzati:
- Quando inserisci un elemento personalizzato associato al modulo all'interno di un
<form>
, viene associato automaticamente al modulo, ad esempio 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 l'input è valido o meno. Se uno dei controlli del modulo contiene un input non valido, 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 sullo stato predefinito.
- L'elemento supporta le pseudoclassi CSS standard per i controlli dei moduli, come
:disabled
e:invalid
.
Sono davvero tante funzionalità. Questo articolo non li illustra tutti, ma descrive le nozioni di base necessarie per integrare l'elemento personalizzato con 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. - Chiama il metodo
attachInternals()
sull'elemento per accedere a metodi e proprietà aggiuntivi per i controlli dei moduli, comesetFormValue()
esetValidity()
. - Aggiungi le proprietà e i metodi comuni supportati dai controlli dei moduli, come
name
,value
evalidity
.
Ecco come questi elementi si inseriscono 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 registrato, puoi utilizzare questo elemento ovunque utilizzeresti un controllo del 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ù semplice è il metodo setFormValue()
, che imposta il valore corrente del controllo.
Il metodo setFormValue()
può accettare uno dei tre tipi di valori:
- Un valore di stringa.
- Un oggetto
File
. - Un oggetto
FormData
. Puoi utilizzare un oggettoFormData
per trasmettere più valori (ad esempio, un controllo di immissione della carta di credito potrebbe trasmettere un numero di carta, la data di scadenza e il codice di verifica).
Per impostare un valore semplice:
this.internals_.setFormValue(this.value_);
Per impostare più valori, puoi procedere nel seguente 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 dell'input
Il controllo può anche partecipare alla convalida del modulo chiamando il setValidity()
metodo 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 applicare uno stile a un elemento personalizzato associato a un modulo con le pseudoclassi :valid
e :invalid
, proprio come per un controllo del modulo integrato.
Callback del ciclo di vita
Un'API elemento personalizzato associato al modulo include un insieme di callback del ciclo di vita aggiuntivi da collegare al ciclo di vita del modulo. I callback sono facoltativi: implementa un callback solo se l'elemento deve fare qualcosa in quel punto del ciclo di vita.
void formAssociatedCallback(form)
Viene chiamato quando il browser associa l'elemento a un elemento del modulo o lo disaccoppia da un elemento del modulo.
void formDisabledCallback(disabled)
Viene chiamato dopo la modifica dello stato disabled
dell'elemento, perché l'attributo disabled
di questo elemento è stato aggiunto o rimosso oppure perché lo stato disabled
è cambiato in un <fieldset>
che è un antenato di questo elemento. Il parametro disabled
rappresenta il nuovo stato disattivato dell'elemento. Ad esempio, l'elemento potrebbe disattivare gli elementi nel relativo DOM ombra quando è disattivato.
void formResetCallback()
Viene chiamato dopo il ripristino del modulo. L'elemento dovrebbe ripristinarsi in un qualche tipo di stato predefinito. Per gli elementi <input>
, in genere è 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)
Viene chiamato in una delle due seguenti circostanze:
- Quando il browser ripristina lo stato dell'elemento (ad esempio dopo una navigazione o quando il browser si riavvia). 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 dal modo in cui è stato chiamato il metodo setFormValue()
. Per maggiori dettagli, vedi Ripristinare lo stato del modulo.
Ripristino dello stato del modulo
In alcune circostanze, ad esempio quando si torna a una pagina o si riavvia il browser, il browser potrebbe tentare di ripristinare il modulo nello stato in cui è stato lasciato dall'utente.
Per un elemento personalizzato associato a un 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
: può essere una stringa, un oggetto File
o FormData
.
Il parametro state
è utile quando non riesci a ripristinare lo stato di un controllo in base al solo valore. Ad esempio, supponiamo di creare un selettore di colori con più modalità: una tavolozza o una ruota dei colori RGB. Il valore inviabile sarà il colore selezionato in una forma canonica, ad esempio "#7fff00"
. Tuttavia, per ripristinare il controllo in uno stato specifico, devi anche sapere in quale modalità si trovava, quindi lo stato potrebbe essere "palette/#7fff00"
.
this.internals_.setFormValue(this.value_,
this.mode_ + '/' + this.value_);
Il codice deve ripristinare il proprio stato in base al valore dello stato memorizzato.
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 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;
}
Un esempio funzionante
L'esempio seguente riunisce molte delle funzionalità degli elementi personalizzati associati al modulo. Assicurati di eseguirlo su Chrome 77 o versioni successive per vedere l'API in azione.
Rilevamento di funzionalità
Puoi utilizzare il rilevamento delle funzionalità per determinare se sono disponibili l'evento formdata
e gli elementi personalizzati associati al modulo. Al momento non sono stati rilasciati 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 ai moduli potrebbero essere difficili o impossibili da eseguire 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 la creazione di controlli dei moduli personalizzati.
L'evento formdata
non offre nuove funzionalità, ma fornisce un'interfaccia per aggiungere i dati del modulo alla procedura di invio, senza dover creare un elemento <input>
nascosto.
L'API degli elementi personalizzati associati al modulo fornisce un nuovo insieme di funzionalità per creare controlli dei moduli personalizzati che funzionano come i controlli dei moduli integrati.
Immagine hero di Oudom Pravat su Unsplash.