Événements et le SDK
Les événements des widgets et de l'écran, l'éditeur de code, les actions prédéfinies et le SDK keplin en TypeScript — données, widgets, navigation, session, modales et workflows.
Il y a beaucoup d'écrans qui se font sans écrire une ligne : relier un datastore, faire glisser des widgets, pointer un bouton vers un autre écran. Mais tôt ou tard arrive le « quand ceci arrive, fais cela » — enregistrer et revenir en arrière, recharger une table après un filtre, confirmer avant de supprimer, ouvrir un modal et utiliser ce qu'il a renvoyé.
C'est à cela que servent les événements : des points de l'écran où s'exécute
votre code, écrit en TypeScript, avec un SDK — l'objet keplin — qui donne
accès à tout ce que l'écran contient.
Où sont les événements
Dans l'inspecteur, la dernière catégorie d'un widget (et de l'écran lui-même) s'appelle Événements. Elle a une ligne par événement disponible, et sur chaque ligne :
- un point à gauche : plein quand cet événement a déjà du code, vide quand il n'en a pas ;
- un bouton … à droite, qui ouvre l'éditeur.

Les noms des événements ne sont pas traduits — ils sont les mêmes dans toutes les
langues (onClick, onRowClick, onLoad), parce que ce sont aussi les noms qui
apparaissent dans le code et dans les registres du Radar.
Les événements de l'écran
Cliquez sur une zone vide du canvas pour que l'inspecteur montre l'écran. La catégorie Événements en a trois :
| Événement | Quand il se déclenche | Pour quoi |
|---|---|---|
onLoad |
Une fois, à l'ouverture de l'écran. | Préparer l'état, charger des choses que les datastores ne chargent pas, souhaiter la bienvenue. |
onParamsChange |
Chaque fois que les paramètres de la route changent — et pas à la première ouverture. | Réagir à un changement d'enregistrement sans rouvrir l'écran. |
onUnload |
Quand l'écran est quitté. | Nettoyer l'état, conserver des brouillons. |

Les événements de chaque widget
Chaque type de widget déclare les siens. Au-delà du nom, ce qui compte est le
payload — les données que l'événement transporte, et que le code lit dans
keplin.event.
Champs de formulaire
| Widget | Événements | keplin.event |
|---|---|---|
| Champ de texte, Zone de texte, Nombre, Oui/Non, Liste, Date, Couleur | onChange |
{ value } |
| Fichier | onChange, onUpload |
onUpload: { file, name } |
Actions et navigation
| Widget | Événements | keplin.event |
|---|---|---|
| Bouton | onClick |
{} |
| Bouton avec menu | onClick, onMenuItem |
onMenuItem: { id, label } |
| Lien | onClick |
{} |
| Exporter | onExport, onDataLoaded |
onExport: { rows, filename } |
Structure et contenu
| Widget | Événements | keplin.event |
|---|---|---|
| Onglets | onTabChange |
{ tab } |
| Rapport | onLoad |
{ report } |
Widgets de données
| Widget | Événements | keplin.event |
|---|---|---|
| Table | onRowClick, onRowDoubleClick, onSelectionChange, onDataLoaded |
{ row, index } · onSelectionChange: { row, rows } |
| Liste, Cartes | onRowClick, onDataLoaded |
{ row, index } |
| Graphique | onClick |
{ name, seriesName, value, dataIndex } |
| KPI | onClick, onDataLoaded |
{ value, indicatorId } |
Tableaux et planification
| Widget | Événements | keplin.event |
|---|---|---|
| Kanban | onCardClick, onCardCreate, onCardMoved, onDataLoaded |
{ row } · { column } · { row, from, to, index } |
| Calendrier | onEventClick, onDayClick, onRangeSelect, onRangeChange, onDataLoaded |
{ row } · { date } · { start, end } · { start, end, view } |
| Gantt | onBarClick, onEmptyClick, onDataLoaded |
{ row } · { date } |
Processus
| Widget | Événements | keplin.event |
|---|---|---|
| État du processus | onDecide |
{ task, outcome } |
| Mes tâches | onOpen, onDecide |
{ task, screenId } |
Nota
Les widgets programmés par vous (ceux qui apparaissent dans la palette sous Personnalisé) déclarent leurs propres événements, et apparaissent ici comme tous les autres.
L'éditeur de code
Le bouton … d'un événement ouvre l'éditeur dans une fenêtre. Le titre dit où
vous êtes : l'id du widget (ou le nom de l'écran) et le nom de l'événement —
w_fic_sav1 · onClick.

