סיכום
<howto-checkbox>
מייצג אפשרות בוליאנית בטופס. הסוג הנפוץ ביותר של תיבות סימון הוא סוג כפול המאפשר למשתמש לעבור בין שתי אפשרויות - הן מסומנות ולא מסומנות.
הרכיב מנסה להחיל את המאפיינים role="checkbox"
ו-tabindex="0"
על עצמו בפעם הראשונה שהוא נוצר. המאפיין role
עוזר לטכנולוגיות מסייעות, כמו קורא מסך, לומר למשתמש איזה סוג של אמצעי בקרה זה. המאפיין tabindex
מוסיף את הרכיב לסדר הטאבים, כך שניתן להתמקד בו באמצעות המקלדת ולהפעיל אותו. למידע נוסף על שני הנושאים האלה, אפשר לעיין במאמרים מה אפשר לעשות באמצעות ARIA? ושימוש ב-tabindex.
כשתיבת הסימון מסומנת, המערכת מוסיפה מאפיין בוליאני checked
ומגדירה את checked
התואם ל-true
. בנוסף, הרכיב מגדיר את המאפיין aria-checked
לערך "true"
או "false"
, בהתאם למצב שלו. לחיצה על תיבת הסימון בעזרת העכבר או מקש הרווח משנה את המצבים האלה.
בתיבת הסימון יש תמיכה גם במצב disabled
. אם המאפיין disabled
מוגדר כ-true או שהמאפיין disabled
מיושם, התיבה מסמנת את aria-disabled="true"
, מסירה את המאפיין tabindex
ומחזירה את המיקוד למסמך אם התיבה היא activeElement
הנוכחי.
תיבת הסימון מוצמדת לרכיב howto-label
כדי לוודא שיש לה שם נגיש.
חומרי עזר
- איך משתמשים ברכיבים ב-GitHub
- דפוס של תיבת סימון במסמך ARIA Authoring Practices 1.1
- מה אפשר לעשות בעזרת ARIA?
- שימוש ב-tabindex
הדגמה (דמו)
דוגמה לשימוש
<style>
howto-checkbox {
vertical-align: middle;
}
howto-label {
vertical-align: middle;
display: inline-block;
font-weight: bold;
font-family: sans-serif;
font-size: 20px;
margin-left: 8px;
}
</style>
<howto-checkbox id="join-checkbox"></howto-checkbox>
<howto-label for="join-checkbox">Join Newsletter</howto-label>
קוד
(function() {
הגדרת קודי מפתחות כדי לעזור בטיפול באירועים של מקלדת.
const KEYCODE = {
SPACE: 32,
};
העתקת תוכן מרכיב <template>
משפרת את הביצועים בהשוואה לשימוש ב-innerHTML, כי היא מונעת עלויות נוספות של ניתוח HTML.
const template = document.createElement('template');
template.innerHTML = `
<style>
:host {
display: inline-block;
background: url('../images/unchecked-checkbox.svg') no-repeat;
background-size: contain;
width: 24px;
height: 24px;
}
:host([hidden]) {
display: none;
}
:host([checked]) {
background: url('../images/checked-checkbox.svg') no-repeat;
background-size: contain;
}
:host([disabled]) {
background:
url('../images/unchecked-checkbox-disabled.svg') no-repeat;
background-size: contain;
}
:host([checked][disabled]) {
background:
url('../images/checked-checkbox-disabled.svg') no-repeat;
background-size: contain;
}
</style>
`;
class HowToCheckbox extends HTMLElement {
static get observedAttributes() {
return ['checked', 'disabled'];
}
ה-constructor של הרכיב מופעל בכל פעם שיוצרים מכונה חדשה. אפשר ליצור את המופעים על ידי ניתוח HTML, קריאה ל-document.createElement('howto-checkbox') או קריאה ל-new HowToCheckbox();. ה-constructor הוא מקום טוב ליצירת DOM בצל, אבל כדאי להימנע משינוי מאפיינים או צאצאים של DOM בהיר, כי יכול להיות שהם עדיין לא יהיו זמינים.
constructor() {
super();
this.attachShadow({mode: 'open'});
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
האירוע connectedCallback()
מופעל כשהרכיב מוכנס ל-DOM. זהו מקום טוב להגדיר בו את role
, tabindex
והמצב הפנימי, ולהתקין פונקציות event listener להתקנה.
connectedCallback() {
if (!this.hasAttribute('role'))
this.setAttribute('role', 'checkbox');
if (!this.hasAttribute('tabindex'))
this.setAttribute('tabindex', 0);
משתמש יכול להגדיר מאפיין למופיע של רכיב לפני שהאב טיפוס שלו מחובר לכיתה הזו. השיטה _upgradeProperty()
תבדוק אם יש מאפייני מופעים ותריץ אותם דרך הגדרות ה-set של הכיתה המתאימה. פרטים נוספים זמינים בקטע מאפיינים מושהה.
this._upgradeProperty('checked');
this._upgradeProperty('disabled');
this.addEventListener('keyup', this._onKeyUp);
this.addEventListener('click', this._onClick);
}
_upgradeProperty(prop) {
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
האירוע disconnectedCallback()
מופעל כשהרכיב מוסר מה-DOM. זהו מקום טוב לבצע פעולות ניקוי כמו שחרור הפניות והסרת מאזינים לאירועים.
disconnectedCallback() {
this.removeEventListener('keyup', this._onKeyUp);
this.removeEventListener('click', this._onClick);
}
הנכסים והמאפיינים התואמים שלהם צריכים לשקף זה את זה. ה-setter של המאפיין checked מטפל בערכים נכונים/לא נכונים ומשקף אותם במצב של המאפיין. פרטים נוספים זמינים בקטע הימנעות מחדירה חוזרת.
set checked(value) {
const isChecked = Boolean(value);
if (isChecked)
this.setAttribute('checked', '');
else
this.removeAttribute('checked');
}
get checked() {
return this.hasAttribute('checked');
}
set disabled(value) {
const isDisabled = Boolean(value);
if (isDisabled)
this.setAttribute('disabled', '');
else
this.removeAttribute('disabled');
}
get disabled() {
return this.hasAttribute('disabled');
}
הפונקציה attributeChangedCallback()
תופעל בכל שינוי של אחד מהמאפיינים במערך החשיפות (edAttributes). זהו מקום טוב לטיפול בתופעות לוואי, כמו הגדרת מאפייני ARIA.
attributeChangedCallback(name, oldValue, newValue) {
const hasValue = newValue !== null;
switch (name) {
case 'checked':
this.setAttribute('aria-checked', hasValue);
break;
case 'disabled':
this.setAttribute('aria-disabled', hasValue);
המאפיין tabindex
לא מספק דרך להסיר באופן מלא את יכולת המיקוד מאלמנט. עדיין אפשר להתמקד ברכיבים עם tabindex=-1
באמצעות העכבר או קריאה לרכיב focus()
. כדי לוודא שרכיב מסוים מושבת ואי אפשר להתמקד בו, צריך להסיר את המאפיין tabindex
.
if (hasValue) {
this.removeAttribute('tabindex');
אם המיקוד נמצא כרגע ברכיב הזה, מבטלים את המיקוד על ידי קריאה ל-method HTMLElement.blur()
this.blur();
} else {
this.setAttribute('tabindex', '0');
}
break;
}
}
_onKeyUp(event) {
לא מטפלים במקשי קיצור עם שינוי (modifier) שבדרך כלל משמשים לטכנולוגיה מסייעת.
if (event.altKey)
return;
switch (event.keyCode) {
case KEYCODE.SPACE:
event.preventDefault();
this._toggleChecked();
break;
המערכת מתעלמת מכל הקשה אחרת על מקש, ומועברת חזרה לדפדפן.
default:
return;
}
}
_onClick(event) {
this._toggleChecked();
}
_toggleChecked()
קורא למגדיר המאומת ומשנה את המצב שלו. _toggleChecked()
נגרמה רק על ידי פעולת משתמש, ולכן היא גם תשלח אירוע שינוי. האירוע הזה עובר 'בועה' כדי לחקות את ההתנהגות המקורית של <input type=checkbox>
.
_toggleChecked() {
if (this.disabled)
return;
this.checked = !this.checked;
this.dispatchEvent(new CustomEvent('change', {
detail: {
checked: this.checked,
},
bubbles: true,
}));
}
}
customElements.define('howto-checkbox', HowToCheckbox);
})();