Table Builder para Quasar

🇺🇸 English | 🇪🇸 Versión en español

npm version license Vue Quasar TypeScript Node

Extensão Quasar para tabelas declarativas em apps Vue 3 + Quasar: listagens com filtros (via Form Builder), paginação servidor/cliente, ordenação no backend, seleção de linhas, slots de coluna e consultas canceláveis.

Índice

Em 30 segundos

Fluxo base recomendado:

  1. Definir tipos de filtros F e linha R.
  2. Configurar campos de filtro com Form Builder (defineForm ou filters.form inline).
  3. Criar a tabela com defineTable<F, R>({ ... }) a partir de useTableBuilder().
  4. Renderizar <TableBuilder ref="tableRef" :config="config" /> com slots #column-* e #toolbar.

A extensão registra boot e CSS no projeto host automaticamente após quasar ext add.

O que é e o que inclui

Table Builder orquestra listagens: toolbar, filtros Form Builder, tabela, paginação e fetch. Não substitui Form Builder — os filtros são sempre um formulário FB.

Requisitos e compatibilidade

Item Valor
Node >= 20.0.0
Quasar ^2.6.0
Vue ^3.4.18
Pinia ^2.0.11 | ^3.0.0
DataTable @benjaminor-dev/datatable ^0.1.0 — obrigatória
Form Builder @benjaminor-dev/form-builder ^0.9.0 — obrigatório
moment ^2.30.1 (peer; usado ao formatar chips de filtros com datas/horas)
Formato do pacote ES modules
CLI recomendado @quasar/app-vite ^2.x ou ^3.x

O boot de Pinia do host deve rodar antes de Form Builder e Table Builder.

Instalação em app Quasar

Instale DataTable e Form Builder antes de Table Builder (peers obrigatórios):

quasar ext add @benjaminor-dev/datatable
quasar ext add @benjaminor-dev/form-builder
quasar ext add @benjaminor-dev/table-builder

Remover:

quasar ext remove @benjaminor-dev/table-builder

O que a extensão adiciona ao host

Recurso Caminho npm
Boot store ~@benjaminor-dev/quasar-app-extension-table-builder/boot/store
Estilos Table Builder ~@benjaminor-dev/quasar-app-extension-table-builder/main.css
Estilos DataTable ~@benjaminor-dev/quasar-app-extension-datatable/main.css (peer; registrado também por esta extensão)
Scripts scaffold bor:make-tablescripts/bor/bor-make-table.mjs
Defaults boot (host) src/boot/bor/bor-table-builder-defaults.ts — criado no install; Skip/Overwrite se já existir

Install (quasar ext add / invoke): cria src/boot/bor/bor-table-builder-defaults.ts (ou .js) e registra em quasar.configboot: [] automaticamente. Não é preciso editar manualmente numa instalação normal. O boot do pacote (boot/store) e CSS são injetados pela extensão em cada dev/build.

Remove (quasar ext remove): remove o script scaffold, o defaults boot, sua entrada em quasar.config e bor:make-table em package.json (se ainda apontar para o arquivo do install).

Se você apagar o arquivo de defaults, a app continua com os built-ins da biblioteca (fetchOnMount: false, pageSize: 10, etc.).

Ordem de boots (esta extensão): Pinia → DataTable (CSS) → Form Builder → boot store TB → bor/bor-table-builder-defaults → resto.

Defaults globais (boot)

Evite repetir filters, pagination, fetchOnMount, persistData e abort em cada defineTable. Prioridade: defineTable → boot (configureTableBuilder) → built-in da biblioteca.

Após quasar ext add, revise src/boot/bor/bor-table-builder-defaults.ts — template com fetchAbort e opções comentadas (pagination, persistData). Defaults built-in: JSDoc @default nos tipos.

// src/boot/bor/bor-table-builder-defaults.ts (trecho)
import { configureTableBuilder } from "@benjaminor-dev/quasar-app-extension-table-builder";

