Skip to content
Draft
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
132 changes: 132 additions & 0 deletions Guide/auto-refresh.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,135 @@ action StatsAction = autoRefresh do
```

The [`trackTableRead`](https://ihp.digitallyinduced.com/api-docs/IHP-ModelSupport.html#v:trackTableRead) marks the table as accessed for Auto Refresh and leads to the table being watched.

### Using Auto Refresh with HTMX

HTMX endpoints often render just a fragment and swap it into an existing container. Auto Refresh can cooperate with that flow as long as the client knows which element to morph and the fragment exposes the session meta data. You can use multiple Auto Refresh-powered HTMX fragments on one page as long as each swap target has its own stable `id`.

Auto Refresh decides which DOM node to update by looking at a target selector stored on the meta tag:

- If the meta tag has `data-ihp-auto-refresh-target`, that selector is used.
- Otherwise, after an HTMX swap, the client uses the swap target `id` (from `htmx:afterSwap`) and treats it as `#id`.
- If neither is available, Auto Refresh falls back to the full page, which is usually not what you want for fragments.

In practice:

1. Wrap the HTMX action in `autoRefresh`.
2. Include `{autoRefreshMeta}` inside the fragment that HTMX swaps in, or omit it and let Auto Refresh inject it automatically. The meta tag can be anywhere in the fragment; the client moves it into `<head>` after the swap.
3. Give the swap target a stable `id` so Auto Refresh can infer `#id`. If the target has no `id`, Auto Refresh will generate one in the browser (e.g. `ihp-auto-refresh-target-1`). If you want a different selector, set it explicitly with [`setAutoRefreshTarget`](https://ihp.digitallyinduced.com/api-docs/IHP-AutoRefresh.html#v:setAutoRefreshTarget).
4. Keep the container stable (e.g. the same `id`) so morphdom can update its children without losing your `hx-*` attributes.

#### Example 1: Basic fragment swap (no setAutoRefreshTarget)

```haskell
-- Controller
action RefineChatPaneAction { chatId } = autoRefresh do
messages <- query @Message
|> filterWhere (#chatId, chatId)
|> orderByDesc #createdAt
|> fetch
render RefineChatPaneView { .. }

-- View
instance View RefineChatPaneView where
html RefineChatPaneView { .. } = [hsx|
{autoRefreshMeta}
{forEach messages renderMessage}
|]
```

On the page you can keep your skeleton loader and HTMX setup. Because HTMX swaps into `<div id="chat-pane">`, the `htmx:afterSwap` handler derives the target selector `#chat-pane` automatically:

```haskell
[hsx|
<div
id="chat-pane"
class="h-full"
hx-get={pathTo RefineChatPaneAction { chatId }}
hx-trigger="load once"
hx-swap="innerHTML"
>
{skeleton}
</div>
|]
```

After HTMX swaps in the fragment, the Auto Refresh client moves the meta tag into `<head>`, reuses the session id, reconnects the WebSocket, and limits updates to `#chat-pane`. Avoid rendering another `#chat-pane` inside the fragment when using `hx-swap="innerHTML"`, or you will end up with duplicate `id` values.

#### Example 2: No `id` on the swap target (use setAutoRefreshTarget)

If the HTMX target is selected by class or some other selector, Auto Refresh cannot infer the target. Set it explicitly:

```haskell
-- Controller
action SidebarAction = autoRefresh do
setAutoRefreshTarget ".sidebar-pane"
items <- query @Item |> fetch
render SidebarView { .. }

-- View
instance View SidebarView where
html SidebarView { .. } = [hsx|
{autoRefreshMeta}
{forEach items renderItem}
|]
```

```haskell
[hsx|
<aside
class="sidebar-pane"
hx-get={pathTo SidebarAction}
hx-trigger="load once"
hx-swap="innerHTML"
></aside>
|]
```

#### Example 3: Outer swap (fragment includes the container)

If you want the fragment to include the wrapper, use `hx-swap="outerHTML"`:

```haskell
-- View
instance View RefineChatPaneView where
html RefineChatPaneView { .. } = [hsx|
{autoRefreshMeta}
<div id="chat-pane" class="h-full">
{forEach messages renderMessage}
</div>
|]
```

```haskell
[hsx|
<div
id="chat-pane"
class="h-full"
hx-get={pathTo RefineChatPaneAction { chatId }}
hx-trigger="load once"
hx-swap="outerHTML"
>
{skeleton}
</div>
|]
```

#### Example 4: Multiple fragments on one page

Each fragment has its own target `id` and its own Auto Refresh session:

```haskell
[hsx|
<div id="chat-pane" hx-get={pathTo RefineChatPaneAction { chatId }} hx-trigger="load once" hx-swap="innerHTML"></div>
<div id="activity-pane" hx-get={pathTo ActivityPaneAction} hx-trigger="load once" hx-swap="innerHTML"></div>
|]
```

```haskell
-- RefineChatPaneView
[hsx|{autoRefreshMeta}{forEach messages renderMessage}|]

-- ActivityPaneView
[hsx|{autoRefreshMeta}{forEach activities renderActivity}|]
```
8 changes: 8 additions & 0 deletions Guide/database.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ When dumping the database into the `Fixtures.sql` first and then rebuilding the

To have the full database dumped in a portable manner, you can do `make sql_dump > /tmp/my_app.sql`, which will generate a full SQL database dump, without owner or ACL information.

## Typed SQL

When Query Builder is not expressive enough and `sqlQuery` feels too loose,
use `typedSql` for compile-time checked SQL with IHP type inference. It uses
your schema to return `Id` and generated record types automatically.

See [Typed SQL](https://ihp.digitallyinduced.com/Guide/typed-sql.html).

## Haskell Bindings

### Model Context
Expand Down
1 change: 1 addition & 0 deletions Guide/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
<a class="nav-link secondary" href="database.html">Basics</a>
<a class="nav-link secondary" href="relationships.html">Relationships</a>
<a class="nav-link secondary" href="querybuilder.html">Query Builder</a>
<a class="nav-link secondary" href="typed-sql.html">Typed SQL</a>
<a class="nav-link secondary" href="database-migrations.html">Migrations</a>


Expand Down
Loading