ข้ามไปยังเนื้อหา

Form-Associated Element

Form-Associated Custom Elements API ช่วยให้ custom element เข้าร่วมใน <form> ได้เหมือนกับ input แบบเนทีฟทุกประการ element สามารถรายงานค่า (value) และความถูกต้อง (validity) ของตัวเอง รวมถึงถูกรวมเข้าไปใน FormData และ form.submit() ได้

หากไม่มี API นี้ custom element ที่วางอยู่ภายใน <form> จะมองไม่เห็นโดยกลไกการ submit form ของ browser ค่าที่เก็บไว้จะไม่ถูก serialise และการตรวจสอบความถูกต้องในตัว (built-in validation) ก็จะไม่ทำงานกับ element นั้น แต่เมื่อใช้ API นี้ element ของคุณจะกลายเป็นผู้ร่วมใน form ระดับเฟิร์สคลาสที่แยกไม่ออกจาก <input> แบบเนทีฟ

มีสองสิ่งที่จำเป็น: property แบบ static formAssociated = true บน class และการเรียก this.attachInternals() ใน constructor

class MyInput extends HTMLElement {
static formAssociated = true;
constructor() {
super();
this.internals = this.attachInternals();
}
}

ต้องประกาศ property แบบ static นี้ก่อนที่จะเรียก attachInternals() เพราะ browser จะอ่านค่านี้ตอนที่สร้าง element (construction time) เพื่อตัดสินใจว่าจะมอบความสามารถในการเข้าร่วม form ให้หรือไม่

เมื่อคุณมี object internals แล้ว นี่คือ method ที่เกี่ยวข้องกับ form ที่สำคัญ:

  • internals.setFormValue(value) — กำหนดค่าที่จะถูกรวมเข้าไปใน FormData เมื่อ form ถูก submit
  • internals.setValidity(flags, message, anchor) — ควบคุมสถานะการตรวจสอบความถูกต้องของ element ส่ง object ว่างและ string ว่างเพื่อล้าง error ทั้งหมด
  • internals.reportValidity() — เรียก UI การตรวจสอบความถูกต้องในตัวของ browser (กล่อง tooltip) ในแบบเดียวกับที่ input แบบเนทีฟทำ
  • internals.checkValidity() — คืนค่า boolean ที่บอกว่าตอนนี้ element ผ่านการตรวจสอบความถูกต้องหรือไม่

property internals.role และ property ที่สะท้อน (reflection) ค่า ARIA จะกล่าวถึงในบทเรียน Accessibility

// Form-Associated Custom Elements API (Chrome 77+, Firefox 98+, Safari 16.4+)
// static formAssociated = true — must be on the class before attachInternals()
// this.internals = this.attachInternals()
//
// internals.setFormValue(value) — submittable value
// internals.setFormValue(value, state) — value + restore-state hint
// internals.setValidity({}, '') — clear validity errors
// internals.setValidity({ valueMissing: true }, 'Required', anchorEl)
// internals.reportValidity() — show browser validation UI
// internals.checkValidity() — boolean
// internals.form — the associated <form> element
// internals.labels — NodeList of associated <label>s
// internals.willValidate — boolean

เดโมด้านล่างตรวจจับฟีเจอร์ (feature-detect) ว่ารองรับ attachInternals และ formAssociated หรือไม่ หาก browser ไม่รองรับ จะแสดงข้อความสำรอง (fallback) แทน

พิมพ์ลงใน custom input แล้วดูย่อหน้าด้านล่าง form สะท้อนค่าที่จะถูก submit ไปพร้อมกับ form

ตัวเลือกBenefitCost
Native form-associated (static formAssociated = true + ElementInternals)custom element เข้าร่วมกับ <form> ได้เหมือน native control เช่น validate, reset, submit ค่าไปพร้อม form โดยไม่ต้องเขียน JavaScript เพิ่มรองรับเฉพาะ browser ที่ค่อนข้างใหม่ และ API ของ ElementInternals ยังมีรายละเอียดปลีกย่อยที่ต้องเรียนรู้
Polyfill / native <input> ที่ซ่อนอยู่ข้างใน shadow DOMรองรับ browser เก่าได้กว้างกว่าเพิ่มความซับซ้อนของ DOM ต้อง sync ค่าระหว่าง custom element กับ input ที่ซ่อนไว้เอง และเสี่ยงเรื่อง accessibility ถ้า sync พลาด
  • ลืมตั้ง static formAssociated = true — ถ้าไม่ตั้ง flag นี้ attachInternals() จะไม่คืนค่า ElementInternals ที่เข้าร่วม form ได้ และ setFormValue() จะไม่มีผลใด ๆ ทำให้ค่าของ custom input ไม่เคยถูก submit ไปพร้อม form เลย
  • เรียก attachInternals() ใน connectedCallback แทน constructor — object internals ต้องถูกสร้างเพียงครั้งเดียวต่อ element ถ้าเรียกซ้ำใน connectedCallback (ซึ่งอาจทำงานหลายครั้ง) จะได้ error เพราะเรียก attachInternals ซ้ำไม่ได้
  • ไม่เรียก setFormValue() ทุกครั้งที่ค่าเปลี่ยน — ถ้าอัปเดต internal state ของ element แต่ลืม sync ไปยัง form value ผ่าน internals.setFormValue() ฟอร์มจะ submit ค่าเก่าหรือค่าว่างแทนค่าที่ผู้ใช้เพิ่งกรอก

💡 ตัวอย่างจากของจริง

Adobe Photoshop on the web — ใช้ Lit ร่วมกับ form-associated custom elements เพื่อให้ custom input controls ของแอปเข้าร่วมกับ native <form> semantics ได้เต็มรูปแบบ

Shoelace — form control อย่าง <sl-input> และ <sl-checkbox> implement form-associated custom elements เพื่อให้ทำงานร่วมกับ native <form> submission และ validation ได้เหมือน native element

property แบบ static ใดที่ต้องตั้งค่าเป็น true เพื่อทำให้ custom element เป็น form-associated?
method ใดที่กำหนดค่าที่จะปรากฏใน FormData เมื่อ submit form?
form-associated element ควรทำอย่างไรเมื่อ attachInternals ไม่พร้อมใช้งาน?