การควบคุมแบบฟอร์มที่มีความสามารถมากขึ้น

การเข้าร่วมในแบบฟอร์มทำได้ง่ายขึ้นกว่าเดิมมาก ด้วยเหตุการณ์ใหม่และ API ขององค์ประกอบที่กำหนดเอง

อาร์เธอร์ อีแวนส์

นักพัฒนาซอฟต์แวร์จำนวนมากสร้างการควบคุมแบบฟอร์มขึ้นเอง ไม่ว่าจะเป็นการควบคุมที่ไม่ได้มีมาในตัวเบราว์เซอร์ หรือเพื่อปรับแต่งรูปลักษณ์และความรู้สึกที่มากกว่าที่เคยด้วยการควบคุมแบบฟอร์มที่มีในตัว

อย่างไรก็ตาม การทำซ้ำฟีเจอร์ของตัวควบคุมแบบฟอร์ม HTML ในตัวอาจเป็นเรื่องยาก ลองพิจารณาฟีเจอร์บางอย่างที่องค์ประกอบ <input> จะรับโดยอัตโนมัติเมื่อคุณเพิ่มลงในแบบฟอร์ม

  • อินพุตจะเพิ่มลงในรายการควบคุมของแบบฟอร์มโดยอัตโนมัติ
  • ระบบจะส่งค่าของอินพุตไปกับแบบฟอร์มโดยอัตโนมัติ
  • อินพุตเข้าร่วมการตรวจสอบแบบฟอร์ม คุณจัดรูปแบบอินพุตได้โดยใช้ Pseudoclass ของ :valid และ :invalid
  • ระบบจะแจ้งเตือนข้อมูลที่ป้อนเมื่อมีการรีเซ็ตแบบฟอร์ม เมื่อโหลดแบบฟอร์มซ้ำ หรือเมื่อเบราว์เซอร์พยายามป้อนข้อมูลแบบฟอร์มอัตโนมัติ

โดยปกติแล้ว การควบคุมแบบฟอร์มที่กำหนดเองจะมีฟีเจอร์เหล่านี้เพียงไม่กี่อย่าง นักพัฒนาซอฟต์แวร์สามารถหลีกเลี่ยงข้อจำกัดบางประการใน JavaScript เช่น การเพิ่ม <input> ที่ซ่อนไว้ในแบบฟอร์มเพื่อเข้าร่วมการส่งแบบฟอร์ม แต่ฟีเจอร์อื่นๆ นั้นไม่สามารถเลียนแบบใน JavaScript เพียงอย่างเดียวได้

ฟีเจอร์เว็บใหม่ 2 รายการช่วยให้สร้างการควบคุมแบบฟอร์มที่กำหนดเองได้ง่ายขึ้น และนำข้อจำกัดของการควบคุมที่กำหนดเองในปัจจุบันออก

  • เหตุการณ์ formdata ช่วยให้ออบเจ็กต์ JavaScript ที่กําหนดเองเข้าร่วมการส่งแบบฟอร์มได้ คุณจึงเพิ่มข้อมูลแบบฟอร์มได้โดยไม่ต้องใช้ <input> ที่ซ่อนอยู่
  • API องค์ประกอบที่กำหนดเองที่เกี่ยวข้องกับแบบฟอร์มจะช่วยให้องค์ประกอบที่กำหนดเองทำงานคล้ายกับการควบคุมแบบฟอร์มในตัวมากขึ้น

คุณสามารถใช้ฟีเจอร์ 2 อย่างนี้เพื่อสร้างการควบคุมประเภทใหม่ที่ทำงานได้ดีกว่า

API ตามเหตุการณ์

เหตุการณ์ formdata เป็น API ระดับล่างที่อนุญาตให้โค้ด JavaScript ใดๆ เข้าร่วมการส่งแบบฟอร์ม กลไกการทำงานในลักษณะนี้

  1. คุณเพิ่ม Listener เหตุการณ์ formdata ลงในแบบฟอร์มที่ต้องการโต้ตอบด้วย
  2. เมื่อผู้ใช้คลิกปุ่มส่ง แบบฟอร์มจะเริ่มเหตุการณ์ formdata ซึ่งมีออบเจ็กต์ FormData ที่เก็บข้อมูลทั้งหมดที่ส่ง
  3. ผู้ฟัง formdata แต่ละรายมีโอกาสเพิ่มหรือแก้ไขข้อมูลก่อนที่จะส่งแบบฟอร์ม

ต่อไปนี้คือตัวอย่างการส่งค่าเดียวใน Listener เหตุการณ์ 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

ความเข้ากันได้กับเบราว์เซอร์

การสนับสนุนเบราว์เซอร์

  • 5
  • 12
  • 4
  • 5

แหล่งที่มา

องค์ประกอบที่กำหนดเองซึ่งเกี่ยวข้องกับแบบฟอร์ม

คุณสามารถใช้ API ที่อิงตามเหตุการณ์กับคอมโพเนนต์ประเภทใดก็ได้ แต่จะอนุญาตให้คุณโต้ตอบกับกระบวนการส่งข้อมูลเท่านั้น

