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

ملخّص

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