Уважаемые пользователи Голос!
Сайт доступен в режиме «чтение» до сентября 2020 года. Операции с токенами Golos, Cyber можно проводить, используя альтернативные клиенты или через эксплорер Cyberway. Подробности здесь: https://golos.io/@goloscore/operacii-s-tokenami-golos-cyber-1594822432061
С уважением, команда “Голос”
GOLOS
RU
EN
UA
nizovtsevnv
6 лет назад

Пример создания собственного веб-компонента без использования фрэймворков

Веб-компоненты
Учебный пример проведёт вас сквозь создание собственного HTML5 тэга <howto-checkbox> реализующего элемент ввода "флажок", который позволяет пользователю переключать себя между двумя состояниями.

Исходный пример вы можете посмотреть здесь.

При создании элемент устанавливает атрибуты role = "checkbox" и tabindex = "0", которые позволяют использовать его с клавиатурой и такими технологиями, как устройство чтения с экрана. Более подробное описание можно найти по ссылкам в исходном примере.

В примере не реализована отправка данных в составе блока <form>.

Когда флажок установлен, он добавляет логический атрибут checked и устанавливает его значение в true. Также, элемент устанавливает атрибут aria-checked. Щелчок по элементу с помощью мыши или пробела переключает состояние между true и false.

Флажок также поддерживает отключенное состояние. Если указано свойство disabled = "true" или атрибут disabled, флажок устанавливает aria-disabled = "true", удаляет атрибут tabindex и убирает фокус с себя.

Полный JavaScript код элемента с комментариями:

// Инкапсулируем код в анонимную функцию
(function() {
  // Установим код кнопки клавиатуры для переключения
  const KEYCODE = {
    SPACE: 32,
  };

  // Клонирование содержимого из шаблона лучше использования
  // innerHTML при создании элемента тк не требуется многократно 
  // парсить содержимое.
  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, если встречен тэг <howto-checkbox>, 
    // - при вызове document.createElement('howto-checkbox'),
    // - при вызове конструктора new HowToCheckbox(); 
    // Конструктор - хорошее место для создания теневого DOM, но не следует
    // использовать атрибуты и DOM потомков, тк они ещё не доступны.
    constructor() {
      super();
      this.attachShadow({mode: 'open'});
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }

    // connectedCallback() исполняется при вставке элемента в DOM.
    // Это хорошее место для инициализации атрибутов и обработчиков
    // событий.
    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);
    }

    // Свойства и связанные с ними атрибуты тэгов должны отражать друг друга.
    // Сэттер связывающий свойство логического состояния и атрибут тэга.
    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 мешает удалению фокуса c элемента.
          // Элементы с 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() исполняется только по действию пользователя,
    // то метод также генерирует событие изменения состояния.
    // Режим bubbles используется для большей схожести с <input type=checkbox>.
    _toggleChecked() {
      if (this.disabled)
        return;
      this.checked = !this.checked;
      this.dispatchEvent(new CustomEvent('change', {
        detail: {
          checked: this.checked,
        },
        bubbles: true,
      }));
    }
  }

  window.customElements.define('howto-checkbox', HowToCheckbox);
})();

Пример использования в HTML:

<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>
3
0.000 GOLOS
На Golos с October 2017
Комментарии (0)
Сортировать по:
Сначала старые