Documentation
¶
Overview ¶
Package render is part of the GoFastr framework. See https://github.com/DonaldMurillo/gofastr for documentation.
Package render provides a type-safe HTML template engine built from scratch. Templates are Go code producing HTML with compile-time type checking and auto-escaping. Inspired by Templ.
All text content is HTML-escaped by default. Use Raw for trusted markup.
Index ¶
- Variables
- func Attr(key, value string) string
- func ClassIf(cond bool, name string) string
- func Classes(parts ...string) string
- func Escape(s string) string
- func GetComponent(name string) (any, bool)
- func HTMLHandler(fn func(r *http.Request) HTML) http.HandlerFunc
- func HasIDScope(ctx context.Context) bool
- func RegisterComponent(name string, fn any)
- func RegisterFunc(name string, fn any)
- func RegisterLayout(name string, layout Layout)
- func ReserveID(ctx context.Context, id string, children ...string)
- func RespondHTML(w http.ResponseWriter, h HTML)
- func SettleIDs(ctx context.Context, s string) string
- func UniqueID(ctx context.Context, id string, children ...string) string
- func WithIDNamespace(ctx context.Context, ns string) context.Context
- func WithIDNamespaceKind(ctx context.Context, kind byte, ns string) context.Context
- func WithIDScope(ctx context.Context) context.Context
- type Component
- type HTML
- func If(cond bool, html HTML) HTML
- func Join(children ...HTML) HTML
- func Raw(s string) HTML
- func RenderWithLayout(layoutName, title string, content HTML) HTML
- func Tag(name string, attrs map[string]string, children ...HTML) HTML
- func Text(s string) HTML
- func VoidTag(name string, attrs map[string]string) HTML
- func When(cond bool, fn func() HTML) HTML
- type Layout
- type RegionBatch
Constants ¶
This section is empty.
Variables ¶
var FuncMap = map[string]any{ "ToUpper": strings.ToUpper, "ToLower": strings.ToLower, "Trim": strings.TrimSpace, "Title": strings.Title, "DateFormat": func(t time.Time, layout string) string { return t.Format(layout) }, "NumberFormat": func(n float64, prec int) string { return strconv.FormatFloat(n, 'f', prec, 64) }, "Truncate": func(s string, maxRunes int) string { if maxRunes < 0 { return "" } if utf8.RuneCountInString(s) <= maxRunes { return s } runes := []rune(s) return string(runes[:maxRunes]) + "…" }, }
FuncMap is the default set of template functions available for use in rendering pipelines. Call RegisterFunc to add custom functions.
Functions ¶
func Attr ¶
Attr formats a single HTML attribute as key="value" with the value HTML-escaped.
The key is validated against a strict allow-list (ASCII letters, digits, `-`, `_`, `:`). Keys containing whitespace would let a caller smuggle a second attribute, e.g. `src onerror` becomes a fresh `onerror=` attribute when concatenated into a tag. Keys beginning with `on` (case-insensitive) are inline event handlers and are never legitimately constructed via this builder. A host that needs one should be writing handlers in runtime.js, not injecting JS into the HTML. Both cases return the empty string so the attribute is dropped from any tag that includes it.
func ClassIf ¶
ClassIf returns name when cond is true, otherwise the empty string. Argument order matches Go's `if cond { name }` reading order. Designed for use inside Classes:
render.Classes("base", render.ClassIf(isActive, "active"))
func Classes ¶
Classes joins non-empty string args with spaces. Pair with ClassIf for conditional class lists:
class := render.Classes(
"p-cond-row",
render.ClassIf(active, "active"),
render.ClassIf(hasError, "error"),
)
The returned string is plain text and is HTML-escaped automatically when assigned to a tag attribute via Tag or any html.* config.
For wide predicate-driven sets, see html.Classes(map[string]bool).
func Escape ¶
Escape replaces the five special HTML characters with their entity equivalents: &, <, >, ", '. This prevents XSS when inserting untrusted data into HTML documents.
func GetComponent ¶
GetComponent retrieves a previously registered component by name. Returns the function value and true if found, or nil and false. The caller should type-assert the result to the expected func signature.
func HTMLHandler ¶
func HTMLHandler(fn func(r *http.Request) HTML) http.HandlerFunc
HTMLHandler adapts a function that returns HTML into an http.HandlerFunc. This is the primary way to connect GoFastr templates to the Router.
func HasIDScope ¶ added in v0.96.0
HasIDScope reports whether ctx carries an id scope (WithIDScope, WithIDNamespace, or a region of a RegionBatch), so UniqueID hands out page-unique ids from it. A component that falls back to a process-wide counter when no scope is there asks this first: a counter gives an island one id on the page and another in its RPC answer.
func RegisterComponent ¶
RegisterComponent registers a component function under the given name. The function must have the signature func(T) HTML for some concrete type T. It panics if fn is nil.
func RegisterFunc ¶
RegisterFunc adds a named function to the global FuncMap. It panics if fn is nil. A later call under the same name replaces the earlier function, built-ins included.
func RegisterLayout ¶
RegisterLayout registers a Layout under the given name for later retrieval via RenderWithLayout.
func ReserveID ¶ added in v0.96.0
ReserveID records an explicit id and its family (see UniqueID) in ctx's scope without changing it, so a derived id that would repeat any of them is suffixed.
func RespondHTML ¶
func RespondHTML(w http.ResponseWriter, h HTML)
RespondHTML writes the given HTML to the response with the correct Content-Type header and a 200 OK status.
func SettleIDs ¶ added in v0.96.0
SettleIDs replaces the placeholders in s that the regions settled so far under ctx's scope stand for. A value a component built during a region's render (HTML seeded into a signal slice, say) holds the region's placeholders, and the region's Settle only rewrote the HTML it was handed: the host runs a value that leaves the render some other way through SettleIDs once the render is done. With no scope s is returned as is.
func UniqueID ¶ added in v0.96.0
UniqueID returns id the first time ctx's scope sees it and id-2, id-3, … on each repeat, skipping a suffixed id already taken. children are the suffixes of the ids the caller builds from the result ("-title" for "<id>-title"): the scope picks the first candidate whose whole family is free and takes all of it. With no scope (or a nil ctx) or an empty id it returns id unchanged. On a region of a RegionBatch it returns a placeholder that Settle replaces with the final id.
func WithIDNamespace ¶ added in v0.96.0
WithIDNamespace returns a context carrying a fresh id scope for a part of the page that also renders on its own (an island an RPC handler re-renders, a deferred outlet a part request fills). A derived id in it is "<ns>--<id>" ("cart--features"), suffixed within the namespace only, so the part's ids are the same whether it renders inside the page or alone: render it under the same ns both times. ns is spelled in id-safe bytes first: letters, digits and single inner hyphens stay, any other byte is "_" and its hex ("cart.v2" is "cart_2ev2"), so no namespace puts markup in an attribute and two namespaces never meet in one id. A slug never holds "--", so a namespaced id does not meet a derived id of the page around it, and each id the namespace hands out or reserves is reserved in ctx's scope as well. Explicit ids are kept as they are. An empty ns gives a plain fresh scope.
func WithIDNamespaceKind ¶ added in v0.96.0
WithIDNamespaceKind is WithIDNamespace for a second family of namespaces that must never meet the first (core-ui/app's deferred outlets, beside islands, whose ids nothing validates). The namespace reads "_<kind>-<encoded ns>" ("_l-shell-aside"): an encoded plain namespace holds "_" only before two hex digits, so with kind a letter from g to z no island id gives the same namespace. ns must not be empty.
Types ¶
type Component ¶
Component is a reusable template function parameterised by data of type T. Any function matching func(T) HTML can be used as a Component.
type HTML ¶
type HTML string
HTML is a type-safe wrapper around an HTML string fragment. Values of type HTML are assumed to contain safe, well-formed markup. Construct HTML values using Tag, Text, Raw, VoidTag, and Join.
func If ¶
If returns html when cond is true, otherwise an empty fragment. Useful for inline conditional rendering:
Tag("div", nil,
Text("hello"),
If(user.Admin, adminBadge()),
)
Both arguments evaluate eagerly. Go has no lazy semantics here. When the truthy branch is expensive (database query, heavy allocation), use When instead so the function only runs when cond is true.
func Raw ¶
Raw wraps the given string as HTML without any escaping. Only use Raw when the content is known to be safe, e.g. markup produced by trusted builder calls.
func RenderWithLayout ¶
RenderWithLayout looks up a registered Layout by name and applies it to the given title and content. Panics if no layout is registered under the given name.
func Tag ¶
Tag builds an HTML element from a tag name, optional attributes, and zero or more child HTML fragments.
Tag("div", nil, Text("hello")) → <div>hello</div>
Tag("a", map[string]string{"href": "/"}, Text("home")) → <a href="/">home</a>
The tag name is validated against a strict allow-list (ASCII letters, digits and `-`). A name containing whitespace or other characters would otherwise let a caller smuggle attributes, e.g. a name of `div onclick="alert(1)"` would render as `<div onclick="alert(1)">`, so any invalid name is replaced with a neutral `span`.
func Text ¶
Text wraps the given string as auto-escaped HTML text content. The input is HTML-escaped to prevent injection of raw markup.
func VoidTag ¶
VoidTag builds a self-closing HTML element (e.g. <img>, <br>, <hr>, <input>, <meta>, <link>). The tag is rendered without a closing tag. The tag name is validated like Tag. See that doc for rationale.
type Layout ¶
Layout is a function that wraps page content with a common chrome (head, navigation, footer, etc.). The title parameter allows the page to specify its own <title>.
type RegionBatch ¶ added in v0.96.0
type RegionBatch struct {
// contains filtered or unexported fields
}
RegionBatch hands out the contexts of n page regions and settles their ids once they have rendered. Build it with NewRegionBatch, render region i with Context(i) (on any goroutine), then call Settle for each region in order.
func NewRegionBatch ¶ added in v0.96.0
func NewRegionBatch(ctx context.Context, n int) *RegionBatch
NewRegionBatch prepares n regions under ctx's id scope. With no scope on ctx every region renders with ctx and Settle changes nothing.
func (*RegionBatch) Context ¶ added in v0.96.0
func (b *RegionBatch) Context(i int) context.Context
Context returns the render context of region i.
func (*RegionBatch) ReserveRendered ¶ added in v0.96.0
func (b *RegionBatch) ReserveRendered(i int, hs ...HTML)
ReserveRendered reserves, for region i, every id attribute written in hs: an element's id="..." with no placeholder in it, read as the page spells it once its character references are decoded. Most components forward an explicit id without calling ReserveID; reading their output catches every one of them, so no derived id lands on an id the region wrote. Call it with each region's output after every region has rendered and before the first Settle; a call after that reserves in the parent scope at once.
func (*RegionBatch) Settle ¶ added in v0.96.0
func (b *RegionBatch) Settle(i int) func(HTML) HTML
Settle hands region i's recorded ids out from the parent scope and returns the function that replaces each placeholder with its id. The first call reserves the explicit ids of every region; each call then hands out region i's derived ids in the order the region asked for them. Call it once per region, in region order, after every region has rendered, and apply the function to everything region i produced: region 1's repeat of an id region 0 used comes back suffixed, and no derived id lands on an explicit one.