Lists and keys
REALM repeats content with <repeat-list>, or with the repeat attribute on the element to repeat.
<repeat-list of="#todo.items" as="item" index="i" key="id">
<article>
<h3><slot name="#item.title"></slot></h3>
<p>Item <slot name="#i"></slot></p>
</article>
</repeat-list>
<ul>
<li repeat="#todo.items" as="item" key="id"><slot name="#item.title"></slot></li>
</ul>| Attribute | Meaning | Default |
|---|---|---|
of (on <repeat-list>) or repeat (on any element) |
A reference to an array | |
as |
Name of the item variable | item |
index |
Name of the index variable, if you need one | none |
key |
Name of a property that identifies each item | the position |
Inside each copy:
#item.titleand#iwork in slots, bindings and conditions.itemandiare variables in event handlers:onclick="remove(item)".- Everything else still resolves to the component's script, so
#todo.filterworks inside the loop too.
A value that is not an array renders nothing.
Which form to use
<repeat-list> lays out as if it were not there (display: contents) and can repeat several elements per item. Use it anywhere unknown elements are allowed.
Use the repeat attribute inside <ul>, <ol>, <table>, <tbody>, <tr> and <select>. The HTML parser moves unknown tags such as <repeat-list> out of tables and selects, and lists only allow <li> children.
<table>
<tbody>
<tr repeat="#report.rows" as="row" key="id">
<td><slot name="#row.name"></slot></td>
<td><slot name="#row.total"></slot></td>
</tr>
</tbody>
</table>
<select>
<option repeat="#form.countries" as="country" key="code" _value="#country.code"><slot name="#country.name"></slot></option>
</select>Keys
With key, REALM matches items by that property between updates:
- an item that is still there keeps its DOM nodes, so focus, typed text, scroll position and running transitions survive,
- moved items are moved, not re-created,
- removed items are torn down, including their listeners and requests,
- new items are created.
Without key, items are matched by position, so changing the first item updates the first row in place. This is fine for static or append-only lists. Use a key when items can be reordered, inserted or removed.
Keys must be unique. A duplicate key logs a warning and the duplicate gets its own row.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Lists and keys demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/input.html"></import-element>
<import-element from="./sortable-names.html"></import-element>
<sortable-names></sortable-names>
</body>
</html><custom-element name="sortable-names">
<script>
const people = useState({
list: [
{ id: 1, name: "Ana" },
{ id: 2, name: "Bo" },
{ id: 3, name: "Chen" },
],
});
function shuffle() {
people.list.sort(() => Math.random() - 0.5);
}
function addFirst() {
const id = people.list.length + 1;
people.list.unshift({ id, name: `Person ${id}` });
}
</script>
<style>
:host {
display: grid;
gap: calc(var(--spacing) * 3);
width: min(calc(var(--spacing) * 96), 100%);
}
ol {
margin: 0;
padding-left: calc(var(--spacing) * 5);
}
li {
padding: calc(var(--spacing) * 1) 0;
}
li ui-input {
display: inline-block;
width: calc(var(--spacing) * 56);
margin-left: calc(var(--spacing) * 2);
vertical-align: middle;
}
.actions {
display: flex;
gap: calc(var(--spacing) * 2);
}
</style>
<ol>
<li repeat="#people.list" as="person" key="id">
<slot name="#person.name"></slot>
<ui-input aria-label="Note" placeholder="Type a note, then shuffle"></ui-input>
</li>
</ol>
<div class="actions">
<ui-button size="sm" onclick="shuffle()">Shuffle</ui-button>
<ui-button size="sm" variant="outline" onclick="addFirst()">Add first</ui-button>
</div>
</custom-element>Type a note, then shuffle. The note stays with its person because the row is moved, not rebuilt.
Updating lists
Change the array in the script. All mutating array methods are tracked:
todo.items.push(item);
todo.items.splice(index, 1);
todo.items.sort((a, b) => a.title.localeCompare(b.title));
todo.items = todo.items.filter((item) => !item.done); // replacing works too
item.done = true; // changing a field updates only what reads itWhen a keyed item is replaced by a new object with the same key, its row is kept and its bindings show the new object's values.
Filtered and sorted views
Keep the source array as it is and expose a getter for what the markup shows:
const todo = useState({
items: [],
filter: "all",
get visible() {
return this.items.filter(FILTERS[this.filter]);
},
});<li repeat="#todo.visible" as="item" key="id">…</li>Nested loops
Loops nest. Inner loops can use outer variables:
<repeat-list of="#board.columns" as="column" key="id">
<section>
<h3><slot name="#column.title"></slot></h3>
<ul>
<li repeat="#column.cards" as="card" key="id" onclick="open(column, card)"><slot name="#card.title"></slot></li>
</ul>
</section>
</repeat-list>