Poznajemy HTMLDialogElement, świetną rzecz w HTML5, z której będziemy często korzystać i dziwić się, że kiedyś mogło tego nie być w HTMLu i zachodzić w głowę jak wtedy ludzie sobie radzili. Do dzieła.

Ok, oto przykładowy dialog:

<dialog open>
  <button autofocus>Close</button>
  <p>This modal dialog has a groovy backdrop!</p>
</dialog>
<button>Show the dialog</button>

Ten dialog ma atrybut open. Jeżeli ten atrybut istnieje, dialog jest pokazywany. Jeżeli go nie ma – nie ma dialogu. Atrybut open odpowiada za pokazywanie dialogu bez backdropu.

Są inne sposoby na pokazywanie dialogu, razem z tłem:

  • metoda d.show() otwiera dialog bez backdropu i pozwana na interakcje z jego otoczeniem
  • metoda d.close(retVal) zamyka dialog i ewentualny backdrop, może przyjąć return value, które można odczytać poprzez event close
  • metoda d.showModal() otwiera dialog razem z backdropem
  • formularz w dialogu z method dialog na submit zamyka dialog i przekazuje do close do return value dane z formularza, które można odczytać eventem close

Ok, jak wygląda pseudoelement backdrop, czyli stylujemy tło, które pojawia się w przypadku showModal:

::backdrop {
  background-image: linear-gradient(
    45deg,
    magenta,
    rebeccapurple,
    dodgerblue,
    green
  );
  opacity: 0.75;
}

Warto zwrócić uwagę, że element też można przekazać i różnie elementy też mogą mieć backdrop:

video::backdrop {
  background: hsla(0, 0%, 0%, .5);
}

Ale backdropu z np. alerta już sobie nie ostylujemy, jak nam się nie podoba, to po prostu swój własny alert napiszmy.

Ok, zamykanie jest dość proste, tworzymy w dialogu guzik, robimy event listener, dajemy mu metodę close z dialogu, koniec. Teoretycznie button type submit formmethod dialog też by zamknął podejrzewam.

Ok, ale czasem możemy chcieć mieć formularz. Oto przykład:

<dialog id="my-dialog" >
        <form method="dialog">
          <p>Would you like to continue?</p>
          <button type="submit" value="no">No</button>
          <button type="submit" value="yes">Yes</button>
        </form>
</dialog>

Te dane lądują w close jako ret val i można je czytać przez dialog.returnValue:

let dialog = document.getElementById("my-dialog");
setTimeout(() => dialog.showModal(), 1000);
dialog.addEventListener("close", function(event) {
  alert('Submitted value: "' + dialog.returnValue + '"');
});

Też pewnie dałoby radę czytać z formdata na guzik nie-submit z prevent default poza formem i przekazywać do close, ale to bez sensu by było.

Czytając o tym elemencie, zauważyłem na pewnym blogu ciekawą rzecz. Dopiszmy event listenera:

dialog.addEventListener("click", function(event) {
    console.log("click")
  });

Gdziekolwiek nie klikniemy odpalany jest event. A teraz ten kod:

dialog.addEventListener("click", e => {
    const dialogDimensions = dialog.getBoundingClientRect()
    if (
      e.clientX < dialogDimensions.left ||
      e.clientX > dialogDimensions.right ||
      e.clientY < dialogDimensions.top ||
      e.clientY > dialogDimensions.bottom
    ) {
        console.log("clicked outside")
    }
  })

Tu mamy taki bajer, że tylko kliknięcie poza dialogiem będzie uznane jako ten event. Można tu coś dopisać:

dialog.addEventListener("click", e => {
    const dialogDimensions = dialog.getBoundingClientRect()
    if (
      e.clientX < dialogDimensions.left ||
      e.clientX > dialogDimensions.right ||
      e.clientY < dialogDimensions.top ||
      e.clientY > dialogDimensions.bottom
    ) {
        dialog.close("aborted")
    }
  })

Teraz mamy submited value aborted, bo to przekazaliśmy do close jako return value a dzieje się to wtedy, gdy mamy odpalony dialog, ale klikniemy poza nim.