configureTableBuilder({
  filters: { layout: "drawer", openFiltersOnMount: false, showFilterChips: true },
  fetchOnMount: false,
  // persistData: true, // descomente para cache SPA global
  filterUi: {
    buttonStyle: {
      light: {
        filtersColor: "teal",
        filtersVariant: "outline",
        searchColor: "primary",
        searchVariant: "outline",
        clearVariant: "flat",
        submitColor: "primary",
        submitVariant: "unelevated",
        stopColor: "negative",
        stopVariant: "unelevated",
      },
      dark: {
        filtersColor: "teal",
        filtersVariant: "unelevated",
        searchColor: "primary",
        searchVariant: "unelevated",
        clearColor: "white",
        clearVariant: "flat",
        submitColor: "primary",
        submitVariant: "unelevated",
        stopColor: "negative",
        stopVariant: "unelevated",
      },
    },
    panelHeader: {
      light: { color: "primary", textColor: "white" },
      dark: { color: "primary", textColor: "white" },
    },
    chips: {
      light: { color: "primary", textColor: "white" },
      dark: { color: "primary", textColor: "white" },
    },
    chipsPanel: {
      light: {
        borderColor: "rgba(25, 118, 210, 0.16)",
        backgroundColor: "rgba(25, 118, 210, 0.04)",
      },
      dark: {
        borderColor: "rgba(255, 255, 255, 0.12)",
        backgroundColor: "rgba(255, 255, 255, 0.04)",
      },
    },
  },
  // fetchAbort opcional — veja arquivo gerado no install
});
Opção boot Descrição
filters Defaults do painel (sem form; o formulário fica em cada tabela)
filterUi.buttonStyle Cores e variantes de Filtros / Buscar / Limpar / Stop por tema (light / dark)
filterUi.panelHeader Cabeçalho do drawer (color, textColor Quasar por tema)
filterUi.chips Chips de filtros ativos (color, textColor por tema)
filterUi.chipsPanel Borda e fundo do contêiner de chips (borderColor, backgroundColor CSS por tema)
pagination pageSize, placement, scrollOnPageChange, pageSizeOptions
fetchOnMount Carga inicial ao montar (default biblioteca: false)
persistData Conservar sessão Pinia ao desmontar a tabela
onFetchError Hook global de erros de fetch
fetchAbort Abort global opcional (useTableFetchAbortStore ou adapter próprio) + allowUserCancel

Use configureTableBuilderDefaults se você só precisa de defaults sem registrar fetchAbort.

Prioridade de filterUi: defineTable({ filterUi }) → boot → built-in. O tema ativo segue $q.dark do host.

Cancelamento detalhado: § Cancelamento.

Comandos scaffold no host

A extensão registra bor:make-table no host (scripts/bor/bor-make-table.mjsrunBorMakeTable em /scaffold). Execute npm run bor:help (Form Builder) para ver todos os geradores instalados.

Tabelas (bor:make-table)

npm run bor:make-table -- <nombre-o-ruta>

Exemplo — criar UsuariosTable.vue:

npm run bor:make-table -- usuarios

Resultado:

Regras de naming:

Uso rápido

<script setup lang="ts">
import { TableBuilder, useTableBuilder } from "@benjaminor-dev/quasar-app-extension-table-builder";

type Filters = { search: string | null; role: string | null };
type Row = { id: number; name: string; role: string };

const { defineTable, useTableRef } = useTableBuilder();
const tableRef = useTableRef();

const config = defineTable<Filters, Row>({
  tableName: "usuarios",
  title: "Usuários",
  columns: [
    { name: "name", label: "Nome", field: "name", align: "left", sortable: true },
    { name: "role", label: "Papel", field: "role", sortable: true },
  ],
  pagination: { mode: "server", pageSize: 10, placement: "auto", defaultSort: { sortBy: "name" } },
  fetchOnMount: false,
  filters: {
    layout: "drawer",
    minActiveFilters: 1,
    form: {
      fields: [
        { type: "InputSearch", model: "search", label: "Buscar" },
        {
          type: "InputSelect",
          model: "role",
          label: "Papel",
          props: { options: [{ label: "Admin", value: "admin" }], clearable: true },
        },
      ],
    },
  },
  fetch: async ({ filters, page, pageSize, sortBy, descending, signalAbort }) => {
    const params = new URLSearchParams({
      page: String(page),
      pageSize: String(pageSize),
    });
    if (sortBy) {
      params.set("sortBy", sortBy);
      params.set("descending", String(descending));
    }
    if (filters.search) params.set("search", filters.search);
    if (filters.role) params.set("role", filters.role);

    const res = await fetch(`/api/users?${params}`, { signal: signalAbort });
    const data = await res.json();
    return {
      rows: data.items,
      pagination: { total: data.total, page, pageSize },
    };
  },
});
</script>

