JavaScript Dialog Utility Code (Demo Video + Source)

November 02, 2024

JavaScript dialog utility demo: prompting user.

YouTube video

index.html

<!DOCTYPE html>
<html>
<head>

</head>
<body>
  
  <template id="tmp-dialog-alert">
    <dialog class="wg-Windog">
    <!-- ^# tmp-dialog-alert -->
      <div class="backdrop"></div>
      <div class="wrapper">
        
        <form method="dialog" data-ref="form">
          <div data-slot="message"></div>
          <div class="void void-2x"></div>
          <div class="text-center">
            <button>Ok</button>
          </div>
        </form>
      </div>
    </dialog>
  </template>
  <template id="tmp-dialog-prompt">
    <dialog class="wg-Windog">
    <!-- ^# tmp-dialog-prompt -->
      <div class="backdrop"></div>
      <div class="wrapper">
        
        <form method="dialog" data-ref="form">
          <div data-slot="message"></div>
          <div class="void void-half"></div>
          
          <input class="input-style-default" type="text" data-slot="input"/>
          <div class="void void-2x"></div>
          
          <div class="d-flex justify-content-end gap-full">
            <button value="ok">Ok</button>
            <button>Cancel</button>
          </div>
        </form>
      </div>
    </dialog>
  </template>
  <template id="tmp-dialog-confirm">
    <dialog class="wg-Windog">
    <!-- ^# tmp-dialog-confirm -->
      <div class="backdrop"></div>
      <div class="wrapper">
      
        <form method="dialog" data-ref="form">
          <div data-slot="message"></div>
          <div class="void void-2x"></div>
          <div class="d-flex justify-content-end gap-full">
            <button value="ok">OK</button>
            <button>Cancel</button>
          </div>
        </form>
      </div>
    </dialog>
  </template>
  
  <style>
    .wg-Windog {
    &::backdrop {
      background: rgba(0, 0, 0, 0.4);
    }
    & {
      padding: 0;
      background: transparent;
    }
    .wrapper {
      background: white;
      position: relative;
      padding: 1rem;
    }
    .backdrop {
      width: 100%;
      height: 100%;
      position: fixed;
      top: 0;
      left: 0;
    }
    button:focus {
      border-color: #4CAF50;
      box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.3);
    }
  }
  </style>
  
  <script src="windog.js"></script>
  <script src="index.js"></script>
  
</body>
</html>

index.js

windog.prompt('Value').then(userVal => {
  
  console.log(userVal)
  
});

windog.js

let windog = (function() {

  let $ = document.querySelector.bind(document);
  
  let SELF = {
    alert,
    confirm,
    prompt,
    showDialogAsync,
  };
  
  // # options
  
  let dialogOptions = {
    alert: {
      templateId: 'tmp-dialog-alert',
    },
    confirm: {
      templateId: 'tmp-dialog-confirm',
      onClose: (dialogEl) => {
        return (dialogEl.returnValue == 'ok');
      },
    },
    prompt: {
      defaultValue: null,
      templateId: 'tmp-dialog-prompt',
      onClose: (dialogEl) => {
        if (dialogEl.returnValue == 'ok') {
          return dialogEl.querySelector('[data-slot="input"]')?.value;
        }
        return null;
      },
    },
  };
  
  // # function

  // # prompt
  async function prompt(message='', defaultValue='') {
    return await showDialogAsync(dialogOptions.prompt, onShowDefault, {
      message,
      defaultValue,
    });
  }
  
  async function confirm(message='') {
    return await showDialogAsync(dialogOptions.confirm, onShowDefault, {
      message,
    });
  }
  
  // # alert
  async function alert(message='') {
    return await showDialogAsync(dialogOptions.alert, onShowDefault, {
      message,
    });
  }
  
  function onShowDefault(dialogEl, extraData) {
    let {message, defaultValue} = extraData;
    dialogEl.querySelector('[data-slot="message"]')?.replaceChildren(message);
    dialogEl.querySelector('[data-slot="input"]')?.setAttribute('value', defaultValue); 
  }
  
  async function showDialogAsync(dialogOptions, onShow, extraData) {
    return new Promise(async resolve => {
      let persistentOptions = {
        resolver: {
          resolve,
        },
      };
      let mixedOptions = Object.assign(dialogOptions, extraData, persistentOptions);
      let {templateId, message, defaultValue} = mixedOptions;
      
      // queue this dialog and wait for previous dialog to resolve
      // await subscribeDialogAsync();
      
      let el = $(`#${templateId}`).content.cloneNode(true);
      let dialogEl = el.querySelector('dialog');
      let dialogData = {
        dialogItem: mixedOptions,
      };
      
      dialogEl.querySelector('.backdrop')?.addEventListener('click', async () => {
        if (typeof(dialogOptions.onBeforeClose) == 'function') {
          let isShouldClose = await dialogOptions.onBeforeClose?.(dialogEl);
          if (!isShouldClose) return;
        }
        dialogEl.close();
      });
      dialogEl.addEventListener('close', onClose);
      attachKeytrap(dialogEl, dialogData);
      dialogEl._windogData = dialogData;
      
      document.body.append(el);
      dialogEl.showModal();
      onShow?.(dialogEl, extraData);
    });
  }
  
  async function onClose(evt) {
    let dialogItem = evt.target._windogData.dialogItem;
    let dialogEl = evt.target;
    
    dialogEl.remove();
    let dialogResult = await dialogItem.onClose?.(dialogEl);
    dialogItem.resolver.resolve(dialogResult);
  }

  function attachKeytrap(dialogEl, dialogData) {
    let focusableContent = dialogEl.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
    
    dialogData.firstFocusableElement = focusableContent[0];
    dialogData.lastFocusableElement = focusableContent[focusableContent.length - 1];

    dialogEl.addEventListener('keydown', keyTrapper);
  }
        
  function keyTrapper(evt) {
    let isTabPressed = (evt.key == 'Tab');
    if (!isTabPressed) return;
    
    let dialogEl = evt.target.closest('dialog');
    let {firstFocusableElement, lastFocusableElement} = dialogEl._windogData;
    
    if (evt.shiftKey) { 
      if (document.activeElement === firstFocusableElement) {
        lastFocusableElement.focus(); 
        evt.preventDefault();
      }
    } else if (document.activeElement === lastFocusableElement) {
      firstFocusableElement.focus();
      evt.preventDefault();
    }
  }

  return SELF;
  
})();

Comments

Thank You

for your visit