Documentation
¶
Overview ¶
Package component defines the component model for GoFastr's core-ui framework.
A Component describes its visual appearance via the Render method, returning render.HTML. Components that handle user events implement the InteractiveComponent interface by adding an Actions method.
The package provides:
- Component and InteractiveComponent interfaces
- ComponentBase for embedding common fields (ID, Class)
- ComponentContext for event data and state access
- ActionRegistry and On() for declarative event handling
- Lifecycle hooks (Mount, Update, Unmount)
- ComponentList for rendering multiple components
Basic usage:
type Greeting struct {
component.ComponentBase
Name string
}
func (g *Greeting) Render() render.HTML {
return render.Tag("p", nil, render.Text("Hello, "+g.Name))
}
Index ¶
- func ComponentList(components ...Component) render.HTML
- func IsInteractive(c Component) bool
- func RenderComponent(c Component) render.HTML
- func RenderComponentCtx(ctx context.Context, c Component) render.HTML
- func SafeRender(c Component) (html render.HTML, err error)
- func SafeRenderCtx(ctx context.Context, c Component) (html render.HTML, err error)
- func SlugifyWidgetID(id string) string
- type ActionDef
- type ActionOption
- type ActionRegistry
- type Component
- type ComponentBase
- type ComponentContext
- func (ctx *ComponentContext) Deadline() (time.Time, bool)
- func (ctx *ComponentContext) Done() <-chan struct{}
- func (ctx *ComponentContext) Err() error
- func (ctx *ComponentContext) GetState(key string) any
- func (ctx *ComponentContext) Param(name string) string
- func (ctx *ComponentContext) ParamInt(name string) (int, error)
- func (ctx *ComponentContext) SetState(key string, value any)
- func (ctx *ComponentContext) Value(key any) any
- type ContextComponent
- type ContextOnly
- type ErrorBoundary
- type InteractiveComponent
- type Lifecycle
- type LifecycleHook
- type ServerCall
- type Widget
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ComponentList ¶
ComponentList renders multiple components joined together.
func IsInteractive ¶
IsInteractive returns true if the component implements InteractiveComponent.
func RenderComponent ¶
RenderComponent calls the Render method of any Component and returns its HTML. This is a convenience function for composing components.
func RenderComponentCtx ¶ added in v0.7.0
RenderComponentCtx renders a component with a request context, preferring RenderCtx when the component implements ContextComponent (the same preference the screen/layout dispatch uses). Falls back to Render otherwise. Lets per-request chrome (e.g. role-aware nav in a drawer widget) see the signed-in user instead of a background context.
func SafeRender ¶
SafeRender calls Render() with panic recovery. If the component panics, it returns a fallback error UI. Components implementing ErrorBoundary get a custom fallback; others get a generic red-bordered box.
func SafeRenderCtx ¶
SafeRenderCtx is the context-aware variant of SafeRender. If c implements ContextComponent its RenderCtx(ctx) is called; otherwise Render() is called. Panic recovery and ErrorBoundary handling are identical to SafeRender.
func SlugifyWidgetID ¶ added in v0.81.0
SlugifyWidgetID maps a widget ID onto the [A-Za-z0-9_-] alphabet the runtime's data-behavior gate honours (runtime.js matches `/__gofastr/widget/[A-Za-z0-9_-]+.js` exactly and refuses anything else, because a loosened value is a script-loading sink). Any rune outside that alphabet becomes "-", so an ID built from a display name, path segment, or other request-derived text still hydrates: emitting the raw value would make the browser gate silently drop the behavior script and the widget would never hydrate. Valid IDs pass through unchanged.
Types ¶
type ActionDef ¶
type ActionDef struct {
Event string // "click", "submit", "input", "change", "keydown", etc.
Handler func(*ComponentContext) // the server-side handler (runs in Go)
Server bool // if true, this action must run on the server
Debounce int // debounce in milliseconds (0 = no debounce)
// ClientJS is the JavaScript handler body. This is what gets compiled
// into the actions.js bundle. It receives a `params` object with:
// params.value : input value (for input/change events)
// params.* : any data-param-* attributes from the trigger element
//
// Available runtime helpers via `G` (window.__gofastr):
// G.getState(key, default) G.setState(key, value)
// G.updateText(sel, text) G.updateHTML(sel, html)
// G.toast(msg) G.navigate(path)
// G.addClass(sel, cls) G.removeClass(sel, cls)
//
// Example: "G.setState('count', G.getState('count', 0) + 1); G.updateText('[data-count]', G.getState('count', 0));"
ClientJS string
}
ActionDef defines a single event handler within a component.
func On ¶
func On(event string, handler func(*ComponentContext), opts ...ActionOption) ActionDef
On registers an event handler into the current registry. This is the primary API for .ui.go files.
Usage: On("click", func(ctx *ComponentContext) { ... }, WithClientJS("G.setState('x', 1)"))
type ActionOption ¶
type ActionOption func(*ActionDef)
ActionOption is a functional option for ActionDef.
func WithClientJS ¶
func WithClientJS(js string) ActionOption
WithClientJS sets the client-side JavaScript handler body. This JS runs in the browser when the action is triggered.
type ActionRegistry ¶
type ActionRegistry struct {
// contains filtered or unexported fields
}
ActionRegistry holds all registered actions for a component.
func ExtractActions ¶
func ExtractActions(c Component) *ActionRegistry
func NewActionRegistry ¶
func NewActionRegistry() *ActionRegistry
NewActionRegistry creates a new action registry.
func (*ActionRegistry) All ¶
func (r *ActionRegistry) All() []ActionDef
All returns all registered actions.
func (*ActionRegistry) Get ¶
func (r *ActionRegistry) Get(eventName string) (ActionDef, bool)
Get retrieves the action for an event name.
func (*ActionRegistry) HasActions ¶
func (r *ActionRegistry) HasActions() bool
HasActions returns true if any actions are registered.
func (*ActionRegistry) Register ¶
func (r *ActionRegistry) Register(action ActionDef)
Register adds an action handler for an event.
type Component ¶
Component is the base interface for all UI components. A component describes its appearance via Render.
type ComponentBase ¶
ComponentBase provides common fields for all components. Embed this in your component structs for convenience.
type ComponentContext ¶
type ComponentContext struct {
// Ctx is the request context the action arrived on. Nil is treated as
// context.Background(), so a ComponentContext built by hand (tests,
// in-process invocation) is still safe to use as a context.
Ctx context.Context
// Event data
EventName string
TargetID string
Params map[string]string
// State access
StateGetter func(key string) any
StateSetter func(key string, value any)
}
ComponentContext holds the execution context for a component action. It provides access to event data and component state.
It also IS a context.Context: an action handler receives this value and nothing else, so anything the handler needs from the request has to be reachable through it. That includes the caller. The server-action endpoint's own contract says "a handler that mutates anything must check authorization itself", and with no request context threaded through, handler.GetUser had nothing to read and that check was unimplementable. Passing it to any context-taking API — a DB call, an authorization lookup, handler.GetUser — now works.
func NewComponentContext ¶
func NewComponentContext(eventName, targetID string, params map[string]string) *ComponentContext
NewComponentContext creates a new context with the given event data and no request context. Prefer NewComponentContextFor on any path that has a request: without it the handler cannot see the caller.
func NewComponentContextFor ¶ added in v0.78.0
func NewComponentContextFor(ctx context.Context, eventName, targetID string, params map[string]string) *ComponentContext
NewComponentContextFor creates a context carrying the request context the action arrived on, so the handler can read the caller and honour cancellation.
func (*ComponentContext) Deadline ¶ added in v0.78.0
func (ctx *ComponentContext) Deadline() (time.Time, bool)
Deadline implements context.Context.
func (*ComponentContext) Done ¶ added in v0.78.0
func (ctx *ComponentContext) Done() <-chan struct{}
Done implements context.Context.
func (*ComponentContext) Err ¶ added in v0.78.0
func (ctx *ComponentContext) Err() error
Err implements context.Context.
func (*ComponentContext) GetState ¶
func (ctx *ComponentContext) GetState(key string) any
GetState retrieves state by key.
func (*ComponentContext) Param ¶
func (ctx *ComponentContext) Param(name string) string
Param returns a named parameter from the event context.
func (*ComponentContext) ParamInt ¶
func (ctx *ComponentContext) ParamInt(name string) (int, error)
ParamInt returns a named integer parameter from the event context.
func (*ComponentContext) SetState ¶
func (ctx *ComponentContext) SetState(key string, value any)
SetState updates state by key.
func (*ComponentContext) Value ¶ added in v0.78.0
func (ctx *ComponentContext) Value(key any) any
Value implements context.Context.
type ContextComponent ¶
ContextComponent is the optional ctx-aware render interface. Implementations receive the request context and can branch on auth state, locale, or any other request-scoped value that isn't loaded into struct fields by Load(ctx).
The framework prefers RenderCtx over Render when both exist. ContextComponent intentionally does NOT embed Component, a type that only wants the ctx-aware shape can satisfy Component via the ContextOnly{} embed below, avoiding the `func (c) Render() HTML { return c.RenderCtx(context.Background()) }` boilerplate.
type ContextOnly ¶
type ContextOnly struct{}
ContextOnly is a zero-byte embed helper for components that only want to implement RenderCtx. Embedding it provides a Render() satisfying the Component interface; that Render is never called when the type also implements RenderCtx (the framework dispatch prefers RenderCtx and skips Render in that case).
type Home struct {
component.ContextOnly
// ... your fields
}
func (h *Home) RenderCtx(ctx context.Context) render.HTML { ... }
No need to write a Render() stub on Home, ContextOnly provides one.
func (ContextOnly) Render ¶
func (ContextOnly) Render() render.HTML
Render satisfies the Component interface. In practice it is never called: SafeRenderCtx and Screen.RenderCtx detect RenderCtx via a structural type check and route to it directly.
type ErrorBoundary ¶
ErrorBoundary is implemented by components that provide a custom error fallback.
type InteractiveComponent ¶
type InteractiveComponent interface {
Component
Actions()
}
InteractiveComponent extends Component with event handling. Components that implement this interface can respond to user events.
type Lifecycle ¶
type Lifecycle struct {
// contains filtered or unexported fields
}
Lifecycle manages mount, update, and unmount hooks.
func (*Lifecycle) HasMountHooks ¶
HasMountHooks returns true if any mount hooks are registered.
func (*Lifecycle) OnMount ¶
func (l *Lifecycle) OnMount(fn LifecycleHook)
OnMount registers a hook to run when the component first renders.
func (*Lifecycle) OnUnmount ¶
func (l *Lifecycle) OnUnmount(fn LifecycleHook)
OnUnmount registers a hook to run when the component is removed.
func (*Lifecycle) OnUpdate ¶
func (l *Lifecycle) OnUpdate(fn LifecycleHook)
OnUpdate registers a hook to run when the component re-renders.
func (*Lifecycle) TriggerMount ¶
func (l *Lifecycle) TriggerMount()
TriggerMount runs all mount hooks.
func (*Lifecycle) TriggerUnmount ¶
func (l *Lifecycle) TriggerUnmount()
TriggerUnmount runs all unmount hooks.
func (*Lifecycle) TriggerUpdate ¶
func (l *Lifecycle) TriggerUpdate()
TriggerUpdate runs all update hooks.
type LifecycleHook ¶
type LifecycleHook func()
LifecycleHook is a function that runs at a specific point in a component's life.
type ServerCall ¶
ServerCall represents a call that should be executed on the server.
func Server ¶
func Server(action string, params ...string) *ServerCall
Server marks an action as requiring server-side execution. When the compiler sees this, it generates JS that POSTs to the server instead of running locally. The server handler runs in a goroutine.
type Widget ¶
type Widget struct {
ID string // unique widget ID (used for data-widget attribute)
Component Component // the component that renders this widget
Actions *ActionRegistry // extracted actions from the component
}
Widget is a self-contained interactive unit that serves as a hydration boundary. Widgets are hydrated incrementally on first user interaction. Components inside a Widget are compiled to JS for client-side execution.
func NewWidget ¶
NewWidget creates a new widget wrapping a component. It automatically extracts actions if the component implements InteractiveComponent.
func (*Widget) IsInteractive ¶
IsInteractive returns true if the widget's component has actions.