คอมโพเนนต์วิธีการ – ช่องทำเครื่องหมายวิธีการ

สรุป

<howto-checkbox> แสดงตัวเลือกบูลีนในแบบฟอร์ม ประเภทที่พบบ่อยที่สุด ในช่องทำเครื่องหมายเป็นแบบคู่ ซึ่งให้ผู้ใช้สามารถสลับระหว่างช่อง ทั้งหมด โดยเลือกและยกเลิกการเลือกแล้ว

องค์ประกอบพยายามใช้แอตทริบิวต์ role="checkbox" ด้วยตนเองและ tabindex="0" เมื่อสร้างขึ้นครั้งแรก แอตทริบิวต์ role ช่วยในการอำนวยความสะดวก เทคโนโลยีอย่างโปรแกรมอ่านหน้าจอ จะบอกผู้ใช้ว่าการควบคุมนี้เป็นแบบใด แอตทริบิวต์ tabindex ทำให้องค์ประกอบเรียงลำดับแท็บได้ ทำให้เป็นแป้นพิมพ์ โฟกัสและใช้งานได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับทั้ง 2 หัวข้อนี้ได้ที่ ARIA ทำอะไรได้บ้างและการใช้ Tabindex

เมื่อเลือกช่องทำเครื่องหมาย ระบบจะเพิ่มแอตทริบิวต์บูลีน checked และชุด พร็อพเพอร์ตี้ checked ที่เกี่ยวข้องกับ true นอกจากนี้ องค์ประกอบจะกำหนด aria-checked เป็นแอตทริบิวต์ "true" หรือ "false" ทั้งนี้ขึ้นอยู่กับ เมื่อคลิกช่องทำเครื่องหมายด้วยเมาส์หรือแป้นเว้นวรรค จะเป็นการสลับ รัฐที่ตรวจสอบแล้ว

ช่องทำเครื่องหมายยังรองรับสถานะ disabled ด้วย หากพร็อพเพอร์ตี้ disabled ตั้งค่าเป็น "จริง" หรือใช้แอตทริบิวต์ 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> มีประสิทธิภาพมากกว่าการใช้ teriorHTML เนื่องจากช่วยป้องกันต้นทุนการแยกวิเคราะห์ 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') หรือเรียกใช้ HowToCheckbox(); เครื่องมือสร้างเป็นพื้นที่ที่ดีในการสร้าง Shadow DOM อย่างไรก็ตาม คุณควรหลีกเลี่ยงการสัมผัสแอตทริบิวต์หรือองค์ประกอบย่อยของ Light DOM เนื่องจากอาจยังไม่พร้อมใช้งาน

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

connectedCallback() เริ่มทำงานเมื่อแทรกองค์ประกอบลงใน DOM ซึ่งเป็นจุดที่เหมาะสมในการตั้งค่า role, tabindex, สถานะภายใน, และติดตั้ง Listener เหตุการณ์

    connectedCallback() {
      if (!this.hasAttribute('role'))
        this.setAttribute('role', 'checkbox');
      if (!this.hasAttribute('tabindex'))
        this.setAttribute('tabindex', 0);

ผู้ใช้อาจตั้งค่าพร็อพเพอร์ตี้บนอินสแตนซ์ขององค์ประกอบ ก่อนที่จะเชื่อมต่อต้นแบบขององค์ประกอบกับคลาสนี้ เมธอด _upgradeProperty() จะตรวจสอบพร็อพเพอร์ตี้อินสแตนซ์และเรียกใช้ผ่านตัวตั้งค่าคลาสที่เหมาะสม ดูรายละเอียดเพิ่มเติมได้ที่ส่วนพร็อพเพอร์ตี้แบบ Lazy Loading

      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 จุดนี้เป็นตำแหน่งที่ดีในการจัดการข้อมูล เช่น การเผยแพร่ข้อมูลอ้างอิงและนำ 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);
})();