Ereignisse und das SDK
Die Ereignisse der Widgets und des Bildschirms, der Code-Editor, die vordefinierten Aktionen und das keplin-SDK in TypeScript — Daten, Widgets, Navigation, Sitzung, Modals und Workflows.
Viele Bildschirme entstehen, ohne eine Zeile zu schreiben: einen Datastore binden, Widgets ziehen, einen Button auf einen anderen Bildschirm richten. Aber früher oder später kommt das „wenn dies passiert, tu jenes" — speichern und zurückgehen, eine Tabelle nach einem Filter neu laden, vor dem Löschen bestätigen, ein Modal öffnen und verwenden, was es zurückgegeben hat.
Genau dafür sind die Ereignisse da: Punkte des Bildschirms, an denen Ihr
Code läuft, in TypeScript geschrieben, mit einem SDK — dem Objekt keplin —
das Zugang zu allem gibt, was der Bildschirm hat.
Wo die Ereignisse stehen
Im Inspektor heißt die letzte Kategorie eines Widgets (und des Bildschirms selbst) Ereignisse. Sie hat eine Zeile je verfügbarem Ereignis, und in jeder Zeile:
- einen Punkt links: gefüllt, wenn dieses Ereignis schon Code hat, leer, wenn nicht;
- einen Button … rechts, der den Editor öffnet.

Die Namen der Ereignisse werden nicht übersetzt — sie sind in jeder Sprache
dieselben (onClick, onRowClick, onLoad), denn es sind auch die Namen, die
im Code und in den Protokollen des Radar erscheinen.
Die Ereignisse des Bildschirms
Klicken Sie auf eine leere Zone des Canvas, damit der Inspektor den Bildschirm zeigt. Die Kategorie Ereignisse hat drei:
| Ereignis | Wann es auslöst | Wofür |
|---|---|---|
onLoad |
Einmal, wenn der Bildschirm öffnet. | Zustand vorbereiten, Dinge laden, die die Datastores nicht laden, willkommen heißen. |
onParamsChange |
Immer, wenn sich die Parameter der Route ändern — und nicht beim ersten Öffnen. | Auf einen Datensatzwechsel reagieren, ohne den Bildschirm neu zu öffnen. |
onUnload |
Wenn der Bildschirm verlassen wird. | Zustand aufräumen, Entwürfe sichern. |

Die Ereignisse jedes Widgets
Jeder Widget-Typ deklariert seine eigenen. Neben dem Namen kommt es auf den
Payload an — die Daten, die das Ereignis mitbringt und die der Code in
keplin.event liest.
Formularfelder
| Widget | Ereignisse | keplin.event |
|---|---|---|
| Textfeld, Textbereich, Zahl, Ja/Nein, Auswahlliste, Datum, Farbe | onChange |
{ value } |
| Datei-Upload | onChange, onUpload |
onUpload: { file, name } |
Aktionen und Navigation
| Widget | Ereignisse | keplin.event |
|---|---|---|
| Button | onClick |
{} |
| Menü-Button | onClick, onMenuItem |
onMenuItem: { id, label } |
| Link | onClick |
{} |
| Export | onExport, onDataLoaded |
onExport: { rows, filename } |
Struktur und Inhalt
| Widget | Ereignisse | keplin.event |
|---|---|---|
| Tabs | onTabChange |
{ tab } |
| Bericht | onLoad |
{ report } |
Daten-Widgets
| Widget | Ereignisse | keplin.event |
|---|---|---|
| Tabelle | onRowClick, onRowDoubleClick, onSelectionChange, onDataLoaded |
{ row, index } · onSelectionChange: { row, rows } |
| Liste, Karten | onRowClick, onDataLoaded |
{ row, index } |
| Diagramm | onClick |
{ name, seriesName, value, dataIndex } |
| KPI | onClick, onDataLoaded |
{ value, indicatorId } |
Boards und Planung
| Widget | Ereignisse | keplin.event |
|---|---|---|
| Kanban | onCardClick, onCardCreate, onCardMoved, onDataLoaded |
{ row } · { column } · { row, from, to, index } |
| Kalender | onEventClick, onDayClick, onRangeSelect, onRangeChange, onDataLoaded |
{ row } · { date } · { start, end } · { start, end, view } |
| Gantt | onBarClick, onEmptyClick, onDataLoaded |
{ row } · { date } |
Prozesse
| Widget | Ereignisse | keplin.event |
|---|---|---|
| Status des Prozesses | onDecide |
{ task, outcome } |
| Meine Aufgaben | onOpen, onDecide |
{ task, screenId } |
Nota
Von Ihnen programmierte Widgets (die, die in der Palette unter Benutzerdefiniert erscheinen) deklarieren ihre eigenen Ereignisse und erscheinen hier wie alle anderen.
Der Code-Editor
Der Button … eines Ereignisses öffnet den Editor in einem Modal. Der Titel
sagt, wo Sie sind: die id des Widgets (oder der Name des Bildschirms) und
der Name des Ereignisses — w_fic_sav1 · onClick.

