jsegd-bpm
v0.1.24
Published
Biblioteca runtime para desenvolvimento EGD BPM — EGD Tecnologia
Readme
jsEGD BPM
Biblioteca TypeScript de runtime para desenvolvimento de formulários e workflows em um ambiente BPM genérico.
Fornece componentes de anexo, gerenciamento de workers para datasets, controle do ciclo de vida do fluxo e utilitários de formulário integrados ao DOM.
Instalação
npm install jsegd-bpm jsegd alpinejsEntradas do pacote
| Entrada | Importação | Conteúdo |
| --------- | ----------- | ----------------------------------------------------------------------------- |
| Principal | jsegd-bpm | Workers, anexos, validação, workflow, form-repository, workflow-view e alpine |
WorkerManager — Consultas de Dataset em Web Workers
Gerencia um pool de Web Workers para executar consultas à API de datasets fora da thread principal. Suporta deduplicação de requisições, cache em IndexedDB e cancelamento HTTP real via AbortController.
import { WorkerManager, ConstraintsType } from 'jsegd-bpm'
import { LocalCache } from 'jsegd'
// Compartilhando um cache existente entre componentes:
const cache = LocalCache.getInstance()
cache.initialize({ storeName: 'datasets', cacheExpirationMs: 60 * 60 * 1000 })
const manager = new WorkerManager({ poolSize: 4, cache })
const { promise, cancel } = manager.query('https://exemplo.local', {
datasetId: 'dsRegistros',
field: ['nome', 'email', 'matricula'],
constraintsField: ['ativo'],
constraintsInitialValue: ['true'],
constraintsType: [ConstraintsType.MUST],
})
// Cancelar requisição em andamento, se necessário:
// cancel()
const result = await promise
console.log(result.columns) // ['nome', 'email', 'matricula']
console.log(result.values) // [{ nome: 'Ana', email: '[email protected]', matricula: '1234' }, ...]
// Liberar recursos ao desmontar o componente:
manager.terminate()WorkerManagerOptions
| Opção | Tipo | Padrão | Descrição |
| -------------- | -------------- | ------ | -------------------------------------------------------------- |
| poolSize | number | 4 | Número de workers paralelos |
| cache | ICache | — | Instância de cache existente (compartilhada entre componentes) |
| cacheOptions | CacheOptions | — | Cria um LocalCache interno com storeName e TTL em ms |
Se nem
cachenemcacheOptionsfor informado, o gerenciador opera sem cache.
ConstraintsType — enum: MUST, SHOULD, MUST_NOT.
AttachButton / AttachmentsTable — Componentes de Anexo
Custom Elements prontos para uso em formulários EGD BPM. Registre-os uma vez no ponto de entrada da aplicação.
import { AttachButton, AttachmentsTable } from 'jsegd-bpm'
customElements.define('attachment-button', AttachButton)
customElements.define('attachments-table', AttachmentsTable)<attachment-button>
Botão com dropdown para upload de arquivos vinculados ao processo.
<!-- Tipo de anexo fixo -->
<attachment-button description="DOCUMENTO"></attachment-button>
<!-- Prompt ao usuário para informar a descrição no upload -->
<attachment-button></attachment-button>
<!-- Somente leitura: oculta botão de adicionar -->
<attachment-button description="DOCUMENTO" noaddbutton></attachment-button>| Atributo | Tipo | Descrição |
| ---------------- | -------- | ---------------------------------------------------------------- |
| description | string | Tipo do anexo. Vazio abre modal para o usuário informar |
| accept | string | Filtro de tipos aceitos pelo seletor nativo (ex: ".pdf,.docx") |
| noaddbutton | — | Oculta o botão de adicionar |
| noviewbutton | — | Oculta o botão de visualizar |
| noeditbutton | — | Oculta o botão de editar descrição |
| nodeletebutton | — | Oculta o botão de excluir |
<attachments-table>
Tabela que lista os anexos do processo com suporte a drag-and-drop para reordenação.
<attachments-table mode="edit"></attachments-table>BeforeSendValidate — Validação com feedback visual
Integra validação de campos com o sistema de erros visuais do tema do formulário (data-group / data-helper).
import { BeforeSendValidate } from 'jsegd-bpm'
import type { FieldError } from 'jsegd-bpm'
function validarFormulario() {
const erros: FieldError[] = []
const campoTexto = document.querySelector<HTMLInputElement>('[name="nome"]')
if (!campoTexto?.value) {
erros.push({ field: campoTexto, errorMessage: 'Informe o nome' })
}
if (erros.length > 0) {
BeforeSendValidate.throwValidationError(erros)
// Lança Error com HTML formatado para o widget de processo exibir
}
}throwValidationError lança sempre — use dentro de beforeSendValidate do formulário ou em hooks de WorkflowViewPatcher.
Validator — Validação de campos e de submit
Validator.getInstance().register(component, rules) é a única chamada necessária no init() de um componente Alpine. Ela combina:
- Validação reativa por campo —
$watchdebounced por campo, atualizandoerrorsautomaticamente a cada mudança - Validação de submit — regras agrupadas pelos
sequencesdo componente, executadas porexecute(sequence)no momento do envio
As funções de validação devem ser arrow functions para capturar this do componente Alpine via escopo léxico. A avaliação usa curto-circuito: a primeira regra que falha retorna imediatamente com sua mensagem, sem executar as demais.
Tipos principais
| Tipo | Descrição |
| --------------------- | ---------------------------------------------------------------------- |
| ValidationRule | { fn: () => boolean \| Promise<boolean>; message: string } |
| ValidationRules | Record<string, ValidationRule[]> — mapa de campo → array de regras |
| FieldResult | { isValid: boolean; message: string } — resultado por campo |
| BaseError | Record<string, FieldResult> — estado de validação de todos os campos |
| BaseAlpineComponent | Interface base para componentes Alpine no escopo BPM |
register — Única chamada em init()
import { Validator } from 'jsegd-bpm'
import type { AlpineComponent } from 'alpinejs'
import type { ValidationRules, BaseAlpineComponent } from 'jsegd-bpm'
interface ContatoComponent extends BaseAlpineComponent {
telefone: string
email: string
}
export function contatoComponent({ key, sequences }: { key: string; sequences: number[] }): AlpineComponent<ContatoComponent> {
return {
key,
sequences,
telefone: '',
email: '',
errors: {},
init() {
Validator.getInstance().register(this, {
telefone: [
{ fn: () => this.telefone !== '', message: 'Telefone obrigatório' },
{
fn: () => /^\(?([1-9]{2})\)? ?(2|3|4|5|7|8)\d{3}[- ]?\d{4}$/.test(this.telefone),
message: 'Formato de telefone inválido',
},
],
email: [
{
fn: () => this.email === '' || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.email),
message: 'E-mail inválido',
},
],
})
// ↑ inicializa errors por campo, registra $watch debounced e cadastra regras de submit
},
}
}O terceiro argumento opcional debounceMs (padrão: 300) controla o tempo de espera antes de disparar a validação após cada mudança. register retorna uma função de cancelamento que remove as regras de submit — útil ao desmontar o componente.
Exemplo no HTML
<div x-data="contatoComponent({ key: '<%= id %>', sequences: [0] })">
<div class="form-group" :class="{ 'has-error': !errors.telefone?.isValid }">
<label for="<%= id %>_telefone">Telefone</label>
<input type="tel" id="<%= id %>_telefone" name="<%= id %>_telefone" x-model="telefone" />
<p class="help-block" x-show="!errors.telefone?.isValid" x-text="errors.telefone?.message"></p>
</div>
<div class="form-group" :class="{ 'has-error': !errors.email?.isValid }">
<label for="<%= id %>_email">E-mail</label>
<input type="text" id="<%= id %>_email" name="<%= id %>_email" x-model="email" />
<p class="help-block" x-show="!errors.email?.isValid" x-text="errors.email?.message"></p>
</div>
</div>
x-modelé suficiente. O$watchregistrado porregisterjá detecta mudanças reativas — não é necessário@inputseparado. O?.(optional chaining) protege o acesso antes deinit()inicializar o campo emerrors.
execute — Validação de submit
execute(sequence) executa todas as regras registradas para aquele sequence. Rejeita a Promise se qualquer regra falhar — use dentro de WorkflowViewPatcher.addBeforeValidate para bloquear o envio do formulário.
WorkflowViewPatcher.addBeforeValidate(async () => {
await Validator.getInstance().execute(0) // um dos sequences do componente
})unregister(component, rules) remove um conjunto específico por referência. clear(sequences?) remove todas as regras dos sequences informados ou limpa tudo se chamado sem argumento.
WorkflowViewPatcher — Hooks no widget de processo
Substitui métodos do widget nativo ECM_WKFView para interceptar o ciclo de envio do fluxo sem modificar o formulário.
import { WorkflowViewPatcher } from 'jsegd-bpm'
// 1. Inicializar — aguarda o widget carregar no frame pai
await WorkflowViewPatcher.init()
// 2. Aplicar todos os patches registrados
WorkflowViewPatcher.patchAll()
// 3. Registrar hooks
WorkflowViewPatcher.addBeforeValidate(async () => {
// Executado antes da validação do widget.
// Lançar exceção cancela o envio e reabilita os botões.
const valor = document.querySelector<HTMLInputElement>('[name="campo_principal"]')?.value
if (!valor) throw new Error('Campo principal obrigatório')
})
WorkflowViewPatcher.addAfterValidate(async () => {
// Executado após validação bem-sucedida, antes do envio efetivo.
await salvarRascunho()
})
WorkflowViewPatcher.addOnMoveSuccess(() => {
console.log('Processo movimentado com sucesso')
})
WorkflowViewPatcher.addOnMoveError(() => {
console.error('Erro na movimentação — verifique os logs')
})
WorkflowViewPatcher.addOnCompletionLinkAdd((message) => {
// Adiciona links à tela de conclusão após a movimentação
message.links.push({ description: 'Abrir próxima etapa', href: '/proxima-etapa' })
})
// Ao desmontar o formulário (SPAs): restaura os métodos originais
WorkflowViewPatcher.restore()Métodos estáticos
| Método | Descrição |
| ---------------------------- | ------------------------------------------------------- |
| init(timeout?) | Aguarda ECM_WKFView ficar disponível no frame pai |
| patchAll() | Aplica todos os overrides registrados |
| patch(attr) | Aplica um override específico |
| restore() | Reverte todos os métodos e limpa hooks e flags internos |
| addBeforeValidate(fn) | Hook antes da validação do widget |
| addAfterValidate(fn) | Hook após validação, antes do envio |
| addOnMoveSuccess(fn) | Callback de sucesso na movimentação |
| addOnMoveError(fn) | Callback de erro na movimentação |
| addOnCompletionLinkAdd(fn) | Manipula links da tela de conclusão |
IFormRepository — Acesso tipado ao formulário
Interface agnóstica ao DOM com duas implementações: BpmFormRepository (produção) e MockFormRepository (testes unitários).
Convenção de nomenclatura de campos
Os campos do formulário EGD BPM devem seguir o padrão:
{domínio}_{atributo_snake_case}onde {domínio} é o identificador do agregado de negócio ao qual o campo pertence — não a tela em que aparece nem a fase do workflow. O total não deve ultrapassar 30 caracteres (limite de coluna no banco EGD).
| Campo no DOM | Domínio | Atributo no modelo |
| --------------------- | -------------- | ------------------ |
| departamento_indice | departamento | indice |
| area_cep | area | cep |
| vigencia_prazo | vigencia | prazo |
| cadastro_cep | cadastro | cep |
Importante: o
{domínio}é o prefixo persistido no banco — é parte integrante do nome da coluna e não deve ser omitido no HTML. Oloade osavegerenciam a conversão entre o nome DOM e o atributo do modelo de forma transparente.
O moduleId do workflow (quem exibe o campo na tela) e o stage (em que fase do processo estamos) são independentes do domínio do campo — um mesmo módulo pode exibir campos de múltiplos domínios.
Produção — BpmFormRepository
Lê e escreve diretamente nos elementos input, select e textarea do formulário EGD BPM. Encapsula toda a interação com wdkAddChild para tabelas filhas.
load(section) remove o prefixo {section}_ e converte snake_case → camelCase antes de retornar.save(section, data) faz o caminho inverso: converte camelCase → snake_case e adiciona o prefixo ao escrever no DOM.
import { BpmFormRepository } from 'jsegd-bpm'
interface Departamento {
indice: string
primeiroPagamento: string
reajuste: string
}
interface Area {
cep: string
logradouro: string
municipio: string
}
interface ItemTabela {
descricao: string
valor: string
}
const repo = new BpmFormRepository()
// Ler domínio 'departamento' — lê campos com name="departamento_*" do DOM
// DOM: departamento_indice, departamento_primeiro_pagamento, departamento_reajuste
const departamento = repo.load<Departamento>('departamento')
console.log(departamento.indice) // valor de input[name="departamento_indice"]
console.log(departamento.primeiroPagamento) // valor de input[name="departamento_primeiro_pagamento"]
// Ler domínio 'area' — desambigua de 'cadastro_cep', 'departamento_cep', etc.
const area = repo.load<Area>('area')
console.log(area.cep) // valor de input[name="area_cep"]
// Ler tabela filha
const itens = repo.loadTable<ItemTabela>('tabItens')
// Persistir no DOM — save converte camelCase → prefixo+snake_case
repo.save<Departamento>('departamento', { indice: 'IGPM', primeiroPagamento: '2026-02-01' })
// escreve: input[name="departamento_indice"] e input[name="departamento_primeiro_pagamento"]
// Substituir linhas da tabela filha
repo.saveTable<ItemTabela>('tabItens', [
{ descricao: 'Notebook', valor: '4500.00' },
{ descricao: 'Mouse', valor: '120.00' },
])Testes — MockFormRepository
Implementação in-memory sem dependência de DOM. Aplica a mesma lógica de prefixo e conversão de BpmFormRepository para que os testes reflitam o comportamento real.
import { MockFormRepository } from 'jsegd-bpm'
interface Vigencia {
prazo: string
tipoDePrazo: string
}
const repo = new MockFormRepository()
// save armazena internamente como 'vigencia_prazo', 'vigencia_tipo_de_prazo'
repo.save<Vigencia>('vigencia', { prazo: '12', tipoDePrazo: 'Meses' })
// load recupera { prazo: '12', tipoDePrazo: 'Meses' }
const data = repo.load<Vigencia>('vigencia')
// data.prazo === '12'
// data.tipoDePrazo === 'Meses'
repo.saveTable('tabItens', [{ descricao: 'Cadeira', valor: '800.00' }])
const itens = repo.loadTable('tabItens')
repo.reset() // limpa todos os dadosProcessos legados — loadFields
Processos existentes cujos campos não seguem a convenção {domínio}_{atributo} não devem ser alterados para evitar migração de dados no banco. Para esses casos, jsegd-bpm exporta a função utilitária loadFields, que recebe um mapeamento explícito { propriedadeModelo: nomeDOM }:
import { loadFields } from 'jsegd-bpm'
interface Requisitante {
nome: string
email: string
}
// Campo legado: sufixo em vez de prefixo — nome_requisitante, email_requisitante
const dados = loadFields<Requisitante>({
nome: 'nome_requisitante',
email: 'email_requisitante',
})
// dados.nome === valor de input[name="nome_requisitante"]
// dados.email === valor de input[name="email_requisitante"]Regra:
loadFieldsnão faz parte deIFormRepository— é um utilitário de compatibilidade para processos legados. Novos processos devem usarrepository.load(section)exclusivamente.
Módulo workflow/ — Configuração e controle de tela
Conjunto de utilitários para mapear as atividades BPM em estados de tela — barra de progresso, visibilidade de módulos e editabilidade — sem hardcode de números de sequência nos Presenters.
defineWorkflowConfig — Configuração tipada
Define módulos e atividades em uma declaração única. O tipo ModuleId é inferido automaticamente — nenhum type alias manual é necessário.
import { defineWorkflowConfig } from 'jsegd-bpm'
export const workflowConfig = defineWorkflowConfig({
modules: [
{
moduleId: 'areaInicial',
label: 'Dados Iniciais',
icon: 'file-text',
order: 1,
showInNavigation: true,
renderContent: true,
contentIncludePath: 'area-inicial.html',
},
{
moduleId: 'areaA',
label: 'Área A',
icon: 'gavel',
order: 2,
showInNavigation: true,
renderContent: true,
contentIncludePath: 'area-a.html',
},
{
moduleId: 'areaB',
label: 'Área B',
icon: 'dollar-sign',
order: 3,
showInNavigation: true,
renderContent: true,
contentIncludePath: 'area-b.html',
},
],
activities: [
{
sequence: 9,
description: 'Etapa inicial',
kind: 'human',
stage: 'inicio',
modules: { visible: ['areaInicial'], editable: ['areaInicial'], active: 'areaInicial' },
},
{
sequence: 53,
description: 'Análise da área A',
kind: 'human',
stage: 'analiseA',
modules: { visible: ['areaInicial', 'areaA'], editable: ['areaA'], active: 'areaA' },
},
{
sequence: 61,
description: 'Análise da área B',
kind: 'human',
stage: 'analiseB',
modules: { visible: ['areaInicial', 'areaA', 'areaB'], editable: ['areaB'] },
},
{ sequence: 88, description: 'Roteamento interno', kind: 'routing' },
],
} as const)
// Tipo de ModuleId inferido: 'areaInicial' | 'areaA' | 'areaB'ActivityKind — controla o comportamento na barra de progresso:
| Valor | Comportamento |
| ----------- | ------------------------------------------------------------------- |
| 'human' | Ação de usuário — estágio atual destacado na barra |
| 'system' | Aguardando retorno externo (webhook) — exibe spinner |
| 'support' | Suporte transversal — sobrepõe banner de alerta; não avança a barra |
| 'routing' | Roteamento interno do motor BPM — invisível na barra |
extractCompletedStages / extractCurrentStage — Histórico do processo
Parseia o histórico bruto retornado por parent.ECM.workflowView.processDefinition.urlHistory e produz um ProcessHistory para consumo pelo ScreenContext.
import { extractCompletedStages, extractCurrentStage } from 'jsegd-bpm'
import type { BpmHistoryMap, ProcessHistory } from 'jsegd-bpm'
const rawHistory = parent.ECM.workflowView.processDefinition.urlHistory as BpmHistoryMap
const currentSequence: number = parent.ECM.workflowView.processDefinition.cardIndex
const historyType: string | null = parent.ECM.workflowView.processDefinition.historyType ?? null
const completedStages = extractCompletedStages(rawHistory, workflowConfig.activities)
// Ex: ['inicio', 'analiseA']
const currentStage = extractCurrentStage(currentSequence, historyType, workflowConfig.activities)
// Ex: { stage: 'analiseB', status: 'current' }
const processHistory: ProcessHistory = { completedStages, currentStage }resolveWorkflowScreen + ScreenContext — Estado da tela
Cruza a configuração com o histórico para produzir um ScreenContext — o único objeto que os Presenters precisam consultar.
import { resolveWorkflowScreen } from 'jsegd-bpm'
const screen = resolveWorkflowScreen(workflowConfig, currentSequence, processHistory)
// Visibilidade e editabilidade
screen.isVisible('areaA') // true
screen.isEditable('areaB') // false
screen.isActive('areaA') // true
// Estado da atividade
screen.isAwaitingSystem() // true quando kind === 'system' ou status === 'waiting'
screen.isInSupport() // true para kind === 'support'
// Barra de progresso
const stages = screen.getProgressBarStages()
// [
// { stage: 'inicio', status: 'done' },
// { stage: 'analiseA', status: 'done' },
// { stage: 'analiseB', status: 'current' },
// ]
// Módulos
const visiveis = screen.getVisibleModules() // ResolvedModule[] — apenas isVisible = true
const navegacao = screen.getNavigationModules() // ResolvedModule[] — apenas showInNavigation = true e visívelregisterStores — Alpine stores tipados
Registra múltiplos Alpine stores em uma única chamada e retorna um proxy tipado para acesso direto sem casting manual.
import Alpine from 'alpinejs'
import { registerStores } from 'jsegd-bpm'
type PerfilId = 'admin' | 'analista' | 'visualizador'
const stores = registerStores(Alpine, {
usuario: { nome: '', perfil: '' as PerfilId, matricula: '' },
ui: { carregando: false, erro: null as string | null },
})
Alpine.start()
// Acesso tipado — sem Alpine.store('usuario') as UsuarioStore
stores.usuario.nome // string
stores.usuario.perfil // PerfilId
stores.ui.carregando // boolean
registerStoresaceitaPick<Alpine, 'store'>— compatível com qualquer mock em testes unitários.
createMenuComponent / JsegdMenu — Menu Alpine por sequência do processo
Disponibiliza um componente Alpine customizado para renderizar menu de navegação por etapas, com exibição e ativação dos itens baseada na workflow sequence atual lida do ECM.
Contratos
import type { MenuItem } from 'jsegd-bpm'
const ETAPAS = {
DISPLAY_ALWAYS: 0,
INICIO: 9,
ANALISE: 17,
APROVACAO: 23,
} as const
const menuConfig: MenuItem[] = [
{
id: 'dados-iniciais',
label: 'Dados iniciais',
icon: 'fluigicon fluigicon-file-text',
displayOn: [ETAPAS.DISPLAY_ALWAYS],
activateOn: [ETAPAS.INICIO],
},
{
id: 'analise',
label: 'Análise',
icon: 'fluigicon fluigicon-search',
displayOn: [ETAPAS.ANALISE, ETAPAS.APROVACAO],
activateOn: [ETAPAS.ANALISE],
},
{
id: 'aprovacao',
label: 'Aprovação',
icon: 'fluigicon fluigicon-check-circle',
displayOn: [ETAPAS.APROVACAO],
activateOn: [ETAPAS.APROVACAO],
},
]Registro Alpine
import Alpine from 'alpinejs'
import { createMenuComponent } from 'jsegd-bpm'
Alpine.data('jsegdMenu', createMenuComponent(menuConfig))
Alpine.start()Uso no HTML
<bpm-menu logo="/assets/logo.svg" theme="light" data-name="jsegdMenu"></bpm-menu>API pública exportada
import { createMenuComponent, JsegdMenu } from 'jsegd-bpm'
import type { MenuItem, BaseMenuComponent } from 'jsegd-bpm'Notas de uso:
displayOn: define em quais sequences o item aparece.activateOn: define em quais sequences o item fica ativo.0funciona como curinga (DISPLAY_ALWAYS) para exibir/ativar em qualquer etapa.- A sequence utilizada pelo menu é obtida do ECM em tempo de execução.
createAutocompleteComponent / BpmAutocomplete — Autocomplete Alpine por dataset
Custom element <bpm-autocomplete> para busca reativa em datasets do Fluig. Substitui os
widgets legados jQuery zoom e autocomplete, utilizando exclusivamente as classes CSS do
Fluig Style Guide disponíveis globalmente no ambiente.
Atributos
| Atributo | Tipo | Obrigatório | Padrão | Descrição |
| --------------- | --------- | ----------- | --------------------- | ----------------------------------------------------------------------------------- |
| dataset-id | string | ✅ | — | Identificador do dataset a consultar |
| key-field | string | — | "CODIGO" | Campo de chave única do registro |
| display-field | string | — | "DESCRICAO" | Campo exibido como rótulo no dropdown |
| label | string | — | "Search" | Texto do <label> do campo |
| placeholder | string | — | "Type to search..." | Placeholder do input |
| data-name | string | — | "bpmAutocomplete" | Prefixo do nome do Alpine.data; sufixo único adicionado por instância |
| initial-value | string | — | — | Valor de chave inicial; o componente hidrata o rótulo automaticamente via API |
| filters | string | — | "[]" | JSON de AutocompleteFilter[] — filtros estáticos aplicados a toda busca |
| limit | integer | — | 20 | Número máximo de registros retornados por consulta (parâmetro limit da API Fluig) |
Eventos (despachados no próprio elemento, com bubbles: true)
| Evento | detail | Quando |
| --------------------------- | ---------------------- | ---------------------------- |
| jsegd:autocomplete:load | { count: number } | Resultados recebidos da API |
| jsegd:autocomplete:select | { item, key, label } | Usuário selecionou um item |
| jsegd:autocomplete:change | { item, key, label } | Disparado junto com select |
| jsegd:autocomplete:clear | void | Seleção foi limpa |
| jsegd:autocomplete:error | { message: string } | Requisição falhou |
Filtros (AutocompleteFilter)
Cada item do array filters é um objeto com os seguintes campos:
| Campo | Tipo | Padrão | Descrição |
| ------------ | ---------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| field | string | — | Nome do campo no dataset (constraintsField na API) |
| value | string | — | Valor a comparar (constraintsInitialValue = constraintsFinalValue) |
| operator | 'MUST' \| 'SHOULD' \| 'MUST_NOT' | 'MUST' | Tipo de constraint (constraintsType na API) |
| likeSearch | boolean | false | Quando true, compara parcialmente (constraintsLikeSearch na API); o símbolo % pode ser usado em value |
import type { AutocompleteFilter } from 'jsegd-bpm'
// Igualdade exata: STATUS = 'ATIVO'
const filtroStatus: AutocompleteFilter = { field: 'STATUS', value: 'ATIVO', operator: 'MUST' }
// Busca parcial: NOME contém 'Silva'
const filtroNome: AutocompleteFilter = { field: 'NOME', value: 'Silva', operator: 'SHOULD', likeSearch: true }
// Exclusão: TIPO diferente de 'INATIVO'
const filtroExclusao: AutocompleteFilter = { field: 'TIPO', value: 'INATIVO', operator: 'MUST_NOT' }Uso no HTML
<!-- Uso básico -->
<bpm-autocomplete dataset-id="dsRegistros" key-field="CODIGO" display-field="DESCRICAO" label="Registro"></bpm-autocomplete>
<!-- Com valor inicial (dado já salvo no banco — formulário de edição) -->
<bpm-autocomplete dataset-id="dsRegistros" key-field="CODIGO" display-field="DESCRICAO" label="Registro" initial-value="ABC123"></bpm-autocomplete>
<!-- Com filtros estáticos e limite personalizado -->
<bpm-autocomplete
dataset-id="dsRegistros"
key-field="CODIGO"
display-field="DESCRICAO"
limit="10"
filters='[
{"field":"STATUS","value":"ATIVO","operator":"MUST"},
{"field":"DESCRICAO","value":"cliente","operator":"SHOULD","likeSearch":true}
]'
></bpm-autocomplete>
<!-- Ouvindo eventos via listener direto -->
<script>
document.querySelector('bpm-autocomplete').addEventListener('jsegd:autocomplete:select', (e) => {
console.log('Selecionado:', e.detail.item) // objeto completo da API
console.log('Chave:', e.detail.key) // e.detail.item[keyField]
console.log('Rótulo:', e.detail.label) // e.detail.item[displayField]
})
</script>
<!-- Integração Alpine: evento na tag + estado local -->
<div x-data="{ fornecedor: null }">
<bpm-autocomplete
dataset-id="dsFornecedores"
key-field="CODIGO"
display-field="RAZAO_SOCIAL"
label="Fornecedor"
@jsegd:autocomplete:select="fornecedor = $event.detail.item"
@jsegd:autocomplete:clear="fornecedor = null"
></bpm-autocomplete>
<p x-show="fornecedor" x-text="'CNPJ: ' + fornecedor?.CNPJ"></p>
</div>Filtros dinâmicos via setFilters
Quando o valor de outro campo condiciona os resultados, obtenha a referência ao componente Alpine via _x_dataStack e chame setFilters:
<div x-data="meuFormulario()">
<bpm-autocomplete id="ac-produto" dataset-id="dsProdutos" display-field="DESCRICAO"></bpm-autocomplete>
</div>
<script>
// Após Alpine inicializar:
const el = document.querySelector('#ac-produto')
// Filtro estático pelo atributo (JSON) — preferível para filtros fixos:
// <bpm-autocomplete filters='[{"field":"CATEGORIA","value":"ELETRONICO","operator":"MUST"}]'>
// Filtro dinâmico via API JavaScript — para filtros que dependem de estado em runtime:
function atualizarFiltros(categoria) {
const component = el.querySelector('[x-data]')?._x_dataStack?.[0]
if (component) {
component.setFilters([
{ field: 'CATEGORIA', value: categoria, operator: 'MUST' },
{ field: 'DESCRICAO', value: '%' + categoria, operator: 'SHOULD', likeSearch: true },
])
}
}
</script>API pública exportada
import { createAutocompleteComponent, BpmAutocomplete, FILTER_OPERATOR_MAP } from 'jsegd-bpm'
import type {
AutocompleteConfig,
AutocompleteFilter,
AutocompleteFilterOperator,
AutocompleteItem,
BaseAutocompleteComponent,
AutocompleteSelectDetail,
AutocompleteErrorDetail,
AutocompleteLoadDetail,
} from 'jsegd-bpm'Notas de uso:
- O
BpmAutocompleteregistra-se automaticamente como<bpm-autocomplete>ao importar o módulo; não é necessário chamarcustomElements.define. - Cada instância na página tem um
Alpine.datacom nome único, portanto múltiplos componentes coexistem sem compartilhar estado. - A busca usa
GET /dataset/api/v2/dataset-handle/search?datasetId=...&constraintsField=...com debounce de 300 ms, mínimo de 2 caracteres e cancelamento de requisição anterior viaAbortController. - Para suporte a valores salvos em banco (edição de formulário), use o atributo
initial-value— o componente realiza automaticamente a busca por chave (constraintsType=MUST) para montar a exibição. - O operador
likeSearch: trueem um filtro mapeia paraconstraintsLikeSearch=truena API; o símbolo%pode ser usado emvaluecomo curinga. - O atributo
limit(padrão:20) controla o parâmetrolimitda API; valores menores melhoram a performance do dropdown.
Uso típico em um formulário EGD BPM
import Alpine from 'alpinejs'
import {
AttachButton,
AttachmentsTable,
WorkflowViewPatcher,
BpmFormRepository,
extractCompletedStages,
extractCurrentStage,
resolveWorkflowScreen,
registerStores,
} from 'jsegd-bpm'
import type { BpmHistoryMap } from 'jsegd-bpm'
import { workflowConfig } from './workflow.config'
// Registrar Custom Elements
customElements.define('attachment-button', AttachButton)
customElements.define('attachments-table', AttachmentsTable)
// Alpine stores
const stores = registerStores(Alpine, {
departamento: { nome: '', area: '' },
ui: { carregando: false },
})
// Histórico e tela
const rawHistory = parent.ECM.workflowView.processDefinition.urlHistory as BpmHistoryMap
const seq: number = parent.ECM.workflowView.processDefinition.cardIndex
const histType: string | null = parent.ECM.workflowView.processDefinition.historyType ?? null
const processHistory = {
completedStages: extractCompletedStages(rawHistory, workflowConfig.activities),
currentStage: extractCurrentStage(seq, histType, workflowConfig.activities),
}
const screen = resolveWorkflowScreen(workflowConfig, seq, processHistory)
// Patcher
await WorkflowViewPatcher.init()
WorkflowViewPatcher.patchAll()
WorkflowViewPatcher.addBeforeValidate(async () => {
const repo = new BpmFormRepository()
const dados = repo.load<{ nome: string }>('departamento')
if (!dados.nome) throw new Error('Informe o nome')
})
Alpine.start()Licença
Proprietária — uso restrito a projetos autorizados.
