Dostępne Akcje (ActionHandler)
Źródło:
ActionHandler.js
Akcje definiują zachowanie widoków obsługujących zdarzenia użytkownika (np. onClick w ButtonView, TextView, BadgeView). Wszystkie akcje są wykonywane przez wspólny ActionHandler, dzięki czemu interfejs jest spójny w całym silniku.
Schemat akcji
Akcja to obiekt przypisywany do pola onClick (lub innego pola zdarzeniowego) w schemacie widoku:
action: nazwaAkcji
parametry: ...
Pole zdarzeniowe może też zawierać tablicę akcji — wykonają się po kolei.
Kiedy rozwiązywane są templatki
Konfiguracja akcji jest rozwiązywana w chwili wykonania, nie podczas budowania widoku. Templatka {{ $page.orderId }} w onClick widzi więc wartość aktualną w momencie kliknięcia, a {{ $response.id }} w onSuccess — dopiero odpowiedź serwera.
Akcje zagnieżdżone (onSuccess, onError, then, onYes, onNo, onClick toasta) czekają ze swoim rozwiązaniem do momentu, w którym faktycznie się uruchomią.
Zakres zmiennych akcji
Poza wartościami ze VarStore ($page, $global oraz dane widoków pod ich id) akcja dostaje zakres zależny od źródła wywołania:
| Zmienna | Dostępna w |
|---|---|
$response | onSuccess / onError akcji callMethod |
$error | onError akcji callMethod — { message, status, data } |
$value | onChange kontrolki wartości |
$event | onEvent, onVarChange, onKey |
$args | akcja uruchomiona przez action: run |
$props | akcja wewnątrz szablonu komponentu |
Typowane referencje (valueFrom)
Templatki {{ }} służą do składania tekstu — Handlebars zawsze zwraca string. Tam, gdzie potrzebna jest surowa wartość z zachowanym typem (liczba, boolean, tablica, obiekt odpowiedzi), używa się referencji valueFrom:
# pole akcji
action: setVar
scope: page
key: lastResponse
valueFrom: $response.data # cały obiekt, bez stringifikacji
# dowolna wartość w drzewie konfiguracji
params:
orderId: { valueFrom: $page.orderId } # liczba pozostaje liczbą
limit: 50
Ścieżka jest zapisana w dot-notation i może zawierać placeholder indeksu .$ (valueFrom: products.$.packedQty). Gdy ścieżka nie istnieje, użyte zostaje value — pełni więc rolę wartości domyślnej.
Te same referencje działają jako operandy warunków (renderIf, condition):
renderIf:
packedQty:
$gte: { valueFrom: requiredQty }
openModal
Otwiera okno modalne o podanym ID.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "openModal" |
target | Tak | ID modala do otwarcia |
action: openModal
target: addOrderModal
closeModal
Zamyka okno modalne o podanym ID.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "closeModal" |
target | Tak | ID modala do zamknięcia |
action: closeModal
target: addOrderModal
openPopover
Otwiera popover przy elemencie wywołującym.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "openPopover" |
target | Tak | ID widoku popover do otwarcia |
width | Nie | Szerokość popovera (np. "300px") |
placement | Nie | Pozycja względem elementu wywołującego (domyślnie: "bottom") |
action: openPopover
target: filterPopover
placement: bottom-start
width: 320px
closePopover
Zamyka popover o podanym ID.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "closePopover" |
target | Tak | ID widoku popover do zamknięcia |
action: closePopover
target: filterPopover
emit
Emituje niestandardowe zdarzenie na magistrali zdarzeń.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "emit" |
event | Nie | Nazwa zdarzenia (domyślnie action:<viewId>) |
data | Nie | Dodatkowe dane przekazane ze zdarzeniem |
action: emit
event: customEvent
data:
key: value
Nawigacja do strony modułu:
action: emit
event: generatePage
data:
module: core/orders
page: details/52949
Renderowanie inline w kontenerze:
action: emit
event: generatePage
data:
module: core/orders
page: summary
target: orderContainer
reload
Przeładowuje dane jednego lub wielu widoków.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "reload" |
target | Tak | ID widoku albo tablica ID |
action: reload
target: ordersTable
action: reload
target: [orderDetail, shpList]
callMethod
Wywołuje metodę API modułu. Podczas wywołania emituje zdarzenia cyklu życia:
callMethod:preCall:<viewId>– przed wywołaniem (np. ButtonView pokazuje spinner)callMethod:success:<viewId>– po sukcesiecallMethod:error:<viewId>– po błędziecallMethod:afterCall:<viewId>– zawsze po zakończeniu (np. ButtonView chowa spinner)
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "callMethod" |
module | Tak | Ścieżka modułu, np. "core/orders" |
method | Tak | Nazwa metody, np. "create" |
params | Nie | Parametry metody; wspierają {{ }} oraz referencje { valueFrom } |
withData | Nie | Dane pobierane ze VarStore — lista kluczy albo wpisy { from, as } |
refresh | Nie | ID widoku (lub ich tablica) do przeładowania po sukcesie |
onSuccess | Nie | Akcja (lub tablica akcji) wykonywana po sukcesie |
onError | Nie | Akcja (lub tablica akcji) wykonywana po błędzie |
then | Nie | Deprecated – alias onSuccess (kompatybilność wsteczna) |
action: callMethod
module: core/orders
method: update
params:
_id: '{{ orderId }}'
name: "Zamowienie z formularza"
withData:
- statusInput001
- "$page.somePageVar"
onSuccess:
action: closeModal
target: editOrderModal
onError:
- action: toast
type: alert
content: Mamy błąd {{ $response.error }}
refresh — odświeżenie widoków po zapisie
Skrót na najczęstszą reakcję po udanym wywołaniu API. Zastępuje pisanie onSuccess z listą akcji reload:
action: callMethod
module: core/orders
method: product.update
refresh: [orderDetail, shpList]
onSuccess:
action: closeModal
target: editProductModal
refresh wykonuje się wyłącznie po sukcesie i przed onSuccess, więc widoki zaczynają pobierać nowe dane od razu. Nie zastępuje ogólnego onSuccess — obu można używać równocześnie.
withData — aliasowanie danych
Wpis może być zwykłym kluczem (wtedy nazwa parametru API jest równa ścieżce) albo obiektem { from, as }, który oddziela id widoku od nazwy parametru. Kropka w as buduje strukturę zagnieżdżoną:
withData:
- statusInput001 # { statusInput001: ... }
- { from: customerNameInput, as: customer.name } # { customer: { name: ... } }
- { from: $page.orderId, as: orderId } # { orderId: ... }
Odpowiedź serwera w akcjach reakcji ($response / $error)
Akcje uruchamiane jako onSuccess / onError otrzymują dodatkowy zakres zmiennych dostępny w templatkach {{ }} oraz w polu valueFrom (patrz setPageVar/setGlobalVar):
| Zmienna | Dostępność | Zawartość |
|---|---|---|
$response | sukces | body odpowiedzi serwera |
$response | błąd | body błędu z serwera (error.response.data) |
$error | błąd | { message, status, data } |
Wyświetlenie treści błędu w toaście:
onError:
- action: toast
type: alert
title: Błąd ({{ $error.status }})
content: Mamy błąd {{ $response.error }}
Zapis odpowiedzi do zmiennej strony – value zwraca string (renderowany z {{ }}), a valueFrom zapisuje surową, niezstringifikowaną wartość (np. cały obiekt):
onSuccess:
- action: setPageVar
key: createdId
value: '{{ $response.id }}' # string
- action: setPageVar
key: lastResponse
valueFrom: '$response' # cały obiekt odpowiedzi
confirm
Wyświetla okno dialogowe potwierdzenia (ConfirmDialog) przed wykonaniem właściwej akcji.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "confirm" |
type | Nie | Styl okna: alert, success, neutral (domyślnie: neutral) |
title | Nie | Tytuł okna |
description | Nie | Opis (obsługuje HTML) |
yesLabel | Nie | Tekst przycisku potwierdzenia (domyślnie: Tak) |
noLabel | Nie | Tekst przycisku anulowania (domyślnie: Nie) |
onYes | Nie | Akcja wykonywana po kliknięciu Tak |
onNo | Nie | Akcja wykonywana po kliknięciu Nie / Escape / X |
action: confirm
type: alert
title: Usunąć zamówienie?
description: Operacja jest nieodwracalna.
yesLabel: Usuń
noLabel: Anuluj
onYes:
action: callMethod
module: core/orders
method: delete
params:
_id: '{{ orderId }}'
toast
Wyświetla niemodalne powiadomienie Toast (Tabler / Bootstrap 5) w prawym dolnym rogu ekranu.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "toast" |
type | Nie | Wariant kolorystyczny: neutral (domyślnie), success, alert |
title | Nie | Tytuł toasta |
content | Nie | Treść toasta (obsługuje HTML) |
buttonLabel | Nie | Etykieta opcjonalnego przycisku akcji |
onClick | Nie | Akcja wykonywana po kliknięciu przycisku (wymagane buttonLabel) |
delay | Nie | Czas autohide w ms (domyślnie 5000; 0 = bez autohide) |
Prosty toast informacyjny:
action: toast
title: Info
content: Operacja zakończona.
Toast z wariantem success:
action: toast
type: success
title: Zapisano
content: Dane zostały zapisane.
Toast z wariantem alert:
action: toast
type: alert
title: Błąd
content: Coś poszło nie tak.
Toast z przyciskiem akcji:
action: toast
type: success
title: Zamówienie utworzone
content: Zamówienie #1234 zostało dodane.
buttonLabel: Zobacz
onClick:
action: emit
event: generatePage
data:
module: core/orders
page: details/{{ orderId }}
setVar
Ustawia zmienną w wybranym zakresie VarStore. Jedna akcja dla wszystkich trzech zakresów.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "setVar" |
scope | Nie | view / page / global (domyślnie page) |
key | Tak | Klucz zmiennej |
value | Nie* | Wartość do zapisania (wspiera {{ }}) |
valueFrom | Nie* | Ścieżka do surowej wartości — zachowuje typ (patrz „Typowane referencje") |
* wymagane jest podanie value albo valueFrom.
| Zakres | Trwałość | Zdarzenie |
|---|---|---|
view | do zmiany strony (bez localStorage) | viewVar:changed:<key> |
page | localStorage pod kluczem strony | pageVar:changed:<key> |
global | localStorage, wspólny dla stron | globalVar:changed:<key> |
Zakres view jest właściwym miejscem na stan UI — tryb edycji, rozwinięty panel, zaznaczony wiersz. Taki stan nie powinien przeżywać przeładowania strony, więc nie należy trzymać go w page. Zmienna jest widoczna w templatkach pod swoim kluczem ({{ editingCustomer }}), tak samo jak dane widoku — dlatego klucz nie może kolidować z id istniejącego widoku.
action: setVar
scope: view
key: editingCustomer
value: true
Nasłuch zmiany:
onVarChange:
- scope: view
key: editingCustomer
action: { action: reload, target: customerCard }
arrayPush / arrayRemove / arraySet
Edycja kolekcji trzymanej w zmiennej — pozycji dokumentu, listy załączników, koszyka. Adres kolekcji podaje się tak samo jak w setVar (scope + key), a jeśli tablica leży wewnątrz zmiennej — dodatkowo path:
action: arrayPush
scope: view # view | page | global (domyślnie page)
key: docItems # zmienna
path: lines.items # opcjonalnie — tablica wewnątrz zmiennej
Wszystkie trzy akcje zapisują kopię (nowa tablica, nowy obiekt elementu), więc widoki i porównania widzą zmianę, a dane z poprzedniego stanu nie zostają po cichu zmutowane. Błąd (brak key, nieznany scope, zmienna nie jest tablicą, indeks poza zakresem) kończy się komunikatem w konsoli i niewykonaniem akcji.
arrayPush
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "arrayPush" |
scope / key / path | — | Adres kolekcji (jak wyżej) |
value | Nie* | Dodawany element (wspiera {{ }}, może być obiektem) |
valueFrom | Nie* | Ścieżka do surowej wartości — zachowuje typ |
at | Nie | Pozycja wstawienia (domyślnie koniec) |
* wymagane jest podanie value albo valueFrom.
action: arrayPush
scope: view
key: docItems
value:
name: ''
qty: 1
vat: '23'
arrayRemove
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "arrayRemove" |
scope / key / path | — | Adres kolekcji |
index | Tak | Indeks usuwanej pozycji |
action: arrayRemove
scope: view
key: docItems
index: '{{ $ }}' # indeks bieżącego wiersza listy
arraySet
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "arraySet" |
scope / key / path | — | Adres kolekcji |
index | Tak | Indeks zmienianej pozycji |
field | Nie | Pole pozycji (z kropką: customer.name). Bez niego podmieniany jest cały element |
value | Nie* | Nowa wartość (wspiera {{ }}) |
valueFrom | Nie* | Ścieżka do surowej wartości — zachowuje typ |
rerender | Nie | true wymusza pełne przeładowanie listy (domyślnie false) |
* wymagane jest podanie value albo valueFrom.
index przyjmuje liczbę albo string liczbowy — w wierszu listy pisze się index: '{{ $ }}', a Handlebars zawsze zwraca tekst.
Zdarzenia: dlaczego arraySet zapisuje „cicho”
| Akcja | Zdarzenie | Kto reaguje |
|---|---|---|
arrayPush, arrayRemove | <zakres>Var:changed:<key> | wszyscy — listvar, datatable, text, warunki |
arraySet (domyślnie) | <zakres>Var:itemChanged:<key> | wyłącznie widoki text |
arraySet z rerender: true | <zakres>Var:changed:<key> | wszyscy |
Dodanie i usunięcie pozycji zmienia liczbę wierszy, więc lista musi się przebudować. Zmiana wartości pozycji — nie: przebudowa listy niszczy element DOM inputa, w którym użytkownik właśnie pisze (razem z fokusem i pozycją kursora). Dlatego arraySet emituje osobne zdarzenie itemChanged, które listvar i datatable ignorują, a widoki text — nie, więc kwoty i sumy przeliczają się na bieżąco.
Konsekwencja dla autora schematu: po
arraySetbezrerender: truewiersze listy nie przeliczą niczego poza widokamitext. Jeśli wiersz ma zmienić wygląd, użyjclassIf(przelicza się z warunków), a nierenderIfczyvariantIfzależnych od edytowanej kolekcji — te przebudowują zawartość i zabierają fokus.
Przykład: edytowalna lista pozycji
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.$
inputType: number
value: '{{ docItems.$.qty }}'
debounce: 200
onChange:
action: arraySet
scope: view
key: docItems
index: '{{ $ }}'
field: qty
valueFrom: $value
- type: text
content: '{{ money (mul docItems.$.qty docItems.$.price) }}'
- 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") }}'
setPageVar / setGlobalVar
Aliasy kompatybilności wstecznej — działają dokładnie jak setVar z scope: page oraz scope: global. W nowych schematach używaj setVar.
action: setPageVar
key: activeTab
value: orders
setValue
Ustawia wartość kontrolki przez jej publiczne API (setValue z ValueView), bez sięgania do wewnętrznego DOM-u widoku. Zastępuje ukryte inputy-proxy, przez które schemat przemycał wartości do formularza.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "setValue" |
target | Tak | id kontrolki wartości |
value | Nie* | Wartość do ustawienia (wspiera {{ }}) |
valueFrom | Nie* | Ścieżka do surowej wartości — zachowuje typ |
emitChange | Nie | Czy uruchomić onChange kontrolki (domyślnie false) |
* wymagane jest podanie value albo valueFrom.
Wartość trafia równocześnie do kontrolki i do VarStore. Wartość spoza dozwolonego zbioru (nieistniejąca opcja, poza zakresem suwaka) jest odrzucana — kontrolka wraca do stanu, który potrafi zaprezentować.
action: setValue
target: customerName
valueFrom: $response.customer.name
emitChange: false
run i actionDefinitions
Uruchamia nazwaną akcję zadeklarowaną w actionDefinitions. Pozwala zdefiniować akcję raz i wywołać ją z wielu miejsc — zamiast kopiować konfigurację albo klikać ukryty przycisk przez JS.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "run" |
name | Tak | Nazwa akcji z actionDefinitions |
args | Nie | Argumenty widoczne w definicji jako $args |
actionDefinitions deklaruje się w schemacie strony, w dowolnym widoku-kontenerze albo w definicji komponentu. Wyszukiwanie idzie od widoku wywołującego w górę drzewa — najbliższy komponent przesłania definicję strony.
args są rozwiązywane w kontekście widoku wywołującego (widzą .$ bieżącego wiersza listy oraz $props), a wewnątrz definicji dostępne są jako $args.
actionDefinitions:
saveProduct:
action: callMethod
module: core/orders
method: product.update
params:
productIndex: { valueFrom: $args.productIndex }
onSuccess:
action: reload
target: productsList
type: listvar
varPath: products
template:
type: button
label: Zapisz
onClick:
action: run
name: saveProduct
args:
productIndex: '{{ $ }}'
Nazwana akcja może wywołać kolejną. Cykl (a → b → a) oraz przekroczenie maksymalnego zagnieżdżenia są przerywane z błędem w konsoli, a nie zapętlają aplikacji. Nieznana nazwa również kończy się czytelnym błędem.
navigate
Przechodzi do strony modułu. Zastępuje techniczne action: emit, event: generatePage — schemat opisuje dokąd chce przejść, a host silnika decyduje jak (router aplikacji, render w miejscu).
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "navigate" |
module | Tak | Kod modułu, np. "core/orders", albo "core" dla strony rdzenia |
page | Tak | Ścieżka strony w module, np. "details/123"; dla module: core ścieżka routera rdzenia |
params | Nie | Parametry zapytania |
target | Nie | ID widoku — strona renderuje się w nim zamiast zmieniać adres |
action: navigate
module: core/orders
page: details/{{ ordersTable.$._id }}
Aplikacja hosta może podpiąć własny router zamiast domyślnego zdarzenia:
import { setNavigationHandler } from '@/viewengine';
setNavigationHandler(({ module, page, params }) => router.push(buildUrl(module, page, params)));
Strona rdzenia (module: core)
Z module: core akcja prowadzi na stronę rdzenia, a nie modułu: page jest wtedy ścieżką routera frontu (np. settings/automation-runs), a params trafiają do query adresu. Poniższy przycisk otwiera historię biegów automatyzacji dotyczących zamówienia:
- type: button
icon: settings-automation
variant: secondary
outline: true
title: Automatyzacje
onClick:
- action: navigate
module: core
page: settings/automation-runs
params:
eventValue: '{{ $page.urlParams.orderId }}'
redirect
Przekierowuje bieżącą kartę na zewnętrzny adres, typowo na stronę zgody OAuth dostawcy, której adres buduje metoda modułu (zob. OAuth).
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "redirect" |
url | Tak | Bezwzględny adres http:// albo https:// (templatka albo { valueFrom }) |
action: callMethod
method: oauth.begin
onSuccess:
action: redirect
url: { valueFrom: $response.0.url }
Adres spoza http(s) (javascript:, data:, ścieżka względna) jest odrzucany z błędem w konsoli. Templatka {{ }} escapuje & i =, więc adres z parametrami zapytania podaje się referencją valueFrom. Do nawigacji między stronami Orbito służy navigate.
focus / selectText
Ustawia fokus na kontrolce albo zaznacza jej treść — przez publiczne API widoku, bez sięgania do jego wewnętrznego DOM-u. Potrzebne przy edycji inline: po włączeniu trybu edycji kursor ma od razu stać w polu.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "focus" albo "selectText" |
target | Nie | ID widoku; bez niego akcja działa na widoku wywołującym |
onClick:
- action: setVar
scope: view
key: editingCustomer
value: true
- action: focus
target: customerNameInput
copyToClipboard
Kopiuje wartość do schowka.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "copyToClipboard" |
value | Nie* | Wartość do skopiowania (wspiera {{ }}) |
valueFrom | Nie* | Ścieżka do surowej wartości — zachowuje typ |
feedback | Nie | toast (domyślnie), icon albo none |
successMessage | Nie | Treść potwierdzenia (domyślnie „Skopiowano do schowka") |
errorMessage | Nie | Treść komunikatu błędu |
* wymagane jest podanie value albo valueFrom.
feedback: icon zamienia ikonę widoku wywołującego na „ptaszek" i przywraca ją po chwili; gdy widok nie ma ikony, akcja wraca do potwierdzenia toastem.
type: button
icon: copy
onClick:
action: copyToClipboard
valueFrom: $page.orderNumber
feedback: icon
Schowek bywa niedostępny — przeglądarka udostępnia go wyłącznie pod HTTPS (lub na localhost), a użytkownik może odmówić uprawnienia. Akcja nie przerywa wtedy działania strony: pokazuje komunikat błędu i zapisuje powód w konsoli.
downloadFile / openFile
Pobiera plik na dysk albo otwiera go w nowej karcie.
| Pole | Wymagany | Opis |
|---|---|---|
action | Tak | "downloadFile" albo "openFile" |
contentBase64 | Nie* | Treść pliku w base64 (typowo z odpowiedzi API) |
content | Nie* | Treść tekstowa (CSV, JSON, HTML) |
url | Nie* | Gotowy adres pliku |
value / valueFrom | Nie* | Skrót — źródło wskazuje pole source |
source | Nie | base64 (domyślnie), text albo url — dotyczy value/valueFrom |
filename | Nie | Nazwa zapisywanego pliku (downloadFile) |
mimeType | Nie | Typ MIME, np. application/pdf |
* wymagane jest wskazanie dokładnie jednego źródła pliku.
action: callMethod
module: core/orders
method: invoice.render
withData: [orderId]
onSuccess:
action: downloadFile
valueFrom: $response.pdfBase64
filename: 'faktura-{{ $page.orderNumber }}.pdf'
mimeType: application/pdf
Tymczasowy adres pliku jest zwalniany automatycznie. openFile otwiera kartę bez dostępu do okna źródłowego, a adresy o wykonywalnym schemacie (javascript:) są odrzucane — url bywa budowany z danych zwróconych przez backend.
