arizona_template (arizona v0.4.0)
View SourceRuntime template API: bindings, descriptors, value rendering, and snapshots.
Most of this module's surface is exposed through the macros in
include/arizona_common.hrl (?get, ?html, ?each, ?stateful,
?stateless). Templates themselves are produced by arizona_parse_transform
at compile time -- html/1 is only a runtime stub that errors if the parse
transform was not applied.
Concepts
- Bindings -- the map passed to
render/1.get/2andget/3access bindings while tracking which keys were read, so the differ knows which dynamics depend on which bindings. - Templates -- compile-time maps
#{s := Statics, d := Dynamics, f := Fp}emitted by the parse transform. - Descriptors -- lightweight tuples returned by
stateful/2andstateless/2,3that tell the renderer how to mount a child component. - Snapshots -- cached
#{s, d, deps, ...}maps used by the differ to detect what changed between renders.
Example
render(Bindings) ->
?html({'div', [{class, ?get(theme)}], [?get(name)]}).
Summary
Functions
Stamps the value a conditional branch produced with that branch's index within
its slot, when the value is an az-nodiff template or a ?stateless descriptor
(whose template takes the stamp when it is rendered). Two branches can produce
the same template -- the same markup, the same helper -- and the stamp is what
tells them apart once their az-nodiff elements are no longer evaluated. Any other value is
returned as is.
Classifies the escape-exempt content shapes: a raw/1 opt-out
({arizona_raw, V}, returned unwrapped) and an already-encoded effect command
or command list ({arizona_effect, _}). Every other value is value.
Wraps a template/0 produced by the parse transform with an each source.
Escape the inner value of an {arizona_esc, _} marker.
Renders a value to its output binary, escaping via Backend (an
arizona_renderer): HTML entity-escaping, terminal control-char sanitizing, or
JSON string encoding. raw/1-wrapped values and already-encoded
arizona_effect commands are classified out and pass through unescaped (the
?raw opt-out and effects are trusted, backend-independent).
Formats compile/runtime error reasons emitted by this module.
Formats runtime errors raised with an error_info annotation pointing at
this module. Picked up by erl_error:format_exception/3.
Reads Key from Bindings and tracks the access for diff dependency analysis.
Reads Key from Bindings, returning Default if absent. Tracks the access.
Like get/3 but the default is computed lazily by a 0-arity fun.
Compile-time stub. The parse transform replaces every ?html(...) (and
arizona_template:html/1) call with a precomputed template/0 map. If
this function runs, the parse transform was not applied -- include
arizona_stateful.hrl or arizona_stateless.hrl.
Builds a client-owned slot value. The server renders Init once and never
diffs the slot (diff => false); the browser owns the value via Key,
updating it client-side with no round-trip. Used via the ?local macro in
content or attribute-value position. Key may be a binary or an atom (an atom
is normalized to a binary, so it reaches the client as the same string).
Builds a stateful child snapshot, propagating diff => false from the template.
Marks an evaluated value for HTML escaping at output. Only scalars are marked --
nested templates/descriptors and stateless-child snapshots (all maps) and
effects pass through untouched so they render structurally (their own inner
dynamics carry their own marks). A stateless descriptor returned from a
conditional content slot reduces to a bare snapshot map here, so the is_map
clause passes it through and scope_slot/2 later namespaces it -- the same
treatment a directly-slotted stateless child gets.
Propagates the template-level fields that must survive onto a snapshot: f
(fingerprint), the optional diff => false flag, the render backend, and the
a attributes the template declares.
Compile-time stub for the native (JSON) render target. The parse transform
replaces every ?native(...) (and arizona_template:native/1) call with a
precomputed template/0 map whose statics are JSON fragments. If this
function runs, the parse transform was not applied.
The value of the az-nodiff attribute on the root of an az-nodiff element: its
identity as same_nodiff/3 decides it -- the template's fingerprint F, the
conditional branch that produced it and its az_key -- spelled as one opaque
token. The client matches az-nodiff elements by it, so a wholesale re-render around a
az-nodiff element (a reconnect, a container ?OP_TEXT) carries the existing DOM over to a
new render naming the same az-nodiff element, and replaces it otherwise.
Wraps a value to opt out of HTML auto-escaping. Use only for trusted, already-safe HTML fragments -- never for user-controlled data.
Whether two az-nodiff snapshots of the same fingerprint are the same az-nodiff element: the
same producing branch and az_key, and the same child view when they are one.
Whether the template Tmpl, about to fill the slot Az, is the az-nodiff element
the slot's snapshot Old already holds: the same scoped fingerprint, the same
producing branch and the same az_key.
Namespaces a slot's rendered value by the enclosing slot Az, so a repeated
stateless child (or any inline nested template) is an independent diff target.
Prefixes the value's fingerprint, its statics' baked az/marker ids, and every
inner dynamic az (recursing through inline nested templates) with the slot az.
Splits a list of {Az, Val, Deps} triples into the snapshot d-list and
deps list. (arizona_render:render/2 needs the document too and fuses this
split into its zip walk, zip_triples/3.)
Builds a stateful child descriptor. The renderer mounts Handler with Props.
Builds a stateless child descriptor from a 1-arity render fun.
Builds a stateless child descriptor from a Handler:Fun/1 reference.
Renders a ?stateless descriptor to its template, passing on the branch stamp
the descriptor carries (see branch/2).
Compile-time stub for the terminal (ANSI) render target. The parse transform
replaces every ?terminal(...) (and arizona_template:terminal/1) call with a
precomputed template/0 map whose statics are ANSI-decorated text. If this
function runs, the parse transform was not applied.
Converts a template value to its binary HTML representation.
Records Key as a dependency of the dynamic element currently being rendered.
Materializes an attribute dynamic into its rendered binary through Backend
(an arizona_renderer), leaving plain values untouched.
Returns the visible portion of a stream order buffer given a Limit.
Projects Bindings to a sub-map of only Keys, tracking each key as a
dependency.
Types
-nominal az() :: binary() | undefined.
-type bindings() :: map().
-nominal deps() :: #{term() => true}.
-nominal each_container() :: #{t := 0, source := term(), template := each_template()}.
-nominal loc() :: {module(), pos_integer()}.
-nominal nodiff_identity() :: {pos_integer() | undefined, term()}.
-nominal stateless_descriptor() :: #{callback := fun((map()) -> template()), props := map(), branch => pos_integer()}.
Functions
-spec branch(Branch, Value) -> Value1 when Branch :: pos_integer(), Value :: term(), Value1 :: term().
Stamps the value a conditional branch produced with that branch's index within
its slot, when the value is an az-nodiff template or a ?stateless descriptor
(whose template takes the stamp when it is rendered). Two branches can produce
the same template -- the same markup, the same helper -- and the stamp is what
tells them apart once their az-nodiff elements are no longer evaluated. Any other value is
returned as is.
The parse transform stamps a literal template itself and calls this only for a branch value it cannot see at compile time.
-spec classify_trusted(Value) -> {raw, term()} | {effect, Effect} | value when Value :: term(), Effect :: term().
Classifies the escape-exempt content shapes: a raw/1 opt-out
({arizona_raw, V}, returned unwrapped) and an already-encoded effect command
or command list ({arizona_effect, _}). Every other value is value.
The definition of the shape vocabulary -- escape_value/2, mark_esc/1, and
arizona_html:render_attr/2 dispatch on it. to_bin/1 matches the same shapes
in its clause heads instead, because it runs right after one of those callers
has already classified the value; growing the vocabulary means updating both.
Each call site decides the policy for a shape (mark_esc/1 deliberately unwraps
and re-escapes a helper-produced raw).
-spec each(Source, Template) -> each_container() when Source :: term(), Template :: each_template().
Wraps a template/0 produced by the parse transform with an each source.
Called by the parse transform to compile ?each(Fun, Source) -- pairs
the per-item template with its source list, stream, or map.
?each(Fun, Source) expands to a call on this same name, which the parse
transform replaces with the compiled pairing above. Reached with the macro's
own (Fun, Source) argument order, the transform did not run: say so with
parse_transform_not_applied rather than a bare function_clause.
Escape the inner value of an {arizona_esc, _} marker.
The marker itself is the classification: mark_esc/1 is its only producer and it
wraps only what already came back as a plain value, so re-asking
classify_trusted/1 here can only ever get the same answer. Escapable content is
most of what a template renders, so that second ask is paid per value per render.
Renders a value to its output binary, escaping via Backend (an
arizona_renderer): HTML entity-escaping, terminal control-char sanitizing, or
JSON string encoding. raw/1-wrapped values and already-encoded
arizona_effect commands are classified out and pass through unescaped (the
?raw opt-out and effects are trusted, backend-independent).
Formats compile/runtime error reasons emitted by this module.
-spec format_error(Reason, Stacktrace) -> ErrorInfo when Reason :: term(), Stacktrace :: [tuple()], ErrorInfo :: #{general := iolist()}.
Formats runtime errors raised with an error_info annotation pointing at
this module. Picked up by erl_error:format_exception/3.
Reads Key from Bindings and tracks the access for diff dependency analysis.
Errors with missing_binding (carrying an error_info annotation that
routes through format_error/2) if not present. Use get/3 for a default.
-spec get(Key, Bindings, Default) -> Value when Key :: term(), Bindings :: map(), Default :: term(), Value :: term().
Reads Key from Bindings, returning Default if absent. Tracks the access.
-spec get_lazy(Key, Bindings, DefaultFun) -> Value when Key :: term(), Bindings :: map(), DefaultFun :: fun(() -> term()), Value :: term().
Like get/3 but the default is computed lazily by a 0-arity fun.
Compile-time stub. The parse transform replaces every ?html(...) (and
arizona_template:html/1) call with a precomputed template/0 map. If
this function runs, the parse transform was not applied -- include
arizona_stateful.hrl or arizona_stateless.hrl.
Builds a client-owned slot value. The server renders Init once and never
diffs the slot (diff => false); the browser owns the value via Key,
updating it client-side with no round-trip. Used via the ?local macro in
content or attribute-value position. Key may be a binary or an atom (an atom
is normalized to a binary, so it reaches the client as the same string).
-spec make_child_snap(Template, ChildD, ChildDeps, Id) -> snapshot() when Template :: template(), ChildD :: [{az(), term()}], ChildDeps :: [deps()], Id :: binary().
Builds a stateful child snapshot, propagating diff => false from the template.
Marks an evaluated value for HTML escaping at output. Only scalars are marked --
nested templates/descriptors and stateless-child snapshots (all maps) and
effects pass through untouched so they render structurally (their own inner
dynamics carry their own marks). A stateless descriptor returned from a
conditional content slot reduces to a bare snapshot map here, so the is_map
clause passes it through and scope_slot/2 later namespaces it -- the same
treatment a directly-slotted stateless child gets.
mark_esc/1 is reached only for an escape-tagged content slot ({esc, Fun}),
which the parse transform emits for every content interpolation that is not a
literal block -- a literal raw/1 at the template site compiles to a bare
(un-esc-tagged) slot and never reaches here. So a {arizona_raw, V} arriving
here was produced by a helper returning raw/1 into an escaping slot, which
the documented rule forbids ("the parse transform only recognizes the opt-out
when the raw call is literal at the template site"). Honor that rule: unwrap
the raw and escape its inner value like any plain scalar, so a helper wrapping
user data in raw/1 cannot smuggle trusted markup past the escaper (on SSR or
across the live diff).
-spec maybe_propagate(Template, Snapshot) -> Snapshot1 when Template :: template(), Snapshot :: snapshot(), Snapshot1 :: snapshot().
Propagates the template-level fields that must survive onto a snapshot: f
(fingerprint), the optional diff => false flag, the render backend, and the
a attributes the template declares.
Used by both the render path (arizona_render) and the diff path
(arizona_diff), so a diffed snapshot carries the same backend as a freshly
rendered one -- the field is a uniform invariant on every snapshot.
Compile-time stub for the native (JSON) render target. The parse transform
replaces every ?native(...) (and arizona_template:native/1) call with a
precomputed template/0 map whose statics are JSON fragments. If this
function runs, the parse transform was not applied.
-spec nodiff_token(F, Branch, Key) -> binary() when F :: binary(), Branch :: pos_integer() | undefined, Key :: term().
The value of the az-nodiff attribute on the root of an az-nodiff element: its
identity as same_nodiff/3 decides it -- the template's fingerprint F, the
conditional branch that produced it and its az_key -- spelled as one opaque
token. The client matches az-nodiff elements by it, so a wholesale re-render around a
az-nodiff element (a reconnect, a container ?OP_TEXT) carries the existing DOM over to a
new render naming the same az-nodiff element, and replaces it otherwise.
The token is not scoped: where the az-nodiff element sits (its slot, the item it is in) is read off the DOM by the client, which is where those addresses already are.
Wraps a value to opt out of HTML auto-escaping. Use only for trusted, already-safe HTML fragments -- never for user-controlled data.
This is an HTML-target feature: on the live diff a ?raw value is tagged so the
client innerHTMLs it (vs text-noding a plain ?get value -- the escaping boundary).
The ?native and ?terminal targets do not HTML-escape, so ?raw there is a no-op
with no defined meaning; using it in a ?native/?terminal template is unsupported.
Whether two az-nodiff snapshots of the same fingerprint are the same az-nodiff element: the
same producing branch and az_key, and the same child view when they are one.
-spec same_nodiff(Az, Tmpl, Old) -> boolean() when Az :: binary(), Tmpl :: term(), Old :: snapshot().
Whether the template Tmpl, about to fill the slot Az, is the az-nodiff element
the slot's snapshot Old already holds: the same scoped fingerprint, the same
producing branch and the same az_key.
Namespaces a slot's rendered value by the enclosing slot Az, so a repeated
stateless child (or any inline nested template) is an independent diff target.
Prefixes the value's fingerprint, its statics' baked az/marker ids, and every
inner dynamic az (recursing through inline nested templates) with the slot az.
A stateless component has no view_id boundary of its own, and its inner az
ids derive only from its own template fingerprint (<Fp>-<n>). So the same
render function rendered more than once in a view produces byte-identical az
targets; on the client every diff op resolves with querySelector (first match)
and lands on the first instance. Namespacing by the slot az -- applied here,
where the slot is in scope -- keeps each instance distinct. The same prefixing
makes two structurally identical inline nested templates (e.g. same-shaped
conditional branches) in different slots distinct too.
The prefix is made colon-free (: -> -): the client treats : as the
content-slot-index separator (a base element az never contains one) and
recovers a base element by stripping at the first colon, so an internal colon
in the prefix would misroute multi-slot and ?each-among-siblings ops. A
stateful child (view_id boundary), an ?each container (items are addressed
relative to the container, not by global az), and a client-owned ?local
slot are left untouched; a scalar (escaped or bare) has no az to prefix and
passes through. This is exactly the structural discrimination scope_val/2
applies inside an already-scoped snapshot, reused here for the top-level slot
value -- so a stateless child evaluates to a plain snapshot map (no wrapper tag)
and is scoped by the same #{s, d} path as an inline nested template.
-spec split_triples(Triples) -> {DList, DepsList} when Triples :: [{az(), term(), deps()}], DList :: [{az(), term()}], DepsList :: [deps()].
Splits a list of {Az, Val, Deps} triples into the snapshot d-list and
deps list. (arizona_render:render/2 needs the document too and fuses this
split into its zip walk, zip_triples/3.)
-spec stateful(Handler, Props) -> stateful_descriptor() when Handler :: module(), Props :: map().
Builds a stateful child descriptor. The renderer mounts Handler with Props.
-spec stateless(Callback, Props) -> stateless_descriptor() when Callback :: fun((map()) -> template()), Props :: map().
Builds a stateless child descriptor from a 1-arity render fun.
-spec stateless(Handler, Fun, Props) -> stateless_descriptor() when Handler :: module(), Fun :: atom(), Props :: map().
Builds a stateless child descriptor from a Handler:Fun/1 reference.
-spec stateless_template(Descriptor) -> term() when Descriptor :: stateless_descriptor().
Renders a ?stateless descriptor to its template, passing on the branch stamp
the descriptor carries (see branch/2).
Compile-time stub for the terminal (ANSI) render target. The parse transform
replaces every ?terminal(...) (and arizona_template:terminal/1) call with a
precomputed template/0 map whose statics are ANSI-decorated text. If this
function runs, the parse transform was not applied.
Converts a template value to its binary HTML representation.
Errors with {bad_template_value, V} for unsupported types.
-spec track(Key) -> ok when Key :: term().
Records Key as a dependency of the dynamic element currently being rendered.
Called automatically by get/2,3 and get_lazy/3. Public so render code
can attribute dependencies that bypass binding access.
It is a no-op outside a dynamic's evaluation, since that is where the
dependency scope is opened. Do not read that as "a ?get in the body of
render/1 records nothing" -- what a slot depends on is decided at compile
time, not here. The parse transform inlines a fresh read into the slot's own
closure, so the body-level call is the one that no-ops while the inlined one
records. erlc -P shows which reads ended up inside which closure, and is the
only reliable way to answer "why does this slot not update". See "The
invariant" in docs/architecture.md.
Materializes an attribute dynamic into its rendered binary through Backend
(an arizona_renderer), leaving plain values untouched.
-spec visible_keys(Order, Limit) -> Order1 when Order :: {[term()], [term()]}, Limit :: pos_integer() | infinity, Order1 :: [term()].
Returns the visible portion of a stream order buffer given a Limit.
The order is the arizona_stream record's {Front, BackRev} 2-list
buffer (Back holds recently-appended keys NEWEST-first). This function
flushes the buffer to a flat oldest-first list and applies the limit.
infinity returns the full order; an integer truncates via
lists:sublist/2.
-spec with(Keys, Bindings) -> Bindings1 when Keys :: [term()], Bindings :: map(), Bindings1 :: map().
Projects Bindings to a sub-map of only Keys, tracking each key as a
dependency.
Use it to hand a bindings subset to a sub-context (a helper, a child, a
passed-through map) while declaring exactly which keys it depends on: the
projection means the sub-context cannot silently read an untracked key (an
omitted key fails loudly with missing_binding rather than freezing), and the
tracking makes the enclosing slot re-render when any listed key changes.
Equivalent to track/1 on each key followed by maps:with(Keys, Bindings).