*for
Summary
*for repeats the host element for each entry in a list, object, or other iterable. The host element itself is duplicated as many times as needed, and each clone is rendered with its own iteration scope. The directive understands JavaScript-like in and of loop syntax and has an alias n-for.
There is also a special host-level form when *for is placed directly on a Sercrod host (<serc-rod>). In that case the host is not repeated; instead, the inner template is rendered multiple times inside the same host with different scopes.
Structural restriction:
- Do not combine
*forand*eachon the same element.- In the implementation,
*eachtakes precedence and*foris ignored. - Use either
*foror*each, not both, and prefer moving one directive to a child or parent element if you need both behaviors.
- In the implementation,
Basic example
Classic list items:
<serc-rod id="app" data='{"items":["Apple","Banana","Cherry"]}'>
<ul>
<li *for="item of items">
<span *print="item"></span>
</li>
</ul>
</serc-rod>
Behavior:
- The
<ul>is rendered once. *for="item of items"clones the<li>for each element initems.- For each clone, Sercrod renders the
<li>subtree with a local variableitembound to the current value. - The result is a
<ul>containing three<li>elements as siblings.
Behavior
Element-level *for:
*foris a structural directive that controls how many times the element itself is rendered.- The host element and all of its attributes and children are cloned for each iteration.
- The original element acts as a template and is not rendered directly.
- The directive is available as both
*forandn-for; they behave the same.
Host-level *for on <serc-rod>:
- When you put
*foron a Sercrod host (<serc-rod>), the host is not duplicated. - Instead, the host clears its content and repeatedly renders its internal template with different iteration scopes.
- This is useful when you want a single Sercrod host that manages multiple repeated blocks of content with a shared outer container.
Expression syntax
The expression to the right of *for uses a restricted, JS-like loop syntax:
value of iterable(key, value) of iterablekey in object(key, value) in object(allowed but not recommended;ofis clearer)
key and value must be simple identifiers (no destructuring patterns).
Supported patterns for element-level *for:
-
Arrays:
-
item of items
Iterates an array, bindingitemto each element. -
(index, item) of items
Iterates an array, bindingindexto the numeric index anditemto the element. -
key in items
Iterates an array-like object by key, bindingkeyto the property name as a string. For a normal array this means"0","1", and so on. -
(key, value) in items
Iterates an array-like object by key and value.keyreceives the property name andvaluereceives the value at that index.
-
-
Objects:
-
key in obj
Iterates over enumerable property names ofobj.keyreceives each property name. -
(key, value) in obj
Iterates over enumerable properties ofobj.keyreceives the property name,valuereceives the value. -
value of obj
Iterates overObject.entries(obj)and bindsvalueto each value. An extra implicit variablekeyis also added when you use the single-variablevalue of objform. -
(key, value) of obj
Iterates overObject.entries(obj)and binds bothkeyandvalue.
-
Host-level *for on <serc-rod> uses the same syntax but normalizes it slightly differently (see the dedicated section below).
Value semantics (element-level *for)
Element-level *for distinguishes of and in like JavaScript:
-
For arrays with
of:-
item of items:- Sercrod loops with
for (const v of items). - For each value
v, it clones the host and bindsitemtov.
- Sercrod loops with
-
(index, item) of items:- Sercrod uses
items.entries(). indexreceives the numeric index.itemreceives the value.
- Sercrod uses
-
-
For objects with
of:-
value of obj:- Sercrod uses
Object.entries(obj). - For each
[k, v], it clones the host and binds:keyto the property name (implicit when you do not specify a key variable).valueto the value.
- Sercrod uses
-
(key, value) of obj:- Sercrod uses
Object.entries(obj). keyreceives the property name,valuereceives the value.
- Sercrod uses
-
-
For arrays and objects with
in:-
key in expr:- Sercrod uses
for (const k in expr). - The single variable receives the key (property name).
- Sercrod uses
-
(key, value) in expr:- Sercrod uses
for (const k in expr). keyreceives the key.valuereceivesexpr[k].
- Sercrod uses
-
Deprecation note:
(key, value) in expris supported for compatibility but is discouraged.- Sercrod prints a console warning when you use
inwith two variables. - Prefer
(key, value) of exprfor new code when you need both key and value.
Evaluation timing
Element-level *for participates in Sercrod’s structural evaluation order inside renderNode:
- Text interpolation and static/dynamic handling happen first.
*ifand its chain (*elseif,*else) are evaluated and may select a specific branch.*switchand its branches (*case,*case.break,*default) are processed.*eachruns before*forwhen both directives are present.*foris then evaluated on the element if it is still in play.
Important consequences:
- If
*ifor*switchis on the same element as*for, the conditional logic runs first and can select or skip that element. Once an element is selected by*ifor a*case, the cloned branch is fed back intorenderNode, where*foris then applied inside the clone. - If both
*eachand*forare present on the same element,*eachruns first and returns early. The*forblock is never reached in that case. This is why combining*eachand*foron a single element is considered unsupported.
Host-level *for is evaluated during the host’s update cycle, before its template is rendered. When a host <serc-rod> has *for, it clears its content, runs the host-level loop, and calls the internal template renderer once per iteration.
Execution model
Element-level *for:
- Sercrod locates the
*for(orn-for) attribute on the element. - It parses the expression into
keyName,valName,modeWord("in"or"of"), andsrcExpr. - It evaluates
srcExprin the current scope with{ el: work, mode: "for" }. - If the result is falsy,
*foracts as an empty loop and renders nothing. - For each entry in the collection (interpreted according to
inorof):- Sercrod clones the entire element subtree with
cloneNode(true). - It removes
*forandn-forfrom the clone. - It merges the iteration variables into the scope for that clone.
- It calls the internal element renderer on the clone.
- Sercrod clones the entire element subtree with
- The clones are appended to the original parent in order.
- The original element is not appended; it serves only as the template.
Host-level *for on <serc-rod>:
- During an update, the host decides whether it should re-render.
- The host clears its current content (
innerHTML = ""). - It determines the current top-level scope (
_stageif a staging branch is active, otherwise_data). - It reads the host’s
*fororn-forexpression and parses it with the same(key, value) in|of exprpattern. - It evaluates the iterable expression with
{ el: this, mode: "update" }. - It normalizes the result into
[key, value]pairs, using a helper that:- Treats
x in arraywith a single variable similar tox of arrayfor backward compatibility (values rather than keys). - For objects, returns
[key, value]pairs for bothofandin, but emphasizesofas the clearer option when you need(key, value).
- Treats
- For each
[k, v]pair:- If both
keyNameandvalNameare present, the host calls its template renderer with{ ...scope, [keyName]: k, [valName]: v }. - If only
valNameis present, it renders with{ ...scope, [valName]: v }.
- If both
- The result is a single
<serc-rod>instance whose children are repeated blocks rendered from the host’s template.
Variable creation and scope layering
Element-level *for:
- Creates loop variables in the per-iteration scope:
iteminitem of items.indexanditemin(index, item) of items.keyinkey in obj.keyandvaluein(key, value) in objor(key, value) of obj.
- These variables shadow any outer variables with the same names for the duration of the iteration.
- All original scope entries remain available, including:
- Host data.
$data,$root,$parent, and any global helpers.- Methods defined through Sercrod configuration.
Host-level *for:
- Works with the same pattern of loop variables, but applies them at the host scope level:
- Each iteration constructs a new top-level scope for rendering the host’s template.
- The loop variables are added on top of the base scope (
_stageor_data).
- This allows each iteration to treat the host’s template as a root-level view for a different item.
Guidelines:
- Prefer descriptive names like
user,row, orentryrather than very short names when it improves readability. - Be careful not to unintentionally shadow important data names used elsewhere in the template.
Parent access
*for does not introduce a dedicated parent reference, but you can still access parent data as usual:
- Through the data tree, for example
state.items,config, ordata.users. - Through
$root, which points to the root Sercrod data. - Through
$parent, which points to the nearest ancestor Sercrod host’s data.
Loop variables exist alongside these references and do not prevent you from reading outer scopes.
Use with conditionals and loops
You can safely combine *for with other directives when they are placed thoughtfully:
-
Host-level
*ifand*switch:*ifand*switchcan appear on the same element as*for.- The conditional logic is resolved first.
- Once a branch is selected, that branch is cloned and then inspected again;
*foron the cloned node runs normally.
<li *if="items && items.length" *for="item of items"> <span *print="item"></span> </li>- In practice, most code is easier to read if you put
*ifon a parent or child rather than combining them on the same element, but the combination is supported.
-
Child-level conditions and nested loops:
- You can use
*ifor nested*for/ nested*eachinside the body of a*forloop.
<ul> <li *for="item of items"> <span *if="item.visible" *print="item.label"></span> <ul *each="tag of item.tags"> <li *print="tag"></li> </ul> </li> </ul> - You can use
-
Interaction with
*each:- Use
*forwhen you want to repeat the element itself as a sibling. - Use
*eachwhen you want to keep a single container and repeat its children. - Do not put
*forand*eachon the same element; as noted above,*eachwill take precedence and*foris effectively ignored.
- Use
Use with templates and *include
*for works well with templates and *include:
-
Typical pattern:
<serc-rod id="app" data='{"items":[{"name":"Ann"},{"name":"Bob"}]}'> <template *template="user-item"> <li> <span *print="item.name"></span> </li> </template> <ul> <li *for="item of items" *include="'user-item'"></li> </ul> </serc-rod> -
In this pattern:
*forrepeats the<li>for eachitem.*includefills each<li>body from the named template, and the included template can useitem.
-
Because
*forclones the entire element (including structural attributes) before rendering,*includeruns inside each clone and uses the iteration scope correctly.
Comparison with *each
Both *for and *each iterate collections, but they operate on different structural levels:
-
*for:- Repeats the host element itself.
- Typical for repeated siblings, such as list items, cards, and table rows.
<ul> <li *for="item of items"> <span *print="item"></span> </li> </ul> -
*each:- Keeps the host element as a single container and repeats its children.
- Useful when the container must remain unique (for example
<ul>,<tbody>, or SVG groups).
<ul *each="item of items"> <li *print="item"></li> </ul>
Guideline:
- When in doubt, ask whether you want multiple copies of the container element or just one container with repeated children.
- Multiple containers: use
*for. - Single container: use
*each.
- Multiple containers: use
Host-level *for on (advanced)
Placing *for directly on a Sercrod host is an advanced but powerful pattern:
Basic example:
<serc-rod id="host" *for="user of users" data='{
"users": [
{ "name": "Alice", "age": 30 },
{ "name": "Bob", "age": 25 }
]
}'>
<section class="user-card">
<h2 *print="user.name"></h2>
<p *print="user.age + ' years old'"></p>
</section>
</serc-rod>
Behavior:
- The
<serc-rod>element itself appears once in the DOM. - Its internal template (the
<section>block) is rendered once peruser. - Each iteration receives a scope where
userrefers to the current user. - This is similar in spirit to
*eachon a top-level container, but it is implemented at the Sercrod host level and uses the host’s template renderer.
Notes on semantics:
- Single-variable
x of expris the recommended form for values in host-level*for. - Single-variable
x in expris treated as a value loop for arrays for backward compatibility and, with objects, also yields values when you accessx. - When you need both key and value, use
(key, value) of expr. Using(key, value) in expris supported but emits a console warning;ofis clearer.
Best practices
-
Prefer
offor new code:- Use
item of itemsor(index, item) of itemsfor arrays. - Use
(key, value) of objfor objects when you need both key and value. - Reserve
infor cases where you explicitly care about keys and understand the differences.
- Use
-
Keep loop expressions simple:
- Complex filtering, sorting, or mapping is easier to maintain when done in data or helper methods rather than written inline in the
*forexpression.
- Complex filtering, sorting, or mapping is easier to maintain when done in data or helper methods rather than written inline in the
-
Avoid mutating the iterated collection while rendering:
- Modifying the array or object you are looping over during rendering can lead to hard-to-follow behavior.
- Prefer to compute a derived collection, then iterate over that.
-
Choose between
*forand*eachexplicitly:- If your design calls for multiple sibling elements, use
*for. - If your design calls for a single container with repeated children, use
*each.
- If your design calls for multiple sibling elements, use
-
Do not combine
*forwith*eachon the same element:- In current Sercrod,
*eachwins when both are present, so*fornever runs. - For clarity, always keep them on separate elements.
- In current Sercrod,
Additional examples
Iterating over an object map with both key and value:
<serc-rod id="app" data='{
"users": {
"u1": { "name": "Alice" },
"u2": { "name": "Bob" }
}
}'>
<ul>
<li *for="(id, user) of users">
<strong *print="id"></strong>
<span *print="user.name"></span>
</li>
</ul>
</serc-rod>
Using *for on table rows:
<serc-rod id="table" data='{
"rows": [
{ "id": 1, "name": "Alpha" },
{ "id": 2, "name": "Beta" }
]
}'>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr *for="row of rows">
<td *print="row.id"></td>
<td *print="row.name"></td>
</tr>
</tbody>
</table>
</serc-rod>
Notes
*forandn-forare aliases; projects should choose one style and use it consistently.- The expression on
*foris evaluated as normal JavaScript inside Sercrod’s expression sandbox. - Element-level
*forrepeats the element itself, while host-level*foron<serc-rod>repeats the inner template. - Single-variable
x in arrayis treated differently at the host level for backward compatibility; for clarity and predictability, preferx of exprand(key, value) of exprin new code. - Structural combinations where
*eachand*forcompete for control of the same host element are not supported. Use one structural directive per element.