有了新事件和自訂元素 API,參與表單的過程就變得簡單許多。
許多開發人員會建立自訂表單控制項,以提供瀏覽器未內建的控制項,或自訂外觀和外觀,超越內建表單控制項的功能。
不過,要複製內建 HTML 表單控制項的功能可能很困難。將 <input>
元素新增至表單時,可以參考下列幾項功能:
- 系統會自動將輸入內容加入表單的控制項清單。
- 輸入內容的值會自動隨表單提交。
- 輸入內容會進行表單驗證。您可以使用
:valid
和:invalid
虛擬類別為輸入項目設定樣式。 - 在重設表單、重新載入表單,或瀏覽器嘗試自動填入表單項目時,系統會通知輸入內容。
自訂表單控制項通常只提供少數上述功能。開發人員可以解決部分 JavaScript 限制,例如在表單提交後在表單中加入隱藏的 <input>
。但其他功能無法單靠 JavaScript 重現。
我們推出兩項新的網頁功能,讓您更輕鬆地建構自訂表單控制項,並移除目前自訂控制項的限制:
formdata
事件可讓任意 JavaScript 物件參與表單提交作業,因此您不必使用隱藏的<input>
即可新增表單資料。- 表單相關自訂元素 API 可讓自訂元素更像內建的表單控制項。
這兩項功能可用於建立更有效的新控制項類型。
事件型 API
formdata
事件是低階 API,可讓任何 JavaScript 程式碼參與表單提交作業。機制運作方式如下:
- 您可以將
formdata
事件監聽器新增至要互動的表單。 - 使用者按下提交按鈕時,表單會觸發
formdata
事件,其中包含一個FormData
物件,可保留所有提交的資料。 - 每個
formdata
事件監聽器都有機會在提交表單前加入或修改資料。
以下是 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);
});
請試試 Glitch 上的範例。請務必在 Chrome 77 以上版本中執行,才能查看 API 運作情形。
瀏覽器相容性
與表單相關聯的自訂元素
事件式 API 可與任何類型的元件搭配使用,但該 API 只能用來與提交程序互動。
除了提交作業之外,標準化表單控制項也會參與表單生命週期的許多部分。表單相關自訂元素旨在彌平自訂小工具與內建控制項之間的隔閡。與表單相關聯的自訂元素與許多標準化表單元素的多項功能相符:
- 將表單相關聯的自訂元素放入
<form>
中時,系統會自動將該元素與表單建立關聯,例如瀏覽器提供的控制項。 - 您可以使用
<label>
元素為元素加上標籤。 - 元素可設定值,並自動透過表單提交。
- 元素可以設定旗標,指出其是否具備有效的輸入值。如果其中一個表單控制項有無效的輸入內容,就無法提交表單。
- 元素可針對表單生命週期的不同部分提供回呼,例如表單停用或重設為預設狀態時。
- 這個元素支援表單控制項的標準 CSS 擬似類別,例如
:disabled
和:invalid
。
這可說是眾多功能!本文不會介紹所有這些元素,但會說明將自訂元素與表單整合所需的基本知識。
定義與表單相關聯的自訂元素
如要將自訂元素轉換為與表單相關聯的自訂元素,您必須採取幾個額外步驟:
- 在自訂元素類別中新增靜態
formAssociated
屬性。這會告訴瀏覽器將元素視為表單控制項。 - 對元素呼叫
attachInternals()
方法,即可存取表單控制項的其他方法和屬性,例如setFormValue()
和setValidity()
。 - 新增表單控制項支援的常見屬性和方法,例如
name
、value
和validity
。
這些項目如何融入基本自訂元素定義:
// 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);
註冊完成後,無論您要使用瀏覽器提供的表單控制項,都可以使用這個元素:
<form>
<label>Number of bunnies: <my-counter></my-counter></label>
<button type="submit">Submit</button>
</form>
設定值
attachInternals()
方法會傳回 ElementInternals
物件,提供表單控制項 API 的存取權。其中最基本的是 setFormValue()
方法,可設定控制項的目前值。
setFormValue()
方法可以採用下列三種值的其中一種:
如要設定簡易值:
this.internals_.setFormValue(this.value_);
如要設定多個值,您可以執行以下操作:
// 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);
輸入驗證
控制項也可以呼叫內部物件上的 setValidity()
方法,參與表單驗證。
// 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_);
}
您可以使用 :valid
和 :invalid
擬似類別為表單相關聯的自訂元素設定樣式,就像內建的表單控制項一樣。
生命週期回呼
與表單相關聯的自訂元素 API 包含一組額外的生命週期回呼,可與表單生命週期連結。回呼是選用項目:只有在元素需要在生命週期的該點執行特定作業時,才會實作回呼。
void formAssociatedCallback(form)
當瀏覽器將元素與表單元素建立關聯,或取消與表單元素間的關聯時,會呼叫此方法。
void formDisabledCallback(disabled)
在元素的 disabled
狀態變更之後呼叫,原因可能是這個元素的 disabled
屬性新增或移除了,或是在 <fieldset>
為此元素的祖系變更。disabled
disabled
參數代表元素的新停用狀態。舉例來說,元素可能會在停用時停用其陰影 DOM 中的元素。
void formResetCallback()
在表單重設後呼叫。元素應將自身重設為某種預設狀態。對於 <input>
元素,這通常涉及將 value
屬性設為與標記中設定的 value
屬性相符 (或在核取方塊的情況下,將 checked
屬性設為與 checked
屬性相符)。
void formStateRestoreCallback(state, mode)
在下列兩種情況下呼叫:
- 當瀏覽器還原元素狀態時 (例如導覽後或瀏覽器重新啟動時)。在本例中,
mode
引數為"restore"
。 - 當瀏覽器的輸入輔助功能 (例如表單自動填入功能) 設定值時。在本例中,
mode
引數為"autocomplete"
。
第一個引數的類型取決於 setFormValue()
方法的呼叫方式。詳情請參閱「還原表單狀態」。
還原表單狀態
在某些情況下,例如返回網頁或重新啟動瀏覽器時,瀏覽器可能會嘗試將表單還原為使用者離開時的狀態。
如果是與表單相關聯的自訂元素,則還原的狀態取自您傳遞至 setFormValue()
方法的值。您可以使用單一值參數呼叫此方法,如前述範例所示,也可以使用兩個參數:
this.internals_.setFormValue(value, state);
value
代表控制項的提交資料表值。選用的 state
參數是控制項狀態的「內部」表示法,其中可能包括未傳送至伺服器的資料。state
參數採用的類型與 value
參數相同,可以是字串、File
或 FormData
物件。
如果無法只根據值還原控制項的狀態,state
參數就非常實用。舉例來說,假設您建立的顏色挑選器有多種模式:調色盤或 RGB 色盤。提交的表格值是標準格式中選取的顏色,例如 "#7fff00"
。不過,如要將控制項還原為特定狀態,您也需要知道控制項處於哪種模式,因此狀態可能會像 "palette/#7fff00"
一樣。
this.internals_.setFormValue(this.value_,
this.mode_ + '/' + this.value_);
程式碼需要根據儲存的狀態值還原狀態。
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.
}
若是較簡單的控制項 (例如數字輸入),值可能就足以將控制項還原至先前的狀態。如果您在呼叫 setFormValue()
時省略 state
,則值會傳遞至 formStateRestoreCallback()
。
formStateRestoreCallback(state, mode) {
// Simple case, restore the saved value
this.value_ = state;
}
實際範例
以下範例將表單相關聯的自訂元素的許多功能整合在一起。請務必在 Chrome 77 以上版本中執行這個指令碼,以便查看這個 API 的實際運作情形。
特徵偵測
您可以利用功能偵測功能,判斷是否有 formdata
事件和表單相關的自訂元素可用。目前沒有針對這兩項功能發布的 Polyfill。無論是哪種情況,您都可以改為新增隱藏的表單元素,將控制項的值傳播至表單。表單相關自訂元素的許多進階功能,可能很難或無法以 polyfill 實現。
if ('FormDataEvent' in window) {
// formdata event is supported
}
if ('ElementInternals' in window &&
'setFormValue' in window.ElementInternals.prototype) {
// Form-associated custom elements are supported
}
結論
formdata
事件和表單相關聯的自訂元素提供建立自訂表單控制項的新工具。
formdata
事件不會提供任何新功能,但提供介面,讓您不必建立隱藏的 <input>
元素,即可將表單資料加入提交程序。
與表單相關聯的自訂元素 API 提供一組新功能,讓自訂表單控制項的運作方式與內建表單控制項相同。
主頁橫幅由 Oudom Pravat 提供 Unsplash 網站上。