KEPLIN Docs

Eventos e o SDK

Os eventos dos widgets e do ecrã, o editor de código, as acções pré-definidas e o SDK keplin em TypeScript — dados, widgets, navegação, sessão, modais e workflows.

Há muito ecrã que se faz sem escrever uma linha: ligar um datastore, arrastar widgets, apontar um botão a outro ecrã. Mas mais cedo ou mais tarde aparece o "quando isto acontecer, faz aquilo" — gravar e voltar atrás, recarregar uma tabela depois de um filtro, confirmar antes de apagar, abrir um modal e usar o que ele devolveu.

É para isso que servem os eventos: pontos do ecrã onde corre código teu, escrito em TypeScript, com um SDK — o objecto keplin — que dá acesso a tudo o que o ecrã tem.

Onde estão os eventos

No inspector, a última categoria de um widget (e do próprio ecrã) chama-se Eventos. Tem uma linha por evento disponível, e em cada linha:

  • um ponto à esquerda: cheio quando aquele evento já tem código, vazio quando não tem;
  • um botão à direita, que abre o editor.

A categoria Eventos de um Botão: um ponto cheio marca os eventos que já têm código; o … abre o editor.
A categoria Eventos de um Botão: um ponto cheio marca os eventos que já têm código; o … abre o editor.

Os nomes dos eventos não são traduzidos — são os mesmos em qualquer língua (onClick, onRowClick, onLoad), porque são também os nomes que aparecem no código e nos registos do Radar.

Os eventos do ecrã

Clica numa zona vazia do canvas para o inspector mostrar o ecrã. A categoria Eventos tem três:

Evento Quando dispara Para quê
onLoad Uma vez, quando o ecrã abre. Preparar estado, carregar coisas que os datastores não carregam, dar as boas-vindas.
onParamsChange Sempre que os parâmetros da rota mudam — e não na primeira abertura. Reagir a uma mudança de registo sem reabrir o ecrã.
onUnload Quando o ecrã sai. Limpar estado, guardar rascunhos.

Os eventos do próprio ecrã — onLoad, onParamsChange e onUnload — no inspector sem selecção.
Os eventos do próprio ecrã — onLoad, onParamsChange e onUnload — no inspector sem selecção.

Os eventos de cada widget

Cada tipo de widget declara os seus. Além do nome, interessa o payload — os dados que o evento traz consigo, e que o código lê em keplin.event.

Campos de formulário

Widget Eventos keplin.event
Caixa de texto, Área de texto, Número, Sim/Não, Lista, Data, Cor onChange { value }
Ficheiro onChange, onUpload onUpload: { file, name }

Acções e navegação

Widget Eventos keplin.event
Botão onClick {}
Botão com menu onClick, onMenuItem onMenuItem: { id, label }
Link onClick {}
Exportar onExport, onDataLoaded onExport: { rows, filename }

Estrutura e conteúdo

Widget Eventos keplin.event
Abas onTabChange { tab }
Relatório onLoad { report }

Widgets de dados

Widget Eventos keplin.event
Tabela onRowClick, onRowDoubleClick, onSelectionChange, onDataLoaded { row, index } · onSelectionChange: { row, rows }
Lista, Cartões onRowClick, onDataLoaded { row, index }
Gráfico onClick { name, seriesName, value, dataIndex }
KPI onClick, onDataLoaded { value, indicatorId }

Quadros e planeamento

Widget Eventos keplin.event
Kanban onCardClick, onCardCreate, onCardMoved, onDataLoaded { row } · { column } · { row, from, to, index }
Calendário onEventClick, onDayClick, onRangeSelect, onRangeChange, onDataLoaded { row } · { date } · { start, end } · { start, end, view }
Gantt onBarClick, onEmptyClick, onDataLoaded { row } · { date }

Processos

Widget Eventos keplin.event
Estado do processo onDecide { task, outcome }
As minhas tarefas onOpen, onDecide { task, screenId }

Nota

Widgets programados por ti (os que aparecem na palette em Custom) declaram os seus próprios eventos, e aparecem aqui como quaisquer outros.

O editor de código

O botão de um evento abre o editor num modal. O título diz onde estás: o id do widget (ou o nome do ecrã) e o nome do evento — w_fic_sav1 · onClick.

