Tutorial

Example Ten (Modal dialog)

Modal dialog

Lorem Ipsum

You can dismiss this modal by the buttons or pressing Escape. All form elements are removed from tab index except those in the modal. Result of closing model is printed to the console.

Do you like waffles?

<button type="button" id="open-modal">Open example modal</button>

<simple-modal id="my-modal">
  <span slot="header">Lorem Ipsum</span>
  <div slot="content">
    <p>You can dismiss this modal by the buttons or pressing <kbd>Escape</kbd>. All form elements are removed from tab index except those in the modal. Result of closing model is printed to the console.</p>
    <p>Do you like waffles?</p>
  </div>
  <div class="btn-group" slot="footer">
    <button type="button" data-action="reject">No</button>
    <button type="button" data-action="confirm">Yes</button>
  </div>
</simple-modal>

SimpleModal

import Component from 'https://fancy-pants.js.org/min/component.js';
import { confineTabbing, releaseTabbing } from 'https://tritarget.org/cdn/tabbing.js';

const uniqueId = (() => {
  let lastId = 0;
  return () => lastId++;
})();

class SimpleModal extends Component {
  releaseKeyboard = () => {};
  modalResolver = { resolve() {}, reject() {} };
  constructor() {
    super();
    this.id = this.id || `simple-model${uniqueId()}`;
  }
  connectedCallback() {
    super.connectedCallback();
    this.dialog = this.querySelector('dialog');
    this.addEventListener('click', (e) => this.handleAction(e));
    this.addEventListener('close', () => this.cancel());
  }
  handleAction(event) {
    event.stopPropagation();
    let { action } = event.target.dataset;
    if (['cancel', 'confirm', 'reject', 'error'].includes(action)) {
      this[action]();
    } else if (event.target === this) {
      this.cancel();
    }
  }
  open() {
    return new Promise((resolve, reject) => {
      this.modalResolver = { resolve, reject };
      this.dialog.showModal();
    }).finally(() => this.dialog.close());
  }
  cancel(value) {
    this.modalResolver.resolve({ reason: 'cancelled', value });
  }
  confirm(value) {
    this.modalResolver.resolve({ reason: 'confirmed', value });
  }
  reject(value) {
    this.modalResolver.resolve({ reason: 'rejected', value });
  }
  error(error) {
    this.modalResolver.reject(error);
  }
  render() {
    let ariaLabelId = `${this.id}-title`;
    this.dialog.querySelector('header h1').id = ariaLabelId;
    this.dialog.setAttribute('aria-labelledby', ariaLabelId);
  }
  static get observedAttributes() {
    return ['open'];
  }
  static get template() {
    return `
      <dialog>
        <header>
          <button type="button" aria-label="dismiss" data-action="cancel">&cross;</button>
          <h1><slot name="header"></slot></h1>
        </header>
        <section><slot name="content"></slot></section>
        <footer><slot name="footer"></slot></footer>
      </dialog>
    `;
  }
}
SimpleModal.register();