KEPLIN Docs

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ã

  1. Clica numa zona vazia do canvas para o inspector mostrar o ecrã.
  2. Na categoria Dados, clica em + registo ou + lista.
  3. Clica no datastore criado para abrir o modal Configurar datastore.
  4. Dá-lhe um Nome do datastore — é por este nome que os widgets e o código o encontram (ex.: conta, contas).
  5. 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.

A categoria Dados do ecrã Ficha de Conta: o datastore de registo, o de lista e os botões + registo / + lista.
A categoria Dados do ecrã Ficha de Conta: o datastore de registo, o de lista e os botões + registo / + lista.

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

  1. Clica em + campo da chave.
  2. 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 id no endereço e carrega a conta com esse id.
  3. 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.

O ecrã Ficha de Conta: campos de formulário ligados ao datastore de registo, prontos a gravar.
O ecrã Ficha de Conta: campos de formulário ligados ao datastore de registo, prontos a gravar.

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

O modal Configurar datastore: API, chave/filtros, carregamento e página.
O modal Configurar datastore: API, chave/filtros, carregamento e página.

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.