Patch lab
Each control sends a page event that uses a different Datastar patch mode. The target area makes insertion, replacement, and removal explicit.
Demo
Modes
Patch target
selector: #lab-target · mode: :outer · count: 0
Target node
#lab-targetInner content starts here.
List children
#patch-listHow it works
- outer replaces the target node.
- inner replaces only target contents.
- append and prepend add children to a list.
- before and after insert siblings around an anchor.
- remove deletes the selected element.
- The selector is fixed per mode; the header and the ringed node show which one is next.
Every control posts a page event whose handler picks a different Datastar patch mode.
mode: :outer
swaps the target node, :inner
only its children, :append
and :prepend
add list items, :before
and :after
insert siblings around an anchor, and
remove_elements/2
deletes outright. The selector and the mode are the whole API.
"before" ->
patch(conn, &patch_badge/1, [mode: mode, count: count],
selector: "#anchor-row",
mode: :before
)
"after" ->
patch(conn, &patch_badge/1, [mode: mode, count: count],
selector: "#anchor-row",
mode: :after
)
"remove" ->
remove_elements(conn, "#removable-note")
If this were LiveView
Only three of the seven modes have a LiveView counterpart, which is the point of the comparison rather than a gap in it.
Dstar
"before" ->
patch(conn, &patch_badge/1, [mode: mode, count: count],
selector: "#anchor-row",
mode: :before
)
"after" ->
patch(conn, &patch_badge/1, [mode: mode, count: count],
selector: "#anchor-row",
mode: :after
)
"remove" ->
remove_elements(conn, "#removable-note")
LiveView
# Illustrative LiveView equivalent — not compiled or run.
#
# LiveView has no patch-mode API at all: you change assigns and it works
# out the DOM operation. The nearest thing to append and prepend is a
# stream with :at; outer, inner, before and after have no equivalent
# because you never name the operation.
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(count: 0, note_visible?: true)
|> stream(:rows, [%{id: "seed-row", label: "Seed row"}])}
end
def handle_event("append", _params, socket) do
count = socket.assigns.count + 1
{:noreply,
socket
|> assign(count: count)
|> stream_insert(:rows, %{id: "row-" <> to_string(count), label: "append patch"}, at: -1)}
end
def handle_event("prepend", _params, socket) do
count = socket.assigns.count + 1
{:noreply,
socket
|> assign(count: count)
|> stream_insert(:rows, %{id: "row-" <> to_string(count), label: "prepend patch"}, at: 0)}
end
def handle_event("remove", _params, socket) do
{:noreply, assign(socket, note_visible?: false)}
end
def render(assigns) do
~H"""
<div id="patch-list" phx-update="stream">
<div :for={{dom_id, row} <- @streams.rows} id={dom_id}>{row.label}</div>
</div>
<div :if={@note_visible?} id="removable-note">Remove mode deletes this note.</div>
"""
end