Data di pubblicazione: 8 agosto 2019
Molti sviluppatori creano controlli di modulo personalizzati, sia per fornire controlli non integrati nel browser, sia per personalizzare l'aspetto oltre quanto possibile con i controlli di modulo integrati.
Tuttavia, può essere difficile replicare le funzionalità dei controlli dei moduli HTML integrati. Prendiamo in considerazione 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 pseudo-classi
:valide:invalid. - L'input viene notificato 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 limitazioni di JavaScript, ad esempio aggiungendo un <input> nascosto a un modulo per partecipare all'invio del modulo. Ma altre funzionalità non possono essere
duplicate solo in JavaScript.
Due funzionalità web semplificano la creazione di controlli personalizzati per i moduli e rimuovono le limitazioni dei controlli personalizzati:
- L'evento
formdataconsente 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 Custom Elements associati al modulo consente agli elementi personalizzati di comportarsi più come controlli del modulo 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.
- Aggiungi un listener di eventi
formdataal modulo con cui vuoi interagire. - Quando un utente fa clic su Invia, il modulo attiva un evento
formdata, che include un oggettoFormDatache contiene tutti i dati inviati. - Ogni listener
formdataha la possibilità di aggiungere o modificare i dati prima dell'invio del modulo.
Ecco un esempio di invio di un singolo valore in un listener 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);
});
Elementi personalizzati associati al modulo
Puoi utilizzare l'API basata sugli eventi con qualsiasi tipo di componente, ma ti consente solo di interagire con la procedura di invio.
I controlli dei moduli standardizzati partecipano a 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 del modulo standardizzati:
- Quando inserisci un elemento personalizzato associato a un modulo all'interno di un
<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 l'input è valido o meno. Se uno dei controlli del modulo ha 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 è disattivato o ripristinato allo stato predefinito.
- L'elemento supporta le pseudoclassi CSS standard per i controlli dei moduli, ad esempio
:disablede:invalid.
Questo documento non copre tutto, ma descrive le nozioni di base necessarie per integrare l'elemento personalizzato con un modulo.
Definisci un elemento personalizzato associato al modulo
Per trasformare un elemento personalizzato in un elemento personalizzato associato a un modulo sono necessari alcuni passaggi aggiuntivi:
- Aggiungi una proprietà statica
formAssociatedalla 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 del modulo, comesetFormValue()esetValidity(). - Aggiungi le proprietà e i metodi comuni supportati dai controlli dei moduli, come
name,valueevalidity.
Ecco come si inseriscono questi elementi in una definizione di elemento personalizzato di base:
// Form-associated custom elements must be autonomous custom elements.
// 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 modulo fornito dal browser:
<form>
<label>Number of bunnies: <my-counter></my-counter></label>
<button type="submit">Submit</button>
</form>
Impostare 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 stringa.
- Un oggetto
File. - Un oggetto
FormData. Puoi utilizzare un oggettoFormDataper passare più valori. Ad esempio, un controllo di inserimento di una carta di credito potrebbe trasmettere un numero di carta, una data di scadenza e un codice di verifica.
Per impostare un valore:
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 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 applicare uno stile a 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 di elementi personalizzati associati al modulo include un set di callback del ciclo di vita aggiuntivi per collegarsi al ciclo di vita del modulo. I callback sono facoltativi: implementa un callback solo se l'elemento deve eseguire un'azione in quel punto del ciclo di vita.
void formAssociatedCallback(form)
Chiamato quando il browser associa l'elemento a un elemento del modulo o lo dissocia da un elemento del modulo.
void formDisabledCallback(disabled)
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 elemento principale di questo elemento.
Ad esempio, l'elemento potrebbe disattivare gli elementi nel relativo shadow DOM quando è disattivato.
void formResetCallback()
Chiamato dopo il ripristino del modulo. L'elemento dovrebbe ripristinarsi a una sorta di stato predefinito. Per gli elementi <input>, in genere si tratta di impostare la proprietà value
in modo che corrisponda all'attributo value impostato nel markup. Con una casella di controllo, questo è
correlato all'impostazione della proprietà checked in modo che corrisponda all'attributo checked.
void formStateRestoreCallback(state, mode)
Chiamato in una delle due circostanze:
- Quando il browser ripristina lo stato dell'elemento, ad esempio dopo la navigazione o quando il browser si riavvia. L'argomento
modeè"restore". - Quando le funzionalità di inserimento dati del browser, come la compilazione automatica dei moduli, impostano un valore. L'argomento
modeè"autocomplete".
Il tipo del primo argomento dipende da come è stato chiamato il metodo setFormValue().
Ripristina lo 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 allo stato in cui l'utente lo aveva lasciato.
Per un elemento personalizzato associato al modulo, lo stato ripristinato deriva dai valori passati 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: una stringa, un oggetto File o FormData.
Il parametro state è utile quando non è possibile ripristinare lo stato di un controllo basandosi solo sul valore. Supponiamo, ad esempio, di creare un selettore di colori con più modalità: una tavolozza o una ruota dei colori RGB. Il valore inviabile è
il colore selezionato in forma canonica, ad esempio "#7fff00". Per ripristinare il controllo a uno stato specifico, dovresti 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 dovrà ripristinare il suo stato in base al valore di 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 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 si omette state quando si chiama setFormValue(), il valore viene passato a formStateRestoreCallback().
formStateRestoreCallback(state, mode) {
// Simple case, restore the saved value
this.value_ = state;
}
Rilevamento delle caratteristiche
È possibile utilizzare il rilevamento delle funzionalità per determinare se l'evento formdata e gli elementi personalizzati associati al modulo sono disponibili. Non sono stati rilasciati polyfill per nessuna delle due funzionalità. In entrambi i casi, è possibile ricorrere all'aggiunta di un elemento nascosto del modulo per propagare il valore del controllo al modulo.
Molte delle funzionalità più avanzate degli elementi personalizzati associati al modulo sono probabilmente difficili o impossibili da polyfill.
if ('FormDataEvent' in window) {
// formdata event is supported
}
if ('ElementInternals' in window &&
'setFormValue' in window.ElementInternals.prototype) {
// Form-associated custom elements are supported
}
L'evento formdata fornisce un'interfaccia per aggiungere i dati del modulo al processo di invio, senza dover creare un elemento <input> nascosto. Con l'API degli elementi personalizzati associati al modulo, è possibile fornire un nuovo set di funzionalità per i controlli dei moduli personalizzati che funzionano come controlli dei moduli integrati.