Các chức năng kiểm soát biểu mẫu hiệu quả hơn

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.

Arthur7 tiên

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: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:

  1. 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.
  2. 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ượng FormData chứa tất cả dữ liệu đang được gửi.
  3. 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

Hỗ trợ trình duyệt

  • 5
  • 12
  • 4
  • 5

Nguồn

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: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()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, valuevalidity.

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ượng FormData để 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: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"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"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.