רכיבי HowTo – תיבת סימון של מדריך

אווה גספרוביץ'

סיכום

<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

שימוש לדוגמה

<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('הודעה-checkbox') או קריאה חדשה ל-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() תבדוק אם יש מאפייני מכונה ותריץ אותם באמצעות רכיבי המגדירים המתאימים של המחלקות. פרטים נוספים זמינים בקטע נכסים עצלניים.

      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. זה מקום טוב לבצע בו פעולות כמו הפצת קובצי עזר והסרת פונקציות event listener.

    disconnectedCallback() {
      this.removeEventListener('keyup', this._onKeyUp);
      this.removeEventListener('click', this._onClick);
    }

המאפיינים והמאפיינים התואמים צריכים לשקף זה את זה. מגדיר המאפיינים עבור נבדק מטפל בערכים אמיתיים/מקוריים ומשקף אותם למצב המאפיין. פרטים נוספים זמינים בקטע הימנעות מכניסה מחדש.

    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() כאשר מתבצע שינוי באחד מהמאפיינים במערך שנצפתה. זהו מקום טוב לטיפול בתופעות לוואי, כמו הגדרת מאפיינים של 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');

אם המיקוד נמצא כרגע על הרכיב הזה, יש לבטל את ההתמקדות על ידי קריאה לשיטה HTMLElement.blur()

            this.blur();
          } else {
            this.setAttribute('tabindex', '0');
          }
          break;
      }
    }

    _onKeyUp(event) {

אל תטפלו במקשי קיצור של מגבילים שמשמשים בדרך כלל בטכנולוגיה מסייעת.

      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);
})();