Click to Edit

Show a record read-only, then swap the same region for an edit form on demand — no client state, no separate edit route. An Editing boolean on the server state picks the branch the template renders; Save commits the fields and flips it back, Cancel flips it back untouched.

Click To Edit

First NameJohn
Last NameDoe
Emailjohn@example.com

Template

The whole pattern is one {{if .Editing}} branch — view mode renders a table plus an Edit button; edit mode renders the form with Save/Cancel. Each button's name is the action it triggers.

{{define "content"}}
<article>
    <h3>Click To Edit</h3>
    {{if .Editing}}
    <form method="POST">
        <label>First Name
            <input name="firstName" value="{{.FirstName}}" required>
        </label>
        <label>Last Name
            <input name="lastName" value="{{.LastName}}" required>
        </label>
        <label>Email
            <input name="email" type="email" value="{{.Email}}" required>
        </label>
        <fieldset role="group">
            <button name="save">Save</button>
            <button name="cancel" class="secondary">Cancel</button>
        </fieldset>
    </form>
    {{else}}
    <table>
        <tbody>
            <tr><th>First Name</th><td>{{.FirstName}}</td></tr>
            <tr><th>Last Name</th><td>{{.LastName}}</td></tr>
            <tr><th>Email</th><td>{{.Email}}</td></tr>
        </tbody>
    </table>
    <form method="POST">
        <button name="edit" class="outline">Edit</button>
    </form>
    {{end}}
</article>
{{end}}

click-to-edit.tmpl

Handler & state

Three tiny actions toggle Editing; Save also reads the submitted fields.

type ClickToEditController struct{}

func (c *ClickToEditController) Edit(state ClickToEditState, ctx *livetemplate.Context) (ClickToEditState, error) {
	state.Editing = true
	return state, nil
}

func (c *ClickToEditController) Save(state ClickToEditState, ctx *livetemplate.Context) (ClickToEditState, error) {
	state.FirstName = ctx.GetString("firstName")
	state.LastName = ctx.GetString("lastName")
	state.Email = ctx.GetString("email")
	state.Editing = false
	return state, nil
}

func (c *ClickToEditController) Cancel(state ClickToEditState, ctx *livetemplate.Context) (ClickToEditState, error) {
	state.Editing = false
	return state, nil
}

func clickToEditHandler() http.Handler {
	tmpl := newLayoutTmpl("templates/layout.tmpl", "templates/forms/click-to-edit.tmpl")
	return tmpl.Handle(&ClickToEditController{}, livetemplate.AsState(&ClickToEditState{
		Title:     "Click To Edit",
		Category:  "Forms & Editing",
		FirstName: "John",
		LastName:  "Doe",
		Email:     "john@example.com",
	}))
}

handlers_forms.go:13-43

type ClickToEditState struct {
	Title     string
	Category  string
	FirstName string
	LastName  string
	Email     string
	Editing   bool
}

state_forms.go:5-13

When to use

Reach for Edit Row instead when many rows in a table each need independent inline editing.

source: livetemplate/docs · path: examples/patterns/templates/forms/click-to-edit.tmpl