O editor do evento onClick do botão Guardar: o código grava o datastore e, se correu bem, avisa e volta à lista.
O editor do evento onClick do botão Guardar: o código grava o datastore e, se correu bem, avisa e volta à lista.

Botão O que faz
Inserir acção Escreve por ti o código de uma tarefa comum (a seguir).
Remover handler Apaga o código deste evento. O ponto volta a vazio.
Cancelar Fecha sem gravar.
Gravar Verifica e grava.

O editor tem sugestões enquanto escreves (Ctrl+Espaço): o keplin inteiro está declarado, com os tipos certos — e, melhor ainda, os ids dos widgets deste ecrã estão lá dentro. Escrever keplin.widgets.get(" mostra a lista dos widgets do ecrã, e um id que não existe é assinalado como erro antes de gravares.

Atenção

Ao gravar, o código é compilado. Se não for executável, a plataforma recusa-o — O evento não foi guardado: o código não é executável — e o modal fica aberto para corrigires. Um ecrã nunca fica com código partido lá dentro.

As acções pré-definidas

Inserir acção abre uma lista das tarefas mais comuns. Escolhes uma e o código é escrito no fim do que já lá está, já com os nomes reais do teu ecrã — o primeiro datastore de registo, a primeira tabela, a primeira caixa de texto.

Inserir acção — as acções pré-definidas que escrevem o código por ti, dos datastores aos workflows.
Inserir acção — as acções pré-definidas que escrevem o código por ti, dos datastores aos workflows.

Acção O que escreve
Gravar datastore Valida e grava o registo, com aviso de sucesso.
Recarregar datastore Volta a ler os dados de um datastore.
Filtrar datastore (por texto) Lê o texto de uma caixa e aplica-o como filtro.
Navegar para um ecrã Salta para outra rota da app.
Recarregar uma tabela Refresca os dados de um widget de tabela.
Filtrar tabela pelo texto de um campo O filtro interactivo clássico.
Mostrar/esconder um widget Alterna a visibilidade de um widget.
Confirmar e mostrar toast Pergunta antes de agir e avisa no fim.
Arrancar um workflow Põe um processo a andar sobre o registo actual.
Ver e concluir tarefas Lista as tarefas de quem está a usar e decide uma.
Mandar um sinal a um workflow Acorda processos que estavam à espera.
Terminar sessão Sai da app.

O código inserido é um ponto de partida: fica teu, e é para editares. Não volta a ser gerado.

Como o código corre

Cada evento é uma função assíncrona que recebe uma coisa só: o keplin. Daí saem três consequências práticas:

  • await funciona no topo do código. Não é preciso embrulhar nada.
  • return sai do evento. É a forma normal de desistir a meio (por exemplo, quando uma confirmação foi recusada).
  • Não há parâmetros. O contexto vem dentro do próprio keplin: keplin.event traz o payload e keplin.ctx diz onde estás (ctx.widget é o widget que disparou — null nos eventos de ecrã —, ctx.event é o nome do evento e ctx.screen o ecrã).

Enquanto o código de um botão não termina, o botão mostra três pontos animados: quem está a usar percebe que a app está a trabalhar. Se o código rebentar, o ecrã não parte: aparece um aviso e o erro fica registado no Radar, com o ecrã, o widget e o evento onde aconteceu.

O SDK keplin

Tudo o que o código pode fazer está debaixo de keplin. Estas são as áreas:

Área Para quê
keplin.event / keplin.ctx O payload do evento e o contexto onde está a correr.
keplin.widgets Falar com os widgets do ecrã.
keplin.data Os datastores: ler, escrever, filtrar, gravar.
keplin.nav Navegar e ler os parâmetros da rota.
keplin.ui Avisos, confirmações e modais.
keplin.state Estado partilhado entre ecrãs.
keplin.session Quem está a usar a app, e o que pode fazer.
keplin.auth Login, registo e recuperação de palavra-passe (ecrãs de sistema).
keplin.i18n Frases traduzidas.
keplin.storage Preferências guardadas no dispositivo.
keplin.api Chamar as APIs da app directamente.
keplin.reports Abrir e descarregar relatórios.
keplin.workflow Arrancar processos, listar e concluir tarefas.

Os widgets

keplin.widgets.get("id") devolve o handle de um widget. Todos os handles têm o mesmo básico:

const w = keplin.widgets.get("w_fic_tel1");
w.show();               // mostrar
w.hide();               // esconder
w.setEnabled(false);    // desactivar
w.set("label", "Telemóvel");   // mudar qualquer propriedade do inspector
w.get("label");         // ler o valor efectivo
w.reset();              // esquecer as alterações feitas em execução

E depois cada família acrescenta o que lhe é próprio:

Família O que acrescenta
Campos de formulário getValue(), setValue(v), validate(), error
Widgets de dados (Tabela, Lista, Cartões, Gráfico, KPI, Kanban, Calendário, Gantt) rows, total, refresh(), setFilter(where), setSort(sort)
Tabela selectedRow, selectedRows, clearSelection()
KPI value, values, valueOf(indicadorId)
Kanban columns, moveCard(id, coluna, índice?)
Calendário view, start, end, goTo(data), setView(vista)
Gantt zoom, setZoom(z)
Abas activeTab, tab("id") — e, na aba, activate(), show(), hide(), setEnabled()
Etiqueta / Botão / Link / Breadcrumb setText(t) / setLabel(t)
Markdown setContent(md)
Página externa setUrl(url), reload()
Exportar export()
Relatório url, download()

Nota

As sugestões do editor oferecem todos os verbos de todas as famílias, porque o editor não sabe de antemão que widget é aquele id. Em execução só existem os do tipo real — moveCard num Botão não faz nada de útil.

Os dados

keplin.data.store("nome") devolve um datastore do ecrã pelo nome (ver Datastores e dados).

Num datastore de registo:

const conta = keplin.data.store("conta");
conta.get("nome");                 // ler um campo
conta.set("estado", "ativo");      // escrever um campo (fica por gravar)
conta.record();                    // o registo inteiro
conta.isDirty();                   // há alterações por gravar?
conta.reset();                     // deitar fora as alterações
const ok = await conta.save();     // valida e grava; true se gravou

Num datastore de lista:

const contas = keplin.data.store("contas");
contas.rows();                     // as linhas carregadas
contas.total();                    // o total (quando o servidor o dá)
contas.reload();                   // voltar a ler
contas.setWhere({ estado: { eq: "ativo" } });   // filtro extra; null limpa
contas.setSort([{ field: "nome", direction: "ASC" }]);
contas.goToPage(2);

Em ambos, status() diz em que pé está o carregamento (idle, loading, ready, error).

keplin.nav.go("/ficha-de-conta/17");   // ir para uma rota (com parâmetros)
keplin.nav.back();                      // voltar atrás
keplin.nav.params;                      // os parâmetros do ecrã actual, por nome

Avisos, confirmações e modais

keplin.ui.toast("Gravado.", "success");        // "success" | "error" | "info"
const ok = await keplin.ui.confirm("Apagar o registo?");
if (!ok) return;

A confirmação é um diálogo com o tema da app — nunca a caixa cinzenta do browser.

Estado, sessão e preferências

keplin.state.set("filtroContas", "activas");   // vive enquanto a aba estiver aberta
keplin.state.get("filtroContas");
keplin.state.remove("filtroContas");

keplin.session.user;              // { id, username, name } — null em ecrãs públicos
keplin.session.roles;             // os papéis de quem está a usar
keplin.session.can("contas.editar");   // tem esta acção? (Definições ▸ Permissões)
await keplin.session.logout();

keplin.storage.set("colunasContas", ["nome", "cidade"]);   // fica no dispositivo
keplin.storage.get("colunasContas");

Dica

Para decidir o que alguém pode fazer, pergunta keplin.session.can("...") e não hasRole("gestor"). As acções são declaradas em Definições ▸ Permissões e sobrevivem a reorganizações de papéis; o nome de um papel, não.

As APIs e os relatórios

const linhas = await keplin.api.query("contas", { estado: "ativo" }, ["id", "nome"]);
await keplin.api.mutate("criarConta", { nome: "Nova" }, ["id"]);

keplin.reports.open("Contactos da conta", { contaId: 17 });
keplin.reports.download("Contactos da conta", { contaId: 17 }, "xlsx");

Numa consulta, a lista de campos é obrigatória — é ela que diz o que queres trazer.

Atenção

keplin.reports.open abre um separador novo e por isso não pode ficar atrás de um await: fora do gesto do utilizador, o browser bloqueia a janela. Abre primeiro, faz o resto depois.

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);

