Datastores e dados
Como um ecrã carrega, filtra e grava dados — datastores de registo e de lista, chaves, filtros, paginação e as ligações a dados.
Um ecrã não fala directamente com a base de dados: fala com datastores — contentores de dados do ecrã que carregam registos através das APIs de tabela da app. Os widgets ligam-se aos datastores: uma Tabela mostra as linhas de um datastore de lista, os campos de um formulário lêem e escrevem num datastore de registo.
Este é o elo entre dois capítulos: as APIs de tabela criam-se sobre o modelo de dados (capítulo APIs & GraphQL); aqui liga-se o ecrã a elas.
Os dois tipos de datastore
| Tipo | O que carrega | Para quê |
|---|---|---|
| Registo | UM registo (ou um registo novo, vazio) | Formulários: os campos ligam-se aos campos do registo, e no fim grava-se. |
| Lista | Uma colecção de registos | Tabelas, listas, cartões, gráficos, kanbans, calendários. |
Os datastores podem viver em dois sítios:
- No ecrã — criados no inspector sem selecção, na categoria Dados. São partilhados: vários widgets podem ler do mesmo, e é o que se usa para formulários e para relações mestre-detalhe.
- Dentro de um widget — os widgets de dados (Tabela, Gráfico, KPI…) têm o seu próprio datastore na categoria Dados deles. É o caso mais comum para grelhas e gráficos independentes.
O motor é o mesmo nos dois sítios; a única diferença está nas origens de valor disponíveis nos filtros (ver as ligações).
Criar um datastore no ecrã
- Clica numa zona vazia do canvas para o inspector mostrar o ecrã.
- Na categoria Dados, clica em + registo ou + lista.
- Clica no datastore criado para abrir o modal Configurar datastore.
- Dá-lhe um Nome do datastore — é por este nome que os widgets e o
código o encontram (ex.:
conta,contas). - Em API, escolhe a API que serve os dados. Os campos da API ficam disponíveis para colunas, ligações e filtros. Num datastore de lista aparecem também as APIs de pipeline, com o crachá pipeline: devolvem a lista inteira, sem filtros nem paginação.

Nota
Sem APIs de tabela publicadas, o selector avisa: Sem APIs de tabela publicadas nesta app. Cria primeiro a API sobre a entidade do modelo — é um passo do capítulo APIs & GraphQL.
Datastore de registo — que registo carregar
Um datastore de registo responde a uma pergunta: qual registo? A resposta dá-se em Que registo carregar (chave):
- Clica em + campo da chave.
- Escolhe o campo (por omissão, a chave primária), o operador e o valor —
tipicamente um Param da rota: o ecrã Ficha de Conta recebe
idno endereço e carrega a conta com esse id. - Várias condições formam uma chave composta — todas têm de bater.
Sem condições, o datastore carrega um registo novo (vazio) — é assim que
o mesmo ecrã de formulário serve para criar: aberto sem id, começa em
branco; gravado, faz a inserção.
Com condições que não encontram registo nenhum (um id que já não existe, ou
que está fora do alcance do role de quem abre), a app avisa que o registo
pedido não existe e o formulário não grava. Só uma chave escrita num campo do
ecrã (uma chave natural, como um código de artigo) continua a ser a de um
registo novo.
Datastore de lista — filtros e carregamento
Filtros (where)
Os Filtros (where) são condições aplicadas sempre que os dados são lidos — é aqui que se limita o que vem da base de dados. Cada condição é campo / operador / valor; + adicionar filtro acrescenta condições e + grupo cria sub-grupos aninhados, com Todas (AND) ou Qualquer (OR) a decidir como se combinam.
Exemplo da Gestão de Clientes: o ecrã Contas filtra estado eq "activa";
o painel "as minhas contas" acrescenta gestor eq → Sessão ▸ username.
Os operadores:
| Operador | O que compara |
|---|---|
eq / neq |
Igual / diferente do valor. neq inclui os registos com o campo vazio. |
contains, startsWith, endsWith |
Texto que contém, começa ou acaba pelo valor. |
gt, gte, lt, lte |
Maior, maior ou igual, menor, menor ou igual — números e datas. |
in / nin |
Qualquer de / nenhum de uma lista de valores. O valor é uma lista: vários valores separados por vírgulas, ou o de uma Lista com Multi-selecção ligada por Widget — é assim que se filtra uma tabela por vários centros ou vários estados de uma vez. nin inclui os registos com o campo vazio. |
Uma condição cujo valor está vazio (a caixa de pesquisa em branco, a lista sem escolha) não filtra nada — o ecrã mostra tudo até a pessoa escolher.
Cada valor de um filtro converte-se pelo tipo da coluna: numa coluna de texto, um NIF ou um código postal («0012») ficam texto, e numa coluna decimal «12,5» é um número.
Carregamento e página
| Opção | O que faz |
|---|---|
| Carregar tudo | Traz todos os registos do filtro de uma vez — mudar de página, ordenar e filtrar no ecrã fica instantâneo. |
| Uma página de cada vez | Vai ao servidor a cada mudança de página — para tabelas grandes, onde trazer tudo não faz sentido. |
| Por página | Quantas linhas se vêem de cada vez no ecrã — não confundir com quantos registos são lidos. |
| Carregar automaticamente | Ler os dados assim que o ecrã abre. Desliga se preferires carregar só depois de uma acção (um botão "Pesquisar", por exemplo). |
Sem Carregar automaticamente, a lista lê quando alguém lho pede: um
reload() (num botão «Pesquisar», por exemplo), uma mudança de página ou de
ordenação, ou um filtro aplicado. Mudar um campo do ecrã não a faz ler sozinha.
Quando o filtro efectivo muda (um campo do ecrã, um parâmetro, o estado da app), a lista volta à primeira página. Se a página em que estavas deixou de existir (apagaste o último registo da última página, por exemplo), a lista passa para a última que existe.
Dica
Em qualquer dos modos, usa os Filtros (where) para limitar o que é lido. "Carregar tudo" com um filtro decente é rápido; sem filtro nenhum, é pedir a tabela inteira.
As ligações — de onde vem um valor
Sempre que um filtro, uma chave ou uma propriedade precisa de um valor, usas a mesma peça: a ligação. O primeiro selector diz a origem; o resto muda conforme ela:
| Origem | O que é |
|---|---|
| Fixo | Um valor escrito ali mesmo, igual para todos. |
| Param | Um parâmetro da rota do ecrã (secção Parâmetros de rota). |
| Sessão | Um campo do utilizador com sessão iniciada (userId, username, name). |
| Estado | Um valor guardado na memória da app com keplin.state.set() — disponível em todos os ecrãs. |
| Datastore | Um campo de outro datastore do ecrã — a base do mestre-detalhe. |
| Widget | O valor actual de outro widget de input — a base dos filtros interactivos. |
Um campo também escreve no estado: na Ligação a dados do campo, escolhe Estado e escreve a Chave. Um filtro com a origem Estado e a mesma chave volta a ler os dados quando o valor muda. É assim que uma área de widgets da barra filtra as páginas; ver Navegação da app.
As origens Datastore e Widget só existem nos datastores dentro de widgets — dependem do resto do ecrã. Nos datastores do ecrã ficam as quatro primeiras.
Com estas peças montam-se os padrões do dia-a-dia sem código:
- Mestre-detalhe — a tabela de oportunidades da conta: no datastore da
tabela, filtro
contaId eq→ Datastore ▸conta▸id. Seleccionar outra conta recarrega o detalhe. - Filtro por texto — uma Caixa de texto "procurar" e, no datastore da
tabela,
nome contains→ Widget ▸ a caixa. (Para filtrar só ao clicar num botão, faz-se por evento — ver Eventos e o SDK.)
Ligar campos de formulário a um registo
Cada campo de formulário tem, na categoria Dados, a secção Ligação a dados: escolhe o datastore de registo e o campo. A partir daí o input mostra o valor carregado e as alterações ficam no datastore — por gravar — até alguém gravar.
O passo final é um botão cujo evento grava:
const ok = await keplin.data.store("conta").save();
if (ok) {
keplin.ui.toast("Gravado.", "success");
}
Este código é exactamente o que a acção pré-definida Gravar datastore do
editor de eventos insere por ti. save() valida primeiro (obrigatórios,
regras, scripts de validação) e só grava se tudo passar; devolve true se
gravou.
Ao gravar um registo que já existia, só seguem os campos que mudaram desde que foi lido. Um campo apagado grava-se vazio (nulo na base de dados), e num campo decimal «12,5» lê-se como número.

