Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions www/content/examples/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ You can copy and paste them and then adjust them for your needs.
| [Dialogs - Browser](@/examples/dialogs.md) | Demonstrates the prompt and confirm dialogs |
| [Dialogs - UIKit](@/examples/modal-uikit.md) | Demonstrates modal dialogs using UIKit |
| [Dialogs - Bootstrap](@/examples/modal-bootstrap.md) | Demonstrates modal dialogs using Bootstrap |
| [Dialogs - Dialog Tag](@/examples/modal-dialog.md) | Demonstrates modal dialogs using html dialogs |
| [Dialogs - Custom](@/examples/modal-custom.md) | Demonstrates modal dialogs from scratch |
| [Tabs (Using HATEOAS)](@/examples/tabs-hateoas.md) | Demonstrates how to display and select tabs using HATEOAS principles |
| [Tabs (Using JavaScript)](@/examples/tabs-javascript.md) | Demonstrates how to display and select tabs using JavaScript |
Expand Down
91 changes: 91 additions & 0 deletions www/content/examples/modal-dialog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
+++
title = "Modal Dialogs with HTML Dialogs"
template = "demo.html"
+++

Since 2022 the HTML spec has included a `<dialog>` tag and it works well with
htmx. Consider the following html:

```html
<button
class="btn primary"
hx-get="/modal"
hx-target="#modal-placeholder"
hx-swap="outerHTML"
>Open Modal</button>

<div id="modal-placeholder"></div>
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My intuition tells me there should be an easier way to do this, without needing this place holder div. But without, there's nothing to do an "outerHTML" swap with. And without that "outerHTML" swap, the hx-on::after-settle doesn't get called on the dialog. For example, if we have the button target the <body> and do a hx-swap="beforeend", then its the body that gets the hx-on::after-settle event.

I think for Locality-of-behavior reasons, it's best to have the call to showModal on the dialog itself. So that's why I went with this. Happy to entertain other ideas.

```

This button sends a `GET` request to `/modal` when the button is clicked. The
server then responds with with a dialog that get swapped into an empty `<div>`.
The `<dialog>` looks like this:

```html
<dialog id="modal-dialog" closedby="none" hx-on::after-settle="this.showModal()">
<h1>Modal Dialog</h1>
This is the modal content. You can put anything here, like text,
or a form, or an image.
<br/>
<br/>
<button class="btn danger" hx-get="/close" hx-target="#modal-dialog" hx-swap="outerHTML">Close</button>
</dialog>`
```

The close button fetches an empty `div` via the `/close` route to replace the dialog when it's closed:

```html
<div id="modal-placeholder"></div>
```

This essentially "resets" the page, and clicking the button again will open a new dialog.

Note that in the dialog:
1. We use `hx-on::after-settle`. This is needed because in order for the dialog to actually be shown,
the `showModal` function must be called.
2. The `closedby` attribute is set to `none`. If we didn't have this, the user might close the dialog,
via some mechanism that doesn't trigger the fetch of the `/close` endpoint.

{{ demoenv() }}

<div id="modal-placeholder"></div>

<style>
dialog {
margin: auto;
padding: 1em;
border: 0;
}
</style>

<script>

//=========================================================================
// Fake Server Side Code
//=========================================================================

// routes
init("/demo", function(request, params) {
return `<button
class="btn primary"
hx-get="/modal"
hx-target="#modal-placeholder"
hx-swap="outerHTML"
>Open Modal</button>
`})

onGet("/modal", function(request, params){
return `<dialog id="modal-dialog" closedby="none" hx-on::after-settle="this.showModal()">
<h1>Modal Dialog</h1>
This is the modal content. You can put anything here, like text,
or a form, or an image.
<br/>
<br/>
<button class="btn danger" hx-get="/close" hx-target="#modal-dialog" hx-swap="outerHTML">Close</button>
</dialog>`
})

onGet("/close", function(request, params){
return `<div id="modal-placeholder"></div>`
})
</script>