Với các API phần tử tuỳ chỉnh và sự kiện mới, việc tham gia vào biểu mẫu trở nên dễ dàng hơn rất nhiều.
Nhiều nhà phát triển tạo ra các biểu mẫu tuỳ chỉnh để cung cấp các chế độ điều khiển không được tích hợp sẵn vào trình duyệt, hoặc để tuỳ chỉnh giao diện ngoài những gì có thể thực hiện bằng các chế độ điều khiển biểu mẫu tích hợp sẵn.
Tuy nhiên, có thể khó sao chép được các tính năng của chế độ kiểm soát biểu mẫu HTML tích hợp sẵn. Hãy cân nhắc một số tính năng mà một phần tử <input>
sẽ tự động có khi bạn thêm phần tử đó vào biểu mẫu:
- Thông tin đầu vào được tự động thêm vào danh sách điều khiển của biểu mẫu.
- Giá trị nhập sẽ được tự động gửi cùng với biểu mẫu.
- Thông tin đầu vào tham gia vào quá trình xác thực biểu mẫu. Bạn có thể tạo kiểu cho đầu vào bằng cách sử dụng lớp giả lập
:valid
và:invalid
. - Thông tin mà bạn nhập sẽ đượ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 vào các mục nhập của 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 vài tính năng 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 biểu mẫu để tham gia gửi biểu mẫu. Nhưng các tính năng khác không thể được sao chép riêng trong JavaScript.
Hai tính năng mới dành cho web giúp bạn dễ dàng tạo các chế độ điều khiển biểu mẫu tuỳ chỉnh và loại bỏ các hạn chế của các chế độ đ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 phần tử tuỳ chỉnh liên kết với biểu mẫu cho phép các phần tử 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 sẵn.
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ế hoạt động như sau:
- Bạn thêm trình nghe sự kiện
formdata
vào biểu mẫu mà mình 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 sự kiện
formdata
, trong đó có đối tượngFormData
chứa tất cả dữ liệu đang được gửi. - Mỗi trình nghe
formdata
có cơ hội thêm hoặc sửa đổi dữ liệu trước khi biểu mẫu được gửi.
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ử điều này qua ví dụ của chúng tôi về Glitch. Hãy nhớ chạy API này 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 quá trình gửi.
Các chế độ kiểm soát biểu mẫu được chuẩn hoá tham gia vào nhiều phần trong vòng đời biểu mẫu ngoài việc gửi biểu mẫu. Phần tử tuỳ chỉnh liên kết với biểu mẫu giúp thu hẹp khoảng cách giữa các tiện ích tuỳ chỉnh và các chế độ điều khiển tích hợp. Phần tử tuỳ chỉnh liên kết với biểu mẫu phù hợp với nhiều tính năng của phần tử biểu mẫu được chuẩn hoá:
- Khi bạn đặt một phần tử tuỳ chỉnh có liên kết với biểu mẫu bên trong
<form>
, phần tử đó sẽ tự động được liên kết với biểu mẫu đó, chẳng hạn như phần tử kiểm soát do trình duyệt cung cấp. - Bạn có thể gắn nhãn phần tử này bằng phần tử
<label>
. - Phần tử có thể đặt một giá trị được tự động gửi cùng với biểu mẫu.
- Phần tử có thể đặ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 chế độ kiểm soát biểu mẫu có thông tin nhập không hợp lệ, thì bạn không thể gửi biểu mẫu.
- Phần tử có thể cung cấp lệnh gọi lại cho nhiều phần trong vòng đời biểu mẫu, chẳng hạn như khi biểu mẫu bị tắt 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 chế độ đ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 yếu tố đó, nhưng sẽ mô tả những yếu tố cơ bản cần thiết để tích hợp phần tử tuỳ chỉnh vào một biểu mẫu.
Xác định phần tử tuỳ chỉnh được liên kết với biểu mẫu
Để chuyể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 vài bước:
- Thêm một thuộc tính
formAssociated
tĩnh vào lớp phần tử tuỳ chỉnh. Điều này 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. - Hãy 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 được 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
.
Dưới đây là cách những 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ứ nơi nào sử dụng chế độ kiểm soát 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 kiểm soát biểu mẫu. Cơ bản nhất trong số này là phương thức setFormValue()
, đặt giá trị hiện tại của đ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ụ: chế độ kiểm soát nhập dữ liệu thẻ tín dụng có thể chuyển số thẻ, ngày hết hạn và mã xác minh).
Cách đặt mộ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 vào quá trình 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ả lập :valid
và :invalid
, giống như một thành phần điều khiển biểu mẫu tích hợp sẵn.
Phương thức 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 phương thức gọi lại trong vòng đời bổ sung để liên kết với vòng đời của biểu mẫu. Các lệnh gọi lại này là không bắt buộc: 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 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 một phần tử biểu mẫu hoặc tách phần tử đó khỏ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 bị xoá; hoặc do trạng thái disabled
đã thay đổi trên <fieldset>
là đối tượng cấp trên của phần tử này. Tham số disabled
thể hiện trạng thái tắt mới của phần tử. Chẳng hạn, phần tử có thể tắt các phần tử trong DOM bóng khi bị tắt.
void formResetCallback()
Được gọi sau khi biểu mẫu được đặt lại. Phần tử phải tự đặt lại về trạng thái mặc định. Đối với các phần tử <input>
, việc này thường bao gồm việc đặt thuộc tính value
sao cho khớp với thuộc tính value
đã đặt trong mã đánh dấu (hoặc trong trường hợp hộp đánh dấu, bạn phải đặt thuộc tính checked
sao cho 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 một thao tác điều hướng hoặc khi trình duyệt khởi động lại). Trong trường hợp này, đối số
mode
là"restore"
. - Khi các tính năng hỗ trợ nhập liệu của trình duyệt, chẳng hạn như tự động điền biểu mẫu, đặt một giá trị. Trong trường hợp này, đối số
mode
là"autocomplete"
.
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, hãy xem phần Khôi phục trạng thái biểu mẫu.
Đang khôi phục trạng thái biểu mẫu
Trong một số trường hợp – như khi điều hướng quay lại 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 sẽ đến từ(các) giá trị mà bạn chuyển vào phương thức setFormValue()
. Bạn có thể gọi phương thức bằng một tham số có giá trị duy nhất, như trình bày trong các ví dụ trước đây hoặc bằng 2 tham số:
this.internals_.setFormValue(value, state);
value
biểu thị giá trị có thể gửi của chế độ kiểm soát. Tham số state
không bắt buộc là nội dung biểu thị nội bộ về trạng thái của chế độ kiểm soát, có thể bao gồm dữ liệu không được gửi đến máy chủ. Tham số state
có cùng kiểu với tham số value
, có thể là một chuỗi, File
hoặc FormData
.
Tham số state
rất hữu ích khi bạn không thể khôi phục trạng thái của một chế độ đ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 với nhiều chế độ: bảng màu hoặc bánh xe màu RGB. value có thể gửi sẽ là màu được 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 ở trạng thái nào, vì vậy, state có thể là "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 được 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 cần điều khiển đơn giản hơn (ví dụ: nhập vào số), giá trị này có thể là đủ để khôi phục chế độ điều khiển về trạng thái trước đó. Nếu bạn bỏ qua state
khi gọi setFormValue()
, giá trị sẽ được chuyển đến formStateRestoreCallback()
.
formStateRestoreCallback(state, mode) {
// Simple case, restore the saved value
this.value_ = state;
}
Một ví dụ về hiệu quả hoạt động
Ví dụ sau đây tổng hợp nhiều tính năng của phần tử tuỳ chỉnh liên kết với biểu mẫu. Hãy nhớ chạy API này trên Chrome 77 trở lên để xem API hoạt động.
Phát hiện tính năng
Bạn có thể dùng tính năng phát hiện tính năng để xác định xem có sự kiện formdata
và các phần tử tuỳ chỉnh liên quan đến biểu mẫu hay không. Hiện tại, chưa có đoạn mã polyfill nào được phát hành cho cả hai tính năng. Trong cả hai trường hợp, bạn có thể quay lại sử dụng thêm phần tử biểu mẫu ẩn để truyền giá trị của chế độ kiểm soát vào biểu mẫu. Nhiều tính năng nâng cao hơn của phần tử tuỳ chỉnh liên kết với biểu mẫu có thể sẽ khó hoặc không thể tạo thành phần 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 chế độ kiểm soát biểu mẫu tuỳ chỉnh.
Sự kiện formdata
không cung cấp cho bạn tính năng mới nào, nhưng sự kiện này 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 phải tạo phần tử <input>
ẩn.
API phần tử 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 chế độ kiểm soát biểu mẫu tuỳ chỉnh hoạt động giống như các chế độ kiểm soát biểu mẫu tích hợp sẵn.
Hình ảnh chính của Oudom Pravat trên Unsplash.