การควบคุมแบบฟอร์มที่เป็นมาตรฐานจะมีส่วนร่วมในหลายส่วนของวงจรแบบฟอร์ม นอกเหนือจากการส่ง องค์ประกอบที่กำหนดเองซึ่งเชื่อมโยงกับแบบฟอร์มมีวัตถุประสงค์เพื่อลดช่องว่างระหว่างวิดเจ็ตที่กำหนดเองกับตัวควบคุมในตัว องค์ประกอบที่กำหนดเองซึ่งเชื่อมโยงกับแบบฟอร์มตรงกับฟีเจอร์ต่างๆ ขององค์ประกอบแบบฟอร์มมาตรฐาน ดังนี้

  • เมื่อวางองค์ประกอบที่กำหนดเองซึ่งเกี่ยวข้องกับแบบฟอร์มไว้ใน <form> องค์ประกอบนั้นจะเชื่อมโยงกับแบบฟอร์มโดยอัตโนมัติ เช่นเดียวกับการควบคุมที่เบราว์เซอร์มีให้
  • ติดป้ายกำกับองค์ประกอบได้โดยใช้องค์ประกอบ <label>
  • องค์ประกอบสามารถตั้งค่าที่ส่งโดยอัตโนมัติพร้อมกับแบบฟอร์มได้
  • องค์ประกอบดังกล่าวสามารถตั้งค่าแฟล็กซึ่งระบุว่ามีอินพุตที่ถูกต้องหรือไม่ หากการควบคุมแบบฟอร์มรายการใดรายการหนึ่งมีการป้อนข้อมูลที่ไม่ถูกต้อง คุณจะส่งแบบฟอร์มไม่ได้
  • องค์ประกอบจะมีโค้ดเรียกกลับสำหรับส่วนต่างๆ ของวงจรแบบฟอร์ม เช่น เมื่อปิดใช้หรือรีเซ็ตแบบฟอร์มเป็นสถานะเริ่มต้น
  • องค์ประกอบรองรับ Pseudoclass ของ 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() ใช้ค่าใดค่าหนึ่งจาก 3 ประเภทดังนี้

  • ค่าสตริง
  • ออบเจ็กต์ File
  • ออบเจ็กต์ FormData คุณสามารถใช้ออบเจ็กต์ FormData เพื่อส่งผ่านค่าหลายค่าได้ (เช่น การควบคุมการป้อนข้อมูลบัตรเครดิตอาจส่งหมายเลขบัตร วันที่หมดอายุ และรหัสยืนยัน)

วิธีกำหนดค่าอย่างง่าย

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_);
}

คุณจัดรูปแบบองค์ประกอบที่กำหนดเองที่เชื่อมโยงกับแบบฟอร์มด้วย Pseudoclass ของ :valid และ :invalid ได้ เช่นเดียวกับการควบคุมแบบฟอร์มในตัว

โค้ดเรียกกลับสำหรับวงจร

API องค์ประกอบที่กำหนดเองซึ่งเชื่อมโยงกับแบบฟอร์มจะมีชุดการเรียกกลับสำหรับวงจรเพิ่มเติมเพื่อเชื่อมโยงกับวงจรของแบบฟอร์ม โค้ดเรียกกลับเป็นตัวเลือกที่ไม่บังคับ ให้ใช้โค้ดเรียกกลับในกรณีที่องค์ประกอบจำเป็นต้องทำอะไรบางอย่าง ณ จุดนั้นในวงจรเท่านั้น

void formAssociatedCallback(form)

เรียกใช้เมื่อเบราว์เซอร์เชื่อมโยงองค์ประกอบกับองค์ประกอบแบบฟอร์ม หรือยกเลิกการเชื่อมโยงองค์ประกอบจากองค์ประกอบแบบฟอร์ม

void formDisabledCallback(disabled)

เรียกใช้หลังจากสถานะ disabled ขององค์ประกอบเปลี่ยนแปลง ซึ่งอาจเป็นเพราะมีการเพิ่มหรือนำแอตทริบิวต์ disabled ขององค์ประกอบนี้ออก หรือเนื่องจากสถานะ disabled มีการเปลี่ยนแปลงใน <fieldset> ที่เป็นระดับบนขององค์ประกอบนี้ พารามิเตอร์ disabled แสดงสถานะการปิดใช้ใหม่ขององค์ประกอบ ตัวอย่างเช่น องค์ประกอบอาจปิดใช้งานองค์ประกอบใน Shadow DOM เมื่อมีการปิดใช้

void formResetCallback()

