*textContent
Summary
*textContent sets the DOM textContent property of an element from an expression. It is a text-oriented directive, similar in spirit to *print, and is paired with the alias n-textContent.
Use *textContent when you want to explicitly bind an expression to an element’s textContent, ignoring any static child markup.
Basic example
Simple binding to a message:
<serc-rod id="app" data='{"message":"Hello Sercrod"}'>
<p *textContent="message"></p>
</serc-rod>
Behavior:
- The
<p>element is rendered. - Sercrod evaluates
messagein the current scope. - The result is converted to text via the
textfilter and assigned top.textContent. - Any static children of
<p>in the template are not rendered;textContentcompletely replaces them.
Behavior
*textContentis a one-way text binding from data to the DOM.- It assigns the stringified result of an expression to the element’s
textContentproperty. - The directive is evaluated once per render of the element (and again on re-renders triggered by data updates).
- The alias
n-textContentbehaves the same as*textContent; only the attribute name differs.
Short manual entry (built into Sercrod):
textContent: set the DOMtextContentproperty from an expression.- Example:
<div *textContent="message"></div>
Expression evaluation
When Sercrod encounters an element with *textContent or n-textContent:
-
It chooses a “source attribute” in this priority:
*printn-print*textContentn-textContent
-
Only the first attribute in this list is actually used.
- If
*printorn-printis present, the*textContent/n-textContentexpression is ignored. - This makes mixing these attributes on the same element pointless; you should choose one.
- If
-
Once the source attribute is chosen:
-
Sercrod reads its value as an expression string.
-
If a bundler-specific
normalizeTplhook is available, it normalizes the expression string. -
It evaluates the expression with
eval_expr(expr, scope, { el: node, mode: "textContent" })for*textContent, or with a corresponding mode forn-textContent. -
The raw evaluation result
vis mapped to a “raw text” value:- If
visnullorfalse, the raw text becomes the empty string"". - For any other value, the raw text is
vas-is and will be converted to a string later.
- If
-
The raw text is passed through the
textfilter:Sercrod._filters.text(raw, { el, expr, scope })- By default, the filter is defined as
String(raw ?? ""). - Projects can override this filter to change how textual values are produced (for example, to clamp length or apply additional formatting).
-
The final string is assigned to
el.textContent.
-
Error handling:
-
If expression evaluation or filtering throws, Sercrod falls back to:
el.textContent = "".
Cleanup:
-
If
this.constructor._config.cleanup.directivesis enabled, Sercrod removes the directive attributes from the rendered element:*printn-print*textContentn-textContent
-
This keeps the output DOM clean, leaving only the resulting text content.
Evaluation timing
*textContent is not a structural directive; it runs after the structural layer for an element has succeeded.
Rough evaluation order for a given element:
-
Structural checks on the template node (host):
*if/n-if,*elseif/*else,*switch/n-switch,*each/n-each,*for/n-for, and similar control directives run at higher stages.- If a structural directive decides to drop or replace the element,
*textContentdoes not run.
-
Sercrod host checks and other element-level decisions.
-
Rendering of a concrete DOM element (
el) for this template node. -
Text directives:
- If the element has
*print,n-print,*textContent, orn-textContent, the combined branch for “print/textContent” is executed. - That branch sets
el.textContentand appendselto the parent. - After this branch returns, Sercrod does not recurse into children and does not process other content directives for this element.
- If the element has
-
Fallback text handling:
- If there is no text directive, but the element has exactly one static text child, Sercrod may:
- Copy the text verbatim, or
- Expand
%expr%placeholders via_expand_text, then assign the result totextContent.
- If there is no text directive, but the element has exactly one static text child, Sercrod may:
-
Other content directives:
- Only when no text directive and no simple static-text optimization applies, Sercrod proceeds to check
*compose/n-compose,*innerHTML/n-innerHTML, and so on.
- Only when no text directive and no simple static-text optimization applies, Sercrod proceeds to check
Important consequence:
- As soon as
*textContent(or*print) is present, that directive “wins” the content for the element:- Child nodes are not rendered.
%expr%inline expansions in static text are not used.*compose/*innerHTMLare never reached for that element.
Execution model
The execution model for *textContent on one element can be summarized as:
-
Sercrod creates a new DOM element
elcorresponding to the template node. -
It detects whether the element has any text directive:
*print,n-print,*textContent,n-textContent.
-
If so:
- It chooses the source attribute (with
*print>n-print>*textContent>n-textContentpriority). - It evaluates the expression and passes the result through the
textfilter. - It sets
el.textContentto the filtered string. - It optionally removes the directive attributes from
eldepending on the cleanup configuration. - It appends
elto the parent. - It returns early; no children are rendered and no other content directives are considered.
- It chooses the source attribute (with
-
If not:
- The renderer falls back to static text or child-node rendering paths.
Combined with reactivity:
- When data changes and triggers an update, the containing Sercrod host re-renders, repeating the same process.
*textContentexpressions are re-evaluated in the new scope, and the newtextContentis applied.
Variable creation and scope layering
*textContent does not create any new variables in the scope.
Inside the expression:
-
You can use all normal scope variables:
- Fields from the host data.
- Loop variables from surrounding
*for/*each. - Temporary variables from
*let. - Special helpers injected by Sercrod such as
$data,$root, and$parent.
Scope behavior:
- The expression for
*textContentis evaluated in the “effective scope” for this element. - The directive itself does not alter dynamic scope; it only reads from it.
- Assigning to variables inside the expression (for example,
x = x + 1) is not the purpose of*textContentand should be avoided; use*let,*global, or explicit methods instead when you need side effects.
Parent access
*textContent has no special concept of “parent data” beyond what Sercrod already supplies:
$parentgives the nearest ancestor Sercrod host’s data.$rootgives the outermost Sercrod host’s data.- Normal lexical names refer to the current element’s scope, including any loop or
*letvariables.
Typical usage:
<serc-rod id="todo" data='{"items":[{"title":"Buy milk"}]}'>
<ul *each="item of items">
<li *textContent="item.title"></li>
</ul>
</serc-rod>
Here, item is introduced by *each, and *textContent simply reads it.
Use with conditionals and loops
*textContent works well inside structural directives:
-
With
*if:<p *if="user" *textContent="user.name"></p>*ifruns first; ifuseris falsy, the<p>is not rendered and*textContentnever runs.- If
useris truthy, the<p>is created and*textContentsetstextContent.
-
With
*each:<ul *each="item of items"> <li *textContent="item.label"></li> </ul>*eachdecides the number of iterations.- For each iteration,
*textContentruns on the<li>clone in that iteration’s scope.
-
With
*for:<ul> <li *for="item of items" *textContent="item.label"></li> </ul>*forrepeats the<li>element itself.*textContentsets thetextContentfor each repeated<li>.
In all cases:
- Structural directives determine whether and how many elements exist.
*textContentcontrols what text each existing element displays.
Interaction with other content directives
*textContent participates in the same “content choice” layer as *print, and it precedes other content directives:
-
*printvs*textContent:- Both share the same implementation branch.
- If both are present on the same element,
*print(orn-print) is used and*textContent(orn-textContent) is ignored. - This is a defined implementation detail but not a useful pattern; in practice, you should choose one directive.
- If you want to emphasize DOM property semantics, use
*textContent; if you prefer “printing” semantics, use*print.
-
*textContentvs*innerHTML/*compose:- The
*print/*textContentbranch runs before the*compose/*innerHTMLbranch. - If an element has both
*textContentand*innerHTML(or*compose),*textContentwins:- The element’s
textContentis set from the expression. - The branch for
*innerHTML/*composeis never reached.
- The element’s
- Combining these directives on the same element therefore has no useful effect; you should choose one.
- The
-
Static
%expr%expansion:- When no text directive is present, Sercrod can expand inline
%expr%placeholders in a single text node and assign the result totextContent. - As soon as
*textContentis present, this static expansion is skipped, because the directive takes full control oftextContent.
- When no text directive is present, Sercrod can expand inline
Recommendation:
- Treat
*textContentas the unique controller oftextContentfor that element. - Avoid putting multiple content-directing attributes (
*print,*textContent,*innerHTML,*compose) on the same element, because only one branch will be effective.
Best practices
-
Keep expressions side-effect free:
*textContentis meant for pure formatting; side effects (mutating data) inside the expression make templates harder to reason about.
-
Pre-format complex text outside the template:
- If you need heavy formatting (e.g. date/time, unit conversion, localization), consider computing those values in data or via helper methods instead of embedding long expressions.
-
Use filters for cross-cutting concerns:
- If you need to sanitize or normalize text globally, override
Sercrod._filters.textin your application. - This way, all
*textContentand*printbindings automatically receive the same treatment.
- If you need to sanitize or normalize text globally, override
-
Do not rely on attribute priority as a “feature”:
- While the runtime clearly prioritizes
*printover*textContent, this is an implementation detail mostly meant to keep behavior predictable when templates accidentally mix them. - In real templates, choose exactly one directive per element to express intent.
- While the runtime clearly prioritizes
-
Use
*textContentwhen you want a clear “property binding” feel:- For readers familiar with the DOM,
*textContentmakes it explicit that the element’stextContentproperty is controlled by the expression.
- For readers familiar with the DOM,
Additional examples
Using *textContent with derived values:
<serc-rod id="price" data='{"price": 1200, "currency":"JPY"}'>
<span *textContent="price + ' ' + currency"></span>
</serc-rod>
Inside a list with conditional prefix:
<serc-rod id="messages" data='{
"messages": [
{ "important": true, "text": "System update required" },
{ "important": false, "text": "Daily backup completed" }
]
}'>
<ul *each="msg of messages">
<li *textContent="(msg.important ? '[!] ' : '') + msg.text"></li>
</ul>
</serc-rod>
Notes
*textContentandn-textContentare aliases; they share the same implementation and differ only in attribute name.- The directive is implemented strictly in terms of
textContent:- No HTML parsing is performed.
- All text is treated as plain text and will be escaped by the browser when rendered.
- Both
*textContentand*printuse the globaltextfilter for final string conversion. - When multiple content directives are present on a single element, the renderer chooses one branch (with
*print>n-print>*textContent>n-textContentpriority) and ignores the rest; this is defined behavior but discouraged in template design. *textContentdoes not interact with network features (*post,*fetch,*websocket, and others); it is purely about local text rendering for a single element.