Skip to content

FtxGrid

A comprehensive data grid component with filtering, sorting, pagination, and more.

Props

PropTypeDefaultDescription
idStringnullUnique grid identifier (required in practice)
rowKeyString'id'Property to use as row key
tableTitleString''Title shown in the grid header
tableSubTitleString''Subtitle under the title
rowsArraynullData rows (omit or use with apiEndpoint / store)
columnsArray[]Column definitions
chooseColumnBooleantrueEnable column chooser UI
advanceFilterBooleantrueEnable advanced filtering
onRequestFunctionnullIf set, grid calls this instead of built-in API request (full control)
paginationObjectnullPagination state; when set, grid emits update:pagination on changes
apiEndpointStringnullAPI URL for listing, delete (when deleteApiEndpoint unset), and print/export
deleteApiEndpointStringnullOptional delete URL; defaults to base path derived from apiEndpoint
loadingBooleanfalseLoading state
isCollapsibleBooleanfalseCollapsible card layout with show/hide
defaultExpandedBooleanfalseWhen collapsible, start expanded
renderGridBooleantrueWhen false, grid body can stay hidden until expanded
placeholderString''Placeholder when grid has no title/content context
selectionString'none''none', 'single', or 'multiple'
handleDeleteActionBooleanfalseEnable row delete action (uses confirm + deleteApiEndpoint / apiEndpoint)
deleteConfirmationMsgString'Are you sure...'Confirm dialog message for delete
deleteSuccessMsgString'Item deleted successfully.'Toast after successful delete
editActionObject | String | FunctionnullEdit action: function, route path, or route object
addLabelString'Add New'Add button label
addLinkObject | StringnullAdd button route or link
addActionFunctionnullAdd button handler
addActionLoadingBooleanfalseLoading state on add button
rowFilterBooleanfalseRow-based filter UI (only when advanced filter is off)
cellSeparatorBooleanfalseVisual separator between cells
additionalClassString''Extra CSS class on the grid root
defaultColumnsInApiBooleantrueWhen true, listing and print/export send a columns parameter built from visible columns (respects excludeFromApi, printFields). Set false to omit that parameter
defaultFiltersObjectnullInitial column filters: keys are column name, values are { operator, value } (see With Default Filters)
defaultAdvanceFilterObjectnullInitial advanced filter value. Supports API-style shape and internal dialog/store shape (see With Default Advanced Filter)
defaultSortObjectnullInitial sort map: keys are column name, values are { descending: boolean, field?: string } (see Default sort)
fixedFiltersObjectnullFilters always applied; same { operator, value } shape per field key (see With Fixed Filters)
fixedFiltersLogicString'and'How fixed filter conditions combine: 'and' or 'or' only
customSelectedRowsArray[]Pre-selected rows when using selection
apiInstanceObjectnullHTTP/API client for requests (overrides store / $api resolution)
isPrintBooleanfalseShow print/export toolbar control
extraPrintParamsObjectnullMerged into print/export request (e.g. printHeader)

Events

EventPayloadWhen
update:paginationObjectParent controls pagination; user changes page or rows per page
tableVisibleBooleanCollapsible 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:itemRow delete succeeded
select-allArray (current rows)Header control to select all on current view
update:tabDeclared for tab integrations
update:searchTextDeclared for search integrations
update:selectedDeclared for selection v-model style integrations

Slots

Slot NameDescriptionProps
top-left-buttonsButtons on the left side of the top toolbar (before Columns/Filters buttons)-
top-buttonsButtons on the right side of the top toolbar (after Add button)-
top-title-left-buttonsButtons next to the grid title (only in non-collapsible mode)-
bodyCustom 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-rowCustom row at the bottom of the table (typically for totals/summaries)props - Contains columns, rows, and other table props

Usage

Basic Grid

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

javascript
import { useFtxGridStore } from '@ftx/ui';

const gridStore = useFtxGridStore('grid-id');
// Access grid state and methods

With Default Filters

