Với một sự kiện mới và các API thành phần tuỳ chỉnh, việc tham gia biểu mẫu trở nên dễ dàng hơn nhiều.
Nhiều nhà phát triển tạo các thành phần điều khiển biểu mẫu tuỳ chỉnh để cung cấp các thành phần điều khiển không được tích hợp sẵn trong trình duyệt hoặc để tuỳ chỉnh giao diện ngoài những gì có thể làm được với các thành phần điều khiển biểu mẫu tích hợp sẵn.
Tuy nhiên, bạn có thể khó sao chép các tính năng của các thành phần điều khiển biểu mẫu HTML tích hợp. Hãy xem xét một số tính năng mà phần tử <input>
tự động nhận được khi bạn thêm phần tử đó vào một biểu mẫu:
- Dữ liệu đầu vào sẽ tự động được thêm vào danh sách thành phần điều khiển của biểu mẫu.
- Giá trị của dữ liệu đầu vào sẽ tự động được gửi cùng với biểu mẫu.
- Dữ liệu đầu vào tham gia quy trình xác thực biểu mẫu. Bạn có thể tạo kiểu cho dữ liệu đầu vào bằng cách sử dụng các lớp giả
:valid
và:invalid
. - Dữ liệu đầu vào được thông báo khi biểu mẫu được đặt lại, khi biểu mẫu được tải lại hoặc khi trình duyệt cố gắng tự động điền các mục nhập biểu mẫu.
Các thành phần điều khiển biểu mẫu tuỳ chỉnh thường có một số tính năng trong số này. Nhà phát triển có thể khắc phục một số hạn chế trong JavaScript, chẳng hạn như thêm <input>
ẩn vào một biểu mẫu để tham gia gửi biểu mẫu. Tuy nhiên, bạn không thể chỉ dùng JavaScript để sao chép các tính năng khác.
Hai tính năng mới trên web giúp bạn dễ dàng tạo các thành phần điều khiển tuỳ chỉnh cho biểu mẫu và loại bỏ các hạn chế của các thành phần điều khiển tuỳ chỉnh hiện tại:
- Sự kiện
formdata
cho phép một đối tượng JavaScript tuỳ ý tham gia gửi biểu mẫu, vì vậy, bạn có thể thêm dữ liệu biểu mẫu mà không cần sử dụng<input>
ẩn. - API thành phần tuỳ chỉnh liên kết với biểu mẫu cho phép các thành phần tuỳ chỉnh hoạt động giống như các thành phần điều khiển biểu mẫu tích hợp.
Bạn có thể sử dụng hai tính năng này để tạo các loại chế độ điều khiển mới hoạt động hiệu quả hơn.
API dựa trên sự kiện
Sự kiện formdata
là một API cấp thấp cho phép mọi mã JavaScript tham gia vào quá trình gửi biểu mẫu. Cơ chế này hoạt động như sau:
- Bạn thêm trình nghe sự kiện
formdata
vào biểu mẫu mà bạn muốn tương tác. - Khi người dùng nhấp vào nút gửi, biểu mẫu sẽ kích hoạt một sự kiện
formdata
, bao gồm một đối tượngFormData
chứa tất cả dữ liệu đang được gửi. - Mỗi trình nghe
formdata
đều có cơ hội thêm hoặc sửa đổi dữ liệu trước khi gửi biểu mẫu.
Dưới đây là ví dụ về cách gửi một giá trị duy nhất trong trình nghe sự kiện 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);
});
Hãy thử cách này bằng ví dụ của chúng tôi trên Glitch. Hãy nhớ chạy ứng dụng trên Chrome 77 trở lên để xem API hoạt động.
Khả năng tương thích với trình duyệt
Phần tử tuỳ chỉnh liên kết với biểu mẫu
Bạn có thể sử dụng API dựa trên sự kiện với bất kỳ loại thành phần nào, nhưng API này chỉ cho phép bạn tương tác với quy trình gửi.
Các thành phần điều khiển biểu mẫu được chuẩn hoá tham gia vào nhiều phần của vòng đời biểu mẫu ngoài việc gửi. Các phần tử tuỳ chỉnh liên kết với biểu mẫu nhằm thu hẹp khoảng cách giữa các tiện ích tuỳ chỉnh và các thành phần điều khiển tích hợp. Các phần tử tuỳ chỉnh liên kết với biểu mẫu khớp với nhiều tính năng của các phần tử biểu mẫu được chuẩn hoá:
- Khi bạn đặt một phần tử tuỳ chỉnh liên kết với biểu mẫu bên trong
<form>
, phần tử đó sẽ tự động liên kết với biểu mẫu, chẳng hạn như một thành phần điều khiển do trình duyệt cung cấp. - Bạn có thể gắn nhãn cho phần tử này bằng phần tử
<label>
. - Phần tử này có thể đặt một giá trị được tự động gửi cùng với biểu mẫu.
- Phần tử này có thể đặt một cờ cho biết liệu phần tử đó có dữ liệu đầu vào hợp lệ hay không. Nếu một trong các thành phần điều khiển biểu mẫu có dữ liệu đầu vào không hợp lệ, thì bạn sẽ không thể gửi biểu mẫu.
- Phần tử này có thể cung cấp lệnh gọi lại cho nhiều phần trong vòng đời của biểu mẫu, chẳng hạn như khi biểu mẫu bị vô hiệu hoá hoặc đặt lại về trạng thái mặc định.
- Phần tử này hỗ trợ các lớp giả lập CSS tiêu chuẩn cho các thành phần điều khiển biểu mẫu, chẳng hạn như
:disabled
và:invalid
.
Có rất nhiều tính năng! Bài viết này sẽ không đề cập đến tất cả các phần tử đó, nhưng sẽ mô tả những kiến thức cơ bản cần thiết để tích hợp phần tử tuỳ chỉnh với một biểu mẫu.
Xác định phần tử tuỳ chỉnh liên kết với biểu mẫu
Để biến một phần tử tuỳ chỉnh thành phần tử tuỳ chỉnh liên kết với biểu mẫu, bạn cần thực hiện thêm một vài bước:
- Thêm thuộc tính
formAssociated
tĩnh vào lớp phần tử tuỳ chỉnh. Thao tác này sẽ yêu cầu trình duyệt coi phần tử này như một thành phần điều khiển biểu mẫu. - Gọi phương thức
attachInternals()
trên phần tử để có quyền truy cập vào các phương thức và thuộc tính bổ sung cho các thành phần điều khiển biểu mẫu, chẳng hạn nhưsetFormValue()
vàsetValidity()
. - Thêm các thuộc tính và phương thức phổ biến mà các thành phần điều khiển biểu mẫu hỗ trợ, chẳng hạn như
name
,value
vàvalidity
.
Sau đây là cách các mục đó phù hợp với định nghĩa phần tử tuỳ chỉnh cơ bản:
// 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);
Sau khi đăng ký, bạn có thể sử dụng phần tử này bất cứ khi nào bạn sử dụng thành phần điều khiển biểu mẫu do trình duyệt cung cấp:
<form>
<label>Number of bunnies: <my-counter></my-counter></label>
<button type="submit">Submit</button>
</form>
Đặt giá trị
Phương thức attachInternals()
trả về một đối tượng ElementInternals
cung cấp quyền truy cập vào các API điều khiển biểu mẫu. Cơ bản nhất trong số này là phương thức setFormValue()
, phương thức này đặt giá trị hiện tại của thành phần điều khiển.
Phương thức setFormValue()
có thể nhận một trong ba loại giá trị:
- Giá trị chuỗi.
- Đối tượng
File
. - Đối tượng
FormData
. Bạn có thể sử dụng đối tượngFormData
để truyền nhiều giá trị (ví dụ: thành phần điều khiển nhập thẻ tín dụng có thể truyền số thẻ, ngày hết hạn và mã xác minh).
Cách đặt giá trị đơn giản:
this.internals_.setFormValue(this.value_);
Để đặt nhiều giá trị, bạn có thể làm như sau:
// 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);
Xác thực dữ liệu đầu vào
Thành phần điều khiển của bạn cũng có thể tham gia xác thực biểu mẫu bằng cách gọi phương thức setValidity()
trên đối tượng nội bộ.
// 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_);
}
Bạn có thể tạo kiểu cho một phần tử tuỳ chỉnh liên kết với biểu mẫu bằng các lớp giả :valid
và :invalid
, giống như một thành phần điều khiển biểu mẫu tích hợp.
Lệnh gọi lại trong vòng đời
API phần tử tuỳ chỉnh liên kết với biểu mẫu bao gồm một tập hợp các lệnh gọi lại vòng đời bổ sung để liên kết với vòng đời biểu mẫu. Bạn không bắt buộc phải sử dụng lệnh gọi lại: chỉ triển khai lệnh gọi lại nếu phần tử của bạn cần thực hiện một thao tác nào đó tại thời điểm đó trong vòng đời.
void formAssociatedCallback(form)
Được gọi khi trình duyệt liên kết phần tử với phần tử biểu mẫu hoặc huỷ liên kết phần tử với phần tử biểu mẫu.
void formDisabledCallback(disabled)
Được gọi sau khi trạng thái disabled
của phần tử thay đổi, do thuộc tính disabled
của phần tử này đã được thêm hoặc xoá; hoặc do trạng thái disabled
thay đổi trên <fieldset>
là phần tử cấp trên của phần tử này. Tham số disabled
đại diện cho trạng thái bị vô hiệu hoá mới của phần tử. Ví dụ: phần tử này có thể tắt các phần tử trong DOM bóng khi bị tắt.
void formResetCallback()
Được gọi sau khi đặt lại biểu mẫu. Phần tử này sẽ tự đặt lại về một trạng thái mặc định nào đó. Đối với các phần tử <input>
, việc này thường liên quan đến việc đặt thuộc tính value
để khớp với thuộc tính value
được đặt trong mã đánh dấu (hoặc trong trường hợp hộp đánh dấu, hãy đặt thuộc tính checked
để khớp với thuộc tính checked
.
void formStateRestoreCallback(state, mode)
Được gọi trong một trong hai trường hợp:
- Khi trình duyệt khôi phục trạng thái của phần tử (ví dụ: sau khi điều hướng hoặc khi trình duyệt khởi động lại). Đối số
mode
là"restore"
trong trường hợp này. - Khi các tính năng hỗ trợ nhập của trình duyệt (chẳng hạn như tính năng tự động điền biểu mẫu) đặt một giá trị. Đối số
mode
là"autocomplete"
trong trường hợp này.
Loại của đối số đầu tiên phụ thuộc vào cách gọi phương thức setFormValue()
. Để biết thêm thông tin chi tiết, hãy xem phần Khôi phục trạng thái biểu mẫu.
Khôi phục trạng thái biểu mẫu
Trong một số trường hợp, chẳng hạn như khi quay lại một trang hoặc khởi động lại trình duyệt, trình duyệt có thể cố gắng khôi phục biểu mẫu về trạng thái mà người dùng đã để lại.
Đối với phần tử tuỳ chỉnh liên kết với biểu mẫu, trạng thái được khôi phục đến từ(các) giá trị mà bạn truyền vào phương thức setFormValue()
. Bạn có thể gọi phương thức này bằng một tham số giá trị duy nhất, như trong các ví dụ trước, hoặc bằng hai tham số:
this.internals_.setFormValue(value, state);
value
đại diện cho giá trị có thể gửi của thành phần điều khiển. Tham số state
không bắt buộc là một đại diện nội bộ của trạng thái của thành phần điều khiển, có thể bao gồm dữ liệu không được gửi đến máy chủ. Tham số state
có cùng loại với tham số value
– có thể là một chuỗi, đối tượng File
hoặc FormData
.
Tham số state
sẽ hữu ích khi bạn không thể khôi phục trạng thái của một thành phần điều khiển chỉ dựa trên giá trị. Ví dụ: giả sử bạn tạo một công cụ chọn màu có nhiều chế độ: bảng màu hoặc bánh xe màu RGB. Giá trị có thể gửi sẽ là màu đã chọn ở dạng chuẩn, chẳng hạn như "#7fff00"
. Tuy nhiên, để khôi phục chế độ điều khiển về một trạng thái cụ thể, bạn cũng cần biết chế độ đó đang ở chế độ nào, vì vậy trạng thái có thể trông giống như "palette/#7fff00"
.
this.internals_.setFormValue(this.value_,
this.mode_ + '/' + this.value_);
Mã của bạn cần khôi phục trạng thái dựa trên giá trị trạng thái đã lưu trữ.
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.
}
Trong trường hợp một thành phần điều khiển đơn giản hơn (ví dụ: nhập số), giá trị này có thể đủ để khôi phục thành phần điều khiển về trạng thái trước đó. Nếu bạn bỏ qua state
khi gọi setFormValue()
, thì giá trị sẽ được truyền đến formStateRestoreCallback()
.
formStateRestoreCallback(state, mode) {
// Simple case, restore the saved value
this.value_ = state;
}
Ví dụ về cách hoạt động
Ví dụ sau đây tập hợp nhiều tính năng của các phần tử tuỳ chỉnh liên kết với biểu mẫu. Hãy nhớ chạy ứng dụng trên Chrome 77 trở lên để xem API hoạt động.
Phát hiện tính năng
Bạn có thể sử dụng tính năng phát hiện tính năng để xác định xem sự kiện formdata
và các phần tử tuỳ chỉnh liên kết với biểu mẫu có sẵn hay không. Hiện tại, không có polyfill nào được phát hành cho cả hai tính năng này. Trong cả hai trường hợp, bạn có thể quay lại việc thêm một phần tử biểu mẫu ẩn để truyền giá trị của thành phần điều khiển đến biểu mẫu. Nhiều tính năng nâng cao hơn của các phần tử tuỳ chỉnh liên kết với biểu mẫu có thể khó hoặc không thể được polyfill.
if ('FormDataEvent' in window) {
// formdata event is supported
}
if ('ElementInternals' in window &&
'setFormValue' in window.ElementInternals.prototype) {
// Form-associated custom elements are supported
}
Kết luận
Sự kiện formdata
và các phần tử tuỳ chỉnh liên kết với biểu mẫu cung cấp các công cụ mới để tạo các thành phần điều khiển biểu mẫu tuỳ chỉnh.
Sự kiện formdata
không cung cấp cho bạn bất kỳ chức năng mới nào, nhưng cung cấp cho bạn một giao diện để thêm dữ liệu biểu mẫu vào quy trình gửi mà không cần tạo phần tử <input>
ẩn.
API thành phần tuỳ chỉnh liên kết với biểu mẫu cung cấp một bộ chức năng mới để tạo các thành phần điều khiển biểu mẫu tuỳ chỉnh hoạt động giống như các thành phần điều khiển biểu mẫu tích hợp.
Hình ảnh chính của Oudom Pravat trên Unsplash.