Frases traduzidas

keplin.i18n.t("{n} contas activas", { n: linhas.length });
keplin.i18n.locale;

Ecrãs modais

Um ecrã do Keplin não é modal porque foi aberto de certa maneira — é modal porque foi configurado assim. A decisão está no inspector do ecrã, na categoria Apresentação:

A secção Apresentação do ecrã: é aqui que um ecrã passa a Modal (centro) ou a Painel lateral (direita).
A secção Apresentação do ecrã: é aqui que um ecrã passa a Modal (centro) ou a Painel lateral (direita).

Opção O que faz
Modo Ecrã (uma página normal), Modal (centro) ou Painel lateral (direita).
Largura (px) / Altura (px) O tamanho do modal. O painel lateral usa a altura toda.
Botão de fechar Mostra o × no canto.
Clique fora fecha / Esc fecha As duas saídas habituais.
Refrescar o ecrã de trás ao fechar Ao fechar, os datastores do ecrã que o chamou voltam a ler.

A dica da própria secção resume: Abre POR CIMA do ecrã que o chama (Link, eventos ou keplin.ui.openModal). Sai da navegação directa.

Abrir e fechar por código

const resultado = await keplin.ui.openModal("/nova-conta", { setor: "banca" });
if (resultado) {
  keplin.data.store("contas").reload();
}
  • openModal recebe a rota (ou o id) do ecrã e, opcionalmente, os parâmetros.
  • A promessa só resolve quando o modal fecha, e traz o valor que o modal devolveu.
  • Dentro do modal, keplin.ui.closeModal(valor) fecha e devolve esse valor.
  • Os modais empilham: um modal pode abrir outro.