<template>
  <TableBuilder ref="tableRef" :config="config">
    <template #column-name="{ row, text }">
      <strong>{{ text || row.name }}</strong>
    </template>
  </TableBuilder>
</template>

Idioma (i18n)

Integrado en, es e pt. Por padrão os textos de toolbar, drawer, estados vazios e skeleton seguem o idioma do Quasar ($q.lang.isoName); se o Quasar não tiver idioma, o fallback é en.

Resolução de locale: boot locale → prefixo de $q.lang.isoName (es*es, pt*pt) → en.

Opção Quando usar
framework.lang em quasar.config.ts Recomendado — alinha Table Builder ao resto do Quasar
locale: 'pt' em boot Fixar idioma (en | es | pt); ignora mudanças de $q.lang
messages: { search: '…' } em boot Alterar um texto sem fixar locale

Exemplo em quasar.config.ts (português para toda a app):

// quasar.config.ts
framework: {
  lang: "pt-BR",
},

Template boot (após install):

configureTableBuilder({
  // locale: "pt", // fixa só o idioma do Table Builder
  // messages: { search: "Consultar" },
});

Chaves i18n: botões (filters, search, refresh, stopQuery, clear), drawer (filtersPanelTitle), estados vazios (emptyNotQueried*, noResults*, fetchCancelled*), tooltip de busca (searchGateDefault), skeleton (loading*Title, loading*Hint) e aria (filtersAppliedAria, removeFilterAria com {label}).

Prioridade de mensagens: boot messages funde sobre o catálogo built-in do locale ativo.

TypeScript no seu IDE

A config da tabela é tipada de ponta a ponta: deixe o IDE guiar você.

Se uma opção não aparecer no autocompletar, revise o import de /types antes de buscar no README.

Configuração de defineTable

Campos principais de defineTable<F, R>():

Campo Descrição
tableName Identificador único (nome do form de filtros derivado se não passar formName).
title, hint, prependIcon, helpTooltip Apresentação na toolbar.
columns Definição de colunas (name, label, field?, format, sortable, align, …). sortable por padrão false.
selection single | multiple para checkboxes; omitir para desativar. Ref: selectedRows, clearSelection().
pagination Modo, pageSize, placement y defaultSort (server).
filters Panel Form Builder o false.
fetch Função async que retorna linhas (e metadados no modo server).
fetchOnMount Carga inicial ao montar (respeita regras de busca).
persistData true conserva a sessão Pinia ao desmontar a tabela (navegar e voltar na SPA).
persistDataConfig Ajuste fino (reuseOnMount). Ganha sobre persistData se definir ambos.
onBeforeFetch, onAfterFetch, onFetchError Hooks do ciclo de consulta.
allowUserCancel Stop / cancelFetch() para o usuário (override do boot). Default: false. Boot global: só via fetchAbort.allowUserCancel.
filterUi Override de botões, cabeçalho drawer, chips e painel de chips por tema (light / dark).

Colunas (columns)

defineTable não exige ordem fixa: o runtime normaliza a lista ao resolver a config.

