Value Select

Picking a Make repopulates the Model select server-side. The Change handler auto-fires when the make select changes, looks up that make's models, and auto-selects the first one so the cascade is visible — no client JS. The Model select stays disabled until a Make is chosen, and Mount seeds the make list (and any pre-selected models) on connect.

Value Select

Cascading dependent selects. The Change() handler auto-fires when the Make select changes and updates the Model options server-side — no client JS.

Template

Both <select> elements live in one form; Change fires on either. The Model options come from .Models, which the server refills whenever make changes.

{{define "content"}}
<article>
    <h3>Value Select</h3>
    <p><small>Cascading dependent selects. The Change() handler auto-fires when the Make select changes and updates the Model options server-side — no client JS.</small></p>
    <form method="POST">
        <label>Make
            <select name="make">
                <option value="">Select Make</option>
                {{range .Makes}}
                <option value="{{.}}" {{if eq $.Make .}}selected{{end}}>{{.}}</option>
                {{end}}
            </select>
        </label>
        <label>Model
            <select name="model" {{if not .Make}}disabled{{end}}>
                <option value="">Select Model</option>
                {{range .Models}}
                <option value="{{.}}" {{if eq $.Model .}}selected{{end}}>{{.}}</option>
                {{end}}
            </select>
        </label>
    </form>
    {{if .Model}}
    <p>Selected: <strong>{{.Make}} {{.Model}}</strong></p>
    {{end}}
</article>
{{end}}

value-select.tmpl

Handler & state

Change checks which field changed: a new make reloads Models and resets the selection; a new model just records it. Mount populates the initial lists.

type ValueSelectController struct{}

func (c *ValueSelectController) Mount(state ValueSelectState, ctx *livetemplate.Context) (ValueSelectState, error) {
	state.Makes = getCarMakes()
	if state.Make != "" {
		state.Models = getCarModels(state.Make)
	}
	return state, nil
}

func (c *ValueSelectController) Change(state ValueSelectState, ctx *livetemplate.Context) (ValueSelectState, error) {
	if ctx.Has("make") {
		state.Make = ctx.GetString("make")
		state.Models = getCarModels(state.Make)
		// Auto-select first model so the user sees the cascade propagate.
		state.Model = ""
		if len(state.Models) > 0 {
			state.Model = state.Models[0]
		}
	}
	if ctx.Has("model") {
		state.Model = ctx.GetString("model")
	}
	return state, nil
}

func valueSelectHandler() http.Handler {
	tmpl := newLayoutTmpl("templates/layout.tmpl", "templates/lists/value-select.tmpl")
	return tmpl.Handle(&ValueSelectController{}, livetemplate.AsState(&ValueSelectState{
		Title:    "Value Select",
		Category: "Lists & Data",
	}))
}

handlers_lists.go:116-149

type ValueSelectState struct {
	Title    string
	Category string
	Makes    []string
	Models   []string
	Make     string
	Model    string
}

state_lists.go:39-47

When to use

For a non-cascading list that grows on demand, see Click To Load.

source: livetemplate/docs · path: examples/patterns/templates/lists/value-select.tmpl