
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@praxisui/table
Advanced tools
Advanced data table for Angular (Praxis UI) with editing, filtering, sorting, virtualization, and settings panel integration.
Para ver esta biblioteca em funcionamento em uma aplicação completa, utilize o projeto de exemplo (Quickstart):
@praxisui/* em um app Angular, incluindo instalação, configuração e uso em telas reais.Componente de tabela empresarial avançado com arquitetura unificada
A biblioteca @praxisui/table fornece um componente de tabela robusto e altamente configurável para aplicações Angular empresariais. Com a nova arquitetura unificada, oferece uma experiência de desenvolvimento simplificada mantendo todos os recursos avançados.
TableConfig consolidadaNota (Regras)
O novo editor é "column-first" e usa uma tipagem compartilhada com o Editor de Colunas. Principais pontos:
enabled; importa com validação de DSL e sanitização de estilos (allowlist).Exemplos de DSL:
contains(status, 'Ativo')
not (status in ['A', 'I'])
(price >= 10 and price <= 20)
(createdAt >= '2024-10-01' and createdAt <= '2024-10-31')
(name == null or name == '')
active == true
Notas sobre enum/CSV:
valueMapping da coluna ou de fields?.options.in/not in em números/strings, use CSV (ex.: 10, 20, 30 ou Alice, Bob).Você pode registrar funções adicionais no DslParser do app host para expressões com JSON e data/tempo.
Exemplo rápido (registro direto):
import { DslParser } from '@praxisui/specification';
import { registerJsonDslFunctions, registerDateDslFunctions } from '@praxisui/table';
const parser = new DslParser<any>();
registerJsonDslFunctions(parser);
registerDateDslFunctions(parser);
Exemplos de DSL:
jsonGet(payload, '$.user.name') == 'Alice'hasJsonKey(payload, '$.meta.etag')jsonPathMatches(payload, '$.roles[0]', '^admin$')Guia completo: projects/praxis-table/docs/DSL-Extensions-Guide.md
O bloco de Efeitos permite aplicar estilo e visuais quando a condição é verdadeira:
aria-label.Dicas
role="toolbar", tooltips e navegação por teclado consistente.Como reverter efeitos
Observação: capturas de tela “antes/depois” podem ser adicionadas no PR conforme necessário.
Campos relacionais (FK/objetos) podem ser editados no Rules Editor com busca assíncrona (typeahead) e seleção single/multiple.
Boas práticas
resourcePath (ou endpoint) no FieldDefinition para habilitar o lookup via GenericCrudService do host.valueField (chave estável, tipicamente id) e displayField (label amigável, como name).multiple: true no FieldDefinition quando aplicável.jsonGet/hasJsonKey/jsonPathMatches.search) facilita a experiência do autocomplete.Exemplo de FieldDefinition (lookup)
import type { FieldDefinition } from '@praxisui/core';
const fields: FieldDefinition[] = [
{
name: 'customer',
type: 'object',
// Habilita o lookup via GenericCrudService
resourcePath: 'customers',
// Mapeia o id/label que virão do backend
valueField: 'id',
displayField: 'name',
// Se quiser multiseleção em todo o app
// multiple: true,
},
];
Operadores e DSL gerada (relacional)
id == → jsonGet(customer, '$.id') == 10id in → jsonGet(customer, '$.id') in [10, 20]has property → hasJsonKey(customer, '$.meta.etag')key == (JSON Path + valor)UI (no editor)
id == (single): autocomplete + chip “label [id]” com ação “Limpar”.id in (multi): autocomplete + chips removíveis por item.has property: input do caminho (ex.: $.meta.etag).Formato esperado do payload (relacional)
Para que o editor/preview e o runtime avaliem corretamente as regras relacionais, o valor do campo deve ser um objeto (ou compatível com JSON) contendo ao menos o identificador e, opcionalmente, propriedades exibidas:
// Exemplo de linha (row) com campo relacional "customer"
const row = {
id: 123,
customer: {
id: 10, // ← usado em id == / id in via jsonGet(customer, '$.id')
name: 'ACME', // ← usado para exibir label no autocomplete/chip
meta: { etag: 'v1' },
},
};
// Regras DSL comuns
// id ==
// jsonGet(customer, '$.id') == 10
// id in
// jsonGet(customer, '$.id') in [10, 20]
// has property
// hasJsonKey(customer, '$.meta.etag')
// caminho/valor (join simples)
// jsonGet(customer, '$.name') == 'ACME'
Notas
displayField (ex.: name) para compor o rótulo, e valueField (ex.: id) para gerar a DSL.customerId e customerName na mesma linha), você ainda pode expressar condições com operadores padrão (==, in) no campo numérico/literal — a abordagem relacional é útil quando o payload contém o objeto.Onde é consumido (preview e runtime)
DslParser interno):
rowConditionalStyles/conditionalStyles):
Observação
DslParserFactory (ex.: registerJsonDslFunctions/registerDateDslFunctions), use a mesma estratégia para o parser que avalia regras no runtime (Tabela) e para qualquer serviço que precise avaliar DSL. O Editor usa um parser interno para validação/preview; as funções de data já são suportadas por padrão, e as de JSON podem exigir extensão no runtime (ver guia).Exemplo de Provider (recomendado)
import { Injectable } from '@angular/core';
import { DslParser } from '@praxisui/specification';
import { registerJsonDslFunctions, registerDateDslFunctions } from '@praxisui/table';
@Injectable({ providedIn: 'root' })
export class DslParserFactory {
private parser: DslParser<any>;
constructor() {
this.parser = new DslParser<any>();
registerJsonDslFunctions(this.parser);
registerDateDslFunctions(this.parser);
}
get(): DslParser<any> {
return this.parser;
}
}
// Em um componente/serviço que avalia regras:
// constructor(factory: DslParserFactory) {
// const parser = factory.get();
// const spec = parser.parse("jsonGet(payload, '$.user.name') == 'Alice'");
// const ok = spec.isSatisfiedBy({ payload: { user: { name: 'Alice' } } });
// }
Para abrir o painel de configurações, habilite o modo de edição na tabela:
<praxis-table [editModeEnabled]="true"></praxis-table>
Um botão de engrenagem será exibido no canto superior direito. Ao clicar nele, o SettingsPanel é aberto permitindo ajustar:
As alterações podem ser aplicadas temporariamente com Aplicar ou salvas de forma persistente com Salvar & Fechar.
npm install @praxisui/core @praxisui/table
Peers necessários (instale no app host):
@angular/core ^20.0.0, @angular/common ^20.0.0@praxisui/core ^0.0.1@praxisui/dynamic-fields ^0.0.1 (quando usar editores/inputs dinâmicos)@praxisui/dynamic-form ^0.0.1 (quando integrar com formulários dinâmicos)@praxisui/settings-panel ^0.0.1 (para painel de configuração embutido)resourcePathA forma mais poderosa de usar a <praxis-table> é conectá-la diretamente a um endpoint de API compatível com o ecossistema Praxis. Isso é feito através do input resourcePath.
Quando resourcePath é fornecido, a tabela se torna "inteligente":
praxis-metadata-core (via anotação @UISchema).<!-- Exemplo no template do seu componente -->
<praxis-table resourcePath="human-resources/departamentos" [editModeEnabled]="true"> </praxis-table>
Neste exemplo:
resourcePath="human-resources/departamentos" instrui a tabela a se comunicar com o endpoint /api/human-resources/departamentos.POST /api/human-resources/departamentos/filter para obter os dados e GET /api/human-resources/departamentos/schemas para obter a configuração das colunas.[editModeEnabled]="true" permite a edição visual da configuração da tabela em tempo real.resourcePath)Se a tabela for renderizada sem resourcePath, um cartão de "Empty State" é exibido convidando a conectar o componente a um recurso. Basta clicar em "Conectar a recurso" para abrir um painel rápido com um único campo:
resourcePath: ex.: human-resources/departamentos
Ao aplicar/salvar, a <praxis-table> é automaticamente configurada para buscar o schema e os dados do backend. Esse fluxo evita telas em branco e simplifica o onboarding do componente em páginas novas.
resourcePathO diagrama abaixo ilustra como a propriedade resourcePath conecta o componente frontend ao controller do backend. O fluxo de inicialização ocorre em três etapas principais: Configurar, Carregar Schema e Buscar Dados.
sequenceDiagram
participant FE_Component as Componente Angular<br>(departamentos.html)
participant Praxis_Table as @praxisui/table<br>(praxis-table.ts)
participant Crud_Service as @praxisui/core<br>(generic-crud.service.ts)
participant BE_Controller as Backend Controller<br>(DepartamentoController.java)
participant Abstract_Controller as AbstractCrudController
FE_Component->>Praxis_Table: Usa o componente com <br> <praxis-table resourcePath="human-resources/departamentos">
activate Praxis_Table
Praxis_Table->>Praxis_Table: ngOnChanges() detecta o @Input() resourcePath
Praxis_Table->>Crud_Service: 1. Chama crudService.configure("human-resources/departamentos")
activate Crud_Service
Crud_Service->>Crud_Service: Armazena o resourcePath
deactivate Crud_Service
Praxis_Table->>Praxis_Table: 2. Chama this.loadSchema()
Praxis_Table->>Crud_Service: Chama crudService.getSchema()
activate Crud_Service
Crud_Service->>Crud_Service: getEndpointUrl('schema') constrói a URL: <br> "/api/human-resources/departamentos/schemas"
Crud_Service->>BE_Controller: Requisição HTTP GET para .../schemas
deactivate Crud_Service
activate BE_Controller
Note over BE_Controller: @RequestMapping("/human-resources/departamentos")
BE_Controller->>Abstract_Controller: Herda o método que lida com @GetMapping("/schemas")
activate Abstract_Controller
Abstract_Controller->>Abstract_Controller: Gera e retorna o Schema da UI
Abstract_Controller-->>BE_Controller: Retorna o Schema
deactivate Abstract_Controller
BE_Controller-->>Crud_Service: Resposta HTTP com o JSON do Schema
deactivate BE_Controller
activate Crud_Service
Crud_Service-->>Praxis_Table: Retorna um Observable com as<br>definições de colunas (FieldDefinition[])
deactivate Crud_Service
Praxis_Table->>Praxis_Table: Constrói as colunas da tabela<br>a partir do schema recebido
Praxis_Table->>Praxis_Table: 3. Chama this.fetchData()
Praxis_Table->>Crud_Service: Chama crudService.filter(...) para buscar dados
activate Crud_Service
Crud_Service->>Crud_Service: getEndpointUrl('filter') constrói a URL: <br> "/api/human-resources/departamentos/filter"
Crud_Service->>BE_Controller: Requisição HTTP POST para .../filter
deactivate Crud_Service
activate BE_Controller
BE_Controller->>Abstract_Controller: Herda o método que lida com @PostMapping("/filter")
activate Abstract_Controller
Abstract_Controller->>Abstract_Controller: Processa a requisição e busca os dados
Abstract_Controller-->>BE_Controller: Retorna os dados
deactivate Abstract_Controller
BE_Controller-->>Crud_Service: Resposta HTTP com os dados (Page<DepartamentoDTO>)
deactivate BE_Controller
activate Crud_Service
Crud_Service-->>Praxis_Table: Retorna um Observable com os dados
deactivate Crud_Service
Praxis_Table->>Praxis_Table: Atualiza o dataSource da tabela com os dados recebidos
deactivate Praxis_Table
Por padrão, a <praxis-table> agora assume a responsabilidade pela rolagem horizontal quando o conteúdo excede a largura do container. Isso evita cortes de conteúdo e comportamentos inconsistentes entre projetos.
horizontalScroll@Input() horizontalScroll: 'auto' | 'wrap' | 'none' = 'auto';
auto (padrão): a tabela cria um viewport com overflow-x: auto e permite que a tabela interna cresça com width: max-content. Quando a soma das colunas > container, aparece a barra de rolagem horizontal neste viewport.wrap: permite quebra de linha nas células (libera white-space: normal), reduzindo a largura necessária; ideal quando você prefere minimizar a rolagem horizontal.none: desabilita o comportamento interno; o host (página) deve fornecer o container com overflow-x: auto e as regras de largura necessárias.Exemplo:
<!-- Comportamento padrão (auto) -->
<praxis-table resourcePath="employees"></praxis-table>
<!-- Reduz rolagem: permite quebra de linha nas células -->
<praxis-table resourcePath="employees" horizontalScroll="wrap"></praxis-table>
<!-- Host controla o scroll horizontal -->
<div class="table-shell" style="overflow-x:auto">
<praxis-table resourcePath="employees" horizontalScroll="none"></praxis-table>
<!-- host pode usar width: max-content na tabela interna conforme seu design system -->
</div>
Na aba “Visão Geral & Comportamento”, há um seletor “Scroll Horizontal” com as opções Auto, Wrap e Host (none). As alterações podem ser aplicadas (Aplicar) ou salvas (Salvar & Fechar). O valor é persistido junto com outras preferências da tabela.
max-content/min-width: 100% se aplicam ao elemento de tabela interno.Quando behavior.virtualization.enabled estiver ativo, as linhas da tabela são renderizadas com cdk-virtual-scroll-viewport (cabeçalho permanece igual).
itemHeight: altura da linha (px); padrão 44.bufferSize: itens adicionais de buffer.minContainerHeight: altura mínima do viewport (px ou CSS, ex.: 320 ou 50vh).strategy: fixed | dynamic (atual uso visual não altera lógica de medição).behavior.pagination.position: top | bottom | both.
behavior.pagination.style: default | compact (aplica classe de estilo no paginator).
Nota sobre estratégia (client vs server)
behavior.pagination.strategy não estiver definido, a tabela assume server automaticamente quando há resourcePath (dados remotos). Caso contrário, usa client.behavior.sorting.strategy.Ative em Comportamento → Interação.
rowDoubleClick com payload { action: string; row: any }.edit | view | custom (quando custom, use customAction no Behavior para definir o identificador).Exemplo de uso (template do host):
<praxis-table
[config]="tableConfig"
[resourcePath]="'human-resources/departamentos'"
(rowDoubleClick)="onRowDoubleClick($event)">
</praxis-table>
onRowDoubleClick(evt: { action: string; row: any }) {
if (evt.action === 'edit') {
// abrir editor
} else if (evt.action === 'view') {
// abrir detalhe somente leitura
} else {
// ação customizada
}
}
behavior.sorting.defaultSort.Exemplos:
sorting: {
enabled: true,
strategy: 'server',
multiSort: false,
defaultSort: { column: 'nome', direction: 'asc' },
}
sorting: {
enabled: true,
strategy: 'server',
multiSort: true,
defaultSort: [
{ column: 'status', direction: 'desc' },
{ column: 'nome', direction: 'asc' },
],
}
Observação: quando informado, o defaultSort é aplicado na carga inicial se não houver estado de ordenação ativo.
Nota: por padrão a coluna de ações vem desabilitada. Habilite explicitamente em actions.row.enabled e defina as ações desejadas.
actions.row.sticky:actions: {
row: {
enabled: true,
sticky: 'end', // true | 'start' | 'end'
width: '120px',
actions: [ /* ... */ ],
},
}
Observação: em modo virtualizado, sticky pode ter limitações dependendo do layout.
Fixe colunas específicas no início/fim usando columns[].sticky:
columns: [
{ field: 'id', header: 'ID', width: '80px', sticky: 'start' },
{ field: 'nome', header: 'Nome' },
{ field: 'status', header: 'Status', sticky: 'end' },
]
true | 'start' | 'end'.true equivale a 'start' (fixa no início).width para evitar jitter de layout.Onde configurar no Editor Visual:
Quando já existe uma configuração salva (colunas presentes), a tabela não baixa o schema bruto do servidor para montar as colunas. Em vez disso, valida se há uma nova versão do schema usando ETag/If-None-Match. Notificações são exibidas somente quando o modo de customização está ativo (editModeEnabled = true).
config.meta (incluindo schemaId, serverHash, lastVerifiedAt)./schemas/filtered com If-None-Match: "<serverHash>".config.meta.lastVerifiedAt e segue usando a configuração local.config.meta.serverHash/lastVerifiedAt, marca estado schemaOutdated=true e (em modo de customização) exibe aviso e CTA para reconciliar. O schema bruto não é usado/armazenado nesta etapa.sequenceDiagram
autonumber
participant PT as PraxisTable
participant GS as GenericCrudService (@praxisui/core)
participant API as Backend (/schemas/filtered)
opt Primeira vez (sem colunas)
PT->>GS: getSchema() (gera colunas)
GS->>API: GET /schemas/filtered (sem If-None-Match)
API-->>GS: 200 + ETag/X-Schema-Hash
GS-->>PT: FieldDefinition[] (normalized)
PT->>PT: Persiste config + meta (serverHash, lastVerifiedAt)
end
opt Próximas vezes (colunas existentes)
PT->>API: GET /schemas/filtered?path=... (If-None-Match: "<serverHash>")
alt Igual (sem mudanças)
API-->>PT: 304 Not Modified (sem body)
PT->>PT: Atualiza lastVerifiedAt; segue usando config
else Diferente (mudou)
API-->>PT: 200 OK (com ETag/X-Schema-Hash)
PT->>PT: Atualiza serverHash + lastVerifiedAt; schemaOutdated=true
Note over PT: Em customização: mostrar banner/snackbar + badge e CTA “Reconciliar”
end
end
editModeEnabled = true):
notifyIfOutdated: 'inline' | 'snackbar' | 'both' | 'none' = 'both'snoozeMs: number = 86400000 (24h)autoOpenSettingsOnOutdated: boolean = falseschemaStatusChange: { outdated: boolean; serverHash?: string; lastVerifiedAt?: string; resourcePath?: string }
loadSchema()).metadataChange: { meta: any; reason: 'bootstrap'|'verification'|'applied' }
config.meta é atualizado (ex.: após bootstrap ou verificação ETag). Útil para sincronizar sidebars/bridges de metadados.GlobalConfigService.getSchemaPrefsGlobal().@Inputs (widget) → Prefs do widget → Prefs da página → Prefs globais → Defaults.schemaIgnore:{tableId}:{serverHash} → ignora avisos para este hash.schemaSnooze:{tableId}:{serverHash} → ISODate até quando não avisar (lembrar depois).schemaNotified:{tableId}:{serverHash} → evita snackbar duplicado.schemaOutdated=true, com ações:
<praxis-table
[editModeEnabled]="true"
[notifyIfOutdated]="'both'"
[snoozeMs]="12 * 60 * 60 * 1000"
[autoOpenSettingsOnOutdated]="false"
(schemaStatusChange)="onSchemaStatus($event)"
resourcePath="employees"
[config]="tableConfig">
</praxis-table>
onSchemaStatus(ev: { outdated: boolean; serverHash?: string; lastVerifiedAt?: string }) {
if (ev.outdated) {
console.log('Schema mudou. Hash:', ev.serverHash);
}
}
config.columns já existe.editModeEnabled=false), não há notificações visuais; ainda assim schemaStatusChange é emitido e config.meta.lastVerifiedAt atualizado.[fields]="[quickFieldMeta]"; prefira um array estável como quickFieldMetaArray atualizado apenas quando o metadata mudar.FormGroup quando possível. Se precisar trocar o FormGroup sem mudar os campos, deixe o DynamicFieldLoader reatribuir os controles (rebind-only) em vez de recriar componentes.ngOnChanges/ngAfterViewInit. Prefira atualizar valores (setValue, patchValue) e manter referências estáveis.overlayOrigin e largura apenas quando houver mudanças reais; isso evita loops de CD.DynamicFieldLoader possui logs de diagnóstico que podem ser habilitados em tempo de execução definindo window.__PRAXIS_DEBUG_DFL__ = true no console do navegador.FormGroup não tiver mudado, a renderização é ignorada.FormGroup mudou (mesmo snapshot de campos), os controles são reatribuídos aos componentes existentes (rebind-only), preservando o estado visual e evitando recriações.FormControl em troca de FormGroup (select/multiselect/autocomplete).controlType muda.PraxisFilter busca schema do DTO de filtro via /schemas/filtered com ETag/If-None-Match
e emite metaChanged com { schemaId, serverHash, context }.advancedConfig.metadata é preenchido para auditoria/telemetria.docs/schemas/fluxo-schema.md (cliente/caching, 200/304, reconciliadores Form/Filter).x-ui.resource.idField (e idFieldValid) via /schemas/filtered.@Input() idField → crudContext.idField → config.meta.idField (persistido) → GenericCrudService.getResourceIdField() (derivado do schema) → 'id'.config.meta.idField divergir do servidor, o componente alerta o usuário e mantém o valor do TableConfig até reconciliação.loadSchema() e também é considerada em tempo de execução para evitar corridas.id, defina getIdFieldName() no controller backend correspondente.Fluxo (Grid): adoção de schema e idField
sequenceDiagram
autonumber
participant PT as PraxisTable
participant GS as GenericCrudService
participant API as Backend
PT->>GS: configure(resourcePath)
PT->>GS: getSchema()
GS->>API: GET {resource}/schemas → 302 → /schemas/filtered
API-->>GS: 200/304 schema + x-ui.resource.idField
GS->>GS: cache + lastResourceMeta.idField
GS-->>PT: FieldDefinition[] (normalizado)
Note over PT: idField = input || context || GS.getResourceIdField() || 'id'
PT->>PT: construir colunas e renderizar
Fluxo (Delete): resolução do identificador
sequenceDiagram
autonumber
participant PT as PraxisTable
participant GS as GenericCrudService
participant API as Backend
PT->>PT: key = getIdField() (precedência)
PT->>PT: id = row[key] || row['id'] || heurísticas
PT->>GS: delete(id)
GS->>API: DELETE {resource}/{id}
API-->>GS: 204 No Content
GS-->>PT: sucesso → refresh
x-ui.resource.idField e se a coluna correspondente existe no dataset.@Input() idField ou crudContext.idField temporariamente; ajuste o backend com getIdFieldName() para persistir o comportamento.GenericCrudService.getResourceIdField() retorna o valor esperado.Se você precisar fornecer os dados manualmente (por exemplo, de uma fonte que não é uma API Praxis), pode usar o input [data] e omitir o resourcePath. Neste modo, todas as operações (paginação, ordenação, filtro) são realizadas no lado do cliente.
import { PraxisTable } from "@praxisui/table";
import { TableConfig } from "@praxisui/core";
@Component({
selector: "app-example",
standalone: true,
imports: [PraxisTable],
template: ` <praxis-table [config]="tableConfig" [data]="employees"> </praxis-table> `,
})
export class ExampleComponent {
// Configuração de colunas e comportamento é obrigatória neste modo
tableConfig: TableConfig = {
columns: [
{ field: "id", header: "ID", type: "number" },
{ field: "name", header: "Nome", type: "string" },
{ field: "email", header: "Email", type: "string" },
],
behavior: {
pagination: { enabled: true, pageSize: 10 },
sorting: { enabled: true },
filtering: { enabled: true },
},
};
employees = [
{ id: 1, name: "João Silva", email: "joao@empresa.com" },
{ id: 2, name: "Maria Santos", email: "maria@empresa.com" },
// ... mais dados
];
}
Quando a <praxis-table> é conectada a um resourcePath, as operações de paginação, ordenação e filtro são delegadas ao backend. Isso garante alta performance, pois apenas os dados visíveis na tela são trafegados pela rede.
Importante: se você não configurar explicitamente as estratégias de paginação/ordenação no TableConfig, a tabela resolve automaticamente como server quando há resourcePath. Se preferir operar no cliente, defina behavior.pagination.strategy = 'client' e/ou behavior.sorting.strategy = 'client' conscientemente.
O diagrama abaixo detalha a sequência de eventos, desde a interação do usuário na UI até a construção da consulta JPA no servidor.
sequenceDiagram
participant UI as @praxisui/table (UI)
participant CrudService as @praxisui/core (GenericCrudService)
participant Controller as Backend (AbstractCrudController)
participant Service as Backend (BaseCrudService)
participant SpecBuilder as Backend (GenericSpecificationsBuilder)
participant Repository as Spring Data JPA (JpaSpecificationExecutor)
UI->>UI: Usuário clica na página 2 e<br>digita "Tech" no filtro de nome.
UI->>UI: onPageChange({pageIndex: 1, pageSize: 10})<br>onFilterChange({nome: 'Tech'})
UI->>UI: Chama fetchData() com:<br>filterCriteria = {nome: 'Tech'}<br>pageable = {pageNumber: 1, pageSize: 10, sort: 'nome,asc'}
UI->>CrudService: Chama .filter({nome: 'Tech'}, pageable)
activate CrudService
CrudService->>CrudService: Cria HttpParams:<br>page=1, size=10, sort=nome,asc
CrudService->>Controller: Requisição POST para /api/.../filter<br>Body: {nome: 'Tech'}<br>Params: ?page=1&size=10&sort=nome,asc
deactivate CrudService
activate Controller
Controller->>Controller: Spring injeta o corpo no FilterDTO<br>e os params no objeto Pageable.
Controller->>Service: Chama .filter(filterDTO, pageable)
deactivate Controller
activate Service
Service->>SpecBuilder: Chama .buildSpecification(filterDTO)
activate SpecBuilder
SpecBuilder->>SpecBuilder: Itera nos campos do FilterDTO.<br>Encontra @Filterable no campo 'nome'.
SpecBuilder->>SpecBuilder: Cria um Predicate JPA:<br> `criteriaBuilder.like(root.get("nome"), "%tech%")`
SpecBuilder->>Service: Retorna a Specification JPA construída.
deactivate SpecBuilder
Service->>Repository: Chama .findAll(specification, pageable)
activate Repository
Repository->>Repository: Spring Data JPA executa a<br>query SQL com WHERE, LIMIT, OFFSET, ORDER BY.
Repository-->>Service: Retorna um objeto Page<Entity> do BD.
deactivate Repository
Service-->>Controller: Retorna o Page<Entity>
deactivate Service
activate Controller
Controller->>Controller: Mapeia Page<Entity> para Page<DTO><br>e encapsula em RestApiResponse.
Controller-->>CrudService: Resposta HTTP 200 com<br>JSON do RestApiResponse.
deactivate Controller
activate CrudService
CrudService-->>UI: Retorna Observable<Page<DTO>>
deactivate CrudService
UI->>UI: Atualiza a tabela com os novos dados e o paginador.
@praxisui/table): Captura eventos do usuário e os traduz em objetos filterCriteria e pageable.@praxisui/core): O GenericCrudService serializa o pageable como parâmetros de URL e o filterCriteria como corpo de uma requisição POST.AbstractCrudController recebe a requisição. O Spring Boot automaticamente popula o DTO de filtro com o corpo da requisição e o objeto Pageable com os parâmetros da URL.praxis-metadata-core): O GenericSpecificationsBuilder inspeciona as anotações @Filterable no DTO de filtro para construir uma Specification JPA dinâmica.JpaSpecificationExecutor (geralmente estendido pelo seu repositório) usa a Specification e o Pageable para gerar e executar a consulta SQL final, otimizada para o banco de dados.A <praxis-table> vem com um poderoso editor de configuração visual que permite personalizar quase todos os aspectos da sua tabela em tempo real, sem escrever uma única linha de código. Ative o editor passando a propriedade [editModeEnabled]="true" para o componente.
A seguir, veja os principais recursos que você pode configurar visualmente:
Controle total sobre as colunas da sua tabela. Dentro do editor, você pode:
150px) ou deixe-a automática.Converta dados brutos em informações claras e formatadas para o usuário.
Moeda, os valores serão formatados como R$ 1.234,56. Se escolher Data, você pode selecionar formatos como dd/MM/yyyy ou 25 de janeiro de 2025.true deve ser exibido como "Ativo" e false como "Inativo", ou que o código 1 significa "Pendente" e 2 significa "Aprovado".Crie novas colunas dinamicamente a partir de dados existentes, sem precisar programar.
nome e sobrenome para criar uma coluna "Nome Completo".preço * quantidade.valor for maior que 1000, e "Baixo" caso contrário.Destaque informações importantes aplicando estilos que mudam com base nos dados da linha.
status tiver o valor igual a 'Urgente'".Habilite e configure as funcionalidades centrais da tabela com um clique. Na aba "Comportamento", você pode:
import { BehaviorConfigEditorComponent } from '@praxisui/table';
// Usar como componente standalone para edição específica
<behavior-config-editor
[config]="tableConfig"
(configChange)="onBehaviorChange($event)">
</behavior-config-editor>
import { ColumnsConfigEditorComponent } from '@praxisui/table';
<columns-config-editor
[config]="tableConfig"
(configChange)="onColumnsChange($event)"
(columnChange)="onColumnChange($event)">
</columns-config-editor>
const highVolumeConfig: TableConfig = {
columns: [...],
performance: {
virtualization: {
enabled: true,
itemHeight: 48,
bufferSize: 10,
minContainerHeight: 400,
strategy: 'fixed'
},
lazyLoading: {
threshold: 100,
images: true,
components: true
}
}
};
const accessibleConfig: TableConfig = {
columns: [...],
accessibility: {
enabled: true,
announcements: {
dataChanges: true,
userActions: true,
loadingStates: true,
liveRegion: 'polite'
},
keyboard: {
shortcuts: true,
tabNavigation: true,
arrowNavigation: true,
skipLinks: true,
focusTrap: false
},
highContrast: false,
reduceMotion: false
}
};
const styledConfig: TableConfig = {
columns: [...],
appearance: {
density: 'compact',
borders: {
showRowBorders: true,
showColumnBorders: false,
showOuterBorder: true,
style: 'solid',
width: 1,
color: '#e0e0e0'
},
elevation: {
level: 2,
shadowColor: 'rgba(0, 0, 0, 0.1)'
},
spacing: {
cellPadding: '8px 16px',
headerPadding: '12px 16px'
},
typography: {
fontWeight: '400',
fontSize: '14px',
headerFontWeight: '500',
headerFontSize: '14px'
}
}
};
<praxis-table
[config]="tableConfig"
[data]="data"
(rowClick)="onRowClick($event)"
(rowSelect)="onRowSelect($event)"
(bulkAction)="onBulkAction($event)"
(configChange)="onConfigChange($event)"
(dataFilter)="onDataFilter($event)"
(dataSort)="onDataSort($event)"
(pageChange)="onPageChange($event)">
</praxis-table>
export class MyComponent {
onRowClick(event: { row: any; index: number }) {
console.log("Row clicked:", event.row);
}
onRowSelect(event: { selectedRows: any[]; isSelectAll: boolean }) {
console.log("Selection changed:", event.selectedRows);
}
onBulkAction(event: { action: string; selectedRows: any[] }) {
switch (event.action) {
case "deleteSelected":
this.deleteMultiple(event.selectedRows);
break;
// Handle other bulk actions
}
}
onConfigChange(newConfig: TableConfig) {
this.tableConfig = newConfig;
}
}
import { createDefaultTableConfig, isValidTableConfig, cloneTableConfig, mergeTableConfigs } from "@praxisui/core";
// Criar configuração padrão
const defaultConfig = createDefaultTableConfig();
// Validar configuração
if (isValidTableConfig(myConfig)) {
// Configuração válida
}
// Clonar configuração
const clonedConfig = cloneTableConfig(originalConfig);
// Merge configurações
const mergedConfig = mergeTableConfigs(baseConfig, overrides);
import { TableConfigService } from '@praxisui/core';
@Component({...})
export class MyComponent {
constructor(private configService: TableConfigService) {}
ngOnInit() {
// Usar serviço para gerenciar configuração
this.configService.setConfig(this.tableConfig);
// Verificar recursos disponíveis
const hasMultiSort = this.configService.isFeatureEnabled('multiSort');
const hasBulkActions = this.configService.isFeatureEnabled('bulkActions');
}
}
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { PraxisTable } from "@praxisui/table";
import { TableConfig } from "@praxisui/core";
describe("PraxisTable", () => {
let component: PraxisTable;
let fixture: ComponentFixture<PraxisTable>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [PraxisTable],
});
fixture = TestBed.createComponent(PraxisTable);
component = fixture.componentInstance;
});
it("should create", () => {
expect(component).toBeTruthy();
});
it("should handle configuration changes", () => {
const config: TableConfig = {
columns: [{ field: "test", header: "Test" }],
};
component.config = config;
fixture.detectChanges();
expect(component.config).toEqual(config);
});
});
Se você estava usando as versões anteriores com dual architecture, aqui estão as principais mudanças:
// Antes
import { TableConfigV1, TableConfigV2, TableConfigUnified } from "@praxisui/core";
// Depois
import { TableConfig } from "@praxisui/core";
// Antes
import { TableConfigAdapterService } from "@praxisui/table";
// Depois - Não mais necessário
// Uso direto da configuração
// Antes
config: TableConfigUnified;
// Depois
config: TableConfig;
TableConfig// Verificar se a configuração é válida
import { isValidTableConfig } from "@praxisui/core";
if (!isValidTableConfig(myConfig)) {
console.error("Configuração inválida:", myConfig);
}
// Habilitar virtualização para grandes datasets
const config: TableConfig = {
// ...
performance: {
virtualization: {
enabled: true,
itemHeight: 48,
bufferSize: 20,
},
},
};
// Garantir que acessibilidade está habilitada
const config: TableConfig = {
// ...
accessibility: {
enabled: true,
announcements: { dataChanges: true, userActions: true, loadingStates: true, liveRegion: "polite" },
},
};
Interface principal para configuração da tabela.
Define configuração individual de colunas.
Configurações de comportamento (paginação, ordenação, etc.).
Configurações de aparência visual.
Para documentação completa da API, consulte a documentação da @praxisui/core.
git checkout -b feature/nova-funcionalidade)git commit -m 'Add: nova funcionalidade')git push origin feature/nova-funcionalidade)O PraxisFilter pode ser acoplado à barra de ferramentas da tabela. O exemplo abaixo mostra a busca de pessoas por CPF e status.
<praxis-filter [resourcePath]="'pessoas'" [formId]="'pessoas-filter'" [persistenceKey]="'pessoas-filter-v1'" [quickField]="'cpf'" [alwaysVisibleFields]="['status']" (submit)="onFilter($event)"></praxis-filter> <praxis-table [data]="tableData"></praxis-table>
onFilter(dto: any) {
this.crud.configure('pessoas', ApiEndpoint.HumanResources);
this.crud.filter(dto, this.pageable).subscribe(page => {
this.tableData = page.content;
});
}
O PraxisFilter possui um painel de configurações acessível pelo ícone de
engrenagem na barra do filtro ou programaticamente através do método
openSettings(). Nesse painel é possível ajustar:
@ViewChild(PraxisFilter) filter!: PraxisFilter;
abrirConfiguracoes() {
this.filter.openSettings();
}
Ao aplicar ou salvar, as escolhas são validadas contra os metadados disponíveis. O componente exibe uma barra de progresso durante o processo de persistência e mensagens de sucesso ou erro via snack bar, garantindo uma experiência consistente.
editModeEnabled = true):
editModeEnabled: boolean — habilita o gate de customização para notificações de schema.notifyIfOutdated: 'inline' | 'snackbar' | 'both' | 'none' = 'both' — canal de notificação quando o schema muda.snoozeMs: number = 86400000 — tempo de soneca para avisos (ms).autoOpenSettingsOnOutdated: boolean = false — abre Configurações ao detectar schema desatualizado.schemaStatusChange: { outdated: boolean; serverHash?: string; lastVerifiedAt?: string; formId?: string } — emitido após verificação leve (304/200).GlobalConfigService.getSchemaPrefsGlobal().@Inputs (widget) → Prefs do widget → Prefs da página → Prefs globais → Defaults.Notas rápidas (Flow ETag no Filter):
/schemas/filtered (path=.../filter, operation=post, schemaType=request, includeInternalSchemas=true).lastVerifiedAt; emite schemaStatusChange(outdated=false).serverHash/lastVerifiedAt; marca outdated=true apenas quando em customização; não aplica o schema automaticamente.alwaysVisibleFields ou ao abrir o painel Avançado).Cada coluna pode declarar type e uma string de formatação format consumida pelo DataFormattingService. Os tipos suportados são: string, number, currency, percentage, date, boolean, custom.
Regra geral: a formatação só é aplicada quando há format não vazio e type !== 'custom'.
Tipos e padrões de format:
minInt.minFrac-maxFrac1.0-0, 1.2-2, 1.0-3|nosep (remove separador de milhar)CURRENCY|DISPLAY|DECIMALS[|nosep]BRL|symbol|2, USD|code|0, EUR|symbol|2|nosep1.0-0 (PercentPipe)1.1-1|x100 (DecimalPipe×100 + %)shortDate, mediumDate, longDate, fullDate, short, shortTimedd/MM/yyyy, yyyy-MM-dd, dd/MM/yyyy HH:mmuppercase | lowercase | titlecase | capitalize | none<transform>|truncate|<max>|<suffix>uppercase|truncate|50|..., titlecasetrue-false | yes-no | active-inactive | on-off | enabled-disabledcustom|Verdadeiro|FalsoExemplos de ColumnDefinition:
{ field: 'salario', type: 'currency', format: 'BRL|symbol|2', header: 'Salário' }
{ field: 'desconto', type: 'percentage', format: '1.1-1|x100', header: 'Desconto' }
{ field: 'criadoEm', type: 'date', format: 'dd/MM/yyyy', header: 'Criado em' }
{ field: 'nome', type: 'string', format: 'titlecase|truncate|32|…', header: 'Nome' }
{ field: 'ativo', type: 'boolean', format: 'yes-no', header: 'Ativo' }
Observações:
|nosep remove separadores de milhar da saída formatada.custom não passam por formatação automática.Exemplos práticos no workspace (rotas):
/table-rules-simple/table-rules-complexApache-2.0 — consulte LICENSE para detalhes.
Parte do Praxis UI Workspace Versão: 2.0.0 (Unified Architecture) Compatibilidade: Angular 18+
FAQs
Advanced data table for Angular (Praxis UI) with editing, filtering, sorting, virtualization, and settings panel integration.
We found that @praxisui/table demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.