Listy i notacja .$
Źródło:
ListView.js,DynamicListView.js,DataTableView.js,BaseView.js
Wiele widoków renderuje kolekcje danych — listę produktów, tabelę zamówień, powtarzalne karty. Zamiast definiować każdy element osobno, podajesz jeden szablon (template), a silnik powiela go dla każdego elementu kolekcji.
Najważniejsze pytanie brzmi: jak wewnątrz szablonu odwołać się do bieżącego elementu listy? Służy do tego notacja .$ — i to jej poświęcona jest ta sekcja.
W skrócie
type: listvar
varPath: ordersTable # skąd brać dane
template: # szablon powielany dla każdego elementu
type: text
content: "{{ ordersTable.$._id }}" # $ = indeks bieżącego elementu
Symbol $ w wyrażeniu ordersTable.$._id jest podczas renderowania zamieniany na indeks aktualnie renderowanego elementu. Dla pierwszego elementu listy silnik podstawi ordersTable.0._id, dla drugiego ordersTable.1._id itd.
Widoki listowe
W silniku dostępne są trzy widoki, które działają w oparciu o szablon powielany dla każdego elementu kolekcji:
| Typ | Widok | Skąd pobiera dane |
|---|---|---|
listvar | ListView | zmienna z VarStore (varPath) — strony, globalna lub dane innego widoku |
list | DynamicListView | metoda API modułu |
datatable | DataTableView | metoda API (z paginacją, sortowaniem, wyszukiwaniem) — szablon definiujesz w columns[].content |
Każdy z nich przyjmuje szablon pojedynczego elementu. To właśnie wewnątrz tego szablonu używasz notacji .$.
Jak działa .$
Gdy widok listowy renderuje element o indeksie n, oznacza go wewnętrznie metadanymi $index = n. Podczas parsowania szablonu (Handlebars) silnik znajduje każde wystąpienie .$ i podmienia je na ten indeks:
{{ ordersTable.$.name }} → {{ ordersTable.2.name }} (dla 3. elementu)
{{ ordersTable.$._id }} → {{ ordersTable.2._id }}
{{ ordersTable.$.customer.name }} → {{ ordersTable.2.customer.name }}
Dzięki temu ordersTable.$ zawsze wskazuje na bieżący element kolekcji ordersTable, a kropki za nim odczytują dowolnie zagnieżdżone pola tego elementu.
Dlaczego
viewId.$, a nie samo pole? Notacja celowo zawiera ID widoku źródłowego (ordersTable). Dzięki temu w szablonie możesz odwołać się zarówno do bieżącego elementu (ordersTable.$), jak i do zmiennych strony ($page.<klucz>), globalnych ($global.<klucz>) czy danych innych widoków — wszystko w jednym wyrażeniu.
Samo $ → goły indeks
Jeśli użyjesz {{ $ }} (samego dolara, bez ścieżki), zostanie on zamieniony na liczbę — sam indeks bieżącego elementu. Przydaje się to do budowania dynamicznych identyfikatorów, np. przy przeładowaniu konkretnego elementu listy:
onClick:
- action: reload
target: productsCellList.{{ $ }} # np. productsCellList.2
Listy zagnieżdżone — rozwiązywanie od prawej do lewej
Gdy listy są zagnieżdżone (lista w liście), w jednym wyrażeniu może pojawić się kilka $. Silnik rozwiązuje je od prawej do lewej: najpierw bierze indeks najbliższego elementu (bieżący widok), potem wspina się po rodzicach drzewa widoków, szukając kolejnego indeksu.
{{ companies.$.emails.$ }}
│ └─ indeks bieżącego elementu (np. lista e-maili)
└──────────── indeks elementu z listy nadrzędnej (firma)
Jeśli silnik nie znajdzie już indeksu wyżej w hierarchii, pozostawia $ bez zmian (kolejne .$ nie są rozwijane).
Notacja .$ poza treścią
.$ działa wszędzie tam, gdzie silnik parsuje szablon — nie tylko w polu content. Możesz jej używać m.in. w warunkach renderIf oraz w wartościach akcji (np. setVar), co pozwala budować w pełni reaktywne, zależne od wiersza zachowania.
W renderIf $ jest rozwijane nie tylko w wartości warunku, ale również w jego kluczu (ścieżce sprawdzanego pola). Dzięki temu warunek może odczytać dane dokładnie tego elementu listy, który jest aktualnie renderowany:
type: html
renderIf:
ordersTable.$.products: # ← $ w kluczu = indeks bieżącego wiersza
$size:
$gt: 3
content: <span class="products-toggle">▾ wyświetl pozostałe</span>
Warunkowe renderowanie zależne od bieżącego wiersza ($ w wartości):
type: html
renderIf:
$page.expandedOrder: '{{ ordersTable.$._id }}' # pokaż tylko dla rozwiniętego zamówienia
content: <span class="products-toggle">▴ zwiń</span>
Zapis ID bieżącego elementu do zmiennej strony:
type: text
content: Szczegóły
onClick:
- action: setPageVar
key: currentOrder
value: '{{ ordersTable.$._id }}'
Pełny przykład: lista produktów wewnątrz tabeli zamówień
Poniższy fragment (z realnego widoku) pokazuje listę listvar osadzoną w komórce tabeli. Zewnętrzny widok (ordersTable) dostarcza zamówienie, a wewnętrzna lista iteruje po jego produktach — stąd dwa różne ID w notacji .$:
type: datatable
id: ordersTable
columns:
- header: Produkty
content:
- type: listvar
id: productsCellList.$
varPath: ordersTable.$.products # produkty bieżącego zamówienia
emptyMessage: Brak produktów
template:
type: html
content: |-
<div>
<span>{{ productsCellList.$.qty }}×</span>
<span>{{ ordersTable.$.products.$.name }}</span>
</div>
Tu ordersTable.$ to bieżący wiersz tabeli, a productsCellList.$ to bieżąca pozycja wewnątrz tej listy produktów. Wyrażenie ordersTable.$.products.$.name korzysta z rozwiązywania od prawej do lewej: prawe $ to indeks produktu, lewe — indeks zamówienia.
Edytowalna lista pozycji
Kolekcję trzymaną w zmiennej można edytować w miejscu — bez zapisu do API po każdej zmianie. Wiersz opisuje kontrolki, a akcje arrayPush / arrayRemove / arraySet zapisują stan pod indeksem bieżącego wiersza ({{ $ }}):
type: flex
onLoad:
action: setVar
scope: view
key: docItems
value:
- { name: 'Usługa', qty: 1, price: 100 }
childs:
- type: listvar
id: docItemsList
varPath: docItems
template:
type: flex
childs:
- type: input
id: docQty.$ # id z `.$` — inaczej wiersze dzielą jeden klucz
inputType: number
value: '{{ docItems.$.qty }}'
debounce: 200
onChange:
action: arraySet # zapis cichy: lista się nie przebudowuje
scope: view
key: docItems
index: '{{ $ }}'
field: qty
valueFrom: $value
- type: text
content: '{{ money (mul docItems.$.qty docItems.$.price) }}' # przeliczy się samo
- type: button
label: Usuń
onClick:
action: arrayRemove
scope: view
key: docItems
index: '{{ $ }}'
- type: button
label: Dodaj pozycję
onClick:
action: arrayPush
scope: view
key: docItems
value: { name: '', qty: 1, price: 0 }
- type: text
content: 'Razem: {{ money (sum docItems "price") }}'
Reguły, o których łatwo zapomnieć:
idkontrolki w wierszu musi mieć.$— bez tego wszystkie wiersze zapisują wartość pod tym samym kluczem.- Kilka odwołań
.$w jednym wyrażeniu ({{ mul docItems.$.qty docItems.$.price }}) dotyczy tego samego wiersza. Zapis od prawej do lewej opisuje zagnieżdżenie tylko w obrębie jednej ścieżki (orders.$.products.$.name). - Nie używaj
renderIfanivariantIfzależnych od edytowanej kolekcji w wierszu — przebudowują zawartość i zabierają fokus inputowi. Do wyglądu wiersza użyjclassIf.
Powiązane: Zmienne (VarStore) · Szablony Handlebars · ListView · DataTableView
