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

Arthur Evans

เผยแพร่: 8 สิงหาคม 2562

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

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

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

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

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

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

คุณลักษณะทั้งสองนี้สามารถนำมาใช้เพื่อสร้างการควบคุมประเภทใหม่ที่ทำงานได้ดีขึ้น

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

Browser Support

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

Source

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

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

นี่คือตัวอย่างของการส่งค่าเดียวในตัวรับฟังเหตุการณ์ 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);
});

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

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

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

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

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

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

การเปลี่ยนองค์ประกอบที่กำหนดเองให้เป็นองค์ประกอบที่กำหนดเองที่เกี่ยวข้องกับแบบฟอร์มต้องมีขั้นตอนเพิ่มเติมบางประการ:

  • เพิ่มคุณสมบัติคงที่ formAssociated ให้กับคลาสองค์ประกอบแบบกำหนดเองของคุณ สิ่งนี้แจ้งให้เบราว์เซอร์ปฏิบัติต่อองค์ประกอบเหมือนกับตัวควบคุมแบบฟอร์ม
  • เรียกใช้เมธอด attachInternals() ในองค์ประกอบเพื่อเข้าถึงเมธอดและพร็อพเพอร์ตี้เพิ่มเติมสำหรับตัวควบคุมแบบฟอร์ม เช่น setFormValue() และ setValidity()
  • เพิ่มคุณสมบัติทั่วไปและวิธีการที่รองรับโดยตัวควบคุมแบบฟอร์ม เช่น name, value และ validity

นี่คือวิธีที่รายการเหล่านั้นพอดีกับคำจำกัดความองค์ประกอบที่กำหนดเองพื้นฐาน:

// Form-associated custom elements must be autonomous custom elements.
// 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_);
}

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

Lifecycle Callback

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

void formAssociatedCallback(form)

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

void formDisabledCallback(disabled)

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

เช่น องค์ประกอบอาจปิดใช้องค์ประกอบใน Shadow DOM เมื่อปิดใช้

void formResetCallback()

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

void formStateRestoreCallback(state, mode)

เรียกใช้ใน 2 กรณีต่อไปนี้

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

ประเภทของอาร์กิวเมนต์แรกจะขึ้นอยู่กับวิธีเรียกใช้เมธอด setFormValue()

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

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

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

this.internals_.setFormValue(value, state);

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

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

การตรวจจับคุณลักษณะ

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

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