Documentation
¶
Overview ¶
Package structpages provides a way to define routing using struct tags and methods. It integrates with the http.ServeMux, allowing you to quickly build up pages and components without too much boilerplate.
Index ¶
- Variables
- func ID(ctx context.Context, v any) (string, error)
- func IDTarget(ctx context.Context, v any) (string, error)
- func RenderComponent(targetOrMethod any, args ...any) error
- func URLFor(ctx context.Context, page any, args ...any) (string, error)
- func WithArgs(args ...any) func(*StructPages)
- func WithErrorHandler(onError func(http.ResponseWriter, *http.Request, error)) func(*StructPages)
- func WithMiddlewares(middlewares ...MiddlewareFunc) func(*StructPages)
- func WithTargetSelector(selector TargetSelector) func(*StructPages)
- func WithWarnEmptyRoute(warnFunc func(*PageNode)) func(*StructPages)
- type MiddlewareFunc
- type Mux
- type Option
- type PageNode
- type Ref
- type RenderTarget
- type StructPages
- type TargetSelector
Constants ¶
This section is empty.
Variables ¶
var ErrSkipPageRender = errors.New("skip page render")
ErrSkipPageRender is a sentinel error that can be returned from a Props method to indicate that the page rendering should be skipped. This is useful for implementing conditional rendering or redirects within page logic.
Functions ¶
func ID ¶ added in v0.1.0
ID generates a raw HTML ID for a component method (without "#" prefix). Use this for HTML id attributes.
Parameters:
- ctx: Context containing parseContext (required for method expressions and Ref)
- v: One of:
- Method expression (p.UserList) - generates ID from page and method name
- Ref type (structpages.Ref("PageName.MethodName")) - looks up page/method dynamically
- Plain string ("my-custom-id") - returned as-is
Example:
<div id={ structpages.ID(ctx, p.UserList) }>
// → <div id="team-management-view-user-list">
<div id={ structpages.ID(ctx, UserStatsWidget) }>
// → <div id="user-stats-widget"> (no page prefix for standalone functions)
<div id={ structpages.ID(ctx, "my-custom-id") }>
// → <div id="my-custom-id">
Returns an error if parseContext is not found in the provided context.
func IDTarget ¶ added in v0.1.0
IDTarget generates a CSS selector (with "#" prefix) for a component method. Use this for HTMX hx-target attributes.
Parameters:
- ctx: Context containing parseContext (required for method expressions and Ref)
- v: One of:
- Method expression (p.UserList) - generates selector from page and method name
- Ref type (structpages.Ref("PageName.MethodName")) - looks up page/method dynamically
- string ("body" or "#my-custom-id") - returned as-is
Example:
<button hx-target={ structpages.IDTarget(ctx, p.UserList) }>
// → <button hx-target="#team-management-view-user-list">
<button hx-target={ structpages.IDTarget(ctx, UserStatsWidget) }>
// → <button hx-target="#user-stats-widget"> (no page prefix for standalone functions)
<button hx-target={ structpages.IDTarget(ctx, "body") }>
// → <button hx-target="body">
Returns an error if parseContext is not found in the provided context.
func RenderComponent ¶ added in v0.0.9
RenderComponent creates an error that instructs the framework to render a specific component instead of the default component.
It supports multiple patterns:
1. Direct component:
comp := MyComponent("data")
return RenderComponent(comp)
2. Custom RenderTarget with Component() method (for custom TargetSelector implementations):
type customTarget struct { data string }
func (ct customTarget) Is(method any) bool { ... }
func (ct customTarget) Component() component { return MyComponent(ct.data) }
// Custom TargetSelector returns customTarget
// Props can then: return Props{}, RenderComponent(target)
3. Same-page component (with target from Props):
func (p DashboardPage) Props(r *http.Request, target RenderTarget) (DashboardProps, error) {
if target.Is(UserStatsWidget) {
stats := loadUserStats()
return DashboardProps{}, RenderComponent(target, stats)
}
}
4. Cross-page component (with method expression):
func (p MyPage) Props(r *http.Request) (Props, error) {
return Props{}, RenderComponent(OtherPage.ErrorComponent, "error message")
}
func URLFor ¶
URLFor returns the URL for a given page type. If args is provided, it'll replace the path segments. Supported format is similar to http.ServeMux
If multiple page type matches are found, the first one is returned. In such situation, use a func(*PageNode) bool as page argument to match a specific page.
Additionally, you can pass []any to page to join multiple path segments together. Strings will be joined as is. Example:
URLFor(ctx, []any{Page{}, "?foo={bar}"}, "bar", "baz")
It also supports a func(*PageNode) bool as the Page argument to match a specific page. It can be useful when you have multiple pages with the same type but different routes.
func WithArgs ¶ added in v0.0.20
func WithArgs(args ...any) func(*StructPages)
WithArgs adds global dependency injection arguments that will be available to all page methods (Props, Middlewares, ServeHTTP etc.).
func WithErrorHandler ¶
func WithErrorHandler(onError func(http.ResponseWriter, *http.Request, error)) func(*StructPages)
WithErrorHandler sets a custom error handler function that will be called when an error occurs during page rendering or request handling. If not set, a default handler returns a generic "Internal Server Error" response.
func WithMiddlewares ¶
func WithMiddlewares(middlewares ...MiddlewareFunc) func(*StructPages)
WithMiddlewares adds global middleware functions that will be applied to all routes. Middleware is executed in the order provided, with the first middleware being the outermost handler. These global middlewares run before any page-specific middlewares.
func WithTargetSelector ¶ added in v0.1.0
func WithTargetSelector(selector TargetSelector) func(*StructPages)
WithTargetSelector sets a custom TargetSelector function that determines which component to render based on the request. The default is HTMXRenderTarget, which handles HTMX partial requests automatically.
The selector function receives the request and page node, and returns a RenderTarget that will be passed to Props. This allows custom logic for component selection.
Example - Custom selector with A/B testing:
sp := Mount(http.NewServeMux(), index{}, "/", "My App",
WithTargetSelector(func(r *http.Request, pn *PageNode) (RenderTarget, error) {
if getABTestVariant(r) == "B" {
// Use different component for variant B
method := pn.Components["ContentB"]
return newMethodRenderTarget("ContentB", method), nil
}
// Fallback to default HTMX behavior
return HTMXRenderTarget(r, pn)
}))
func WithWarnEmptyRoute ¶ added in v0.0.11
func WithWarnEmptyRoute(warnFunc func(*PageNode)) func(*StructPages)
WithWarnEmptyRoute sets a custom warning function for pages that have neither a handler method nor children. These pages are automatically skipped during route registration. If warnFunc is nil, a default warning message is printed to stdout. Set warnFunc to a no-op function to suppress warnings entirely.
Example usage:
// Use default warning (prints to stdout)
sp := structpages.Mount(
http.NewServeMux(), index{}, "/", "App",
structpages.WithWarnEmptyRoute(nil),
)
// Custom warning function
sp := structpages.Mount(
http.NewServeMux(), index{}, "/", "App",
structpages.WithWarnEmptyRoute(func(pn *PageNode) {
log.Printf("Skipping empty page: %s", pn.Name)
}),
)
// Suppress warnings entirely
sp := structpages.Mount(
http.NewServeMux(), index{}, "/", "App",
structpages.WithWarnEmptyRoute(func(*PageNode) {}),
)
Types ¶
type MiddlewareFunc ¶
MiddlewareFunc is a function that wraps an http.Handler with additional functionality. It receives both the handler to wrap and the PageNode being handled, allowing middleware to access page metadata like route, title, and other properties.
type Mux ¶ added in v0.0.15
Mux represents any HTTP router that can register handlers using the Handle method. This interface is satisfied by http.ServeMux and must follow the same pattern support for route registration.
type Option ¶ added in v0.0.20
type Option func(*StructPages)
Option represents a configuration option for StructPages.
type PageNode ¶
type PageNode struct {
Name string
Title string
Method string
Route string
Value reflect.Value
Props map[string]reflect.Method
Components map[string]reflect.Method
Middlewares *reflect.Method
Parent *PageNode
Children []*PageNode
// contains filtered or unexported fields
}
PageNode represents a page in the routing tree. It contains metadata about the page including its route, title, and registered methods. PageNodes form a tree structure with parent-child relationships representing nested routes.
func (*PageNode) All ¶
All returns an iterator that walks through this PageNode and all its descendants in depth-first order. This is useful for traversing the entire page tree.
Example:
for node := range pageNode.All() {
fmt.Println(node.FullRoute())
}
type Ref ¶ added in v0.0.15
type Ref string
Ref represents a dynamic reference to a page or method by name. Use it when static type references aren't available (e.g., configuration-driven menus, generic components, or code generation scenarios).
For URLFor, the string can be:
- Page name: Ref("UserManagement")
- Route path: Ref("/user/management") - must start with /
For IDFor, the string can be:
- Qualified method: Ref("PageName.MethodName")
- Simple method: Ref("MethodName") - must be unambiguous across all pages
Both URLFor and IDFor return descriptive errors if the reference is invalid, providing runtime safety for dynamic references.
Example usage:
// Dynamic menu from configuration
menuItems := []struct{ Page Ref; Label string }{
{Ref("HomePage"), "Home"},
{Ref("UserManagement"), "Users"},
}
for _, item := range menuItems {
url, err := URLFor(ctx, item.Page)
// Handle error if page doesn't exist
}
// Dynamic component reference
targetID, err := IDFor(ctx, Ref("UserManagement.UserList"))
type RenderTarget ¶ added in v0.0.15
type RenderTarget interface {
// Is checks if this target matches the given method or function reference.
// Works with both page methods and standalone functions.
// Uses method/function expressions for compile-time safety.
//
// For function components, Is() has a side effect: it stores the function
// value when a match is found, enabling lazy evaluation of the hxTarget.
Is(method any) bool
}
RenderTarget represents a selected component that will be rendered. It's available to Props methods via dependency injection, allowing Props to load only the data needed for the target component.
RenderTarget is produced by a TargetSelector function (e.g., HTMXRenderTarget). The selector determines which component to render based on the request, and the resulting RenderTarget is passed to Props.
Example usage in Props:
func (p DashboardPage) Props(r *http.Request, target RenderTarget) (DashboardProps, error) {
switch {
case target.Is(UserStatsWidget):
stats := loadUserStats()
return DashboardProps{}, RenderComponent(target, stats)
case target.Is(p.Page):
return DashboardProps{Stats: loadAll()}, nil
}
}
func HTMXRenderTarget ¶ added in v0.1.0
func HTMXRenderTarget(r *http.Request, pn *PageNode) (RenderTarget, error)
HTMXRenderTarget is the default TargetSelector for HTMX integration. It automatically selects the appropriate component based on the HX-Target header.
When an HTMX request is detected (via HX-Request header), it matches the HX-Target value against all available component IDs. For example:
- HX-Target: "content" -> returns methodRenderTarget for Content() method
- HX-Target: "index-page-todo-list" -> returns methodRenderTarget for TodoList() method
- HX-Target: "user-stats-widget" (no method match) -> returns functionRenderTarget for lazy evaluation
- No HX-Target or non-HTMX request -> returns methodRenderTarget for Page() method
This is the default TargetSelector for StructPages, making IDFor work seamlessly with HTMX out of the box.
type StructPages ¶
type StructPages struct {
// contains filtered or unexported fields
}
StructPages holds the parsed page tree context for URL generation. It is returned by Mount and provides URLFor and IDFor methods.
func Mount ¶ added in v0.0.15
Mount parses the page tree and registers all routes onto the provided mux. If mux is nil, routes are registered on http.DefaultServeMux. Returns a StructPages that provides URLFor and IDFor methods.
Parameters:
- mux: Any router satisfying the Mux interface (e.g., http.ServeMux). If nil, uses http.DefaultServeMux.
- page: A struct instance with route-tagged fields
- route: The base route path for this page tree (e.g., "/" or "/admin")
- title: The title for the root page
- options: Optional configuration (WithErrorHandler, WithMiddlewares, etc.) and dependency injection args
Example with custom mux:
mux := http.NewServeMux()
sp, err := structpages.Mount(mux, index{}, "/", "My App",
structpages.WithErrorHandler(customHandler))
sp.URLFor(index.Page)
http.ListenAndServe(":8080", mux)
Example with DefaultServeMux:
sp, err := structpages.Mount(nil, index{}, "/", "My App")
http.ListenAndServe(":8080", nil)
func (*StructPages) ID ¶ added in v0.1.0
func (sp *StructPages) ID(v any) (string, error)
ID generates a raw HTML ID for a component method (without "#" prefix). Use this for HTML id attributes. It works without context by using the structpages's parseContext directly.
Example:
sp.ID(p.UserList) // → "team-management-view-user-list" sp.ID(UserStatsWidget) // → "user-stats-widget" (no page prefix for standalone functions)
func (*StructPages) IDTarget ¶ added in v0.1.0
func (sp *StructPages) IDTarget(v any) (string, error)
IDTarget generates a CSS selector (with "#" prefix) for a component method. Use this for HTMX hx-target attributes. It works without context by using the structpages's parseContext directly.
Example:
sp.IDTarget(p.UserList) // → "#team-management-view-user-list" sp.IDTarget(UserStatsWidget) // → "#user-stats-widget" (no page prefix for standalone functions)
func (*StructPages) URLFor ¶ added in v0.0.15
func (sp *StructPages) URLFor(page any, args ...any) (string, error)
URLFor returns the URL for a given page type. If args is provided, it'll replace the path segments. Supported format is similar to http.ServeMux.
Unlike the context-based URLFor function, this method doesn't have access to pre-extracted URL parameters from the current request, so all required parameters must be provided as args.
If multiple page type matches are found, the first one is returned. In such situation, use a func(*PageNode) bool as page argument to match a specific page.
Additionally, you can pass []any to page to join multiple path segments together. Strings will be joined as is. Example:
sp.URLFor([]any{Page{}, "?foo={bar}"}, "bar", "baz")
It also supports a func(*PageNode) bool as the Page argument to match a specific page. It can be useful when you have multiple pages with the same type but different routes.
type TargetSelector ¶ added in v0.1.0
type TargetSelector func(r *http.Request, pn *PageNode) (RenderTarget, error)
TargetSelector determines which component to render for a request. It returns a RenderTarget that will be passed to Props.
The default selector is HTMXRenderTarget, which handles HTMX partial requests. Custom selectors can be provided via WithTargetSelector option.