Repeats template for each node of the referenced nodeset.
For each data node in the referenced nodeset one <fx-repeatitem> element
will be created that will contain the evaluated template as content.
| Name | Description |
|---|---|
| id | identifier for repeat |
| ref | XPath reference pointing to the bound node |
| size | Caps how many fx-repeatitem elements are materialized from the (always-complete) bound nodeset. Without virtual, this is a growing prefix cap: a trailing sentinel reveals the next size items via IntersectionObserver as the user scrolls, and once rendered, rows are never un-rendered. With virtual, size instead becomes the size of a sliding window. Omit for full, uncapped rendering (the default). |
| virtual | Opt-in true windowed virtualization, only meaningful together with a finite size. Instead of an ever-growing prefix, the DOM stays bounded at roughly size rows regardless of nodeset size: scrolling down appends rows below and evicts rows from the top; scrolling up prepends rows above and evicts from the bottom. virtual without size is ignored (behaves as fully uncapped). |
size only limits DOM materialization. The logical nodeset - and therefore index(), iterate actions, and explicit fx-bind model semantics (calculate, constraint, required, readonly, relevant) - always covers every item, regardless of how many rows are currently rendered. Programmatic or keyboard navigation to an index beyond the currently rendered window (e.g. via setIndex, an index('id')-driven dependent, or an insert/append past the window) materializes rows on demand: without virtual, it front-fills up through the target row; with virtual, it performs a hard jump - the current window is discarded and a fresh one is rendered starting at the target index, resetting scroll position to the top of that window.
Adding virtual alongside size turns on a sliding window: only about size rows are ever in the DOM, no matter how large the nodeset is. Eviction never estimates row height - it measures the actual height of the rows being removed and corrects the scroll position by that exact amount, so the visible content doesn’t jump. One consequence: the native scrollbar no longer represents absolute position in the full dataset (it behaves like a bounded infinite-scroll list, not “thumb at 45% of 10,000 rows”). Item recycling (reusing existing fx-repeatitem subtrees instead of destroying/recreating them) is a possible later enhancement.
<fx-repeat ref="item" size="50" virtual>
<template>
...
</template>
</fx-repeat>
ref is evaluated relative to the current context, so a statically nested
fx-repeat (one written by hand inside another’s template) already renders
correctly at any fixed depth. For a self-similar tree of unknown depth
(file trees, org charts, threaded comments), use
fx-repeat-ref instead of writing
out a depth-capped chain by hand:
<fx-repeat id="tree" ref="folder|file" recursive="true">
<template>
<li>
<span>{@name}</span>
<ul>
<fx-repeat-ref></fx-repeat-ref>
</ul>
</li>
</template>
</fx-repeat>
<fx-repeat-ref> re-applies the enclosing template to the current node’s own
matching children, one level deeper each time, terminating naturally once a
node has no more matching children. See its own page for the ref override
attribute and further detail.
Set draggable="true" and drop-target="<this repeat's id>" on the
template (not on fx-repeat itself) to let items be reordered or moved
between repeats by drag and drop - see the
Kanban Board and
Drag and Drop demos,
and fx-droptarget for dropping onto
non-repeat targets.
By default, a drop is accepted when the dragged item and the drop target
share the same nearest ancestor element carrying an id - a plain string
comparison, not real data parentage. This is what lets a Kanban card be
dragged between different columns of one repeat; but for two
structurally identical repeats at the same depth (notably: every level of a
recursive tree built with fx-repeat-ref, since every level shares one
template and therefore one literal id), it cannot tell “true siblings under
the same real parent” apart from “cousins at the same depth under a different
parent.”
Add drop-scope="parent" to the template to switch the check to real parent
equality (the dragged item’s and the drop target’s actual bound data node
having the same parent), independent of id:
<fx-repeat id="tree" ref="folder|file" recursive="true">
<template draggable="true" drop-target="tree" drop-scope="parent">
...
</template>
</fx-repeat>
drop-scope="parent" is opt-in; existing repeats that don’t set it keep
today’s id-equality behavior unchanged. See the standalone
Drop scope demo, which
sets it to block drags between two structurally identical sibling groups.
The Recursive tree demo
deliberately does the opposite: it does not set drop-scope="parent", since
a file-browser-style tree wants a file draggable into any folder, not just
reorderable within its own. Because every recursion level is synthesized with
no id of its own, the default id-equality check’s “nearest ancestor with
an id” walk passes straight through every synthesized level and lands on
the one real id on the outermost fx-repeat - so every node in the tree
resolves to that same scope, making the whole tree one drop target, which is
exactly the file-browser behavior wanted here.
| Name | Description | Details |
|---|---|---|
| item-created | dispatched when a new repeat entry is created | ‘path’ - a canonical xpath ‘index’ - the index of the new item |
| path-mutated | dispatched when repeat nodeset changes | ‘path’ - the mutation path ‘index’ - the index of the changed repeat item. |
| no-template-error | dispatched when there’s no template defined | ‘id’ - the repeat id |