5

How would you close dialog box by clicking outside of it in addition to clicking the Esc button?

<!DOCTYPE html>
<html>
<body>

<p>Click the button to show the dialog.</p>

<button onclick="myFunction()">Show dialog</button>

<p><b>Note:</b> Use the "Esc" button to close the modal.</p>
<p><b>Note:</b> The dialog element is only supported in Chrome 37+, Safari 6+ and Opera 24+.</p>

<dialog id="myDialog">This is a dialog window</dialog>

<script>
function myFunction() { 
  document.getElementById("myDialog").showModal(); 
} 
</script>

</body>
</html>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
Rod
  • 14,529
  • 31
  • 118
  • 230
  • `click` event handler and `keypress` event handler on `body` or on a full-view overlay. – Ouroborus Jun 21 '22 at 17:44
  • Your question has nothing to do with Blazor. – enet Jun 21 '22 at 17:44
  • Does this answer your question? [How to close the new html tag by clicking on its ::backdrop](https://stackoverflow.com/questions/25864259/how-to-close-the-new-html-dialog-tag-by-clicking-on-its-backdrop) – aloisdg Jul 14 '22 at 14:27
  • When google introduced the W3C dialog element, light dismiss was not available. The reason behind this missing feature is because W3C is introducing the popover attribute. It's a new 2023 feature that will act almost like a dialog but adopt the light dismiss behavior natively. Using popover you can close the dialog by clicking outside (aka a light dismiss). – Kir Kanos Aug 29 '23 at 19:40

5 Answers5

2

You can also try the way described by Adam Argyle here: https://web.dev/building-a-dialog-component/#adding-light-dismiss

  1. Get the dialog element and add an event listener to the dialog element
    const dialog = document.querySelector('dialog);

    dialog.addEventListener('click', lightDismiss);
  1. Create the handler function and check whether the dialog element triggered the event. If yes, call .close() method. This works because, by default, the dialog element creates a backdrop that covers all other elements on the page, and clicking there === clicking on the dialog element itself. This will not affect children elements inside the dialog, so clicking there will not close the dialog.
    const lightDismiss = ({target:dialog}) => {
      if (dialog.nodeName === 'DIALOG')
        dialog.close('dismiss')
    }

By the way, I recommend reading this wonderful dialog tutorial.

a.lavrov
  • 31
  • 4
  • it only works for that specific example. If you click on the padded area of the dialog it will close which is not what the op intended – Ruslan Jun 08 '23 at 15:26
1

The showModal() method of the HTMLDialogElement interface displays the dialog as a modal, over the top of any other dialogs that might be present. It displays into the top layer, along with a ::backdrop pseudo-element. Interaction outside the dialog is blocked and the content outside it is rendered inert. MDN documentation

As you can see, outside of the dialog all other html nodes will be blocked. But, we can add a child node as a wrapper for our content, thus we separate our content from the outside area. Let's remove the extra padding from dialog with css and add it to the modal class.

const dialog = document.getElementById('myDialog');
const button = document.querySelector('button');

function openDialog() {
  dialog.showModal();
}

function closeDialog(event) {
  // If the target dialog is
  if (!event.target.contains(dialog)) return;
  dialog.close();
}

button.addEventListener('click', openDialog);
document.addEventListener('click', closeDialog);
dialog {
  padding: 0;
}

.modal {
  display: flex;
  padding: 1rem;
}

dialog::backdrop {
  background-color: rgba(255, 0, 0, 0.25);
}
<p>Click the button to show the dialog.</p>

<button>Show dialog</button>

<p><b>Note:</b> Use the "Esc" button to close the modal.</p>
<p><b>Note:</b> The dialog element is only supported in Chrome 37+, Safari 6+ and Opera 24+.</p>

<dialog id="myDialog">
  <div class="modal">
    <p>This is a dialog window</p>
  </div>
</dialog>
Anton
  • 8,058
  • 1
  • 9
  • 27
0

In general, you can use the onblur- or onfocusout-event. Both fire when you leave the element aka. click on another element. (Though I haven't tested it with a dialog specifically.)

<dialog onblur="yourFunction()">
A-Tech
  • 806
  • 6
  • 22
0

I know it is a little bit impractical but you can add a div that is as big as the whole screen and, when you click on 'show dialog', the div's display is block and when you click on the div that has the size of the screen, the dialog disappears.

<!DOCTYPE html>
<html>
<body>

<p>Click the button to show the dialog.</p>

<button onclick="myFunction()">Show dialog</button>

<p><b>Note:</b> Use the "Esc" button to close the modal.</p>
<p><b>Note:</b> The dialog element is only supported in Chrome 37+, Safari 6+ and Opera 24+.</p>

<div id="close-dialog-element" onclick="closeDialog()">
    <dialog close id="myDialog">This is a dialog window</dialog>
</div>

<style>
#close-dialog-window {
    width: 100vw;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 10;
}

#myDialog {
    z-index: 11;
}
</style>

<script>
function closeDialog() {
    document.getElementById('close-dialog-element').style.display = 'none';
    document.getElementById('myDialog').close();
}

function myFunction() { 
  document.getElementById("myDialog").showModal(); 
} 
</script>

</body>
</html>
Ouroborus
  • 16,237
  • 4
  • 39
  • 62
0

.showModal() uses a ::backdrop which is a pseudo-element which is invisible to the DOM, therefore untouchable. The example below shows how to use the .show() method so that a click outside a <dialog> is possible.

Details are commented in example

// Reference <dialog> and <button>
const modal = document.querySelector('.modal');
const open = document.querySelector('.open');

// Bind click event to <button>
open.onclick = openModal;
// Bind click event to <main>
document.querySelector('main').onclick = closeModal;
// Bind keydown event close the <dialog> when Esc key is clicked
document.onkeydown = e => {
  if (e.key === "Escape") {
    closeModal(e);
  }
}

function openModal(e) {
  // This opens <dialog> with no ::backdrop
  modal.show();
  // A .open to <html> so it appears there's a ::backdrop
  document.documentElement.classList.add('open');
  // Disable <button>
  this.disabled = true;
}

function closeModal(e) {
  // Reference the tag the user clicked
  const clk = e.target;
  /*
  If the user clicked the <dialog> or anything within the
  <dialog> OR the <button>...
  ... do nothing...
  ... otherwise close <dialog>...
  ... remove .open from <html>...
  ... enable <button>
  */
  if (modal.contains(clk) || clk.matches('.open')) {
    return
  }
  modal.close();
  document.documentElement.classList.remove('open');
  open.disabled = false;
}
/* Simulate a ::backdrop */
:root.open {
  overflow: hidden;
  background-color: rgba(0, 0, 0, 0.2);
}

/* Cover all of viewport */
main {
  width: 100%;
  min-height: 100vh;
}

/* When <dialog> is open <button> should be inert */
.open .open {
  pointer-events: none;
}
<!DOCTYPE html>
<html>

<body>
  <!-- Wrap everything with <main> -->
  <main>
    <p>Click the button to show the dialog.</p>

    <button class='open'>Show Modal</button>

    <dialog class="modal">
      <p><b>Note:</b> Use the "Esc" button to close the modal.</p>
      <p><b>Note:</b> Or click outside of modal to close it.</p>
    </dialog>

  </main>
</body>

</html>
zer00ne
  • 41,936
  • 6
  • 41
  • 68