*updated
Summary
*updated registers a post-update hook.
It is evaluated after a Sercrod host has finished its update cycle, and can be attached in two places:
- On Sercrod hosts themselves (
<serc-rod>or other Sercrod-based custom elements) via*updatedorn-updated. - On ordinary descendant elements inside a Sercrod host via
*updatedorn-updated, in which case the nearest Sercrod ancestor executes the handler.
Typical uses:
- Run side effects after rendering (analytics, third-party widgets, measurements).
- Trigger follow-up updates on other Sercrod hosts.
- Perform low-level DOM adjustments that are hard to express as pure data binding.
For propagating updates upwards in the Sercrod tree, see *updated-propagate / n-updated-propagate.
Basic example
A simple host-level hook that logs every render:
<script>
function onHostUpdated(event) {
console.log("Host updated:", event.type, "id:", event.target.id);
}
</script>
<serc-rod
id="app"
data='{"count": 0}'
*updated="onHostUpdated($event)"
>
<button *on="click: count++">Increment</button>
</serc-rod>
Behavior:
- Whenever
appre-renders (initial render or subsequent updates), Sercrod callsonHostUpdated($event). $event.typeis"sercrod:init"on the first render and"sercrod:update"on later renders.$event.targetand$event.currentTargetboth point to the host Sercrod element (<serc-rod id="app">).
Behavior
High-level behavior:
-
On Sercrod hosts:
*updatedis treated as a lifecycle hook that runs after the host has finished its update.- The attribute value is interpreted in several forms (object name, selector in parentheses, or a general expression).
- The hook is called even when the host decides to skip re-rendering itself because of
*lazy, as long as the update cycle runs.
-
On ordinary elements inside a Sercrod host:
*updatedis absorbed by the nearest ancestor Sercrod host.- The host executes the handler after it has completed a real render pass for that host.
- The handler runs in the host’s data scope, with a reference to the element where
*updatedis attached.
Aliases:
*updatedandn-updatedare aliases.*updated-propagateandn-updated-propagateare separate directives that forward updates to other Sercrod hosts and are documented on their own page.
Handler resolution on Sercrod hosts
For Sercrod hosts, the runtime interprets the attribute value in three stages.
- Split into tokens
-
Sercrod reads the value of
*updated/n-updatedand splits it on whitespace and commas:*updated="Utils"→ entries:["Utils"]*updated="(main) (sidebar)"→ entries:["(main)", "(sidebar)"]*updated="onUpdated($event)"→ entries:["onUpdated($event)"]
- Object-bulk entries
For each entry, Sercrod first checks if it is the name of a global object:
- If
window[entry]is a non-null object, Sercrod calls all enumerable function properties on that object with the host as the only argument.
Example:
<script>
const AppUpdatedHooks = {
log(host) {
console.log("Updated:", host.id);
},
measure(host) {
console.log("Children:", host.childElementCount);
}
};
</script>
<serc-rod
id="app"
data='{"value": 1}'
*updated="AppUpdatedHooks"
>
<p *print="value"></p>
</serc-rod>
- In this example, after each update Sercrod will call
AppUpdatedHooks.log(app)andAppUpdatedHooks.measure(app).
Notes:
- If at least one entry is treated as an object, Sercrod marks the hook as handled and does not fall back to expression evaluation.
- Object-bulk resolution only exists for
*updatedon Sercrod hosts; it is not applied to*updatedon ordinary elements.
- Selector form
"(selector)"
If an entry starts and ends with parentheses, Sercrod treats the content as a CSS selector:
*updated="(selector)"
Resolution:
-
Sercrod finds the nearest ancestor Sercrod host to use as the root for the selector lookup (this may be the host itself or its parent, depending on nesting).
-
It then:
- If that root matches the selector and is a Sercrod host, calls
root.update(true)to re-render it. - Collects Sercrod descendants via its internal index and, for those that match the selector, calls their
*updatedhooks directly (without forcing a re-render on them).
- If that root matches the selector and is a Sercrod host, calls
Effectively:
- On the root that matches the selector:
update(true)is called. - On descendant Sercrod hosts that match the selector: only their
*updatedhooks run, without forcing another render beyond what their own lifecycle does.
- Fallback expression
If no entries were handled as an object or selector, Sercrod evaluates the entire attribute string once as a JavaScript statement in the host data scope:
*updated="onUpdated($event)"→onUpdated($event)is executed once.*updated="doOneThing(); doAnotherThing()"→ both calls run as one statement.
Details:
-
The expression is evaluated via Sercrod’s
eval_letmachinery. -
Scope:
- All properties of the host’s data object are available as bare identifiers.
- Methods registered via
*methods/n-methodsare available by name. - Internal helpers registered by Sercrod itself are also available.
-
Special variables:
-
eland$elboth refer to the host Sercrod element. -
$event(or$e) is provided and has:type:"sercrod:init"or"sercrod:update".target/currentTarget: the host Sercrod element.- It may also carry additional information when the update was triggered by another host.
-
-
The expression is run as a statement, not as a pure expression; you can use semicolons and multiple statements.
Handler resolution on ordinary elements
For *updated / n-updated attached to ordinary elements inside a Sercrod host, the runtime uses a separate path:
-
It walks the subtree of the Sercrod host using a
TreeWalker. -
It skips:
- The host itself.
- All nested Sercrod hosts (their internals are left to those hosts).
-
It does visit:
- Normal HTML elements.
- Other custom elements that are not Sercrod, treating them as normal elements.
For each visited element with *updated or n-updated:
- Determine the host
- The nearest ancestor Sercrod host is located and used as the execution host.
- If no ancestor Sercrod is found, the original host that started the scan is used.
- Build the evaluation environment
-
Sercrod constructs:
evt = { type: "updated", target: el, host }- A data scope that clones
host._dataand may include this event object internally.
-
The handler expression is taken from the attribute value as a whole (no object-bulk split for this case).
- Interpret the value
-
If the value is of the form
"(selector)":- The selector is resolved from the host as root.
- Sercrod re-renders every matching Sercrod host by calling
update(true)on them. - Both the root and matching descendants may be re-rendered, possibly causing their own
*updatedhooks to fire as part of those updates.
-
Otherwise (normal form):
-
The value is interpreted as a JavaScript statement and evaluated in the host’s data scope:
- All data fields are available as bare identifiers.
- Methods registered via
*methods/n-methodson the host are available. elrefers to the element that owns*updated.
-
Important current behavior:
- On ordinary elements, the handler is evaluated with
elbound to the element, but without exposing a$eventvariable to the expression. - If you need the element inside the handler, use
elrather than$event.target. - If you need the host, derive it via selectors (
el.closest("serc-rod")) or from your own data.
Evaluation timing
*updated participates in the Sercrod host update cycle.
For each host:
-
An update cycle may be triggered by:
- Initial connection to the DOM.
- Data changes via the reactive proxy.
- Explicit
update()calls. - Other directives such as
*post,*fetch,*input, etc.
-
At the end of the update cycle, Sercrod runs hooks in the following order (simplified):
- Structural re-render (unless suppressed by
*lazy). - Child Sercrod updates (
_updateChildren). - Host-level
*updated(_call_updated_hooks). - Absorption of
*updatedon ordinary descendants (_absorb_child_updated) - only after a real re-render path. - Finalization steps.
- Structural re-render (unless suppressed by
Practical consequences:
- Host-level
*updatedruns on every update cycle, including cycles where the host is marked*lazyand decides not to redraw its own content. *updatedon ordinary elements is processed only when the host has performed a real template-based re-render; it is not executed when the host skips the re-render due to*lazy.- Selector-based forms that call
update(true)on other hosts may cause those hosts’*updatedhooks to run in turn, subject to the globalloop_limitguard.
Execution model
On Sercrod hosts, conceptually:
-
Sercrod obtains the attribute value and splits it into entries.
-
For each entry:
-
If it matches a global object name, call each function in that object with the host as argument.
-
Else if it is of the form
"(selector)", resolve the selector relative to the nearest Sercrod ancestor and:- Call
update(true)on the matching root. - Call
_call_updated_hookson matching descendant Sercrod hosts.
- Call
-
-
If none of the entries were handled in special ways, treat the whole attribute value as one JavaScript statement and evaluate it in the host’s data scope with
eland$eventbound appropriately.
On ordinary elements inside a host:
-
After the host finishes a real render, it walks its subtree (excluding nested Sercrod instances).
-
For each element with
*updated/n-updated:- Determine the host for that element.
- If the value is
"(selector)", re-render matching Sercrod hosts. - Else evaluate the attribute value as a JavaScript statement in the host’s data scope, with
elbound to the element.
Infinite loop protection:
- The Sercrod host has an internal
loop_limit(default 100) that caps chained updates. - If your
*updatedhandlers repeatedly trigger updates on the same set of hosts, Sercrod will stop the chain after the limit. - You should still design handlers to converge quickly and not depend on this guard.
Variable creation and scope layering
For host-level *updated fallback expressions:
-
Scope is the host’s data proxy (
this._data), with:- All data fields injected as identifiers.
- Methods from
*methods/n-methodsinjected by name. - Internal helpers injected by name.
el/$elbound to the host element.$event/$ebound to the lifecycle event object.
For *updated on ordinary elements:
-
Scope is based on the nearest host’s data, with:
- All data fields from that host available as identifiers.
- Methods from that host’s
*methods/n-methodsavailable. elbound to the element that owns*updated.
Other scope rules:
- Any variables created inside the handler (via assignment) write into the current data scope, following the same rules as
*letand other expressions. $parentis automatically injected byeval_letand refers to the nearest ancestor Sercrod host’s data when available.- Outer data scopes (for example, root vs nested Sercrod) are resolved according to the usual Sercrod scoping model.
Parent access
*updated does not create new parent pointers itself, but leverages the shared scope model:
-
Inside a host-level
*updatedexpression:$root(if you use that convention in your data) and other data fields behave exactly as in any other expression.$parentgives access to the data of the nearest ancestor Sercrod host if this host is nested.
-
Inside an element-level
*updatedexpression:- The data scope corresponds to the nearest Sercrod host.
el.closest("serc-rod")gives you the DOM host, which you can combine withelfor DOM-based parent access.
In both cases, you do not get implicit references to arbitrary DOM ancestors beyond what you can derive via el and selectors.
Use with conditionals and loops
*updated is not structural and does not conflict with *if, *for, *each, or other structural directives.
Guidelines:
-
On Sercrod hosts:
- You rarely combine
*updatedwith structural directives on the same element, because hosts themselves are usually structural roots. - If you do (for example, a custom Sercrod-based element that has both
*forandn-updated), the structural directives control rendering, and*updatedstill runs after each update cycle.
- You rarely combine
-
On ordinary elements:
- You can freely combine
*updatedwith*if,*for,*each, and bindings.
Example:
<div *if="modalOpen" *updated="onModalShown(el)"> <!-- Modal contents --> </div>- Here,
onModalShown(el)will be called after the host re-renders withmodalOpentrue and the<div>present in the DOM. - When
modalOpenbecomes false and the element is removed, no*updatedruns for that removal; the hook is tied to successful updates, not deletions.
- You can freely combine
-
Selector-based
*updatedand*updated-propagatemay cause additional structural updates, but those updates follow the normal structural directive rules on the affected hosts.
Best practices
-
Prefer simple expressions:
- For most hooks, prefer
*updated="onUpdated($event)"on hosts and*updated="onElementUpdated(el)"on ordinary elements. - Use object-bulk (
*updated="AppUpdatedHooks") only when you really want a collection of functions to run for every update.
- For most hooks, prefer
-
Keep handlers fast:
*updatedruns after every update; heavy operations in handlers can quickly degrade performance.- If you need expensive operations, consider throttling or debouncing inside your handler.
-
Use selector forms judiciously:
*updated="(selector)"can trigger multiple host updates and chains of*updatedhooks.- This is powerful but easy to misuse; start with more direct references if possible.
-
Avoid self-triggering loops:
- A host that uses
*updatedto re-trigger its ownupdate(true)without a stable stopping condition will approach the loop limit. - Design your handlers so that repeated updates eventually converge (for example, only updating when some property changes from one state to another).
- A host that uses
-
Prefer
*updated-propagatefor upward signaling:- When you want an inner element or nested host to tell a parent to refresh, use
*updated-propagatewithroot, numeric, or selector targets. - Reserve
*updateditself for local post-update effects.
- When you want an inner element or nested host to tell a parent to refresh, use
Examples
Host-level lifecycle hook:
<script>
function focusFirstInput(event) {
const host = event.target;
const input = host.querySelector("input, textarea, select");
if (input) input.focus();
}
</script>
<serc-rod
id="formHost"
data='{"showForm": true}'
*updated="focusFirstInput($event)"
>
<form *if="showForm">
<input type="text" name="name">
<button type="submit">Save</button>
</form>
</serc-rod>
Element-level hook inside a repeated list:
<script>
function highlightNewItem(el) {
el.classList.add("is-new");
setTimeout(() => el.classList.remove("is-new"), 300);
}
</script>
<serc-rod id="list" data='{"items":[{"id":1},{"id":2}]}'>
<ul>
<li
*for="item of items"
*updated="highlightNewItem(el)"
>
<span *print="item.id"></span>
</li>
</ul>
</serc-rod>
Host-level selector form:
<serc-rod id="root" data='{"filter": "all"}'>
<serc-rod
id="panelA"
data='{"items": []}'
*updated="(serc-rod#panelB)"
>
<!-- ... -->
</serc-rod>
<serc-rod
id="panelB"
data='{"items": []}'
*updated="onPanelBUpdated($event)"
>
<!-- ... -->
</serc-rod>
</serc-rod>
- Whenever
panelAfinishes updating, its*updateduses the selector form to reachpanelBand invoke its*updatedhook. - Depending on your design,
panelBmay callupdate(true)on itself or perform some other side effect inonPanelBUpdated.
Notes
*updatedandn-updatedare aliases; choose one style per project to keep templates consistent.*updatedis evaluated only by Sercrod hosts; ordinary elements rely on the nearest host to absorb and execute their handlers.*updated-propagate/n-updated-propagateare closely related but distinct directives that callupdate(true)on other Sercrod hosts instead of simply running a handler.*updateddoes not wait for images, fonts, or external resources to finish loading; it runs after the Sercrod update cycle, not after full browser layout or paint.- Because
*updatedhooks run in the same expression engine as*let, they obey the same scoping rules ($parent, methods, internal helpers) and should be written with the same safety considerations.