เรียกใช้หลังจากรีเซ็ตแบบฟอร์ม องค์ประกอบควรรีเซ็ตตนเองเป็นสถานะเริ่มต้น สำหรับองค์ประกอบ <input> โดยปกติจะต้องตั้งค่าพร็อพเพอร์ตี้ value ให้ตรงกับแอตทริบิวต์ value ที่ตั้งค่าไว้ในมาร์กอัป (หรือในกรณีของช่องทำเครื่องหมาย ให้ตั้งค่าพร็อพเพอร์ตี้ checked ให้ตรงกับแอตทริบิวต์ checked

void formStateRestoreCallback(state, mode)

เรียกใช้ใน 1 ใน 2 สถานการณ์ต่อไปนี้

  • เมื่อเบราว์เซอร์คืนค่าสถานะขององค์ประกอบ (เช่น หลังจากการนำทาง หรือเมื่อเบราว์เซอร์รีสตาร์ท) ในกรณีนี้ อาร์กิวเมนต์ mode คือ "restore"
  • เมื่อฟีเจอร์ช่วยป้อนข้อมูลของเบราว์เซอร์ เช่น การป้อนข้อมูลแบบฟอร์มอัตโนมัติกำหนดค่า ในกรณีนี้ อาร์กิวเมนต์ mode คือ "autocomplete"

ประเภทของอาร์กิวเมนต์แรกขึ้นอยู่กับวิธีที่เรียกใช้เมธอด setFormValue() โปรดดูรายละเอียดเพิ่มเติมที่การคืนค่าสถานะของแบบฟอร์ม

การคืนค่าสถานะแบบฟอร์ม

ในบางกรณี เช่น เมื่อกลับไปยังหน้าเว็บหรือรีสตาร์ทเบราว์เซอร์ เบราว์เซอร์อาจพยายามกู้คืนแบบฟอร์มเป็นสถานะที่ผู้ใช้กรอกไว้

สำหรับองค์ประกอบที่กำหนดเองซึ่งเชื่อมโยงกับแบบฟอร์ม สถานะที่ได้รับการกู้คืนมาจากค่าที่คุณส่งไปยังเมธอด setFormValue() คุณสามารถเรียกเมธอดด้วยพารามิเตอร์ค่าเดียวตามที่แสดงในตัวอย่างก่อนหน้านี้ หรือใช้พารามิเตอร์ 2 รายการดังนี้

this.internals_.setFormValue(value, state);

value แสดงค่าที่ส่งได้ของกลุ่มควบคุม พารามิเตอร์ state (ไม่บังคับ) คือการแสดงข้อมูลภายในสถานะของตัวควบคุม ซึ่งอาจรวมข้อมูลที่ไม่ได้ส่งไปยังเซิร์ฟเวอร์ พารามิเตอร์ state จะใช้ประเภทเดียวกับพารามิเตอร์ value โดยจะเป็นสตริง File หรือออบเจ็กต์ FormData ก็ได้

พารามิเตอร์ state มีประโยชน์เมื่อคุณกู้คืนสถานะของตัวควบคุมจากค่าเพียงอย่างเดียวไม่ได้ ตัวอย่างเช่น สมมติว่าคุณสร้างตัวเลือกสีที่มีหลายโหมด เช่น จานสีหรือวงล้อสี RGB ค่าที่ส่งได้จะเป็นสีที่เลือกในรูปแบบ Canonical เช่น "#7fff00" แต่หากต้องการคืนค่าตัวควบคุมเป็นสถานะที่เจาะจง คุณจะต้องทราบด้วยว่าโหมดใดอยู่ในโหมดใด stateจึงอาจมีหน้าตาเป็น "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.
}

ในกรณีของการควบคุมที่ง่ายกว่า (เช่น การป้อนตัวเลข) ค่าก็น่าจะเพียงพอต่อการคืนค่าการควบคุมกลับไปเป็นสถานะก่อนหน้า หากไม่รวม state เมื่อเรียกใช้ setFormValue() ระบบจะส่งค่าไปที่ formStateRestoreCallback()

formStateRestoreCallback(state, mode) {
  // Simple case, restore the saved value
  this.value_ = state;
}

ตัวอย่างที่ใช้งานได้

ตัวอย่างต่อไปนี้เป็นการรวมฟีเจอร์ต่างๆ มากมายขององค์ประกอบที่กำหนดเองที่เชื่อมโยงกับแบบฟอร์มเข้าด้วยกัน อย่าลืมเรียกใช้ใน Chrome 77 ขึ้นไปเพื่อดูการทำงานของ API

การตรวจหาฟีเจอร์

คุณสามารถใช้การตรวจหาฟีเจอร์เพื่อระบุว่าเหตุการณ์ formdata และองค์ประกอบที่กำหนดเองที่เชื่อมโยงกับแบบฟอร์มพร้อมใช้งานหรือไม่ ขณะนี้ยังไม่มีการเผยแพร่ Polyfill สำหรับทั้งสองฟีเจอร์ ในทั้ง 2 กรณี คุณสามารถกลับไปใช้การเพิ่มองค์ประกอบแบบฟอร์มที่ซ่อนอยู่เพื่อเผยแพร่ค่าของการควบคุมไปยังแบบฟอร์ม ฟีเจอร์ขั้นสูงหลายๆ อย่างขององค์ประกอบที่กำหนดเองที่เชื่อมโยงกับแบบฟอร์มมักจะทำ 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