Comportamento Detalhe
Coluna # Se não definir name: "index", é injetada no início com label: "#" e field: "index". Largura compacta por padrão. O fetch não deve retorná-la: é calculada em cada página (1, 2, … conforme offset).
field Opcional em colunas só-slot (#column-{name}): omita se o valor vier do slot. Se declarar, usa para value/text e ordenação em client.
label Opcional em colunas só-slot; se faltar, o cabeçalho fica vazio.
align Por padrão "left". Use "center" ou "right" onde aplicável (ex.: ícones ou ações).
Ordem As demais colunas (incluindo actions) aparecem na mesma ordem de columns.
sortable Por padrão false. Marque sortable: true nas colunas que ordenar; em server use sortBy / descending no fetch.

Para personalizar a coluna índice (largura, alinhamento, ocultá-la), declare explicitamente com name: "index".

Filtros e busca

Layout

Valor Comportamento
drawer (default) Formulário em painel lateral; toolbar com grupo Filtros + lupa/refresh.
inline Painel sobre a tabela com cabeçalho Filtros, formulário, Buscar / Limpar. Os chips (se ativos) ficam abaixo do formulário.

Regras para habilitar busca (AND)

filters: {
  minActiveFilters: 1,
  canSearch: (filters) => Boolean(filters.search?.trim()) || filters.role != null,
  searchDisabledMessage: "Indique busca ou papel para consultar.",
  showFilterChips: true,
  form: { fields: [...] },
}
Opção Default Descrição
minActiveFilters 0 Mínimo de filtros com valor ativo.
canSearch true boolean ou (filters: F) => boolean.
searchDisabledMessage "Ajuste os filtros para consultar." Tooltip quando falha canSearch ou minActiveFilters.
showFilterChips true Chips da última busca bem-sucedida.
resetFiltersOnMount false Restaura filtros ao montar.
openFiltersOnMount false Abre drawer ao montar (true | "desktop").

Os chips refletem o snapshot da última busca bem-sucedida, não o formulário ao vivo.

Formulário de filtros (filters.form)

Mesma forma que defineForm do Form Builder:

filters: {
  form: {
    fields: [{ type: "InputSearch", model: "search", label: "Buscar" }],
    defaultValues: { role: "admin" },
  },
}

Você também pode passar o resultado de defineForm(...) se preferir definir o formulário fora de defineTable.

Toolbar com drawer (grupo fundido)

Um QBtnGroup outline agrupa os botões da toolbar somente quando Filtros e Buscar/Stop compartilham variante outline. Se misturar variantes (ex.: outline + flat), renderiza como botões separados.

UI de filtros por tema (filterUi)

Bloco boot O que controla
buttonStyle Botões Filtros / Buscar / Limpar / Stop (*Color, *Variant por tema)
panelHeader Cabeçalho do drawer (color, textColor Quasar)
chips QChip de filtros ativos (color, textColor)
chipsPanel Borda e fundo do contêiner de chips (borderColor, backgroundColor CSS)
filterUi: {
  buttonStyle: {
    light: { filtersColor: "teal", filtersVariant: "outline", searchVariant: "outline" },
    dark: { filtersColor: "teal", filtersVariant: "unelevated", searchVariant: "unelevated" },
  },
  panelHeader: { light: { color: "primary", textColor: "white" }, dark: { color: "primary", textColor: "white" } },
  chips: { light: { color: "primary", textColor: "white" }, dark: { color: "primary", textColor: "white" } },
},

Paginação

pagination: {
  mode: "server",
  pageSize: 25,
  placement: "auto",
  scrollOnPageChange: "auto",
  defaultSort: { sortBy: "name" },
}
placement Comportamento
bottom Só embaixo (default).
top Só em cima.
both Em cima e embaixo.
auto Embaixo sempre; em cima só se a página atual mostrar ≥15 linhas visíveis (conta linhas renderizadas, não o valor do seletor «linhas por página»).
scrollOnPageChange Comportamento
auto (default) Scroll ao título no mobile ou quando a paginação está só embaixo.
true Sempre após mudar de página.
false Nunca.

| defaultSort | Ordem inicial no modo server (sortBy, descending?). Ignorado em client salvo se QTable receber o estado inicial via paginação interna. |

Modo server: fetch deve retornar { rows, pagination } com pelo menos total, page e pageSize. Os campos from, to e lastPage são opcionais (o runtime calcula se faltarem). Ao clicar um cabeçalho sortable, reconsulta com sortBy e descending (página 1).

Modo client: fetch pode retornar R[] ou { rows }; pagina e ordena QTable em memória.

Ordenação

Modo Comportamento
client Marque colunas sortable: true onde quiser. QTable ordena as linhas carregadas sem novo fetch.
server Marque sortable: true nas colunas que sua API suporta. Ao clicar o cabeçalho executa fetch na página 1 com sortBy (nome da coluna) e descending.

Ordem inicial no servidor:

pagination: {
  mode: "server",
  defaultSort: { sortBy: "name", descending: false },
}

sortBy e descending também chegam a onBeforeFetch com a mesma forma que em fetch.

Seleção de linhas

const config = defineTable<Filters, Row>({
  // ...
  selection: "multiple", // ou "single"
});
Valor UI
"multiple" Checkbox por linha
"single" Radio por linha
Omitir / false Sem seleção

A seleção é limpa após cada fetch bem-sucedido (busca, paginação, ordenação ou reload).

No script, leia as linhas pela ref:

const tableRef = useTableRef();
const selectedCount = computed(() => tableRef.value?.selectedRows?.length ?? 0);

No template, use um computed (não acesse tableRef.selectedRows diretamente: vue-tsc não desembrulha o ShallowRef no template).

fetch, cancelamento e hooks

Contexto de fetch

type TableFetchContext<F> = {
  filters: F;
  page: number;
  pageSize: number;
  sortBy: string | null;
  descending: boolean;
  signalAbort?: AbortSignal;
};

Retorna linhas (e metadados no modo server):

// client
fetch: async ({ filters }) => ({ rows: await api.list(filters) }),

// server
fetch: async ({ filters, page, pageSize, sortBy, descending }) => ({
  rows: items,
  pagination: { total, page, pageSize },
}),

Cancelamento — escolha um caminho

O cancelamento não passa por onFetchError.

Quem pode parar a consulta?

Por padrão ninguém (sem botão Stop). Ative no boot (fetchAbort.allowUserCancel) ou por tabela (defineTable.allowUserCancel + signalAbort no fetch).

Prioridade: defineTable.allowUserCancelfetchAbort.allowUserCancel (boot) → false.

Boot fetchAbort.allowUserCancel Tabela Stop / cancelFetch()
true (sem definir) ✅ todas
true false ❌ só essa tabela
false / sem boot true ✅ só essa tabela
false (sem definir) ❌ nenhuma
// Boot — todas as tabelas com Stop (abort global do host)
configureTableBuilder({
  fetchAbort: {
    allowUserCancel: true,
    getSignal: () => useAbortStore().getSignal(),
    abort: () => useAbortStore().abort(),
  },
});

// Uma tabela sem Stop mesmo com boot ativo
defineTable({ tableName: "Logs", allowUserCancel: false, ... });

// Só esta tabela com Stop (Caminho A portátil)
defineTable({
  tableName: "Usuarios",
  allowUserCancel: true,
  fetch: ({ signalAbort, ...ctx }) => UsersService.list(ctx.filters, signalAbort),
});

Uma nova busca ou mudança de página continua abortando a requisição anterior mesmo com Stop desativado (evita sobreposições).

Caminho A — portátil (default)

Table Builder cria um AbortController por consulta. Reencaminhe signalAbort ao cliente HTTP no fetch:

fetch: async ({ filters, page, pageSize, signalAbort }) => {
  const res = await api.get("/users", { params: { filters, page, pageSize }, signal: signalAbort });
  return { rows: res.data.items, pagination: res.data.meta };
},

Se o service vive fora do componente, passe como argumento:

fetch: ({ filters, signalAbort }) => UsersService.list(filters, signalAbort),

Recomendado com várias tabelas em paralelo (cada consulta tem seu próprio sinal).

Caminho B — abort global do host (sem signalAbort em cada tabela)

Se sua app já centraliza o cancelamento (Pinia + axios que auto-injeta signal em cada requisição), registre o adaptador uma vez no boot do host.

Opção incluída — store Pinia da extensão (useTableFetchAbortStore):

configureTableBuilder({
  fetchAbort: {
    useStore: true,
    allowUserCancel: false, // recomendado: false
  },
});

Injete useTableFetchAbortStore().getSignal() na sua camada HTTP (axios, etc.).

Opção custom — store próprio do host:

// src/boot/table-builder.ts
import { boot } from "quasar/wrappers";
import {
  configureTableBuilder,
  defaultIsTableFetchAbortError,
} from "@benjaminor-dev/quasar-app-extension-table-builder";
import useAbortStore from "@/stores/config/useAbortStore";

export default boot(() => {
  configureTableBuilder({
    fetchAbort: {
      allowUserCancel: false,
      getSignal: () => useAbortStore().getSignal(),
      abort: () => useAbortStore().abort(),
      isAbortError: (error) =>
        defaultIsTableFetchAbortError(error) ||
        (error instanceof Error && error.message === "canceled"),
    },
  });
});

Sua camada HTTP deve continuar injetando o mesmo signal do store quando a requisição não traz um explícito (como em ApiUtils.makeNewConfig).

Então o fetch da tabela não precisa usar signalAbort:

fetch: ({ filters, page, pageSize }) =>
  UsersService.index(filters, { page, pageSize }),

Table Builder chamará abort() do store ao cancelar ou iniciar outra consulta; axios captará como antes.

Nota: um abort store global implica uma consulta abortável ativa na app. Com várias tabelas carregando ao mesmo tempo, use o caminho A.

Hooks (controle opcional)

Hook Quando Comportamento
onBeforeFetch Antes de iniciar Retorna false para não consultar (validação prévia).
onAfterFetch Após sucesso Linhas já normalizadas (index incluído).
onFetchError Erro real (rede, HTTP, exceção) Se definir, nãonotify por padrão.

Cancelamento (AbortError, axios canceled, …) não dispara onFetchError.

Exemplo — erros de validação nos filtros (adapte ao contrato da sua API):

import { selectFormStore } from "@benjaminor-dev/quasar-app-extension-form-builder";

onFetchError: (error, ctx) => {
  const raw = (error as { response?: { data?: { errors?: Record<string, string | string[]> } } })
    .response?.data?.errors;
  if (!raw) {
    $q.notify({ type: "negative", message: "Erro ao consultar." });
    return;
  }

  selectFormStore(`table:${tableName}:filters`).setErrors(
    Object.fromEntries(
      Object.entries(raw).map(([key, value]) => [
        key,
        Array.isArray(value) ? value[0] : String(value),
      ]),
    ),
  );
},

Estados de UI após a consulta

Slots

<TableBuilder :config="config">
  <template #toolbar>
    <q-chip v-if="selectedCount > 0" dense color="primary">
      {{ selectedCount }} selecionados
    </q-chip>
    <q-btn outline label="Exportar" />
  </template>

  <template #column-actions="{ row, text, column, value }">
    <q-btn flat icon="edit" @click="edit(row)" />
  </template>
</TableBuilder>
Slot Props
toolbar — (ações extras à direita do grupo Filtros)
column-{name} row, column, value, text, index

Helper tipado: tableColumnSlotName("actions")"column-actions".

Ref de TableBuilder

const tableRef = useTableRef();

await tableRef.value?.reload();
await tableRef.value?.reload({ resetPage: true });
await tableRef.value?.reload({ resetFilters: true });

tableRef.value?.openFilters();
tableRef.value?.resetFilters();
tableRef.value?.cancelFetch();
tableRef.value?.clearSelection();
await tableRef.value?.invalidate();

if (tableRef.value?.loading) { /* ... */ }
const selected = tableRef.value?.selectedRows ?? [];
Método / ref Descrição
reload(options?) Executa fetch novamente. resetFilters: true restaura filtros e vai para página 1 (salvo resetPage: false explícito).
resetFilters() Restaura filtros aos defaults (sem recarregar).
openFilters() / closeFilters() Drawer de filtros.
cancelFetch() Cancela o fetch em curso (abort do signalAbort ativo).
clearSelection() Desseleciona todas as linhas.
invalidate() Esvazia linhas e metadados no Pinia. Útil antes de reload() após CRUD externo se não usar patchItems.
loading true enquanto há um fetch em curso.
selectedRows Linhas selecionadas quando selection está ativo (limpa após cada fetch bem-sucedido).

Tipos TypeScript exportados

Do entrypoint principal e /types:

import type {
  ConfigureTableBuilderOptions,
  TableFilterUiDefaults,
  TableFilterChipStyleConfig,
  TableFilterChipsPanelStyleConfig,
  TableFilterPanelHeaderStyleConfig,
  TableBuilderConfig,
  TableBuilderResolvedConfig,
  TableFiltersConfigInput,
  TableSearchGateFn,
  TableColumnDef,
  TableColumnSlotProps,
  TablePaginationConfig,
  TablePaginationPlacement,
  TableDefaultSort,
  TableBuilderSlots,
} from "@benjaminor-dev/quasar-app-extension-table-builder/types";

import type {
  TableRow,
  TableFilters,
  TableFetchContext,
  TableFetchResult,
  TablePaginationMeta,
  TablePaginationMetaInput,
  TablePaginationMode,
  TableRowSelectionMode,
} from "@benjaminor-dev/quasar-app-extension-table-builder/types";

import type {
  PersistDataConfig,
  PersistDataConfigOption,
} from "@benjaminor-dev/quasar-app-extension-table-builder/types";

import type {
  TableBuilderExpose,
  TableBuilderReloadOptions,
  RefTableBuilder,
} from "@benjaminor-dev/quasar-app-extension-table-builder";

import { tableColumnSlotName } from "@benjaminor-dev/quasar-app-extension-table-builder";

Extensões relacionadas

Extensão Relação
@benjaminor-dev/datatable Obrigatória. Provê DataTable, barra de paginação e estilos de grade usados por TableBuilder.
@benjaminor-dev/form-builder Obrigatório. Filtros, store e validação via defineForm / FormBuilder.

Table Builder não inclui gerador de formulários próprio: reutiliza Form Builder para não duplicar inputs nem regras.

Ordem de boots (stack completo):

1. Pinia (host)
2. DataTable — CSS
3. Form Builder — boot/store
4. bor/bor-form-builder-defaults (host)
5. Table Builder — boot/store
6. bor/bor-table-builder-defaults (host)
7. Dialog File Preview — boot/dialog
8. bor/bor-dialog-file-preview-defaults (host)
9. Dialog Loader — boot/dialog
10. bor/bor-dialog-loader-defaults (host)
11. Dialog Message — boot/dialog
12. bor/bor-dialog-message-defaults (host)

Em quasar.config você verá o defaults boot de cada extensão (ex.: bor/bor-table-builder-defaults); os boots do pacote são injetados pela extensão ao compilar — não aparecem como entradas curtas na sua config.

Sessão e store Pinia

Uma sessão por tableName no Pinia (mesmo padrão do store do Form Builder para filtros).

Acesso: useTableBuilder().selectTableStore(tableName) ou selectTableStore(tableName) importado do pacote.

selectTableStore<R>(tableName)

Visão tipada da sessão. O mesmo tableName em outro .vue ou composable compartilha linhas e metadados.

const { defineTable, selectTableStore } = useTableBuilder();

const usuariosTableStore = selectTableStore<UsuarioRow>("UsuariosTable");

// Após apagar na API, remover linha sem refetch
await api.delete(id);
usuariosTableStore.patchItems((rows) => rows.filter((r) => r.id !== id));

// Leitura reativa
if (usuariosTableStore.hasItems.value) {
  const rows = usuariosTableStore.items.value;
}
Membro Descrição
tableName Identificador (defineTable.tableName)
items Linhas visíveis (reativo)
hasItems true se houver pelo menos uma linha
pagination Metadados no modo server
appliedFilters Snapshot da última busca bem-sucedida
hasSearched true após pelo menos um fetch bem-sucedido
getItems() Snapshot não reativo
setItems / patchItems Mutação local sem refetch
clearItems() Esvazia linhas e metadados
remove() Remove a sessão Pinia

Tipo exportado: TableSessionHandle<R> em /types.

Filtros continuam no Form Builder (table:{tableName}:filters por padrão).

persistData (opcional)

Conserva a sessão Pinia ao desmontar <TableBuilder /> (navegar para outra rota e voltar).

const config = defineTable<Filters, Row>({
  tableName: "usuarios",
  columns: [...],
  fetch: async ({ filters, page, pageSize, signalAbort }) => ({ rows, pagination: { ... } }),
  persistData: true,
  fetchOnMount: false, // vazio até a primeira busca
});
Comportamento Detalhe
persistData: true Ao sair da rota, o store conserva linhas, página e snapshot de filtros aplicados.
persistData: false (default) Ao desmontar, remove a sessão dessa tableName.
Navegar e voltar Com persistData: true, hidrata do Pinia (reuseOnMount: true por padrão).
Recarregar aba (F5) Perde-se (só memória da sessão SPA).
reload() / buscar Sempre vão à rede.
tableRef.invalidate() Esvazia linhas e metadados no store (útil antes de reload() após CRUD externo).

Ajuste fino: persistDataConfig: { reuseOnMount: false } força fetch ao montar mesmo com dados no Pinia.

Entrypoints públicos

Entrypoint Propósito
@benjaminor-dev/quasar-app-extension-table-builder TableBuilder, useTableBuilder, configureTableBuilder, configureTableBuilderDefaults, configureTableFetchAbort, registerTableFetchAbortStore, createTableFetchAbortStoreAdapter, useTableFetchAbortStore, defaultIsTableFetchAbortError, tableFetchAbortIsAbortErrorIncludingAxios, selectTableStore, useTableRef, useTableBuilderBuilderRef, tableColumnSlotName
@benjaminor-dev/quasar-app-extension-table-builder/types Tipos de config, colunas, fetch, boot e sessão (TableBuilderConfig, TableFetchContext, TableSessionHandle, ConfigureTableBuilderOptions, …) — autocompletar no IDE
@benjaminor-dev/quasar-app-extension-table-builder/scaffold runBorMakeTable (bor:make-table)
@benjaminor-dev/quasar-app-extension-table-builder/boot/store Boot Quasar (registrado pela extensão)
@benjaminor-dev/quasar-app-extension-table-builder/main.css Estilos de tabela, drawer, paginação e skeleton

Troubleshooting

Os textos da UI continuam em inglês

O defaults boot não aparece em quasar.config

Em condições normais o install registra sozinho. Se faltar (projeto antigo ou edição manual):

npx quasar ext invoke @benjaminor-dev/table-builder

Erro de Pinia / store de filtros ou tabelas

fetch em modo server sem metadados

Em pagination.mode: "server", retorne { rows, pagination } com pelo menos total, page e pageSize. Não é preciso calcular from/to/lastPage manualmente.

return { rows: items, pagination: { total, page, pageSize } };

Botão buscar desabilitado

Revise minActiveFilters e canSearch. O tooltip indica o motivo. Os valores são lidos do formulário ao vivo, não do último snapshot de chips.

Os chips não coincidem com o que vejo no drawer

É esperado: chips = última busca bem-sucedida. Abra filtros, altere valores e clique buscar para atualizar.

Cancelar durante paginação vs busca

Ordenar em server não faz nada

Verifique se a coluna tem sortable: true e se seu fetch usa sortBy / descending ao chamar a API.

selectedRows no template dá erro de tipos

Use tableRef.value?.selectedRows no script (ex.: com computed), não tableRef.selectedRows no template.

Stop não cancela a requisição HTTP

Erros do endpoint não chegam ao formulário de filtros

Implemente onFetchError e mapeie o corpo do erro com selectFormStore(formName).setErrors(...) conforme o contrato da sua API. Verifique se formName coincide com o do form de filtros.


MIT © Benjamín Olvera R.