אל תשתמשו ב-<input> מוסתר כדי להציג ערך של פקד מותאם אישית בטופס. השתמשו ברכיבים מותאמים אישית שמשויכים לטופס (FACE): באמצעות formAssociated ו-ElementInternals, הרכיב שולח את הערך שלו, משתתף באימות ומשקף את מצב הטופס באופן מקורי.
class RatingInput extends HTMLElement {
// MANDATORY: opt in. Without this, attachInternals() form APIs are unavailable.
static formAssociated = true;
#internals;
#value = '';
constructor() {
super();
this.#internals = this.attachInternals();
this.attachShadow({ mode: 'open', delegatesFocus: true });
}
connectedCallback() {
// Validate initial state once attributes (such as `required`) are present.
this.#validate();
}
get value() { return this.#value; }
set value(v) {
this.#value = v;
// Push the value into the owning <form> for submission.
this.#internals.setFormValue(v);
this.#validate();
}
#validate() {
if (this.hasAttribute('required') && !this.#value) {
// Mark invalid and supply the native validation message + anchor.
this.#internals.setValidity(
{ valueMissing: true },
'Please choose a rating.',
this.shadowRoot.querySelector('[tabindex]'),
);
} else {
this.#internals.setValidity({}); // clear
}
}
// Optional FACE lifecycle hooks:
formResetCallback() { this.value = ''; }
formDisabledCallback(disabled) {
// Disable internal controls so they leave the tab order and ignore clicks.
// Do NOT toggle `disabled` on `this`, or a `<fieldset disabled>` toggle will
// leave the host permanently stuck with its own `disabled` attribute.
for (const el of this.shadowRoot.querySelectorAll('button')) {
el.disabled = disabled;
}
}
formStateRestoreCallback(state) { this.value = state; }
}
customElements.define('rating-input', RatingInput);
נקודות עיקריות:
- השדה
static formAssociated = trueהוא חובה. הוא פותח את השיטות שלElementInternalsשקשורות לטופס ואת מחזור החיים שלformXxxCallback. - השדה
setFormValue(value)הוא השדה שנשלח, ואין צורך בקלט מוסתר. מעביריםFormDataלפקדים עם כמה ערכים. -
setValidity(flags, message, anchor)משתלב עם אימות אילוצים מקורי: הטופס לא יישלח אם הוא לא תקין, חלים:invalid/:valid, ורכיב העוגן מקבל מיקוד בדוח. כדי למחוק, צריך להתקשר אלsetValidity({}). המחלקות המדומות:user-valid/:user-invalidשמודעות לאינטראקציה לא מקושרות באופן עקבי לרכיבים שמשויכים לטפסים בדפדפנים שונים. חשוב לוודא שיש תמיכה לפני שמסתמכים עליהן, ולא להניח שהן עוקבות אחרי האינטראקציה של המשתמשים כמו שהן עושות בפקדים מקוריים. - צריך להטמיע את
formResetCallback,formDisabledCallbackו-formStateRestoreCallbackכדי שהרכיב יתנהג כמו פקד מקורי באיפוס, ב-fieldset[disabled]ובשחזור של מילוי אוטומטי קדימה/אחורה. ב-formDisabledCallback(disabled), צריך להשבית את אמצעי הבקרה הפנימיים (או להסיר אותם מסדר הכרטיסיות ולהתעלם מהקלט) במקום להגדיר אתdisabledבמארח – הגדרת מאפיין המארח בתוך הקריאה החוזרת (callback) מונעת הפעלה מחדש כשמפעילים מחדש את<fieldset>הקודם, והמארח כבר תואם ל-:disabledבאופן אוטומטי. - צריך לפעול לפי המוסכמות של הפלטפורמה לגבי אירועים שנשלחים, ולהעדיף את האירועים המובנים
inputו-changeעל פני אירועים בהתאמה אישית. האירועinputמופעל כשהערך משתנה כתוצאה ישירה מפעולת המשתמש (הקלדה, גרירת פס הזזה). האירועchangeמופעל פעם אחת בכל רצף של שינויים, כשהמשתמש מבצע את הפעולה (שחרור פס ההזזה, טשטוש השדה). אפשר ליצור אותם באמצעותInputEventו-Eventבהתאמה.
אסטרטגיות חלופיות
לפני שמסתמכים על התכונה, צריך לזהות אותה באמצעות תמיכה ב-static formAssociated: 'attachInternals' in HTMLElement.prototype && 'setFormValue' in ElementInternals.prototype. אם הוא לא זמין, צריך לחזור ל-<input> מקורי שמוסתר באופן חזותי ומסונכרן עם הערך של הפקד, כדי שהטופס עדיין יישלח – המקרה היחיד שבו התבנית של הקלט המוסתר מוצדקת.