| Button | Was er tut |
|---|---|
| Aktion einfügen | Schreibt für Sie den Code einer gängigen Aufgabe (siehe unten). |
| Handler entfernen | Löscht den Code dieses Ereignisses. Der Punkt wird wieder leer. |
| Abbrechen | Schließt ohne zu speichern. |
| Speichern | Prüft und speichert. |
Der Editor hat Vorschläge während des Schreibens (Strg+Leertaste): Das
ganze keplin ist deklariert, mit den richtigen Typen — und, noch besser, die
ids der Widgets dieses Bildschirms stecken darin. keplin.widgets.get(" zu
schreiben zeigt die Liste der Widgets des Bildschirms, und eine id, die nicht
existiert, wird als Fehler markiert, bevor Sie speichern.
Atenção
Beim Speichern wird der Code kompiliert. Ist er nicht ausführbar, weist ihn die Plattform zurück — Das Ereignis wurde nicht gespeichert: Der Code ist nicht ausführbar — und das Modal bleibt offen, damit Sie korrigieren. Ein Bildschirm behält niemals kaputten Code in sich.
Die vordefinierten Aktionen
Aktion einfügen öffnet eine Liste der häufigsten Aufgaben. Sie wählen eine aus, und der Code wird an das Ende des bereits Vorhandenen geschrieben, schon mit den echten Namen Ihres Bildschirms — dem ersten Datensatz-Datastore, der ersten Tabelle, dem ersten Textfeld.

| Aktion | Was sie schreibt |
|---|---|
| Datastore speichern | Validiert und speichert den Datensatz, mit Erfolgsmeldung. |
| Datastore neu laden | Liest die Daten eines Datastores erneut. |
| Datastore filtern (nach Text) | Liest den Text eines Feldes und wendet ihn als Filter an. |
| Zu einem Bildschirm navigieren | Springt zu einer anderen Route der App. |
| Eine Tabelle neu laden | Aktualisiert die Daten eines Tabellen-Widgets. |
| Tabelle nach dem Wert eines Textfelds filtern | Der klassische interaktive Filter. |
| Ein Widget ein-/ausblenden | Schaltet die Sichtbarkeit eines Widgets um. |
| Bestätigen und Toast anzeigen | Fragt, bevor es handelt, und meldet am Ende. |
| Einen Workflow starten | Setzt einen Prozess über dem aktuellen Datensatz in Gang. |
| Aufgaben ansehen und abschließen | Listet die Aufgaben des Benutzers auf und entscheidet eine. |
| Ein Signal an einen Workflow senden | Weckt Prozesse, die gewartet haben. |
| Abmelden | Verlässt die App. |
Der eingefügte Code ist ein Ausgangspunkt: Er wird Ihrer, und er ist zum Bearbeiten da. Er wird nicht erneut erzeugt.
Wie der Code läuft
Jedes Ereignis ist eine asynchrone Funktion, die nur eines bekommt: das
keplin. Daraus folgen drei praktische Konsequenzen:
awaitfunktioniert auf der obersten Ebene des Codes. Man muss nichts einwickeln.returnverlässt das Ereignis. Das ist die normale Art, auf halbem Weg aufzugeben (zum Beispiel, wenn eine Bestätigung abgelehnt wurde).- Es gibt keine Parameter. Der Kontext steckt im
keplinselbst:keplin.eventbringt den Payload, undkeplin.ctxsagt, wo Sie sind (ctx.widgetist das Widget, das ausgelöst hat —nullbei den Ereignissen des Bildschirms —,ctx.eventist der Name des Ereignisses undctx.screender Bildschirm).
Solange der Code eines Buttons nicht fertig ist, zeigt der Button drei animierte Punkte: Wer die App nutzt, merkt, dass sie arbeitet. Wenn der Code platzt, bricht der Bildschirm nicht: Es erscheint eine Warnung, und der Fehler wird im Radar protokolliert, mit dem Bildschirm, dem Widget und dem Ereignis, in dem er passiert ist.
Das SDK keplin
Alles, was der Code tun kann, steht unter keplin. Das sind die Bereiche:
| Bereich | Wofür |
|---|---|
keplin.event / keplin.ctx |
Der Payload des Ereignisses und der Kontext, in dem es läuft. |
keplin.widgets |
Mit den Widgets des Bildschirms sprechen. |
keplin.data |
Die Datastores: lesen, schreiben, filtern, speichern. |
keplin.nav |
Navigieren und die Parameter der Route lesen. |
keplin.ui |
Meldungen, Bestätigungen und Modals. |
keplin.state |
Zustand, der zwischen Bildschirmen geteilt wird. |
keplin.session |
Wer die App nutzt, und was er darf. |
keplin.auth |
Login, Registrierung und Passwort-Wiederherstellung (Systembildschirme). |
keplin.i18n |
Übersetzte Sätze. |
keplin.storage |
Einstellungen, die auf dem Gerät gespeichert werden. |
keplin.api |
Die APIs der App direkt aufrufen. |
keplin.reports |
Berichte öffnen und herunterladen. |
keplin.workflow |
Prozesse starten, Aufgaben auflisten und abschließen. |
Die Widgets
keplin.widgets.get("id") gibt das Handle eines Widgets zurück. Alle
Handles haben dieselben Grundlagen:
const w = keplin.widgets.get("w_fic_tel1");
w.show(); // anzeigen
w.hide(); // ausblenden
w.setEnabled(false); // deaktivieren
w.set("label", "Mobiltelefon"); // eine beliebige Eigenschaft des Inspektors ändern
w.get("label"); // den wirksamen Wert lesen
w.reset(); // die zur Laufzeit gemachten Änderungen vergessen
Und danach ergänzt jede Familie, was ihr eigen ist:
| Familie | Was sie ergänzt |
|---|---|
| Formularfelder | getValue(), setValue(v), validate(), error |
| Daten-Widgets (Tabelle, Liste, Karten, Diagramm, KPI, Kanban, Kalender, Gantt) | rows, total, refresh(), setFilter(where), setSort(sort) |
| Tabelle | selectedRow, selectedRows, clearSelection() |
| KPI | value, values, valueOf(indikatorId) |
| Kanban | columns, moveCard(id, spalte, index?) |
| Kalender | view, start, end, goTo(datum), setView(ansicht) |
| Gantt | zoom, setZoom(z) |
| Tabs | activeTab, tab("id") — und, am Tab, activate(), show(), hide(), setEnabled() |
| Beschriftung / Button / Link / Breadcrumb | setText(t) / setLabel(t) |
| Markdown | setContent(md) |
| Externe Seite | setUrl(url), reload() |
| Export | export() |
| Bericht | url, download() |
Nota
Die Vorschläge des Editors bieten alle Verben aller Familien an, weil der
Editor nicht im Voraus weiß, welches Widget diese id ist. Zur Laufzeit gibt es
nur die des echten Typs — moveCard an einem Button tut nichts Nützliches.
Die Daten
keplin.data.store("name") gibt einen Datastore des Bildschirms über seinen
Namen zurück (siehe
Datastores und Daten).
In einem Datensatz-Datastore:
const conta = keplin.data.store("conta");
conta.get("nome"); // ein Feld lesen
conta.set("estado", "ativo"); // ein Feld schreiben (bleibt ungespeichert)
conta.record(); // der ganze Datensatz
conta.isDirty(); // gibt es ungespeicherte Änderungen?
conta.reset(); // die Änderungen wegwerfen
const ok = await conta.save(); // validiert und speichert; true, wenn gespeichert
In einem Listen-Datastore:
const contas = keplin.data.store("contas");
contas.rows(); // die geladenen Zeilen
contas.total(); // die Gesamtzahl (wenn der Server sie liefert)
contas.reload(); // erneut lesen
contas.setWhere({ estado: { eq: "ativo" } }); // zusätzlicher Filter; null löscht ihn
contas.setSort([{ field: "nome", direction: "ASC" }]);
contas.goToPage(2);
In beiden sagt status(), wie es um das Laden steht (idle, loading, ready,
error).
Navigation
keplin.nav.go("/ficha-de-conta/17"); // zu einer Route gehen (mit Parametern)
keplin.nav.back(); // zurückgehen
keplin.nav.params; // die Parameter des aktuellen Bildschirms, nach Namen
Meldungen, Bestätigungen und Modals
keplin.ui.toast("Gespeichert.", "success"); // "success" | "error" | "info"
const ok = await keplin.ui.confirm("Den Datensatz löschen?");
if (!ok) return;
Die Bestätigung ist ein Dialog mit dem Theme der App — niemals der graue Kasten des Browsers.
Zustand, Sitzung und Einstellungen
keplin.state.set("filtroContas", "activas"); // lebt, solange der Tab offen ist
keplin.state.get("filtroContas");
keplin.state.remove("filtroContas");
keplin.session.user; // { id, username, name } — null auf öffentlichen Bildschirmen
keplin.session.roles; // die Rollen dessen, der die App nutzt
keplin.session.can("contas.editar"); // hat er diese Aktion? (Einstellungen ▸ Berechtigungen)
await keplin.session.logout();
keplin.storage.set("colunasContas", ["nome", "cidade"]); // bleibt auf dem Gerät
keplin.storage.get("colunasContas");
Dica
Um zu entscheiden, was jemand darf, fragen Sie keplin.session.can("...") und
nicht hasRole("gestor"). Die Aktionen werden unter Einstellungen ▸
Berechtigungen deklariert und überleben Umbauten an den Rollen; der Name
einer Rolle nicht.
Die APIs und die Berichte
const linhas = await keplin.api.query("contas", { estado: "ativo" }, ["id", "nome"]);
await keplin.api.mutate("criarConta", { nome: "Nova" }, ["id"]);
keplin.reports.open("Kontakte des Kontos", { contaId: 17 });
keplin.reports.download("Kontakte des Kontos", { contaId: 17 }, "xlsx");
Bei einer Abfrage ist die Feldliste Pflicht — sie ist es, die sagt, was Sie mitbringen wollen.
Atenção
keplin.reports.open öffnet einen neuen Tab und darf deshalb nicht hinter
einem await stehen: Außerhalb der Geste des Benutzers blockiert der Browser
das Fenster. Öffnen Sie zuerst, machen Sie den Rest danach.
Workflows
const registo = keplin.data.store("oportunidade").get("id");
await keplin.workflow.start("wf_aprovacao", registo);
const tarefas = await keplin.workflow.tasks();
await keplin.workflow.complete(tarefas[0].id, "aprovar");
const { woken } = await keplin.workflow.signal("documento-recebido", registo);
Übersetzte Sätze
keplin.i18n.t("{n} aktive Konten", { n: linhas.length });
keplin.i18n.locale;
Modale Bildschirme
Ein Bildschirm von Keplin ist nicht modal, weil er auf eine bestimmte Weise geöffnet wurde — er ist modal, weil er so konfiguriert wurde. Die Entscheidung fällt im Inspektor des Bildschirms, in der Kategorie Darstellung:

| Option | Was sie tut |
|---|---|
| Modus | Bildschirm (eine normale Seite), Modal (zentriert) oder Seitenpanel (rechts). |
| Breite (px) / Höhe (px) | Die Größe des Modals. Das Seitenpanel nutzt die volle Höhe. |
| Schließen-Button | Zeigt das × in der Ecke. |
| Klick außerhalb schließt / Esc schließt | Die beiden üblichen Ausgänge. |
| Bildschirm dahinter beim Schließen aktualisieren | Beim Schließen lesen die Datastores des aufrufenden Bildschirms erneut. |
Der Hinweis des Abschnitts selbst fasst es zusammen: Öffnet ÜBER dem
aufrufenden Bildschirm (Link, Ereignisse oder keplin.ui.openModal). Aus der
direkten Navigation ausgenommen.
Per Code öffnen und schließen
const resultado = await keplin.ui.openModal("/nova-conta", { setor: "banca" });
if (resultado) {
keplin.data.store("contas").reload();
}
openModalbekommt die Route (oder die id) des Bildschirms und, optional, die Parameter.- Das Promise löst sich erst auf, wenn das Modal schließt, und bringt den Wert mit, den das Modal zurückgegeben hat.
- Innerhalb des Modals schließt
keplin.ui.closeModal(wert)und gibt diesen Wert zurück. - Modals stapeln sich: Ein Modal kann ein weiteres öffnen.
Nota
keplin.nav.go("/route") auf einen Bildschirm, der als Modal (zentriert)
oder Seitenpanel (rechts) konfiguriert ist, öffnet ihn als Modal,
statt zu navigieren. Das ist Absicht: Ein modaler Bildschirm hat in der
Navigation keine eigene Adresse.
Rezepte
Speichern und zurückgehen (das onClick des Speichern-Buttons des
Kontodatenblatts):
const ok = await keplin.data.store("conta").save();
if (ok) {
keplin.ui.toast("Konto gespeichert");
keplin.nav.go("/contas");
}
Das Datenblatt der angeklickten Zeile öffnen (onRowClick einer Tabelle):
keplin.nav.go(`/ficha-de-conta/${keplin.event.row["id"]}`);
Eine Tabelle über ein Textfeld filtern (onChange des Feldes — ersetzen Sie
die ids durch die Ihres Bildschirms):
const texto = keplin.widgets.get("w_pesquisa").getValue();
keplin.widgets.get("w_cta_tab1").setFilter(texto ? { nome: { contains: texto } } : null);
Vor einer zerstörenden Aktion bestätigen (onClick eines Buttons):
if (!(await keplin.ui.confirm("Dieses Konto löschen?"))) return;
Einen Button vor dem verbergen, der nicht darf (onLoad des Bildschirms):
if (!keplin.session.can("contas.eliminar")) {
keplin.widgets.get("w_apagar").hide();
}
Warum nicht…?
- Warum lässt es mich das Ereignis nicht speichern? Der Code kompiliert nicht. Die Meldung lautet Das Ereignis wurde nicht gespeichert: Der Code ist nicht ausführbar — korrigieren und speichern.
- Warum gibt
keplin.widgets.get("...")einen Fehler? Die id existiert auf diesem Bildschirm nicht. Prüfen Sie sie oben im Inspektor, mit ausgewähltem Widget; und denken Sie daran, dass jedes Gerät ein eigener Baum ist (siehe Layouts und Design je Gerät). - Warum hat
onParamsChangebeim Öffnen nicht ausgelöst? Das ist Absicht: Es löst nur bei Änderungen aus. Für den Start nehmen SieonLoad. - Warum gibt das Modal nichts zurück? Entweder existiert der Zielbildschirm nicht, oder wer die App nutzt, hat keine Berechtigung, ihn zu öffnen — in beiden Fällen löst sich das Promise ohne Wert auf. Prüfen Sie die Route und die Berechtigungen.
- Warum öffnet sich das Fenster des Berichts nicht? Sie haben das
openhinter einawaitgesetzt. Öffnen Sie zuerst. - Warum scheint mein Ereignis nicht zu laufen? Sehen Sie in den Radar: Die Fehler aus dem Code der Ereignisse landen dort, mit Bildschirm, Widget und Ereignis — und der Radar bringt Sie direkt zum Editor dieses Ereignisses.