Delete Row

Each row carries a Delete button whose value is its ID; the handler removes that item from a process-wide in-memory table and re-renders. New rows slide in via lvt-fx:animate="slide", and the stable data-key on every <tr> lets the diff engine drop only the deleted row instead of redrawing the table. Deletions persist across reloads, with a Restore button to refill the demo.

Delete Row

Click Delete on any row. New rows slide in via lvt-fx:animate="slide" (entry-only). Items live in a process-wide in-memory table, so deletions persist across reloads and navigation; use Restore to reset.

IDNameEmail
1 Item 1 item1@example.com
2 Item 2 item2@example.com
3 Item 3 item3@example.com
4 Item 4 item4@example.com
5 Item 5 item5@example.com

Template

The data-key keys each row for the diff engine, and lvt-fx:animate="slide" gives new rows an entry animation. When the list empties, a Restore button replaces it.

{{define "content"}}
<article>
    <h3>Delete Row</h3>
    <p><small>Click Delete on any row. New rows slide in via <code>lvt-fx:animate="slide"</code> (entry-only). Items live in a process-wide in-memory table, so deletions persist across reloads and navigation; use Restore to reset.</small></p>
    {{if gt (len .Items) 0}}
    <table>
        <thead><tr><th>ID</th><th>Name</th><th>Email</th><th></th></tr></thead>
        <tbody>
        {{range .Items}}
        <tr data-key="{{.ID}}" lvt-fx:animate="slide">
            <td>{{.ID}}</td>
            <td>{{.Name}}</td>
            <td>{{.Email}}</td>
            <td>
                <form method="POST" class="inline">
                    <button name="delete" value="{{.ID}}" class="compact contrast outline">Delete</button>
                </form>
            </td>
        </tr>
        {{end}}
        </tbody>
    </table>
    {{else}}
    <p><small>All items deleted.</small></p>
    <form method="POST">
        <button name="restore" class="secondary outline">Restore Items</button>
    </form>
    {{end}}
</article>
{{end}}

delete-row.tmpl

Handler & state

Delete reads the clicked button's value, removes that item under a mutex, and copies a fresh snapshot back into session state; Restore refills the table.

// DeleteRowController holds a shared in-memory "database" protected by a
// mutex. Mount copies the DB snapshot into per-session state on every
// connect, so deletions persist across reloads and cross-handler navigation
// without needing `lvt:"persist"` struct tags. The DB lives for the life
// of the process; restarting the server resets it.
type DeleteRowController struct {
	mu    sync.Mutex
	items []Item
}

const deleteRowInitialCount = 5

func newDeleteRowController() *DeleteRowController {
	return &DeleteRowController{items: getItemPage(1, deleteRowInitialCount)}
}

// snapshot returns an independent copy of the current DB. Caller must not
// hold c.mu when invoking (this method acquires it internally).
func (c *DeleteRowController) snapshot() []Item {
	c.mu.Lock()
	defer c.mu.Unlock()
	return slices.Clone(c.items)
}

func (c *DeleteRowController) Mount(state DeleteRowState, ctx *livetemplate.Context) (DeleteRowState, error) {
	state.Items = c.snapshot()
	return state, nil
}

func (c *DeleteRowController) Delete(state DeleteRowState, ctx *livetemplate.Context) (DeleteRowState, error) {
	// Button sends its `value` attribute as data.value — see
	// docs/references/progressive-complexity-reference.md.
	id := ctx.GetString("value")
	c.mu.Lock()
	c.items = slices.DeleteFunc(c.items, func(item Item) bool {
		return item.ID == id
	})
	c.mu.Unlock()
	state.Items = c.snapshot()
	return state, nil
}

// Restore refills the DB to its initial state. Wired to a button that
// appears after the last item is deleted, so visitors can reset the demo
// without restarting the server.
func (c *DeleteRowController) Restore(state DeleteRowState, ctx *livetemplate.Context) (DeleteRowState, error) {
	c.mu.Lock()
	c.items = getItemPage(1, deleteRowInitialCount)
	c.mu.Unlock()
	state.Items = c.snapshot()
	return state, nil
}

func deleteRowHandler() http.Handler {
	tmpl := newLayoutTmpl("templates/layout.tmpl", "templates/lists/delete-row.tmpl")
	return tmpl.Handle(newDeleteRowController(), livetemplate.AsState(&DeleteRowState{
		Title:    "Delete Row",
		Category: "Lists & Data",
	}))
}

handlers_lists.go:24-84

type DeleteRowState struct {
	Title    string
	Category string
	Items    []Item
}

state_lists.go:5-10

When to use

See Large Table for delete alongside filter, sort, and append on a 10k-row dataset.

source: livetemplate/docs · path: examples/patterns/templates/lists/delete-row.tmpl