2

For the dialog element <dialog>, is there some sort of "open" event that is fired when it is shown, either normally or as a modal?

The spec isn't totally clear on this, and MDN just lists a bunch of inherited events.

The close event does fire, but I can't seem to get any sort of open event.

Example HTML:

<dialog>
  <h1>Oh hey, a dialog...</h1>
</dialog>

And in JavaScript:

document.querySelector('dialog').addEventListener('open', (e) => {
  console.log(e);
});
document.querySelector('dialog').showModal();
Brad
  • 159,648
  • 54
  • 349
  • 530

3 Answers3

2

No, there is no open event.

The dialog has an open property which can determine if it's opened or not, but unfortunately there's no event when this transitions.

Brad
  • 159,648
  • 54
  • 349
  • 530
  • 3
    There is currently no other way to open it than through javascript ( [related issue](https://github.com/whatwg/html/issues/3567) ), so it makes sense there is no event being sent here. However as you noted, the `open` property changes, but what you didn't say is that this IDL attribute reflects an HTML attribute, which is observable: https://jsfiddle.net/vmj8d79q/ Also, I think there should be a `focus` event to fire, but no current implementations seem to do that, and I may just have misread the specs. – Kaiido Feb 08 '20 at 10:20
  • @Kaiido Your MutationObserver method is a good workaround! Please post as an answer so I can accept it. Thanks, as always. :-) – Brad Feb 08 '20 at 19:47
  • @Brad I brought here this missing answer with the use of the MutationObserver(). I improved it by making it global and protected in an IIFE – Mister Jojo Dec 23 '22 at 22:26
2

An easy solution to your problem would be this :

<dialog id="dial">
  <h3> dialog element </h3>
  <button id="close">x</button>
</dialog>
<button id="buttonDialog" > Open   </button>
<button id="buttonDialog2"> Open 2 </button>
const
  dialog  = document.getElementById('dial')
, close   = document.getElementById('close')
, button  = document.getElementById('buttonDialog')
, button2 = document.getElementById('buttonDialog2')
  ;
button.addEventListener('click', event => {
  dialog.showModal()
  })
close.addEventListener('click', event => {
  dialog.close()
  })
let observer = new MutationObserver(function()  {
  if (event[0].attributeName == 'open') console.log('updated')
  });
observer.observe(dialog, { attributes: true })
  ;
button2.addEventListener('click', event => {
  dialog.setAttribute('open', true)
  })
Mister Jojo
  • 20,093
  • 6
  • 21
  • 40
  • Yes, duck punching/monkey patching can work. Though I worry about situations where the dialog is opened/closed by other means. I think dialogs can be opened with anchor fragments, can't they? Or did that part of the spec never land? – Brad Dec 01 '22 at 00:44
  • Hice el mismo codigo capturando la mutación del objeto, es mas general y podes usarlo como evento de cualquier propiedad – Daniel Garcia Dec 01 '22 at 07:03
  • 1
    All posts are expected to be in English. See [here](https://meta.stackexchange.com/questions/13676/do-posts-have-to-be-in-english-on-stack-exchange) – MD Zand Dec 05 '22 at 17:41
  • I edited the same code, it captures the mutation of the object, it is more general and you can use it as an event of any property – Daniel Garcia Dec 06 '22 at 20:06
1

As mentioned in the comments/anwers here, it is possible to generate this event using a MutationObserver().

As the answers here seem cruelly incomplete (even wrong), I did my own experiments to compose an easily exploitable answer.

Nota : _ <dialog> JS objects always have an open property, and it is a boolean value (true or false), even if this attribute is not present on the HTML declaration.

1st solution :

(() =>  // IIFE => add  event 'dialog-open' to all Dialog elements
  {
  const
    OpenEvent = new CustomEvent('dialog-open') 
  , ObserverM = new MutationObserver( recs =>
    {
    recs.forEach( ({attributeName: attr, target: dial }) =>
      {
      if (attr === 'open' && dial.open )
        dial.dispatchEvent( OpenEvent );    
      })
    });
  document.querySelectorAll('dialog').forEach( dial =>
    {
    ObserverM.observe( dial, { attributes: true } );
    })
  })() // IIFE end...

const
  btShowDial = document.querySelector('#bt-show_mDialog')
, dialogBox  = document.querySelector('#dialog-box')
, dialBForm  = document.querySelector('#dialog-box form')
  ;
btShowDial.onclick =_=>
  {
  dialogBox.showModal()
  }
dialogBox.addEventListener('dialog-open', () =>
  {
  dialBForm.inNum.valueAsNumber =  0 | Math.random() *10**4
  dialogBox.returnValue = 'exit with Escape key'
  })
dialogBox.onclose=_=>
  {
  console.log(`${dialBForm.inNum.value } is ${dialogBox.returnValue}` )
  }
<button id="bt-show_mDialog">show modal dialog..</button>

<dialog id="dialog-box">
  <form method="dialog">
    <h3>Dialog -1-</h3>
    <label> mumeric : <input type="number" name="inNum"></label>
    <br>
    <button value="good"> good </button>
    <button value="bad" > bad </button>
  </form>
</dialog>

Personally, I prefer to use their event methods directly on DOM Objects.
They have the advantage of being unique, and of making the code more readable.
so might as well add this one, rather than creating a specific custom event!

2nd solution :

(() =>  // IIFE => add  event 'dialog-open' to all Dialog elements
  {
  const Dialogs = document.querySelectorAll('dialog');

  Dialogs.forEach( dial => dial.onOpen = function(){} )  // add onOpen empty method.

  const ObserverM = new MutationObserver( recs =>
    {
    recs.forEach( ({attributeName: attr, target: dial }) =>
      {
      if (attr === 'open' && dial.open ) dial.onOpen();  // call onOpen Method...  
      })
    });
  Dialogs.forEach( dial => ObserverM.observe( dial, { attributes: true }))
  })() // IIFE end...

const
  btShowDial = document.querySelector('#bt-show_mDialog')
, dialogBox  = document.querySelector('#dialog-box')
, dialBForm  = document.querySelector('#dialog-box form')
  ;
btShowDial.onclick =_=>
  {
  dialogBox.showModal()
  }
dialogBox.onOpen =_=>
  {
  dialBForm.inNum.valueAsNumber =  0 | Math.random() *10**4
  dialogBox.returnValue         = 'exit with Escape key'
  }
dialogBox.onclose=_=>
  {
  console.log(`${dialBForm.inNum.value } is ${dialogBox.returnValue}` )
  }
dialog::backdrop {
  background: #00ddff5d;
  }
<button id="bt-show_mDialog">show modal dialog..</button>

<dialog id="dialog-box">
  <form method="dialog">
    <h3>Dialog -1-</h3>
    <label> mumeric : <input type="number" name="inNum"></label>
    <br>
    <button value="good"> good </button>
    <button value="bad" > bad </button>
  </form>
</dialog>
Mister Jojo
  • 20,093
  • 6
  • 21
  • 40