Two methods on every widget control how it interacts with the partial-redraw protocol: Drawn(r) says whether the widget should be re-rendered on a partial-redraw pass; Shown(r) says whether it should be visible at all. The defaults from WidgetBase cover most cases, and RedrawIfChanged / HideIf are the front-end controls callers reach for. This page is about the rare cases where you need to override them in a custom widget.
For the partial-redraw protocol to work, the widget’s root rendered element MUST carry a data-id attribute set to widget.ID(). The client uses this to find and replace the element when the server returns a redrawn fragment:
// factory is the package-local composite factory; see Packaging as a library.
func (g *GreetingWidget) Draw(w io.Writer, r *http.Request) error {
return factory.Tag("span").
Class("Greeting").
Attr("data-id", g.ID()). // ← required for partial-redraw swap
Add("Hello, ", g.name, "!").
When(g.Shown(r)). // ← respects HideIf*
Draw(w, r)
}
Forget the data-id and partial redraws silently no-op — the widget renders fine on a full page load but stops updating on state changes. This is the single most common bug in a hand-rolled widget.
WidgetBase already implements both. Drawn returns true on full page draws, and on partial-redraw passes only if RedrawIfChanged (or a sibling) was called by the consumer. Shown returns true unless HideIf was called. Most widgets never need to override either.
Override these methods when the widget has its OWN state contract — a name it owns and binds to a state variable, like a modal or a tab switcher. The modal is the canonical example:
// Default behavior — inherited from WidgetBase:
// Drawn(r) is true on full draws, and on partial draws only if RedrawIf
// has been called.
// Shown(r) is true unless HideIf has been called.
// Custom Shown — modal is visible only when its state variable is non-empty:
func (m *ModalWidget) Shown(r *http.Request) bool {
return m.WidgetBase.Shown(r) && factory.StateOf(r).Get(m.name) != ""
}
// Custom Drawn — modal needs to re-render whenever its state variable moves:
func (m *ModalWidget) Drawn(r *http.Request) bool {
return m.WidgetBase.Drawn(r) || factory.StateOf(r).Get(m.name) != ""
}
Both overrides AND with the WidgetBase result — that lets callers still use HideIf or RedrawIfChanged externally if they want extra control.
A widget that’s currently hidden still needs to leave a marker in the DOM so the partial-redraw mechanism can put it back later. Tag.When(false) already does this for you — when When is false but the tag has a data-id, it emits an invisible <span class="Empty" data-id="…"></span> instead of nothing:
// When the widget is hidden, the page still needs an anchor with the
// same data-id so a future redraw can swap content back in. Tag.When(false)
// renders <span class="Empty" data-id="…"></span> for that purpose:
return factory.Tag("div").
Class("MyWidget").
Attr("data-id", w.ID()).
Add(w.children).
When(w.Shown(r)). // false → render the placeholder span
Draw(out, r)
This is why every well-behaved widget pipes Shown(r) through When on its outer Tag call — the framework handles the show/hide round-trip without breaking the DOM swap.
The framework’s traversal pass calls Children() on every widget to assign stable IDs and collect form inputs. If your widget’s Draw builds a sub-tree internally, return that same sub-tree from Children() — see Extend → Composing existing widgets for the pattern.
Basics → Incremental updates — the underlying protocol Drawn / Shown plug into.
Build → When to redraw — the consumer-side view of the same machinery.
Read basic/modal.go for a real widget that overrides both methods.