| Bouton | Ce qu'il fait |
|---|---|
| Insérer une action | Écrit pour vous le code d'une tâche courante (voir plus bas). |
| Retirer le handler | Supprime le code de cet événement. Le point redevient vide. |
| Annuler | Ferme sans enregistrer. |
| Enregistrer | Vérifie et enregistre. |
L'éditeur propose des suggestions pendant que vous écrivez
(Ctrl+Espace) : tout le keplin y est déclaré, avec les bons types — et,
mieux encore, les ids des widgets de cet écran sont là-dedans. Écrire
keplin.widgets.get(" affiche la liste des widgets de l'écran, et un id qui
n'existe pas est signalé comme une erreur avant l'enregistrement.
Atenção
À l'enregistrement, le code est compilé. S'il n'est pas exécutable, la plateforme le refuse — L'événement n'a pas été enregistré : le code n'est pas exécutable — et la fenêtre reste ouverte pour que vous corrigiez. Un écran ne se retrouve jamais avec du code cassé à l'intérieur.
Les actions prédéfinies
Insérer une action ouvre une liste des tâches les plus courantes. Vous en choisissez une et le code est écrit à la suite de ce qui s'y trouve déjà, avec les vrais noms de votre écran — le premier datastore d'enregistrement, la première table, le premier champ de texte.

| Action | Ce qu'elle écrit |
|---|---|
| Enregistrer le datastore | Valide et enregistre l'enregistrement, avec un avis de succès. |
| Recharger le datastore | Relit les données d'un datastore. |
| Filtrer le datastore (par texte) | Lit le texte d'un champ et l'applique comme filtre. |
| Naviguer vers un écran | Saute vers une autre route de l'app. |
| Recharger une table | Rafraîchit les données d'un widget de table. |
| Filtrer une table par le texte d'un champ | Le filtre interactif classique. |
| Afficher/masquer un widget | Bascule la visibilité d'un widget. |
| Confirmer et afficher un toast | Demande avant d'agir et avertit à la fin. |
| Démarrer un workflow | Met un processus en marche sur l'enregistrement actuel. |
| Voir et terminer les tâches | Liste les tâches de la personne connectée et en décide une. |
| Envoyer un signal à un workflow | Réveille des processus qui attendaient. |
| Se déconnecter | Quitte l'app. |
Le code inséré est un point de départ : il devient le vôtre, et il est fait pour être modifié. Il n'est pas régénéré.
Comment le code s'exécute
Chaque événement est une fonction asynchrone qui reçoit une seule chose : le
keplin. Il en découle trois conséquences pratiques :
awaitfonctionne au premier niveau du code. Il n'y a rien à emballer.returnsort de l'événement. C'est la façon normale d'abandonner en cours de route (par exemple, quand une confirmation a été refusée).- Il n'y a pas de paramètres. Le contexte vient à l'intérieur du
keplinlui-même :keplin.eventapporte le payload etkeplin.ctxdit où vous êtes (ctx.widgetest le widget qui a déclenché —nulldans les événements d'écran —,ctx.eventest le nom de l'événement etctx.screenl'écran).
Tant que le code d'un bouton n'est pas terminé, le bouton affiche trois points animés : la personne qui l'utilise comprend que l'app travaille. Si le code explose, l'écran ne casse pas : un avertissement apparaît et l'erreur est enregistrée dans le Radar, avec l'écran, le widget et l'événement où c'est arrivé.
Le SDK keplin
Tout ce que le code peut faire est sous keplin. Voici les domaines :
| Domaine | Pour quoi |
|---|---|
keplin.event / keplin.ctx |
Le payload de l'événement et le contexte dans lequel il s'exécute. |
keplin.widgets |
Parler aux widgets de l'écran. |
keplin.data |
Les datastores : lire, écrire, filtrer, enregistrer. |
keplin.nav |
Naviguer et lire les paramètres de la route. |
keplin.ui |
Avis, confirmations et modales. |
keplin.state |
État partagé entre écrans. |
keplin.session |
Qui utilise l'app, et ce qu'il peut faire. |
keplin.auth |
Connexion, inscription et récupération de mot de passe (écrans système). |
keplin.i18n |
Phrases traduites. |
keplin.storage |
Préférences conservées sur l'appareil. |
keplin.api |
Appeler les APIs de l'app directement. |
keplin.reports |
Ouvrir et télécharger des rapports. |
keplin.workflow |
Démarrer des processus, lister et terminer des tâches. |
Les widgets
keplin.widgets.get("id") renvoie le handle d'un widget. Tous les handles
ont les mêmes bases :
const w = keplin.widgets.get("w_fic_tel1");
w.show(); // afficher
w.hide(); // masquer
w.setEnabled(false); // désactiver
w.set("label", "Mobile"); // changer n'importe quelle propriété de l'inspecteur
w.get("label"); // lire la valeur effective
w.reset(); // oublier les modifications faites à l'exécution
Et ensuite chaque famille ajoute ce qui lui est propre :
| Famille | Ce qu'elle ajoute |
|---|---|
| Champs de formulaire | getValue(), setValue(v), validate(), error |
| Widgets de données (Table, Liste, Cartes, Graphique, KPI, Kanban, Calendrier, Gantt) | rows, total, refresh(), setFilter(where), setSort(sort) |
| Table | selectedRow, selectedRows, clearSelection() |
| KPI | value, values, valueOf(idIndicateur) |
| Kanban | columns, moveCard(id, colonne, index?) |
| Calendrier | view, start, end, goTo(date), setView(vue) |
| Gantt | zoom, setZoom(z) |
| Onglets | activeTab, tab("id") — et, sur l'onglet, activate(), show(), hide(), setEnabled() |
| Libellé / Bouton / Lien / Fil d'Ariane | setText(t) / setLabel(t) |
| Markdown | setContent(md) |
| Page externe | setUrl(url), reload() |
| Exporter | export() |
| Rapport | url, download() |
Nota
Les suggestions de l'éditeur proposent tous les verbes de toutes les
familles, parce que l'éditeur ne sait pas d'avance quel widget est cet id. À
l'exécution, seuls ceux du type réel existent — moveCard sur un Bouton ne
fait rien d'utile.
Les données
keplin.data.store("nom") renvoie un datastore de l'écran par son nom (voir
Datastores et données).
Dans un datastore d'enregistrement :
const conta = keplin.data.store("conta");
conta.get("nome"); // lire un champ
conta.set("estado", "ativo"); // écrire un champ (reste non enregistré)
conta.record(); // l'enregistrement entier
conta.isDirty(); // y a-t-il des modifications non enregistrées ?
conta.reset(); // jeter les modifications
const ok = await conta.save(); // valide et enregistre ; true s'il a enregistré
Dans un datastore de liste :
const contas = keplin.data.store("contas");
contas.rows(); // les lignes chargées
contas.total(); // le total (quand le serveur le donne)
contas.reload(); // relire
contas.setWhere({ estado: { eq: "ativo" } }); // filtre supplémentaire ; null l'efface
contas.setSort([{ field: "nome", direction: "ASC" }]);
contas.goToPage(2);
Dans les deux, status() dit où en est le chargement (idle, loading,
ready, error).
Navigation
keplin.nav.go("/ficha-de-conta/17"); // aller vers une route (avec paramètres)
keplin.nav.back(); // revenir en arrière
keplin.nav.params; // les paramètres de l'écran actuel, par nom
Avis, confirmations et modales
keplin.ui.toast("Enregistré.", "success"); // "success" | "error" | "info"
const ok = await keplin.ui.confirm("Supprimer l'enregistrement ?");
if (!ok) return;
La confirmation est une boîte de dialogue au thème de l'app — jamais la boîte grise du navigateur.
État, session et préférences
keplin.state.set("filtroContas", "activas"); // vit tant que l'onglet est ouvert
keplin.state.get("filtroContas");
keplin.state.remove("filtroContas");
keplin.session.user; // { id, username, name } — null sur les écrans publics
keplin.session.roles; // les rôles de la personne connectée
keplin.session.can("contas.editar"); // a-t-elle cette action ? (Paramètres ▸ Permissions)
await keplin.session.logout();
keplin.storage.set("colunasContas", ["nome", "cidade"]); // reste sur l'appareil
keplin.storage.get("colunasContas");
Dica
Pour décider ce que quelqu'un peut faire, demandez keplin.session.can("...")
et non hasRole("gestor"). Les actions sont déclarées dans Paramètres ▸
Permissions et survivent aux réorganisations de rôles ; le nom d'un rôle,
non.
Les APIs et les rapports
const linhas = await keplin.api.query("contas", { estado: "ativo" }, ["id", "nome"]);
await keplin.api.mutate("criarConta", { nome: "Nova" }, ["id"]);
keplin.reports.open("Contacts du compte", { contaId: 17 });
keplin.reports.download("Contacts du compte", { contaId: 17 }, "xlsx");
Dans une requête, la liste des champs est obligatoire — c'est elle qui dit ce que vous voulez ramener.
Atenção
keplin.reports.open ouvre un nouvel onglet et ne peut donc pas se trouver
derrière un await : hors du geste de l'utilisateur, le navigateur bloque la
fenêtre. Ouvrez d'abord, faites le reste ensuite.
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);
Phrases traduites
keplin.i18n.t("{n} comptes actifs", { n: linhas.length });
keplin.i18n.locale;
Écrans modaux
Un écran de Keplin n'est pas modal parce qu'il a été ouvert d'une certaine manière — il est modal parce qu'il a été configuré ainsi. La décision est dans l'inspecteur de l'écran, dans la catégorie Présentation :

