مكوّنات طريقة التنفيذ - مربّع اختيار طريقة التنفيذ

ملخّص

يمثّل الرمز <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'];
    }

يتم تشغيل أسلوب الإنشاء للعنصر في أي وقت يتم فيه إنشاء مثيل جديد. يتم إنشاء النُسخ عن طريق تحليل HTML أو استدعاء document.createElement('howto-checkbox') أو استدعاء new HowToCheckbox();. إنّ المُنشئ هو مكان جيد لإنشاء DOM الظل، ولكن عليك تجنُّب لمس أي سمات أو عناصر DOM الخفيفة لأنّها قد لا تكون متاحة بعد.

    constructor() {
      super();
      this.attachShadow({mode: 'open'});
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }

يتم تنشيط الحدث connectedCallback() عند إدراج العنصر في DOM. وهي مكان جيد لضبط role وtabindex والحالة الداخلية الأولية وتثبيت مستمعي الأحداث.

    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. وهو مكان جيد لتنفيذ عمليات التنظيف، مثل إزالة الإشارات وإزالة مستمعي الأحداث.

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

يجب أن تتطابق الخصائص والسمات المقابلة لها. يعالج مُعدِّل السمة 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() عند تغيير أيّ من السمات في صفيف observedAttributes. وهي مكان جيد للتعامل مع الآثار الجانبية، مثل ضبط سمات 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() طريقة الضبط checked ويغيّر حالتها. وبما أنّ الحدث _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);
})();