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

การใช้แบบฟอร์มจะง่ายขึ้นมากเมื่อมีเหตุการณ์ใหม่และ API องค์ประกอบที่กําหนดเอง

Arthur Evans

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

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

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

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

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

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

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

API อิงตามเหตุการณ์

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

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

ตัวอย่างการส่งค่าเดียวใน formdata event listener มีดังนี้

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 โปรดตรวจสอบว่าคุณเรียกใช้ API ใน Chrome เวอร์ชัน 77 ขึ้นไปเพื่อดูการทำงานของ API

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

การรองรับเบราว์เซอร์

  • Chrome: 5.
  • Edge: 12.
  • Firefox: 4.
  • Safari: 5.

แหล่งที่มา

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

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

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

  • เมื่อคุณวางองค์ประกอบที่กําหนดเองซึ่งเชื่อมโยงกับแบบฟอร์มภายใน <form> องค์ประกอบดังกล่าวจะเชื่อมโยงกับแบบฟอร์มโดยอัตโนมัติ เช่น การควบคุมที่ให้บริการโดยเบราว์เซอร์
  • คุณสามารถติดป้ายกำกับองค์ประกอบได้โดยใช้องค์ประกอบ <label>
  • องค์ประกอบสามารถตั้งค่าที่จะส่งพร้อมกับแบบฟอร์มโดยอัตโนมัติ
  • องค์ประกอบสามารถตั้งค่า Flag ที่ระบุว่ามีอินพุตที่ถูกต้องหรือไม่ หากตัวควบคุมแบบฟอร์มรายการใดรายการหนึ่งมีการป้อนข้อมูลที่ไม่ถูกต้อง ระบบจะไม่อนุญาตให้ส่งแบบฟอร์ม
  • องค์ประกอบนี้สามารถให้การเรียกกลับสําหรับส่วนต่างๆ ของวงจรชีวิตของแบบฟอร์ม เช่น เมื่อมีการปิดใช้แบบฟอร์มหรือรีเซ็ตเป็นสถานะเริ่มต้น
  • องค์ประกอบรองรับคลาสจำลอง CSS มาตรฐานสำหรับตัวควบคุมแบบฟอร์ม เช่น :disabled และ :invalid

ฟีเจอร์เยอะมากเลย บทความนี้จะไม่กล่าวถึงองค์ประกอบทั้งหมด แต่จะอธิบายพื้นฐานที่จําเป็นในการผสานรวมองค์ประกอบที่กําหนดเองกับแบบฟอร์ม

การกําหนดองค์ประกอบที่กําหนดเองซึ่งเชื่อมโยงกับแบบฟอร์ม

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

  • เพิ่มพร็อพเพอร์ตี้ 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() สามารถใช้ค่าได้ 1 ใน 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_);
}

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

Callback ของวงจร

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)

เรียกใช้ในกรณีใดกรณีหนึ่งต่อไปนี้

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

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

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

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

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

this.internals_.setFormValue(value, state);

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

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

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

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

การตรวจหาองค์ประกอบ

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

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