| Option | Ce qu'elle fait |
|---|---|
| Mode | Écran (une page normale), Modal (centre) ou Panneau latéral (droite). |
| Largeur (px) / Hauteur (px) | La taille du modal. Le panneau latéral utilise toute la hauteur. |
| Bouton de fermeture | Affiche le × dans le coin. |
| Clic à l'extérieur ferme / Échap ferme | Les deux sorties habituelles. |
| Actualiser l'écran sous-jacent à la fermeture | À la fermeture, les datastores de l'écran appelant relisent. |
L'aide de la section elle-même résume : S'ouvre PAR-DESSUS l'écran qui l'appelle
(Link, événements ou keplin.ui.openModal). Exclu de la navigation directe.
Ouvrir et fermer par code
const resultado = await keplin.ui.openModal("/nova-conta", { setor: "banca" });
if (resultado) {
keplin.data.store("contas").reload();
}
openModalreçoit la route (ou l'id) de l'écran et, éventuellement, les paramètres.- La promesse ne se résout que quand le modal se ferme, et apporte la valeur que le modal a renvoyée.
- À l'intérieur du modal,
keplin.ui.closeModal(valeur)ferme et renvoie cette valeur. - Les modales s'empilent : un modal peut en ouvrir un autre.
Nota
keplin.nav.go("/route") vers un écran configuré comme Modal (centre) ou
Panneau latéral (droite) l'ouvre comme modal au lieu de naviguer. C'est
exprès : un écran modal n'a pas d'adresse propre dans la navigation.
Recettes
Enregistrer et revenir (le onClick du bouton Enregistrer de la Fiche de
Compte) :
const ok = await keplin.data.store("conta").save();
if (ok) {
keplin.ui.toast("Compte enregistré");
keplin.nav.go("/contas");
}
Ouvrir la fiche de la ligne cliquée (onRowClick d'une Table) :
keplin.nav.go(`/ficha-de-conta/${keplin.event.row["id"]}`);
Filtrer une table avec un champ de texte (onChange du champ — remplacez les
ids par ceux de votre écran) :
const texto = keplin.widgets.get("w_pesquisa").getValue();
keplin.widgets.get("w_cta_tab1").setFilter(texto ? { nome: { contains: texto } } : null);
Confirmer avant une action destructrice (onClick d'un bouton) :
if (!(await keplin.ui.confirm("Supprimer ce compte ?"))) return;
Masquer un bouton à qui n'y a pas droit (onLoad de l'écran) :
if (!keplin.session.can("contas.eliminar")) {
keplin.widgets.get("w_apagar").hide();
}
Pourquoi ne… ?
- Pourquoi cela ne me laisse-t-il pas enregistrer l'événement ? Le code ne compile pas. Le message est L'événement n'a pas été enregistré : le code n'est pas exécutable — corrigez et enregistrez.
- Pourquoi
keplin.widgets.get("...")donne-t-il une erreur ? L'id n'existe pas sur cet écran. Vérifiez-le en haut de l'inspecteur, avec le widget sélectionné ; et souvenez-vous que chaque appareil est une arborescence à part (voir Layouts et design par appareil). - Pourquoi
onParamsChangene s'est-il pas déclenché à l'ouverture ? C'est exprès : il ne se déclenche que sur des changements. Pour le démarrage, utilisezonLoad. - Pourquoi le modal ne renvoie-t-il rien ? Soit l'écran de destination n'existe pas, soit la personne qui l'utilise n'a pas la permission de l'ouvrir — dans les deux cas, la promesse se résout sans valeur. Vérifiez la route et les permissions.
- Pourquoi la fenêtre du rapport ne s'ouvre-t-elle pas ? Vous avez mis le
openaprès unawait. Ouvrez d'abord. - Pourquoi mon événement semble-t-il ne pas s'exécuter ? Regardez le Radar : les erreurs du code des événements y restent, avec écran, widget et événement — et le Radar vous emmène directement à l'éditeur de cet événement.