fx-repeat-ref is a placeholder used inside an fx-repeat
template to recurse into a data node’s own matching children - the same idea as
XSLT’s apply-templates. It lets one template render an arbitrary-depth,
self-similar tree (file/directory trees, org charts, threaded comments, nested
categories, outline structures) instead of a hand-written, depth-capped chain
of nested fx-repeat elements.
Place <fx-repeat-ref> anywhere inside the nearest ancestor fx-repeat’s
template. On connect, it synthesizes a new fx-repeat bound to that same
ref (or an explicit override, see below), wraps a clone of the ancestor’s
template around it, and replaces itself with that new repeat. The new repeat
then goes through the same lifecycle as any other: if its own template
contains another <fx-repeat-ref>, recursion continues one level deeper,
scoped to the current item via the usual nested-repeat context resolution.
Recursion terminates naturally wherever a node has no more matching children -
no depth check or max-depth attribute is needed for correctness.
<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>
against
<data>
<folder name="src">
<folder name="ui">
<file name="fx-repeat.js"/>
</folder>
<file name="index.js"/>
</folder>
<folder name="docs">
<file name="README.md"/>
</folder>
</data>
renders the whole tree, any depth, from a single template.
| Name | Description | Default |
|---|---|---|
| ref | Overrides the XPath used for the synthesized nested repeat. Without it, the nearest ancestor fx-repeat’s own ref is reused. Useful when a generation should switch to a different child axis (e.g. a folder template recursing into file children). | ancestor repeat’s ref |
The recursive="true" attribute on the enclosing fx-repeat is documentation
only - the recursion itself is driven purely by the presence of
<fx-repeat-ref> in the template - but setting it is recommended so the
intent is clear to readers of the markup.
Because every synthesized repeat is materialized from the same template, each
recursion level emits the same literal id on its fx-repeat/fx-repeatitem
pair - but since a synthesized level never gets an explicit id of its own
(see below), the nearest-ancestor-id walk that Fore’s default drag/drop
scoping performs passes straight through every recursion level and lands on
the one real id on the outermost fx-repeat. That means, by default, every
node anywhere in the tree is in the same drop scope - a node can be dragged
out of one branch and dropped into any other, including a structurally
identical one. The Recursive tree
demo relies on exactly this for its file-browser-style tree: any file can be
dragged into any folder, anywhere.
If that’s not what an app wants - a tree editor where cross-branch drags
should be rejected, say - add drop-scope="parent" to the template (see the
“Drag and drop” section on the fx-repeat
page) to scope drops to each node’s real parent instead, regardless of
recursion depth; see the standalone
Drop scope demo.
Dropping into an empty branch: dropping onto an existing node inserts the
dragged node as its sibling - so the natural way to drop into a folder-like
node is to drop onto one of its existing children. A node with no children
yet has nothing to drop onto for that unless its own synthesized nested
fx-repeat (which is itself a valid drop target - it means “append into
this repeat”) is given a real, non-zero-size box. Left at the browser’s
inline default, an empty fx-repeat collapses to nothing, and there is
nothing left to hit-test a drop against. Give it a block-level display and
some min-height (see the ul.tree > fx-repeat rule in the
Recursive tree demo’s
<style>) so an emptied-out branch stays droppable.
index()Synthesized nested repeats never get an explicit id, and this is
deliberate, not an oversight: index('id') is not a flat lookup by that
id - it’s context-relative whenever more than one element could share it.
Internally it walks up from the calling expression’s own
ancestor::fx-repeatitem chain and returns whichever candidate is contained
in the nearest enclosing one. That’s workable for a hand-nested, non-recursive
chain of repeats reusing one id per generation, evaluated from inside the
tree - but it breaks down for anything evaluated outside any relevant
fx-repeatitem ancestor chain (a master/detail panel elsewhere on the page,
say), where there’s no ancestor chain to disambiguate and, with several
same-id candidates, no single global match either. In that situation
index() silently falls back to 1 rather than erroring.
With <fx-repeat-ref> this is even more clear-cut: synthesized repeats have
no id at all, so there’s nothing for index() to resolve past the single
author-written root - it can only ever report that root repeat’s own
top-level active row, never anything about a deeper recursion level. There is
no “flattened” or “virtualized” index that would fix this: index()’s whole
design is the current row of one specific repeat instance, resolved
relative to where you’re standing, not a position in a global ordinal space
If you need to track or visually mark “the selected node” across a recursive
tree - especially from outside the tree, e.g. a property/detail panel - don’t
reach for index(). Set a marker attribute on the bound data node itself
instead:
<fx-setattribute ref="." name="ui-selected" value="true"></fx-setattribute>
and read it back with //*[@ui-selected='true'] (plus a matching CSS
selector for the visual highlight). This resolves identically no matter where
it’s evaluated from, and needs no per-repeat id at all - it’s keyed by the
data node, not by any repeat instance or its position.
Similarly, use fx-insertchild parent="."-style, context-relative actions to
insert children at any depth, rather than an id-targeted fx-append.
fx-repeat-ref removes itself from the DOM as soon as it connects; it never
appears in a rendered tree and takes no part in the update cycle itself.