Com alterações por gravar, sair da página pede confirmação: pelos menus, pelo
botão Voltar do browser ou ao fechar o separador. Um modal já perguntava ao
fechar. A navegação feita por código (keplin.nav.go) não pergunta: quem a
chama já decidiu, e muitas vezes acabou de gravar.
Ao gravar um registo novo, um campo que o ecrã não mostra não segue: numa
coluna com valor por omissão na base de dados, ou gerada por ela, fica esse
valor; uma coluna obrigatória sem valor por omissão tem de estar no ecrã, e o
formulário diz qual falta. Uma chave escrita num campo do ecrã (uma chave
natural, como um código de artigo) é a de um registo novo; posta por código
com set(), continua a ser a de um registo a alterar. Gravar um registo que
entretanto deixou de existir dá erro, e não «Gravado».
O modal Configurar datastore, campo a campo

| Campo | Registo | Lista |
|---|---|---|
| Nome do datastore | ✓ | ✓ |
| API | ✓ | ✓ |
| Que registo carregar (chave) | ✓ | — |
| Filtros (where) | — | ✓ |
| Carregamento / Por página | — | ✓ |
| Carregar automaticamente | ✓ | ✓ |
Datastores em ecrãs públicos
Num ecrã marcado Ecrã público (sem sessão), os dados vêm apenas de APIs com leitura pública: o selector só mostra essas, e uma API já escolhida que não seja pública fica assinalada — Esta API não tem leitura pública — num ecrã sem sessão não carrega dados. A leitura marca-se como pública no editor da API.
Os dados no código
Tudo o que os datastores fazem está também no SDK dos eventos —
keplin.data.store("nome") devolve o datastore pelo nome, com reload(),
setWhere(), get()/set()/save() e companhia. O capítulo
Eventos e o SDK percorre-o.
Porque não…?
- Porque não carrega dados? Vê, por ordem: Carregar automaticamente está ligado? A API escolhida existe e está publicada? Num ecrã público, a leitura da API é pública? O filtro não está a excluir tudo?
- Porque abre sempre um registo vazio? O datastore de registo não tem condições em Que registo carregar (chave) — ou o parâmetro usado na condição não está a chegar na rota.
- Porque mudei o modelo e a coluna nova não aparece? O datastore guarda um retrato dos campos da API de quando a escolheste. Reabre Configurar datastore e volta a escolher a API para actualizar o retrato. O que cada coluna já escolhida é (tipo, obrigatoriedade, valor por omissão, data) actualiza-se sozinho ao abrir o ecrã; só as colunas novas precisam de ser escolhidas.
- Porque é que a paginação está lenta? Estás em Uma página de cada vez com muitas idas ao servidor — ou em Carregar tudo sem filtros numa tabela enorme. Ajusta o modo ao tamanho real dos dados.