FtxGrid
A comprehensive data grid component with filtering, sorting, pagination, and more.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
id | String | null | Unique grid identifier (required in practice) |
rowKey | String | 'id' | Property to use as row key |
tableTitle | String | '' | Title shown in the grid header |
tableSubTitle | String | '' | Subtitle under the title |
rows | Array | null | Data rows (omit or use with apiEndpoint / store) |
columns | Array | [] | Column definitions |
chooseColumn | Boolean | true | Enable column chooser UI |
advanceFilter | Boolean | true | Enable advanced filtering |
onRequest | Function | null | If set, grid calls this instead of built-in API request (full control) |
pagination | Object | null | Pagination state; when set, grid emits update:pagination on changes |
apiEndpoint | String | null | API URL for listing, delete (when deleteApiEndpoint unset), and print/export |
deleteApiEndpoint | String | null | Optional delete URL; defaults to base path derived from apiEndpoint |
loading | Boolean | false | Loading state |
isCollapsible | Boolean | false | Collapsible card layout with show/hide |
defaultExpanded | Boolean | false | When collapsible, start expanded |
renderGrid | Boolean | true | When false, grid body can stay hidden until expanded |
placeholder | String | '' | Placeholder when grid has no title/content context |
selection | String | 'none' | 'none', 'single', or 'multiple' |
handleDeleteAction | Boolean | false | Enable row delete action (uses confirm + deleteApiEndpoint / apiEndpoint) |
deleteConfirmationMsg | String | 'Are you sure...' | Confirm dialog message for delete |
deleteSuccessMsg | String | 'Item deleted successfully.' | Toast after successful delete |
editAction | Object | String | Function | null | Edit action: function, route path, or route object |
addLabel | String | 'Add New' | Add button label |
addLink | Object | String | null | Add button route or link |
addAction | Function | null | Add button handler |
addActionLoading | Boolean | false | Loading state on add button |
rowFilter | Boolean | false | Row-based filter UI (only when advanced filter is off) |
cellSeparator | Boolean | false | Visual separator between cells |
additionalClass | String | '' | Extra CSS class on the grid root |
defaultColumnsInApi | Boolean | true | When true, listing and print/export send a columns parameter built from visible columns (respects excludeFromApi, printFields). Set false to omit that parameter |
defaultFilters | Object | null | Initial column filters: keys are column name, values are { operator, value } (see With Default Filters) |
defaultAdvanceFilter | Object | null | Initial advanced filter value. Supports API-style shape and internal dialog/store shape (see With Default Advanced Filter) |
defaultSort | Object | null | Initial sort map: keys are column name, values are { descending: boolean, field?: string } (see Default sort) |
fixedFilters | Object | null | Filters always applied; same { operator, value } shape per field key (see With Fixed Filters) |
fixedFiltersLogic | String | 'and' | How fixed filter conditions combine: 'and' or 'or' only |
customSelectedRows | Array | [] | Pre-selected rows when using selection |
apiInstance | Object | null | HTTP/API client for requests (overrides store / $api resolution) |
isPrint | Boolean | false | Show print/export toolbar control |
extraPrintParams | Object | null | Merged into print/export request (e.g. printHeader) |
Events
| Event | Payload | When |
|---|---|---|
update:pagination | Object | Parent controls pagination; user changes page or rows per page |
tableVisible | Boolean | Collapsible grid toggled open/closed |
action-click | { action, row, editAction? } or { action, row, link } | Row action clicked, or column link fallback when router is missing |
deleted:item | — | Row delete succeeded |
select-all | Array (current rows) | Header control to select all on current view |
update:tab | — | Declared for tab integrations |
update:searchText | — | Declared for search integrations |
update:selected | — | Declared for selection v-model style integrations |
Slots
| Slot Name | Description | Props |
|---|---|---|
top-left-buttons | Buttons on the left side of the top toolbar (before Columns/Filters buttons) | - |
top-buttons | Buttons on the right side of the top toolbar (after Add button) | - |
top-title-left-buttons | Buttons next to the grid title (only in non-collapsible mode) | - |
body | Custom body slot to override the entire table body | - |
body-cell-${column.name} | Custom cell rendering for a specific column (dynamic slot) | props - Contains row, rowIndex, col, column, value, etc. |
bottom-row | Custom row at the bottom of the table (typically for totals/summaries) | props - Contains columns, rows, and other table props |
Usage
Basic Grid
<template>
<FtxGrid
id="users-grid"
:rows="users"
:columns="columns"
:pagination="pagination"
@request="handleRequest"
/>
</template>
<script setup>
import { ref } from 'vue';
import { FtxGrid } from '@ftx/ui';
const users = ref([]);
const pagination = ref({
page: 1,
rowsPerPage: 10,
rowsNumber: 0
});
const columns = [
{ name: 'id', label: 'ID', field: 'id', sortable: true },
{ name: 'name', label: 'Name', field: 'name', sortable: true },
{ name: 'email', label: 'Email', field: 'email' }
];
function handleRequest(props) {
// Handle grid data request
// props contains pagination, filters, sort, etc.
}
</script>With Store
The grid uses a Pinia store for state management:
import { useFtxGridStore } from '@ftx/ui';
const gridStore = useFtxGridStore('grid-id');
// Access grid state and methodsWith Default Filters
Use defaultFilters to pre-apply column filters when the grid loads.
<template>
<FtxGrid
id="orders-grid"
:rows="rows"
:columns="columns"
:default-filters="defaultFilters"
@request="handleRequest"
/>
</template>
<script setup>
import { ref } from 'vue'
const defaultFilters = ref({
status: { operator: 'eq', value: 'active' },
})
</script>Default Date Range (between)
For date/datetime columns, use operator: 'between' and pass value as an object with from and to.
<script setup>
import { ref } from 'vue'
const defaultFilters = ref({
createdAt: {
operator: 'between',
value: {
from: '2026-04-01',
to: '2026-04-21',
},
},
})
</script>Notes:
- The filter key (
createdAtabove) must match the columnname. - Operator must be exactly
between(no trailing spaces). - Date format should match what your API expects.
betweenis translated togteandltein the generated filter payload.
More defaultFilters examples
Each entry must match a column filter on that column: the operator string must match one of the operators for that column’s filter type (text, number, date, select). Common patterns:
// Text / default filter type
{
name: { operator: 'contains', value: 'acme' },
code: { operator: 'startswith', value: 'PR-' },
status: { operator: 'eq', value: 'active' },
statusNot: { operator: 'neq', value: 'archived' },
note: { operator: 'notcontains', value: 'draft' },
}
// Number filter type
{
price: { operator: 'gte', value: 100 },
qty: { operator: 'lt', value: 50 },
score: { operator: 'eq', value: 0 },
}
// Date / datetime filter type (same as `between` above)
{
dueDate: { operator: 'gte', value: '2026-04-01' },
updatedAt: { operator: 'lte', value: '2026-04-30' },
createdAt: {
operator: 'between',
value: { from: '2026-04-01', to: '2026-04-21' },
},
}
// Select / ftx-select (value is usually id or primitive)
{
categoryId: { operator: 'eq', value: 12 },
// multi-select columns: value is often an array of ids
tagIds: { operator: 'eq', value: [1, 2, 3] },
}If an operator does not exist for that column’s filter configuration, the grid falls back to that column’s default operator.
With Default Advanced Filter
Use defaultAdvanceFilter when you want to pre-load the Advanced Filters dialog with values exactly like user-selected filters.
Example 1: Simple API-style default advance filter
<template>
<FtxGrid
id="orders-grid"
:rows="rows"
:columns="columns"
:default-advance-filter="defaultAdvanceFilter"
@request="handleRequest"
/>
</template>
<script setup>
import { ref } from 'vue'
const defaultAdvanceFilter = ref({
logic: 'and',
conditions: [
{ field: 'status', operator: 'eq', value: 'active' },
{ field: 'priority', operator: 'neq', value: 'low' },
],
groups: [],
})
</script>Example 2: Date range + select/multi-select
<script setup>
import { ref } from 'vue'
const defaultAdvanceFilter = ref({
logic: 'and',
conditions: [
{
field: 'createdAt',
operator: 'between',
value: { from: '2026-04-01', to: '2026-04-30' },
},
{ field: 'warehouseId', operator: 'eq', value: 12 },
{ field: 'tagIds', operator: 'eq', value: [1, 3, 8] },
],
groups: [],
})
</script>Example 3: Nested groups (AND/OR mix)
<script setup>
import { ref } from 'vue'
const defaultAdvanceFilter = ref({
logic: 'and',
conditions: [{ field: 'isDeleted', operator: 'eq', value: false }],
groups: [
{
logic: 'or',
conditions: [
{ field: 'status', operator: 'eq', value: 'active' },
{ field: 'status', operator: 'eq', value: 'pending' },
],
groups: [],
},
],
})
</script>Notes:
- Recommended shape is API-style conditions:
{ field, operator, value }. fieldcan be a columnnameor a key from columnfields.- It also accepts internal advanced dialog/store shape (with
fieldobject +operatorobject), useful if you persist and restore user filters. - Works with text, number, date/datetime,
select, andftx-select(including multi-select arrays). - Users can still open Advanced Filters and edit/remove defaults manually.
clearAllFilter()resets advanced filters back todefaultAdvanceFilterif provided.- When using
apiEndpoint(notonRequest), the grid automatically runs a listing request after defaults are applied—including when defaults become resolvable later (for examplefilter_optionsloaded after the first render). - Compatibility alias is supported:
defaultAdvnaceFilter/default-advnace-filter.
Default sort (defaultSort)
defaultSort is a map keyed by column name. Each value describes sort direction and optional backend field:
| Property | Type | Description |
|---|---|---|
descending | Boolean | false = ascending, true = descending |
field | String (optional) | Override sort field sent to the API / used in local sort (otherwise derived from column field / sortKeys / fields / name) |
<script setup>
import { ref } from 'vue'
// Sort by `createdAt` descending, then `name` ascending
const defaultSort = ref({
createdAt: { descending: true },
name: { descending: false },
})
// Optional explicit sort field (e.g. API expects a different key)
const defaultSortWithField = ref({
displayName: { descending: false, field: 'sortName' },
})
</script>defaultColumnsInApi
When true (default), listing requests and print/export include a columns query string built from visible columns (using printFields or fields or name, and skipping excludeFromApi).
Set default-columns-in-api="false" when your API should not receive a column list (for example, the backend always returns a fixed shape, or you manage column projection elsewhere).
<template>
<FtxGrid
id="legacy-api-grid"
:columns="columns"
:rows="rows"
:pagination="pagination"
:default-columns-in-api="false"
api-endpoint="/api/legacy-list"
@request="handleRequest"
/>
</template>With Actions
The editAction prop supports three types: Function, String (route path), or Route Object.
Using a Function
<template>
<FtxGrid
id="products-grid"
:rows="products"
:columns="columns"
:pagination="pagination"
:edit-action="editAction"
:add-action="addAction"
handle-delete-action
@request="handleRequest"
/>
</template>
<script setup>
import { ref } from 'vue';
import { FtxGrid } from '@ftx/ui';
const editAction = (row) => {
console.log('Edit:', row);
// Custom logic here
};
const addAction = () => {
console.log('Add new');
};
</script>Using a Route String (with parameter replacement)
<template>
<FtxGrid
id="products-grid"
:rows="products"
:columns="columns"
:pagination="pagination"
edit-action="/products/{id}/edit"
@request="handleRequest"
/>
</template>When using a string, placeholders like {id} will be automatically replaced with values from the row data. For example, if a row has { id: 123 }, the path /products/{id}/edit becomes /products/123/edit.
Using a Route Object
<template>
<FtxGrid
id="products-grid"
:rows="products"
:columns="columns"
:pagination="pagination"
:edit-action="editRoute"
@request="handleRequest"
/>
</template>
<script setup>
import { ref } from 'vue';
import { FtxGrid } from '@ftx/ui';
// Route object with named route
const editRoute = {
name: 'product-edit',
params: { id: '{id}' } // {id} will be replaced with row.id
};
// Or with path
const editRoute = {
path: '/products/{id}/edit' // {id} will be replaced with row.id
};
// Or with query parameters
const editRoute = {
name: 'product-edit',
params: { id: '{id}' },
query: { tab: 'details' }
};
</script>When using a route object, string values containing placeholders like {id} will be automatically replaced with values from the row data.
With Fixed Filters
Fixed filters are always applied to the grid and cannot be removed by users. They are useful for filtering data based on context (e.g., current user, organization, or status).
Basic Usage (AND logic - default)
<template>
<FtxGrid
id="products-grid"
:rows="products"
:columns="columns"
:pagination="pagination"
:fixed-filters="fixedFilters"
@request="handleRequest"
/>
</template>
<script setup>
import { ref } from 'vue';
import { FtxGrid } from '@ftx/ui';
const fixedFilters = ref({
status: { operator: 'eq', value: 'active' },
organizationId: { operator: 'eq', value: 123 }
});
// Fixed filters will be combined with AND logic by default
// Results: status = 'active' AND organizationId = 123
</script>Using OR Logic
To combine fixed filters with OR logic instead of AND, set fixed-filters-logic to 'or'. Omit it or use 'and' (default) to require every fixed condition.
<template>
<FtxGrid
id="products-grid"
:rows="products"
:columns="columns"
:pagination="pagination"
:fixed-filters="fixedFilters"
fixed-filters-logic="or"
@request="handleRequest"
/>
</template>
<script setup>
import { ref } from 'vue';
import { FtxGrid } from '@ftx/ui';
const fixedFilters = ref({
status: { operator: 'eq', value: 'active' },
priority: { operator: 'eq', value: 'high' }
});
// Fixed filters will be combined with OR logic
// Results: status = 'active' OR priority = 'high'
</script>Fixed Filters Format
Each fixed filter should have the following structure:
{
fieldName: {
operator: 'eq', // Filter operator (eq, ne, gt, gte, lt, lte, contains, etc.)
value: 'value' // Filter value
}
}Example with multiple filters:
<script setup>
const fixedFilters = ref({
// Status must be active
status: { operator: 'eq', value: 'active' },
// Price must be greater than or equal to 100
price: { operator: 'gte', value: 100 },
// Category must be in the list
category: { operator: 'in', value: ['electronics', 'books'] }
});
// With default AND logic:
// status = 'active' AND price >= 100 AND category IN ['electronics', 'books']
</script>More fixedFilters examples
Keys are API field names (or the keys your backend expects in advanceFilterJson fixed conditions). fixedFiltersLogic only accepts 'and' or 'or'; it controls how these entries combine with each other before the result is AND‑merged with user filters.
// OR: show rows that are either high priority OR escalated
// fixed-filters-logic="or"
{
priority: { operator: 'eq', value: 'high' },
escalated: { operator: 'eq', value: true },
}
// Text + range (AND by default)
{
tenantId: { operator: 'eq', value: 'tenant-uuid' },
name: { operator: 'contains', value: 'north' },
amount: { operator: 'lte', value: 5000 },
}
// Excluding a value
{
recordType: { operator: 'neq', value: 'template' },
}
// Date-style operators (must match what your API accepts for fixed conditions)
{
validFrom: { operator: 'lte', value: '2026-04-21' },
validTo: { operator: 'gte', value: '2026-04-01' },
}Note: Fixed filters are always applied alongside user-applied filters (column filters and advanced filters). The fixed filters are combined first using the specified logic (fixedFiltersLogic), and then combined with user filters using AND logic.
Print and Export
The grid includes a built-in print/export feature that allows users to export data in various formats while preserving current filters, sorting, and pagination settings.
Enabling Print/Export
There are two ways to enable print/export functionality:
Option 1: Built-in Print Button
To enable the built-in print button, set the isPrint prop to true:
<template>
<FtxGrid
id="products-grid"
:rows="products"
:columns="columns"
:pagination="pagination"
api-endpoint="/api/products"
:is-print="true"
@request="handleRequest"
/>
</template>This will display a print button in the grid toolbar that opens a dialog for selecting export format and scope.
Option 2: Programmatic Print Method
For custom print buttons or programmatic control, use the exposed print method. This opens the same print dialog that appears when clicking the built-in print button:
<template>
<div>
<FtxGrid
ref="gridRef"
id="products-grid"
:rows="products"
:columns="columns"
:pagination="pagination"
api-endpoint="/api/products"
/>
<q-btn @click="openPrintDialog">Export</q-btn>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { FtxGrid } from '@ftx/ui'
const gridRef = ref(null)
const openPrintDialog = () => {
// Opens the print dialog - same as clicking the built-in print button
gridRef.value.print()
}
</script>Print Method Signature:
print(): voidDescription:
The print method opens the print/export dialog, allowing users to select:
- Export Format: PDF, Excel, CSV, or Print
- Export Scope: Current Page or All Records
This is the same dialog that appears when using the built-in print button (isPrint prop). The method takes no parameters - all selections are made by the user in the dialog.
Example: Custom Print Button with extraPrintParams
<template>
<div>
<FtxGrid
ref="gridRef"
id="products-grid"
:columns="columns"
:rows="products"
api-endpoint="/api/products"
:extra-print-params="extraPrintParams"
/>
<q-btn @click="handleCustomExport" icon="print">Export</q-btn>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { FtxGrid } from '@ftx/ui'
const gridRef = ref(null)
const extraPrintParams = ref({
printHeader: {
warehouse: "WAREHOUSE B",
definitionName: "PRODUCT DEFINITION",
status: "IN PROGRESS"
}
})
const handleCustomExport = () => {
// Opens print dialog - user selects format and scope
gridRef.value.print()
}
</script>Note: When the export is executed (after user selects format/scope in dialog), it automatically includes:
- Current visible columns (respects
excludeFromApi) - Current filters (column filters and advanced filters)
- Current sorting configuration
extraPrintParams(if provided)- Column aliases for export headers
Requirements
apiEndpoint- Must be provided for the export functionality to workapiInstance- API instance must be available (provided via prop, store, or global properties)
Export Formats
The print dialog supports the following export formats:
| Format | Value | Description |
|---|---|---|
0 | PDF file download | |
| Excel | 1 | Excel (.xlsx) file download |
| CSV | 2 | CSV file download |
3 | Inline PDF for direct printing (Current Page only) |
Export Scope
Users can choose to export:
- Current Page - Exports only the records visible on the current page
- All Records - Exports all records matching the current filters (may generate a ZIP file for large exports)
Features
The export functionality automatically includes:
- Current Filters - All applied column filters and advanced filters
- Sorting - Current sort configuration
- Column Selection - Only visible columns are exported
- Column Aliases - Column labels are used as headers in exported files
- Pagination Settings - Used for "Current Page" scope
API Instance Resolution
The export feature uses the same API instance resolution as the listing functionality, in the following order:
props.apiInstance- Explicitly passed API instanceftxGridStore.apiInstance- API instance set via storeapp.config.globalProperties.$api- Global API instance
<script setup>
import { createApp } from 'vue'
import { api } from './api'
const app = createApp({})
// Set global API instance (recommended approach)
app.config.globalProperties.$api = api
// Or pass explicitly to grid
const apiInstance = api
</script>
<template>
<!-- Will use global $api automatically -->
<FtxGrid
id="products-grid"
api-endpoint="/api/products"
:is-print="true"
/>
<!-- Or pass explicitly -->
<FtxGrid
id="products-grid"
api-endpoint="/api/products"
:api-instance="apiInstance"
:is-print="true"
/>
</template>Example: Full Configuration
<template>
<FtxGrid
id="products-grid"
:rows="products"
:columns="columns"
:pagination="pagination"
api-endpoint="/api/products"
:api-instance="apiInstance"
:is-print="true"
:table-title="'Product List'"
@request="handleRequest"
/>
</template>
<script setup>
import { ref } from 'vue'
import { FtxGrid } from '@ftx/ui'
import { api } from './api'
const apiInstance = api
const columns = [
{ name: 'id', label: 'ID', field: 'id' },
{ name: 'name', label: 'Product Name', field: 'name' },
{ name: 'price', label: 'Price', field: 'price' },
{ name: 'stock', label: 'Stock', field: 'stock' }
]
function handleRequest(props) {
// Handle grid data request
}
</script>Print Fields
For print/export operations, you can specify different field names than those used for display and filtering by using the printFields property in your column definitions. This is useful when:
- The API expects different field names for export than for display
- You want to export raw field values instead of formatted/display values
- You need to map display fields to different backend field names for exports
How it works:
- If
printFieldsis specified on a column, it will be used for print/export operations - If
printFieldsis not specified, the component falls back tofields - If neither
printFieldsnorfieldsis specified, the columnnameis used
Example:
<script setup>
const columns = [
{
name: 'customerName',
label: 'Customer Name',
field: (row) => `${row.firstName} ${row.lastName}`, // Display: formatted name
fields: 'firstName,lastName', // Filtering: search both fields
printFields: 'customerName', // Export: use single backend field
},
{
name: 'price',
label: 'Price',
field: 'price',
format: (value) => `$${value.toFixed(2)}`, // Display: formatted currency
printFields: 'basePrice', // Export: use different backend field
},
{
name: 'status',
label: 'Status',
field: 'status',
printFields: 'statusCode', // Export: use statusCode instead of status
}
]
</script>Note: The printFields value is used for both the columns parameter (field names sent to API) and the ColumnAliases mapping (which maps field names to column labels) in export requests.
Excluding columns from API (excludeFromApi)
Columns that are derived or calculated (e.g. a column that shows the SUM of two other columns) do not exist as backend fields. Sending them in the columns parameter can cause API errors. Use excludeFromApi: true on the column definition so that column is not included in:
- The
columnsquery parameter for listing requests - The
columnsparameter andColumnAliasesfor print/export requests
The column still appears in the grid, in column visibility, and in sorting/filtering UI; only the API payload excludes it.
Example: calculated column
<script setup>
const columns = [
{ name: 'amountA', label: 'Amount A', field: 'amountA', sortable: true },
{ name: 'amountB', label: 'Amount B', field: 'amountB', sortable: true },
{
name: 'totalAmount',
label: 'Total',
field: (row) => (row.amountA ?? 0) + (row.amountB ?? 0),
excludeFromApi: true, // not sent to backend; avoids errors for non-existent field
},
]
</script>Column definition options (API-related):
| Option | Type | Description |
|---|---|---|
excludeFromApi | Boolean | If true, this column is omitted from the columns parameter and from export ColumnAliases. Use for derived/calculated columns. |
printFields | String | Field name(s) used for export and API column mapping; see Print Fields above. |
Export Parameters
When exporting, the following parameters are sent to the API:
Export: true- Indicates this is an export requestExportFormat- Format number (0=PDF, 1=Excel, 2=CSV, 3=Print)ExportScope- Scope number (0=Current Page, 1=All)IsZipExport- Boolean indicating if export will be zipped (for large exports)page,pageSize,sorts- Current pagination and sortingadvanceFilterJson- Current filters (if any)columns- Visible column field names (usesprintFieldsif available, otherwisefields)ColumnAliases- Object mapping field names to labels (usesprintFieldsif available, otherwisefields)- Any properties from
extraPrintParams- Merged into the request parameters
Extra Print Parameters (extraPrintParams)
Use the extraPrintParams prop to pass additional data to the print/export API that is not related to the grid's internal state (filters, sorting, columns). This is useful for passing contextual information like headers, metadata, or other print-specific data.
Example: Passing printHeader
<template>
<FtxGrid
id="products-grid"
:columns="columns"
:rows="products"
api-endpoint="/api/products"
:is-print="true"
:extra-print-params="extraPrintParams"
/>
</template>
<script setup>
import { ref } from 'vue'
import { FtxGrid } from '@ftx/ui'
const extraPrintParams = ref({
printHeader: {
warehouse: "WAREHOUSE B",
definitionName: "PRODUCT DEFINITION",
status: "IN PROGRESS",
startedBy: "CHRISTOPHER PEARSON",
startedDate: "2026-01-29T02:39:00Z",
finishedBy: null,
finishedDate: null,
reviewedBy: null,
reviewedDate: null
}
})
</script>How it works:
- All properties from
extraPrintParamsare merged into the API request parameters - This happens after all grid-related parameters (columns, filters, sort, etc.) are built
- You can pass any object structure your API expects
- The object is merged using
Object.assign, so properties will override if there are conflicts (though this is unlikely with standard grid parameters)
Use cases:
- Print headers/metadata (e.g.
printHeaderwith warehouse, definition name, status, etc.) - Additional context for exports (e.g. report type, user information, date ranges)
- Custom parameters required by your backend API for print/export operations
Notes
- The Print format (inline PDF) is only available for "Current Page" scope
- Large exports (All Records) may be automatically packaged as ZIP files
- Export requests preserve all current grid state (filters, sorting, pagination)
- The API endpoint should handle the export parameters and return the appropriate file format
Slots
FtxGrid provides several slots for customizing the grid layout and cell rendering.
Top Toolbar Slots
top-left-buttons - Add buttons on the left side of the top toolbar (before Columns/Filters buttons):
<FtxGrid id="grid" :columns="columns" :rows="rows">
<template #top-left-buttons>
<q-btn label="Import" icon="upload" @click="importData" />
<q-btn label="Export" icon="download" @click="exportData" />
</template>
</FtxGrid>top-buttons - Add buttons on the right side of the top toolbar (after Add button):
<FtxGrid id="grid" :columns="columns" :rows="rows">
<template #top-buttons>
<q-btn label="Refresh" icon="refresh" @click="refreshGrid" />
<q-btn label="Settings" icon="settings" @click="openSettings" />
</template>
</FtxGrid>top-title-left-buttons - Add buttons next to the grid title (only in non-collapsible mode):
<FtxGrid id="grid" :columns="columns" :rows="rows" :is-collapsible="false">
<template #top-title-left-buttons>
<q-btn label="Info" icon="info" flat />
</template>
</FtxGrid>Custom Cell Rendering
body-cell-${column.name} - Custom rendering for specific columns. The slot receives props with row data and column definition:
<FtxGrid id="grid" :columns="columns" :rows="rows">
<!-- Custom rendering for 'status' column -->
<template #body-cell-status="props">
<q-td :props="props">
<q-badge :color="props.row.status === 'active' ? 'green' : 'red'">
{{ props.row.status }}
</q-badge>
</q-td>
</template>
<!-- Custom rendering for 'avatar' column -->
<template #body-cell-avatar="props">
<q-td :props="props">
<q-avatar>
<img :src="props.row.avatar" />
</q-avatar>
</q-td>
</template>
<!-- Custom rendering for 'price' column with formatting -->
<template #body-cell-price="props">
<q-td :props="props">
<span class="text-weight-bold text-primary">
${{ props.row.price.toFixed(2) }}
</span>
</q-td>
</template>
<!-- Custom rendering for 'tags' column -->
<template #body-cell-tags="props">
<q-td :props="props">
<q-chip
v-for="tag in props.row.tags"
:key="tag"
:label="tag"
size="sm"
class="q-mr-xs"
/>
</q-td>
</template>
<!-- Custom rendering for 'actions' column -->
<template #body-cell-actions="props">
<q-td :props="props">
<q-btn
dense
flat
icon="edit"
@click="editRow(props.row)"
class="q-mr-xs"
/>
<q-btn
dense
flat
icon="delete"
color="negative"
@click="deleteRow(props.row)"
/>
</q-td>
</template>
</FtxGrid>Slot Props: The body-cell-${column.name} slot receives the following props:
row- The current row data objectrowIndex- The index of the current rowcol- The column definition objectcolumn- The column configuration (includesfield,label,name, etc.)value- The cell valueselected- Whether the row is selected (if selection is enabled)- Other Quasar QTable cell props
Using Field Functions in Custom Slots
When your column definition uses a field function instead of a field path, you can access it through the column prop:
<script setup>
import { isFunction } from '@ftx/utils'
const columns = [
{
name: 'location',
label: 'Location Name',
field: (row) => {
// Dynamic field function that computes the display value
const allLocations = getLocationHierarchy()
return buildDisplayPath(allLocations, row.id)
},
sortable: true,
filter: true,
}
]
</script>
<template>
<FtxGrid id="grid" :columns="columns" :rows="rows">
<!-- Access the field function through column prop -->
<template #body-cell-location="{ row, column }">
<q-td>
<div class="status-badge">
<!-- Call the field function if it exists -->
<template v-if="isFunction(column.field)">
{{ column.field(row) }}
</template>
<template v-else>
{{ row[column.field] || '-' }}
</template>
</div>
</q-td>
</template>
</FtxGrid>
</template>Custom Body (Advanced)
body - Override the entire table body for complete control:
<FtxGrid id="grid" :columns="columns" :rows="rows">
<template #body>
<q-tr v-for="row in rows" :key="row.id">
<q-td>{{ row.name }}</q-td>
<q-td>{{ row.email }}</q-td>
<q-td>
<q-badge :color="row.status === 'active' ? 'green' : 'red'">
{{ row.status }}
</q-badge>
</q-td>
<!-- Custom body implementation -->
</q-tr>
</template>
</FtxGrid>Note: When using the body slot, you have full control over the table body rendering. Make sure to maintain proper table structure with q-tr and q-td components.
Bottom Row (Totals/Summary)
bottom-row - Add a custom row at the bottom of the table, typically used for displaying totals, summaries, or aggregated data:
<FtxGrid id="grid" :columns="columns" :rows="rows">
<template #bottom-row="props">
<q-tr>
<q-td colspan="2" class="text-weight-bold text-right">
Total:
</q-td>
<q-td class="text-weight-bold">
${{ calculateTotal(props.rows) }}
</q-td>
<q-td class="text-weight-bold">
{{ calculateAverage(props.rows) }}
</q-td>
<q-td></q-td>
</q-tr>
</template>
</FtxGrid>Slot Props: The bottom-row slot receives the following props:
columns- Array of column definitionsrows- Array of current row data- Other Quasar QTable props
Example: Displaying Totals
<template>
<FtxGrid id="sales-grid" :columns="columns" :rows="sales" :pagination="pagination">
<template #bottom-row="props">
<q-tr class="bg-grey-2">
<q-td colspan="3" class="text-weight-bold text-right">
Grand Total:
</q-td>
<q-td class="text-weight-bold text-primary">
${{ formatCurrency(calculateTotal(props.rows)) }}
</q-td>
<q-td></q-td>
</q-tr>
</template>
</FtxGrid>
</template>
<script setup>
import { ref } from 'vue';
import { FtxGrid } from '@ftx/ui';
const sales = ref([
{ id: 1, product: 'Widget A', quantity: 10, price: 25.00 },
{ id: 2, product: 'Widget B', quantity: 5, price: 30.00 }
]);
const columns = [
{ name: 'id', label: 'ID', field: 'id' },
{ name: 'product', label: 'Product', field: 'product' },
{ name: 'quantity', label: 'Quantity', field: 'quantity' },
{ name: 'price', label: 'Price', field: 'price' },
{ name: 'actions', label: 'Actions', field: 'actions' }
];
function calculateTotal(rows) {
return rows.reduce((sum, row) => sum + (row.quantity * row.price), 0);
}
function formatCurrency(value) {
return value.toFixed(2);
}
</script>Note: Make sure to use colspan appropriately to align cells with your column structure. The number of q-td elements should match your column count, or use colspan to span multiple columns.
Field Functions
Field functions allow you to compute or transform cell values dynamically. They are fully supported for display, sorting, and filtering operations.
Basic Usage
A field can be a string path (e.g., 'user.name') or a function that receives the row data:
const columns = [
{
name: 'name',
label: 'User Name',
field: (row) => row.firstName + ' ' + row.lastName,
sortable: true,
filter: true
},
{
name: 'location',
label: 'Location Path',
field: (row) => {
// Build a hierarchical path
return buildLocationPath(row.locationId)
},
sortable: true,
filter: true
}
]Sorting with Field Functions
When a column has a field function and sortable: true, the grid will:
- Call the field function on each row to get the value
- Sort rows based on those computed values
- Compare values using standard string/number comparison
{
name: 'fullName',
label: 'Full Name',
field: (row) => `${row.firstName} ${row.lastName}`,
sortable: true // ✅ Sorting works with field functions
}Filtering with Field Functions
When a column has a field function and filter: true, the grid will:
- Call the field function on each row to get the display value
- Compare the computed value against the filter criteria
- Support all filter operators (contains, equals, startswith, etc.)
{
name: 'status',
label: 'Status Badge',
field: (row) => {
if (row.active && !row.archived) return 'Active'
if (row.archived) return 'Archived'
return 'Inactive'
},
filter: true // ✅ Filtering works with field functions
}Complete Example with Field Functions
<template>
<FtxGrid
id="locations-grid"
:rows="locations"
:columns="columns"
:pagination="pagination"
@request="handleRequest"
>
<!-- Custom slot with field function support -->
<template #body-cell-location="{ row, column }">
<q-td>
<div class="location-path">
<!-- Display the computed field value -->
<span v-if="isFunction(column.field)">
{{ column.field(row) }}
</span>
<span v-else>
{{ row[column.field] }}
</span>
</div>
</q-td>
</template>
</FtxGrid>
</template>
<script setup>
import { ref } from 'vue'
import { isFunction } from '@ftx/utils'
import { FtxGrid } from '@ftx/ui'
const locations = ref([
{ id: 1, name: 'New York', parentId: null },
{ id: 2, name: 'Brooklyn', parentId: 1 },
{ id: 3, name: 'Manhattan', parentId: 1 }
])
const locationStore = ref({ location: locations.value })
// Helper to build full path
const buildLocationPath = (locations, id, parentIdKey, displayKey) => {
// Recursively build the path
const location = locations.find(l => l.id === id)
if (!location) return '-'
if (location[parentIdKey]) {
const parent = buildLocationPath(locations, location[parentIdKey], parentIdKey, displayKey)
return `${parent} > ${location[displayKey]}`
}
return location[displayKey]
}
const columns = [
{
name: 'name',
label: 'Location Name',
field: (row) => {
// Dynamic field function that computes display value
const allLocations = locationStore.value.location.map((item) => ({
...item,
displayName: `${item.locationType?.name || ''}-${item.name}`,
}))
const path = buildLocationPath(allLocations, row?.id, 'parentId', 'displayName')
return path && path !== '-' ? path : row.name || '-'
},
sortable: true, // ✅ Sorting works
filter: true, // ✅ Filtering works
align: 'left'
},
{
name: 'type',
label: 'Type',
field: 'locationType.name',
sortable: true
}
]
const pagination = ref({ page: 1, rowsPerPage: 10 })
function handleRequest() {
// Handle pagination, filtering, sorting
}
</script>Key Points
- Sorting: When sorting by a column with a field function, the grid calls the function to get the value for comparison
- Filtering: When filtering a column with a field function, the grid calls the function to get the display value for comparison
- Performance: For large datasets, consider memoizing field functions or caching computed values if they're expensive operations
- Error Handling: If a field function throws an error, it will be caught and logged with a warning, returning
undefined - Custom Slots: Access the field function through the
columnprop in slot templates to display the same computed value
Column Sort Keys (sortKeys)
Use sortKeys when the sort field sent to backend (or used in local sorting fallback) should be different from the column fields.
Sort precedence:
field(if it is a string)sortKeys(first key if comma-separated)fields(first key if comma-separated)name(fallback)
Example:
const columns = [
{
name: 'locationName',
label: 'Location',
field: 'displayName',
fields: 'displayName,code',
sortKeys: 'locationSortName', // used for sorting before `fields`
sortable: true,
filter: true,
},
]Exposed Methods
FtxGrid exposes several methods that can be accessed via a template ref:
<template>
<FtxGrid ref="gridRef" id="grid" :columns="columns" :rows="rows" />
</template>
<script setup>
import { ref } from 'vue'
import { FtxGrid } from '@ftx/ui'
const gridRef = ref(null)
// Access methods
gridRef.value.refreshGrid()
gridRef.value.print()
gridRef.value.clearAllFilter()
</script>Available Methods
| Method | Description | Parameters | Returns |
|---|---|---|---|
refreshGrid() | Refreshes the grid data by triggering a new request | - | void |
print() | Opens the print/export dialog (same as clicking built-in print button) | - | void |
clearAllFilter() | Clears all applied filters (column filters and advanced filters) | - | void |
Method Details
refreshGrid()
Refreshes the grid by triggering a new data request with current pagination, filters, and sorting.
<script setup>
const gridRef = ref(null)
const handleRefresh = () => {
gridRef.value.refreshGrid()
}
</script>print()
Opens the print/export dialog programmatically. This is the same dialog that appears when clicking the built-in print button. Users can select the export format (PDF, Excel, CSV, Print) and scope (Current Page, All Records) from the dialog.
See Programmatic Print Method above for detailed usage and examples.
clearAllFilter()
Clears all filters applied to the grid (both column filters and advanced filters).
<script setup>
const gridRef = ref(null)
const handleClearFilters = () => {
gridRef.value.clearAllFilter()
}
</script>Exposed Properties
| Property | Description | Type |
|---|---|---|
rows | Current grid rows data | Computed<Array> |