Use defaultFilters to pre-apply column filters when the grid loads.

vue
<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.

vue
<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 (createdAt above) must match the column name.
  • Operator must be exactly between (no trailing spaces).
  • Date format should match what your API expects.
  • between is translated to gte and lte in 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:

javascript
// 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

vue
<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

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

vue
<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 }.
  • field can be a column name or a key from column fields.
  • It also accepts internal advanced dialog/store shape (with field object + operator object), useful if you persist and restore user filters.
  • Works with text, number, date/datetime, select, and ftx-select (including multi-select arrays).
  • Users can still open Advanced Filters and edit/remove defaults manually.
  • clearAllFilter() resets advanced filters back to defaultAdvanceFilter if provided.
  • When using apiEndpoint (not onRequest), the grid automatically runs a listing request after defaults are applied—including when defaults become resolvable later (for example filter_options loaded 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:

PropertyTypeDescription
descendingBooleanfalse = ascending, true = descending
fieldString (optional)Override sort field sent to the API / used in local sort (otherwise derived from column field / sortKeys / fields / name)
vue
<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).

vue
<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

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

vue
<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

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

vue
<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.

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

javascript
{
  fieldName: {
    operator: 'eq',  // Filter operator (eq, ne, gt, gte, lt, lte, contains, etc.)
    value: 'value'    // Filter value
  }
}

Example with multiple filters:

vue
<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.

javascript
// 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.

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:

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

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

typescript
print(): void

Description:

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

vue
<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 work
  • apiInstance - API instance must be available (provided via prop, store, or global properties)

Export Formats

The print dialog supports the following export formats:

FormatValueDescription
PDF0PDF file download
Excel1Excel (.xlsx) file download
CSV2CSV file download
Print3Inline 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:

  1. props.apiInstance - Explicitly passed API instance
  2. ftxGridStore.apiInstance - API instance set via store
  3. app.config.globalProperties.$api - Global API instance
vue
<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

vue
<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>

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 printFields is specified on a column, it will be used for print/export operations
  • If printFields is not specified, the component falls back to fields
  • If neither printFields nor fields is specified, the column name is used

Example:

vue
<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 columns query parameter for listing requests
  • The columns parameter and ColumnAliases for 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

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

OptionTypeDescription
excludeFromApiBooleanIf true, this column is omitted from the columns parameter and from export ColumnAliases. Use for derived/calculated columns.
printFieldsStringField 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 request
  • ExportFormat - 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 sorting
  • advanceFilterJson - Current filters (if any)
  • columns - Visible column field names (uses printFields if available, otherwise fields)
  • ColumnAliases - Object mapping field names to labels (uses printFields if available, otherwise fields)
  • 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

vue
<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 extraPrintParams are 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. printHeader with 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):

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

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

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

vue
<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 object
  • rowIndex - The index of the current row
  • col - The column definition object
  • column - The column configuration (includes field, label, name, etc.)
  • value - The cell value
  • selected - 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:

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

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

vue
<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 definitions
  • rows - Array of current row data
  • Other Quasar QTable props

Example: Displaying Totals

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

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

  1. Call the field function on each row to get the value
  2. Sort rows based on those computed values
  3. Compare values using standard string/number comparison
javascript
{
  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:

  1. Call the field function on each row to get the display value
  2. Compare the computed value against the filter criteria
  3. Support all filter operators (contains, equals, startswith, etc.)
javascript
{
  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

vue
<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 column prop 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:

  1. field (if it is a string)
  2. sortKeys (first key if comma-separated)
  3. fields (first key if comma-separated)
  4. name (fallback)

Example:

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

vue
<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

MethodDescriptionParametersReturns
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.

vue
<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).

vue
<script setup>
const gridRef = ref(null)

const handleClearFilters = () => {
  gridRef.value.clearAllFilter()
}
</script>

Exposed Properties

PropertyDescriptionType
rowsCurrent grid rows dataComputed<Array>

Released under the MIT License.