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

Với sự kiện mới và các API phần tử tuỳ chỉnh, việc tham gia vào các biểu mẫu trở nên dễ dàng hơn rất nhiều.

Arthur Evans

Nhiều nhà phát triển xây dựng 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ó sẵn trong trình duyệt hoặc để tuỳ chỉnh giao diện vượt quá khả năng của các thành phần đ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 tính năng của 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à phần tử <input> sẽ tự động được thêm khi bạn thêm phần tử đó vào biểu mẫu:

  • Thông tin đầu vào sẽ tự động được thêm vào danh sách các tuỳ chọn điều khiển của biểu mẫu.
  • Giá trị của thông tin nhập đượ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 dữ liệu đầu vào bằng cách sử dụng các lớp giả :valid:invalid.
  • Đầu vào 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 các mục nhập vào biểu mẫu.

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 sau đâ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. Tuy nhiên, các tính năng khác không thể được sao chép một mình trong JavaScript.

Hai tính năng web mới giúp tạo điều khiển biểu mẫu tùy chỉnh dễ dàng hơn và loại bỏ các hạn chế của điều khiển tùy chỉnh hiện tại:

  • Sự kiện formdata cho phép một đối tượng JavaScript tuỳ ý tham gia vào quá trình 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 chế độ kiểm soát tích hợp sẵn cho biểu mẫu.

Hai tính năng này có thể được sử dụng để tạo ra các loại kiểm soát 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 gửi biểu mẫu. Cơ chế hoạt động như sau:

  1. Bạn thêm một trình nghe sự kiện formdata vào biểu mẫu 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 một sự kiện formdata, bao gồm đối tượng FormData chứa tất cả dữ liệu đang được gửi.
  3. Mỗi trình nghe formdata sẽ có cơ hội thêm vào 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ử làm điều này qua ví dụ của chúng tôi trên Glitch. Hãy nhớ chạy API này trên Chrome 77 trở lên để xem API hoạt động như thế nào.

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 quy trình gửi.

Các biện pháp 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 bên cạnh việc gửi biểu mẫu. 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 tiện ích tuỳ chỉnh và các chế độ đ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 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 vào trong <form>, phần tử đó sẽ tự động được liên kết với biểu mẫu, chẳng hạn như chế độ 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ử 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ử có thể đặt cờ cho biết liệu có dữ liệu đầu vào hợp lệ hay không. Nếu một trong các biện pháp 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ử 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ị tắt hoặc được đặt lại về trạng thái mặc định.
  • Phần tử này hỗ trợ các lớp giả CSS tiêu chuẩn cho các thành phần điều khiển biểu mẫu, như :disabled:invalid.

Vậy là 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 nguyên tắc đó, nhưng sẽ mô tả những điều cơ bản cần thiết để tích hợp phần tử tuỳ chỉnh của bạn 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

Để 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. Thao tác này sẽ yêu cầu trình duyệt xử lý 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ử này để 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 mà 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 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ứ nơi nào bạn muố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 sẽ đặt giá trị hiện tại của chế độ điều khiển.

Phương thức setFormValue() có thể nhận một trong 3 loại giá trị:

  • Một 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 thông tin đầu vào của 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ả :valid:invalid, giống như một tuỳ chọ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. Lệnh gọi lại 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 việ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 một 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 bị vô hiệu hoá mới của phần tử. Ví dụ: phần tử đó có thể tắt các phần tử trong DOM bóng của mình 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ề một loại trạng thái mặc định nào đó. Đố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 được đặt trong mã đánh dấu (hoặc trong trường hợp có 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). 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 phương thức setFormValue() được gọi. Để 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, 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ể tìm cách 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 được lấy 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 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 2 tham số:

this.internals_.setFormValue(value, state);

value thể hiện giá trị có thể gửi của chế độ kiểm soát. Tham số state không bắt buộc là tham số nội bộ thể hiện trạng thái của chế độ kiểm soát, có thể bao gồm những dữ liệu không được gửi đến máy chủ. Tham số state có các kiểu giống với tham số value. Tham số này có thể là chuỗi, File hoặc đối tượng 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 chế độ kiểm soát 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 trong một biểu mẫu chuẩn, chẳng hạn như "#7fff00". Tuy nhiên, để khôi phục quyền kiểm soát về một trạng thái cụ thể, bạn cũng cần biết nút đó đang ở chế độ nào, vì vậy, state có thể có dạ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 chế độ điều khiển đơn giản hơn (ví dụ: nhập số), thì giá trị này có thể đủ để 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(), thì giá trị sẽ được chuyển vào 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ổng 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 API này trên Chrome 77 trở lên để xem API hoạt động như thế nào.

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 quan đến biểu mẫu có được cung cấp hay không. Hiện chưa có đoạn mã 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 đều có thể quay lại thêm phần tử biểu mẫu ẩn để truyền giá trị của điều khiển 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ể điền đầy đủ.

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 mang đến cho bạn tính năng mới nào, nhưng nó 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ộ tính năng mới để tạo các biện pháp kiểm soát biểu mẫu tuỳ chỉnh hoạt động giống như các biện pháp 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.