Nota

keplin.nav.go("/rota") para um ecrã configurado como Modal (centro) ou Painel lateral (direita) abre-o como modal em vez de navegar. É de propósito: um ecrã modal não tem endereço próprio na navegação.

Receitas

Gravar e voltar (o onClick do botão Guardar da Ficha de Conta):

const ok = await keplin.data.store("conta").save();
if (ok) {
  keplin.ui.toast("Conta guardada");
  keplin.nav.go("/contas");
}

Abrir a ficha da linha clicada (onRowClick de uma Tabela):

keplin.nav.go(`/ficha-de-conta/${keplin.event.row["id"]}`);

Filtrar uma tabela por uma caixa de texto (onChange da caixa — substitui os ids pelos do teu ecrã):

const texto = keplin.widgets.get("w_pesquisa").getValue();
keplin.widgets.get("w_cta_tab1").setFilter(texto ? { nome: { contains: texto } } : null);

Confirmar antes de uma acção destrutiva (onClick de um botão):

if (!(await keplin.ui.confirm("Apagar esta conta?"))) return;

Esconder um botão a quem não pode (onLoad do ecrã):

if (!keplin.session.can("contas.eliminar")) {
  keplin.widgets.get("w_apagar").hide();
}

Porque não…?

  • Porque não me deixa gravar o evento? O código não compila. A mensagem é O evento não foi guardado: o código não é executável — corrige e grava.
  • Porque é que keplin.widgets.get("...") dá erro? O id não existe neste ecrã. Confirma-o no topo do inspector, com o widget seleccionado; e lembra-te que cada dispositivo é uma árvore própria (ver Layouts e desenho por dispositivo).
  • Porque é que o onParamsChange não disparou ao abrir? É de propósito: ele só dispara em mudanças. Para o arranque, usa o onLoad.
  • Porque é que o modal não devolve nada? Ou o ecrã de destino não existe, ou quem está a usar não tem permissão para o abrir — nos dois casos a promessa resolve sem valor. Confirma a rota e as permissões.
  • Porque não abre a janela do relatório? Puseste o open depois de um await. Abre primeiro.
  • Porque é que o meu evento parece não correr? Vê o Radar: os erros do código dos eventos ficam lá, com ecrã, widget e evento — e o Radar leva-te directamente ao editor daquele evento.