בעזרת אירוע חדש וממשקי API של רכיבים מותאמים אישית, השתתפות בטופס הפכה להיות הרבה יותר קלה.
מפתחים רבים יוצרים פקדי טפסים מותאמים אישית, כדי לספק פקדים שלא מובְנים בדפדפן או כדי להתאים אישית את המראה והתחושה מעבר למה שאפשר לעשות באמצעות פקדי הטפסים המובְנים.
עם זאת, יכול להיות שיהיה קשה לשחזר את התכונות של פקדי הטפסים המובנים ב-HTML. אלה כמה מהתכונות שמקבל אלמנט <input>
באופן אוטומטי כשמוסיפים אותו לטופס:
- הקלט מתווסף באופן אוטומטי לרשימת אמצעי הבקרה של הטופס.
- הערך של הקלט נשלח באופן אוטומטי עם הטופס.
- הקלט משתתף באימות הטופס. אפשר להגדיר סגנון לקלט באמצעות פסאודו-הקלאסות
:valid
ו-:invalid
. - הקלט מקבל הודעה כשהטופס מתאפס, כשהטופס נטען מחדש או כשהדפדפן מנסה למלא אוטומטית את פרטי הטופס.
בדרך כלל, אמצעי הבקרה של טפסים מותאמים אישית כוללים רק חלק מהתכונות האלה. מפתחים יכולים לעקוף חלק מהמגבלות ב-JavaScript, למשל להוסיף <input>
מוסתר לטופס כדי להשתתף בשליחת הטופס. אבל יש תכונות אחרות שאי אפשר לחקות ב-JavaScript בלבד.
שתי תכונות חדשות לאינטרנט מאפשרות ליצור בקלות רבה יותר רכיבי בקרה מותאמים אישית בטופס, ומבטלות את המגבלות של רכיבי הבקרה המותאמים אישית הקיימים:
- האירוע
formdata
מאפשר לאובייקט JavaScript שרירותי להשתתף בשליחת הטופס, כך שאפשר להוסיף נתוני טפסים בלי להשתמש ב-<input>
מוסתר. - ממשק ה-API של רכיבים מותאמים אישית המשויכים לטופס מאפשר לרכיבים מותאמים אישית לפעול יותר כמו פקדי טופס מובנים.
אפשר להשתמש בשתי התכונות האלה כדי ליצור אמצעי בקרה חדשים שפועלים טוב יותר.
ממשק API מבוסס-אירועים
האירוע 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);
});
אתם יכולים לנסות את זה באמצעות הדוגמה שלנו ב-Glitch. חשוב להריץ אותו ב-Chrome 77 ואילך כדי לראות את ה-API בפעולה.
תאימות דפדפן
רכיבים מותאמים אישית המשויכים לטופס
אפשר להשתמש ב-API מבוסס-האירועים עם כל סוג של רכיב, אבל הוא מאפשר רק אינטראקציה עם תהליך השליחה.
אמצעי הבקרה הסטנדרטיים של טפסים משתתפים בחלקים רבים במחזור החיים של הטפסים, מלבד השליחה. רכיבים מותאמים אישית שמשויכים לטופס נועדו לגשר על הפער בין ווידג'טים מותאמים אישית לבין פקדים מובנים. אלמנטים מותאמים אישית שמשויכים לטופס תואמים לרבים מהמאפיינים של רכיבי טופס סטנדרטיים:
- כשמקפידים להציב רכיב מותאם אישית שמשויך לטופס בתוך
<form>
, הוא משויך באופן אוטומטי לטופס, כמו רכיב בקרה שמסופק על ידי הדפדפן. - אפשר לתייג את הרכיב באמצעות רכיב
<label>
. - האלמנט יכול להגדיר ערך שיישלח באופן אוטומטי עם הטופס.
- הרכיב יכול להגדיר דגל שמציין אם יש לו קלט תקף או לא. אם אחד מאמצעי הבקרה של הטופס מכיל קלט לא תקין, לא ניתן לשלוח את הטופס.
- הרכיב יכול לספק קריאות חזרה (callbacks) לחלקים שונים במחזור החיים של הטופס – למשל, כשהטופס מושבת או מתאפס למצב ברירת המחדל שלו.
- הרכיב תומך בפסאודו-כיתות רגילות של CSS לפקדי טפסים, כמו
:disabled
ו-:invalid
.
יש המון תכונות! במאמר הזה לא נעסוק בכל האפשרויות, אבל נסביר את העקרונות הבסיסיים שצריך להכיר כדי לשלב את הרכיב המותאם אישית בטופס.
הגדרת רכיב מותאם אישית שמשויך לטופס
כדי להפוך אלמנט מותאם אישית לאלמנט מותאם אישית שמשויך לטופס, צריך לבצע כמה שלבים נוספים:
- מוסיפים מאפיין
formAssociated
סטטי לכיתה של הרכיב המותאם אישית. כך הדפדפן יתייחס לרכיב כאל רכיב בקרה בטופס. - קוראים ל-method
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>
הגדרת ערך
ה-method attachInternals()
מחזיר אובייקט ElementInternals
שמספק גישה לממשקי API של רכיבי בקרה בטופס. השיטה הבסיסית ביותר היא השיטה setFormValue()
, שמגדירה את הערך הנוכחי של אמצעי הבקרה.
שיטת setFormValue()
יכולה לקבל אחד משלושה סוגי ערכים:
- ערך מחרוזת.
- אובייקט
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);
אימות קלט
אפשר גם לקרוא ל-method setValidity()
באובייקט internals כדי לאפשר לבקרה להשתתף באימות הטפסים.
// 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
, בדיוק כמו רכיב שליטה מובנה בטופס.
קריאות חזרה במחזור החיים
ממשק API של רכיב מותאם אישית שמשויך לטופס כולל קבוצה של קריאות חזרה נוספות למחזור חיים כדי לקשר אותו למחזור החיים של הטופס. פונקציות ה-call back הן אופציונליות: צריך להטמיע פונקציית call back רק אם הרכיב צריך לעשות משהו בשלב הזה במחזור החיים.
void formAssociatedCallback(form)
הקריאה מתבצעת כשהדפדפן משיייך את האלמנט לאלמנט טופס, או מבטל את השיוך של האלמנט לאלמנט טופס.
void formDisabledCallback(disabled)
הפונקציה נקראת אחרי שהמצב disabled
של הרכיב משתנה, בין אם המאפיין disabled
של הרכיב הזה נוסף או הוסר, ובין אם המצב disabled
השתנה ב-<fieldset>
שהוא אב קדמון של הרכיב הזה. הפרמטר disabled
מייצג את הסטטוס החדש 'מושבת' של הרכיב. לדוגמה, הרכיב עשוי להשבית רכיבים ב-DOM הצל שלו כשהוא מושבת.
void formResetCallback()
הקריאה מתבצעת אחרי איפוס הטופס. הרכיב אמור לאפס את עצמו למצב ברירת מחדל כלשהו. ברכיבי <input>
, בדרך כלל צריך להגדיר את המאפיין value
כך שיתאים למאפיין value
שהוגדר ב-Markup (או במקרה של תיבת סימון, להגדיר את המאפיין checked
כך שיתאים למאפיין checked
.
void formStateRestoreCallback(state, mode)
הקריאה מתבצעת באחת משתי נסיבות:
- כשהדפדפן משחזר את המצב של הרכיב (לדוגמה, אחרי ניווט או כשהדפדפן מופעל מחדש). הארגומנט
mode
הוא"restore"
במקרה הזה. - כשתכונות העזרה בהזנת הנתונים בדפדפן, כמו מילוי אוטומטי של טפסים, מגדירות ערך. הארגומנט
mode
הוא"autocomplete"
במקרה הזה.
הסוג של הארגומנט הראשון תלוי באופן שבו הופעלה שיטת setFormValue()
. פרטים נוספים זמינים במאמר שחזור מצב טופס.
שחזור מצב הטופס
בנסיבות מסוימות, למשל כשעוברים חזרה לדף או מפעילים מחדש את הדפדפן, הדפדפן עשוי לנסות לשחזר את הטופס למצב שבו המשתמש השאיר אותו.
ברכיב בהתאמה אישית שמשויך לטופס, המצב ששוחזר מגיע מהערכים שאתם מעבירים לשיטה setFormValue()
. אפשר לקרוא לשיטה עם פרמטר ערך יחיד, כפי שמוצג בדוגמאות הקודמות, או עם שני פרמטרים:
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 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
והרכיבים המותאמים אישית שמשויכים לטופס זמינים. בשלב הזה לא פורסמו polyfills לאף אחת מהתכונות האלה. בשני המקרים, אפשר להשתמש ברכיב טופס מוסתר כדי להעביר את הערך של אמצעי הבקרה לטופס. סביר להניח שיהיה קשה או בלתי אפשרי להוסיף 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 של רכיבים מותאמים אישית שמשויכים לטופס מספק קבוצה חדשה של יכולות ליצירת רכיבי בקרה מותאמים אישית לטופס, שפועלים כמו רכיבי בקרה מובנים לטופס.
התמונה הראשית (Hero) היא של Oudom Pravat ב-Unsplash.