Table
TkTable is a component that allows you to display data in a tabular manner. It's generally called a datatable.
- React
- Vue
- Angular
import { TkTable } from '@takeoff-ui/react'
import { TkTable } from '@takeoff-ui/vue'
import { TkTable } from '@takeoff-ui/angular'
Basic
The basic features of the TkTable component are demonstrated.
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
];
return (
<div style={{ padding: "8px" }}>
<TkTable columns={column} data={basicData} />
</div>
);
<script setup>
import { TkTable } from '@takeoff-ui/vue'
const column = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
];
</script>
<template>
<div :style="{ padding: '8px' }">
<TkTable :columns.prop="column" :data.prop="basicData" />
</div>
</template>
<div style="padding: 8px">
<tk-table
[columns]="[
{ field: 'id', header: 'Id' },
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' }
]"
[data]="[
{ id: 1, name: 'Product A', category: 'Electronics', quantity: 12 },
{ id: 2, name: 'Product B', category: 'Books', quantity: 8 },
{ id: 3, name: 'Product C', category: 'Groceries', quantity: 20 }
]"
/>
</div>
Striped
To enable striped mode, set the striped prop to true. This feature displays rows with alternating background colors.
- React
- Vue
- Angular
<TkTable columns={column} data={basicData} striped />
<TkTable :columns.prop="column" :data.prop="basicData" striped />
<tk-table
[columns]="[
{ field: 'id', header: 'Id' },
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' }
]"
[data]="[
{ id: 1, name: 'Product A', category: 'Electronics', quantity: 12 },
{ id: 2, name: 'Product B', category: 'Books', quantity: 8 },
{ id: 3, name: 'Product C', category: 'Groceries', quantity: 20 }
]"
striped
/>
Header Type
The headerType prop is used to change the style of the table headers and allows you to customize the headers' style with values such as basic, primary, and dark.
headerType is basic
- React
- Vue
- Angular
<TkTable columns={column} data={headerTypeData} headerType="basic" />
<TkTable :columns.prop="column" :data.prop="headerTypeData" headerType="basic" />
<tk-table
[columns]="[
{ field: 'id', header: 'Id' },
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' }
]"
[data]="[
{ id: 1, name: 'Product A', category: 'Electronics', quantity: 12 },
{ id: 2, name: 'Product B', category: 'Books', quantity: 8 },
{ id: 3, name: 'Product C', category: 'Groceries', quantity: 20 }
]"
headerType="basic"
/>
Selection
This feature allows users to select rows in the table. The selected rows are visually highlighted and can be used for various actions.
[{"id":"h456wer53","name":"Bracelet","category":"Clothing","quantity":45}]
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
];
const [selectionList, setSelectionList] = useState();
const [mode, setMode] = useState();
return (
<div style={{ padding: "8px" }}>
<TkTable
columns={column}
data={basicData}
dataKey="id"
selection={selectionList}
selectionMode={mode}
onTkSelectionChange={(e: CustomEvent) =>
setSelectionList(e.detail)
}
/>
<p>{JSON.stringify(selectionList)}</p>
</div>
);
<script setup>
import { TkTable } from '@takeoff-ui/vue'
import { ref } from 'vue';
const column = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
];
const selectionList = ref([
{
id: 'h456wer53',
name: 'Bracelet',
category: 'Clothing',
quantity: 45,
},
]);
</script>
<template>
<div :style="{ padding: '8px' }">
<TkTable
:columns.prop="column"
:data.prop="basicData"
dataKey="id"
:selection="selectionList"
selectionMode.prop="mode"
@tkSelectionChange="e => (selectionList.value = e.detail)"
/>
<p>{{ JSON.stringify(selectionList) }}</p>
</div>
</template>
Filter and Sorting
The table supports three types of filtering:
- Text input filtering: Allows free text search in columns
- Checkbox filtering: Enables selecting multiple values from predefined options
- Radio filtering: Allows selecting a single value from predefined options
The filtering type can be configured using the filterType property in column definition. For checkbox and radio filters, you need to provide filterOptions with value-label pairs.
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
field: 'id',
header: 'Id',
},
{
field: 'name',
header: 'Input Filter',
searchable: true,
sortable: true,
sorter: (a: any, b: any) => (a.name > b.name ? 1 : -1),
filter: (value: string, row: any) =>
row.name
.toString()
.toLowerCase()
.indexOf(value.toString().toLowerCase() as string) > -1,
},
{
field: 'status',
header: 'Checkbox Filter',
searchable: true,
filterType: 'checkbox',
filterOptions: [
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' },
{ value: 'pending', label: 'Pending' },
],
filterElements: {
icon: 'filter_list',
optionsSearchInput: { show: true, placeholder: 'Filter', emptyMessage: 'No results found' },
},
},
{
field: 'group',
header: 'Radio Filter',
searchable: true,
filterType: 'radio',
filterOptions: [
{ value: 'group 1', label: 'Group 1' },
{ value: 'group 2', label: 'Group 2' },
{ value: 'group 3', label: 'Group 3' },
],
filterElements: {
optionsSearchInput: { show: true, placeholder: 'Filter' },
},
},
{
field: 'quantity',
header: 'Quantity',
sortable: true,
sorter: (a: any, b: any) =>
Number(a.quantity) > Number(b.quantity) ? 1 : -1,
},
{
field: 'date',
header: 'Date',
sortable: true,
searchable: true,
sorter: (a: any, b: any) => (a.date > b.date ? 1 : -1),
filterType: 'datepicker',
filterElements: {
optionsSearchDatepicker: {
label: 'Choose a date',
dateFormat: 'yyyy-MM-dd',
size: 'small',
},
},
},
];
const data = [
{
id: 'f230fh0g3',
name: 'Bamboo Watch',
status: 'active',
group: 'group 1',
quantity: 24,
date: '2025-09-07',
},
{
id: 'nvklal433',
name: 'Black Watch',
status: 'inactive',
group: 'group 2',
quantity: 42,
date: '2025-09-08',
},
{
id: 'zz21cz3c1',
name: 'Blue Band',
status: 'active',
group: 'group 3',
quantity: 87,
date: '2025-09-09',
},
{
id: '244wgerg2',
name: 'Blue T-Shirt',
status: 'pending',
group: 'group 1',
quantity: 12,
date: '2025-09-10',
},
{
id: 'h456wer53',
name: 'Bracelet',
status: 'inactive',
group: 'group 2',
quantity: 45,
date: '2025-09-11',
},
];
return <TkTable columns={column} data={data} />;
<script setup>
import { TkTable } from '@takeoff-ui/vue';
const column = [
{
field: 'id',
header: 'Id',
},
{
field: 'name',
header: 'Input Filter',
searchable: true,
sortable: true,
sorter: (a, b) => (a.name > b.name ? 1 : -1),
filter: (value, row) =>
row.name
.toString()
.toLowerCase()
.indexOf(value.toString().toLowerCase()) > -1,
},
{
field: 'status',
header: 'Checkbox Filter',
searchable: true,
filterType: 'checkbox',
filterOptions: [
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' },
{ value: 'pending', label: 'Pending' },
],
filterElements: {
icon: 'filter_list',
optionsSearchInput: { show: true, placeholder: 'Filter', emptyMessage: 'No results found' },
},
},
{
field: 'group',
header: 'Radio Filter',
searchable: true,
filterType: 'radio',
filterOptions: [
{ value: 'group 1', label: 'Group 1' },
{ value: 'group 2', label: 'Group 2' },
{ value: 'group 3', label: 'Group 3' },
],
filterElements: {
optionsSearchInput: { show: true, placeholder: 'Filter' },
},
},
{
field: 'quantity',
header: 'Quantity',
sortable: true,
sorter: (a, b) => (Number(a.quantity) > Number(b.quantity) ? 1 : -1),
},
{
field: 'date',
header: 'Date',
sortable: true,
searchable: true,
sorter: (a: any, b: any) => (a.date > b.date ? 1 : -1),
filterType: 'datepicker',
filterElements: {
optionsSearchDatepicker: {
label: 'Choose a date',
dateFormat: 'yyyy-MM-dd',
size: 'small',
},
},
},
];
const data = [
{
id: 'f230fh0g3',
name: 'Bamboo Watch',
status: 'active',
group: 'group 1',
quantity: 24,
date: '2025-09-07',
},
{
id: 'nvklal433',
name: 'Black Watch',
status: 'inactive',
group: 'group 2',
quantity: 42,
date: '2025-09-08',
},
{
id: 'zz21cz3c1',
name: 'Blue Band',
status: 'active',
group: 'group 3',
quantity: 87,
date: '2025-09-09',
},
{
id: '244wgerg2',
name: 'Blue T-Shirt',
status: 'pending',
group: 'group 1',
quantity: 12,
date: '2025-09-10',
},
{
id: 'h456wer53',
name: 'Bracelet',
status: 'inactive',
group: 'group 2',
quantity: 45,
date: '2025-09-11',
},
];
</script>
<template>
<TkTable :columns.prop="column" :data.prop="data" />
</template>
Multi Sorting
This feature allows users to sort the table by multiple columns simultaneously. Users can click on the column headers to apply sorting, and the order of sorting can be adjusted by clicking on the headers in the desired sequence.
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
field: 'id',
header: 'Id',
},
{
field: 'name',
header: 'Name',
sortable: true,
sorter: (a: any, b: any) => {
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
},
},
{
field: 'status',
header: 'Status',
sortable: true,
sorter: (a: any, b: any) => {
if (a.status < b.status) return -1;
if (a.status > b.status) return 1;
return 0;
},
},
{
field: 'group',
header: 'Group',
sortable: true,
sorter: (a: any, b: any) => {
if (a.group < b.group) return -1;
if (a.group > b.group) return 1;
return 0;
},
},
{
field: 'quantity',
header: 'Quantity',
sortable: true,
sorter: (a: any, b: any) => {
if (a.quantity < b.quantity) return -1;
if (a.quantity > b.quantity) return 1;
return 0;
},
},
];
const data = [
{
id: 'zz21cz3c1',
name: 'Blue Band',
status: 'active',
group: 'group 3',
quantity: 87,
},
{
id: 'h456wer53',
name: 'Bracelet',
status: 'inactive',
group: 'group 2',
quantity: 45,
},
{
id: 'a789def12',
name: 'Blue Band',
status: 'pending',
group: 'group 1',
quantity: 24,
},
{
id: 'b123ghi34',
name: 'Blue Band',
status: 'inactive',
group: 'group 2',
quantity: 12,
},
{
id: 'c456jkl56',
name: 'Smart Watch',
status: 'active',
group: 'group 3',
quantity: 24,
},
{
id: 'e012pqr90',
name: 'Bracelet',
status: 'active',
group: 'group 1',
quantity: 45,
},
];
return <TkTable columns={column} data={data} multiSort={true} />;
<script setup>
import { TkTable } from '@takeoff-ui/vue';
const column = [
{
field: 'id',
header: 'Id',
},
{
field: 'name',
header: 'Name',
sortable: true,
sorter: (a, b) => {
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
},
},
{
field: 'status',
header: 'Status',
sortable: true,
sorter: (a, b) => {
if (a.status < b.status) return -1;
if (a.status > b.status) return 1;
return 0;
},
},
{
field: 'group',
header: 'Group',
sortable: true,
sorter: (a, b) => {
if (a.group < b.group) return -1;
if (a.group > b.group) return 1;
return 0;
},
},
{
field: 'quantity',
header: 'Quantity',
sortable: true,
sorter: (a, b) => {
if (a.quantity < b.quantity) return -1;
if (a.quantity > b.quantity) return 1;
return 0;
},
},
];
const data = [
{
id: 'zz21cz3c1',
name: 'Blue Band',
status: 'active',
group: 'group 3',
quantity: 87,
},
{
id: 'h456wer53',
name: 'Bracelet',
status: 'inactive',
group: 'group 2',
quantity: 45,
},
{
id: 'a789def12',
name: 'Blue Band',
status: 'pending',
group: 'group 1',
quantity: 24,
},
{
id: 'b123ghi34',
name: 'Blue Band',
status: 'inactive',
group: 'group 2',
quantity: 12,
},
{
id: 'c456jkl56',
name: 'Smart Watch',
status: 'active',
group: 'group 3',
quantity: 24,
},
{
id: 'e012pqr90',
name: 'Bracelet',
status: 'active',
group: 'group 1',
quantity: 45,
},
];
</script>
<template>
<TkTable :columns.prop="column" :data.prop="data" :multiSort.prop="true" />
</template>
Clear Filters and Sorting
This example demonstrates how to clear all filters and sorting settings programmatically using the clearFilters and clearSorting methods.
- React
- Vue
- Angular
const Example = () => {
const tableRef = useRef(null);
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
searchable: true,
sortable: true,
sorter: (a: any, b: any) => (a.name > b.name ? 1 : -1),
filter: (value: string, row: any) =>
row.name.toString().toLowerCase().indexOf(value.toString().toLowerCase() as string) > -1,
},
{
field: "category",
header: "Category",
searchable: true,
filterType: 'checkbox',
filterOptions: [
{ value: 'Accessories', label: 'Accessories' },
{ value: 'Clothing', label: 'Clothing' },
{ value: 'Fitness', label: 'Fitness' },
],
},
{
field: "quantity",
header: "Quantity",
sortable: true,
sorter: (a: any, b: any) =>
Number(a.quantity) > Number(b.quantity) ? 1 : -1,
},
];
const handleClearFilters = () => {
tableRef.current?.clearFilters();
// For server-side pagination, after clearing filters,
// serverRequest method is called to trigger tkRequest event
// tableRef.current?.serverRequest();
};
const handleClearSorting = () => {
tableRef.current?.clearSorting();
// For server-side pagination, after clearing filters,
// serverRequest method is called to trigger tkRequest event
// tableRef.current?.serverRequest();
};
return (
<div>
<div style={{ marginBottom: '1rem', display: 'flex', gap: '0.5rem' }}>
<TkButton onClick={handleClearFilters} label="Clear Filters"></TkButton>
<TkButton onClick={handleClearSorting} label="Clear Sorting"></TkButton>
</div>
<TkTable ref={tableRef} columns={column} data={basicData}/>
</div>
);
};
<script setup>
import { TkTable, TkButton } from '@takeoff-ui/vue'
import { ref } from 'vue'
const tableRef = ref(null)
const column = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
searchable: true,
sortable: true,
sorter: (a, b) => (a.name > b.name ? 1 : -1),
filter: (value, row) =>
row.name.toString().toLowerCase().indexOf(value.toString().toLowerCase()) > -1,
},
{
field: "category",
header: "Category",
searchable: true,
filterType: 'checkbox',
filterOptions: [
{ value: 'Accessories', label: 'Accessories' },
{ value: 'Clothing', label: 'Clothing' },
{ value: 'Fitness', label: 'Fitness' },
],
},
{
field: "quantity",
header: "Quantity",
sortable: true,
sorter: (a, b) =>
Number(a.quantity) > Number(b.quantity) ? 1 : -1,
},
];
const handleClearFilters = () => {
tableRef.value?.$el.clearFilters();
// For server-side pagination, after clearing filters,
// serverRequest method is called to trigger tkRequest event
// tableRef.value?.$el.serverRequest();
};
const handleClearSorting = () => {
tableRef.value?.$el.clearSorting();
// For server-side pagination, after clearing filters,
// serverRequest method is called to trigger tkRequest event
// tableRef.value?.$el.serverRequest();
};
</script>
<template>
<div>
<div style="margin-bottom: 1rem; display: flex; gap: 0.5rem">
<TkButton @click="handleClearFilters" label="Clear Filters"></TkButton>
<TkButton @click="handleClearSorting" label="Clear Sorting"></TkButton>
</div>
<TkTable ref="tableRef" :columns.prop="column" :data.prop="basicData" />
</div>
</template>
Pagination
Client side pagination
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
searchable: true,
sortable: true,
sorter: (a: any, b: any) => (a.name > b.name ? 1 : -1),
filter: (value: string, row: any) =>
row.name
.toString()
.toLowerCase()
.indexOf(value.toString().toLowerCase() as string) > -1,
},
{
field: "category",
header: "Category",
searchable: true,
sortable: true,
sorter: (a: any, b: any) => (a.category > b.category ? 1 : -1),
filter: (value: string, row: any) =>
row.category
.toString()
.toLowerCase()
.indexOf(value.toString().toLowerCase() as string) > -1,
},
{
field: "quantity",
header: "Quantity",
sortable: true,
sorter: (a: any, b: any) =>
Number(a.quantity) > Number(b.quantity) ? 1 : -1,
},
];
return (
<TkTable
columns={column}
data={data}
paginationMethod="client"
rowsPerPage={5}
totalItems={data.length}
/>
);
<script setup>
import { TkTable } from '@takeoff-ui/vue'
const data = [
{
id: "f230fh0g3",
name: "Bamboo Watch",
category: "Accessories",
quantity: 24,
},
{
id: "nvklal433",
name: "Black Watch",
category: "Onyama",
quantity: 42,
},
{
id: "zz21cz3c1",
name: "Blue Band",
category: "Accessories",
quantity: 87,
},
{
id: "244wgerg2",
name: "Blue T-Shirt",
category: "Fitness",
quantity: 12,
},
{
id: "h456wer53",
name: "Bracelet",
category: "Clothing",
quantity: 45,
},
{
id: "344wgerg2",
name: "Art Venere",
category: "Accessories",
quantity: 23,
},
{
id: "144wgerg3",
name: "Simona Morasca",
category: "Clothing",
quantity: 56,
},
{
id: "444wgerg6",
name: "Leota Dilliard",
category: "Fitness",
quantity: 89,
},
{
id: "k14wgerj1",
name: "Sage Wieser",
category: "Accessories",
quantity: 77,
},
{
id: "fq4wgergq",
name: "Kris Marrier",
category: "Clothing",
quantity: 65,
},
{
id: "764wger11",
name: "Abel Maclead",
category: "Clothing",
quantity: 61,
},
{
id: "08ge885f",
name: "Mattie Poquette",
category: "Fitness",
quantity: 42,
},
{
id: "wg57erg2",
name: "Meaghan Garufi",
category: "Accessories",
quantity: 99,
},
{
id: "264w3erg2",
name: "Gladys Rim",
category: "Magalhaes",
quantity: 92,
},
];
const column = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
searchable: true,
sortable: true,
sorter: (a, b) => (a.name > b.name ? 1 : -1),
filter: (value, row) =>
row.name.toString().toLowerCase().indexOf(value.toString().toLowerCase()) > -1,
},
{
field: "category",
header: "Category",
searchable: true,
sortable: true,
sorter: (a, b) => (a.category > b.category ? 1 : -1),
filter: (value, row) =>
row.category.toString().toLowerCase().indexOf(value.toString().toLowerCase()) > -1,
},
{
field: "quantity",
header: "Quantity",
sortable: true,
sorter: (a, b) =>
Number(a.quantity) > Number(b.quantity) ? 1 : -1,
},
];
</script>
<template>
<TkTable
:columns.prop="column"
:data.prop="data"
paginationMethod="client"
:rowsPerPage="5"
:totalItems="data.length"
/>
</template>
Server Side
Server side sorting, filter ang pagination example.
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
searchable: true,
sortable: true,
},
{
field: "category",
header: "Category",
searchable: true,
sortable: true,
},
{
field: "quantity",
header: "Quantity",
sortable: true,
},
];
const tableRef = useRef<HTMLTkTableElement>(null);
const [data, setData] = useState();
const [totalItem, setTotalItem] = useState();
const [rowsPerPage, setRowsPerPage] = useState(5);
const [loading, setLoading] = useState(false);
const handleRequest = async (e: TkTableCustomEvent<ITableRequest>) => {
setLoading(true);
const result: any = await fetchFromServer(
e.detail.currentPage,
e.detail.rowsPerPage,
e.detail.filters,
e.detail.sortField,
e.detail.sortOrder
);
setTotalItem(result?.totalItem);
setRowsPerPage(e.detail.rowsPerPage);
setData(result?.data);
setLoading(false);
};
useEffect(() => {
tableRef.current.serverRequest();
}, []);
return (
<>
<TkButton
icon="refresh"
variant="neutral"
type="text"
onTkClick={async () => {
setLoading(true);
const result: any = await fetchFromServer(1, 5, [], null, null);
setTotalItem(result?.totalItem);
setData(result?.data);
setLoading(false);
tableRef.current!.setCurrentPage(1);
}}
/>
<TkTable
ref={tableRef}
columns={column}
data={data}
paginationMethod="server"
rowsPerPage={rowsPerPage}
totalItems={totalItem}
loading={loading}
onTkRequest={handleRequest}
/>
</>
);
<script setup>
import { TkTable, TkButton } from '@takeoff-ui/vue'
import { ref, onMounted } from 'vue';
const column = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
searchable: true,
sortable: true,
},
{
field: "category",
header: "Category",
searchable: true,
sortable: true,
},
{
field: "quantity",
header: "Quantity",
sortable: true,
},
];
const tableRef = ref(null);
const data = ref();
const totalItem = ref();
const rowsPerPage = ref(5);
const loading = ref(false);
const handleRequest = async (e) => {
loading.value = true;
const result = await fetchFromServer(
e.detail.currentPage,
e.detail.rowsPerPage,
e.detail.filters,
e.detail.sortField,
e.detail.sortOrder
);
totalItem.value = result?.totalItem;
rowsPerPage.value = e.detail.rowsPerPage;
data.value = result?.data;
loading.value = false;
};
onMounted(() => {
tableRef.value.serverRequest();
});
const refreshData = async () => {
loading.value = true;
const result = await fetchFromServer(1, 5, [], null, null);
totalItem.value = result?.totalItem;
data.value = result?.data;
loading.value = false;
tableRef.value.setCurrentPage(1);
};
</script>
<template>
<TkButton
icon="refresh"
variant="neutral"
type="text"
@tkClick="refreshData"
/>
<TkTable
ref="tableRef"
:columns.prop="column"
:data.prop="data"
paginationMethod="server"
:rowsPerPage="rowsPerPage"
:totalItems="totalItem"
:loading="loading"
@tkRequest="handleRequest"
/>
</template>
Custom Cell
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
searchable: true,
sortable: true,
},
{
field: "category",
header: "Category",
searchable: true,
sortable: true,
html: (row) => {
return `<tk-badge label="${row.category}" ></tk-badge>`;
},
},
{
field: "quantity",
header: "Quantity",
sortable: true,
},
{
field: "-",
header: "Actions",
html: (row) => {
const tkButton: HTMLTkButtonElement =
document.createElement("tk-button");
tkButton.label = "Detail";
tkButton.type = "text";
tkButton.variant = "info";
tkButton.addEventListener("tk-click", () => {
alert("clicked row: " + JSON.stringify(row));
});
return tkButton;
},
},
];
const [data, setData] = useState();
const [totalItem, setTotalItem] = useState();
const [loading, setLoading] = useState(false);
const handleRequest = async (e: TkTableCustomEvent<ITableRequest>) => {
setLoading(true);
const result: any = await fetchFromServer(
e.detail.currentPage,
e.detail.rowsPerPage,
e.detail.filters,
e.detail.sortField,
e.detail.sortOrder
);
setData(result?.data);
setTotalItem(result?.totalItem);
setLoading(false);
};
return (
<TkTable
columns={column}
data={data}
paginationMethod="server"
rowsPerPage={5}
totalItems={totalItem}
loading={loading}
onTkRequest={handleRequest}
/>
);
<script setup>
import { TkTable } from '@takeoff-ui/vue'
import { ref } from 'vue';
const column = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
searchable: true,
sortable: true,
},
{
field: "category",
header: "Category",
searchable: true,
sortable: true,
html: (row) => {
return `<tk-badge label="${row.category}" ></tk-badge>`;
},
},
{
field: "quantity",
header: "Quantity",
sortable: true,
},
{
field: "-",
header: "Actions",
html: (row) => {
const tkButton =
document.createElement("tk-button");
tkButton.label = "Detail";
tkButton.type = "text";
tkButton.variant = "info";
tkButton.addEventListener("tk-click", () => {
alert("clicked row: " + JSON.stringify(row));
});
return tkButton;
},
},
];
const data = ref([])
const totalItem = ref(0)
const loading = ref(false)
const handleRequest = async (e) => {
loading.value = true
const result = await fetchFromServer(
e.detail.currentPage,
e.detail.rowsPerPage,
e.detail.filters,
e.detail.sortField,
e.detail.sortOrder
);
data.value = result?.data
totalItem.value = result?.totalItem
loading.value = false
};
</script>
<template>
<TkTable :columns.prop="column" :data.prop="data" paginationMethod="server" :rowsPerPage="5" :totalItems="totalItem"
:loading="loading" @tkRequest="handleRequest" />
</template>
Custom Header
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
headerHtml: () => {
return '<div style="color: red;">Custom Header</div>';
},
},
{
field: "category",
header: "Category",
headerHtml: () => {
const checkbox = document.createElement('tk-checkbox');
checkbox.label = 'Checkbox';
checkbox.addEventListener('tk-change', (e) => {
console.log('checkbox status', e.detail);
});
return checkbox;
},
},
{
field: "quantity",
header: "Quantity",
},
];
return (
<div style={{ padding: "8px" }}>
<TkTable columns={column} data={basicData} />
</div>
);
<script setup>
import { TkTable } from '@takeoff-ui/vue'
const column = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
headerHtml: () => {
return '<div style="color: red;">Custom Header</div>';
},
},
{
field: "category",
header: "Category",
headerHtml: () => {
const checkbox = document.createElement('tk-checkbox');
checkbox.label = 'Checkbox';
checkbox.addEventListener('tk-change', (e) => {
console.log('checkbox status', e.detail);
});
return checkbox;
},
},
{
field: "quantity",
header: "Quantity",
},
];
</script>
<template>
<div :style="{ padding: '8px' }">
<TkTable :columns.prop="column" :data.prop="basicData" />
</div>
</template>
<div style="padding: 8px">
<tk-table
[columns]="[
{ field: 'id', header: 'Id' },
{
field: 'name',
header: 'Name',
headerHtml: () => {
return '<div style="color: red;">Custom Header</div>';
},
},
{
field: 'category',
header: 'Category',
headerHtml: () => {
const checkbox = document.createElement('tk-checkbox');
checkbox.label = 'Checkbox';
checkbox.addEventListener('tk-change', (e) => {
console.log('checkbox status', e.detail);
});
return checkbox;
},
},
{ field: 'quantity', header: 'Quantity' }
]"
[data]="[
{ id: 1, name: 'Product A', category: 'Electronics', quantity: 12 },
{ id: 2, name: 'Product B', category: 'Books', quantity: 8 },
{ id: 3, name: 'Product C', category: 'Groceries', quantity: 20 }
]"
/>
</div>
Header Style
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
style: {
background: 'var(--primary-base)',
color: 'white',
},
},
{
field: "name",
header: "Name",
style: {
background: 'var(--primary-base)',
color: 'white',
},
},
{
field: "category",
header: "Category",
style: {
background: '#222530',
color: 'white',
},
},
{
field: "quantity",
header: "Quantity",
style: {
background: '#222530',
color: 'white',
},
},
];
return (
<div style={{ padding: "8px" }}>
<TkTable
columns={column}
data={basicData}
/>
</div>
);
<script setup>
import { TkTable } from '@takeoff-ui/vue'
const basicData = [
{
id: "f230fh0g3",
name: "Bamboo Watch",
category: "Accessories",
quantity: 24,
},
{
id: "nvklal433",
name: "Black Watch",
category: "Onyama",
quantity: 42,
},
{
id: "zz21cz3c1",
name: "Blue Band",
category: "Accessories",
quantity: 87,
},
{
id: "244wgerg2",
name: "Blue T-Shirt",
category: "Fitness",
quantity: 12,
},
{
id: "h456wer53",
name: "Bracelet",
category: "Clothing",
quantity: 45,
},
];
const column = [
{
field: "id",
header: "Id",
style: {
background: 'var(--primary-base)',
color: 'white',
},
},
{
field: "name",
header: "Name",
style: {
background: 'var(--primary-base)',
color: 'white',
},
},
{
field: "category",
header: "Category",
style: {
background: '#222530',
color: 'white',
},
},
{
field: "quantity",
header: "Quantity",
style: {
background: '#222530',
color: 'white',
},
];
</script>
<template>
<TkTable
:columns.prop="column"
:data.prop="basicData"
/>
</template>
Row/Cell Style
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
];
return (
<div style={{ padding: "8px" }}>
<TkTable
columns={column}
data={basicData}
cellStyle={(row, col) => {
if (col.field == "name" && row.name == "Blue Band") {
return { background: "var(--primary-base)", color: "white" };
}
}}
rowStyle={(row, index) => {
if (row.quantity > 50) {
return {
background: "var(--states-success-sub-base)",
color: "darkgreen",
};
}
if (index % 2 === 0) {
return {
background: "var(--states-info-sub-base)",
};
}
}}
/>
</div>
);
<script setup>
import { TkTable } from '@takeoff-ui/vue'
const basicData = [
{
id: "f230fh0g3",
name: "Bamboo Watch",
category: "Accessories",
quantity: 24,
},
{
id: "nvklal433",
name: "Black Watch",
category: "Onyama",
quantity: 42,
},
{
id: "zz21cz3c1",
name: "Blue Band",
category: "Accessories",
quantity: 87,
},
{
id: "244wgerg2",
name: "Blue T-Shirt",
category: "Fitness",
quantity: 12,
},
{
id: "h456wer53",
name: "Bracelet",
category: "Clothing",
quantity: 45,
},
];
const column = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
];
</script>
<template>
<TkTable
:columns.prop="column"
:data.prop="basicData"
:cellStyle.prop="(row, col) => {
if (col.field == 'name' && row.name == 'Blue Band') {
return { background: 'var(--primary-base)', color: 'white' };
}
}"
:rowStyle.prop="(row, index) => {
if (row.quantity > 50) {
return {
background: 'var(--states-success-sub-base)',
color: 'darkgreen',
};
}
if (index % 2 === 0) {
return {
background: 'var(--states-info-sub-base)',
};
}
}"
/>
</template>
Expanded Rows
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
expander: true,
field: "",
header: "",
},
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "quantity",
header: "Quantity",
sortable: true,
sorter: (a: any, b: any) =>
Number(a.quantity) > Number(b.quantity) ? 1 : -1,
},
];
const [expandedRows, setExpandedRows] = useState<any[]>([]);
const handleExpandedRowsChange = (rows: any[]) => {
console.log(rows);
setExpandedRows([...rows]);
};
const renderExpandedRows = () => {
return expandedRows.map((item, index) => {
return (
<div slot={`expand-content-${item.id}`} key={"expanded-row-" + index}>
<div style={{ display: "flex", gap: "8px" }}>content</div>
</div>
);
});
};
return (
<TkTable
columns={column}
data={data}
dataKey="id"
paginationMethod="client"
rowsPerPage={5}
totalItems={data.length}
expandedRows={expandedRows}
onTkExpandedRowsChange={(e) => handleExpandedRowsChange(e.detail)}
>
{renderExpandedRows()}
</TkTable>
);
<script setup>
import { TkTable } from '@takeoff-ui/vue'
import { ref } from 'vue';
const data = [
{
id: "f230fh0g3",
name: "Bamboo Watch",
category: "Accessories",
quantity: 24,
},
{
id: "nvklal433",
name: "Black Watch",
category: "Onyama",
quantity: 42,
},
{
id: "zz21cz3c1",
name: "Blue Band",
category: "Accessories",
quantity: 87,
},
{
id: "244wgerg2",
name: "Blue T-Shirt",
category: "Fitness",
quantity: 12,
},
{
id: "h456wer53",
name: "Bracelet",
category: "Clothing",
quantity: 45,
},
{
id: "344wgerg2",
name: "Art Venere",
category: "Accessories",
quantity: 23,
},
{
id: "144wgerg3",
name: "Simona Morasca",
category: "Clothing",
quantity: 56,
},
{
id: "444wgerg6",
name: "Leota Dilliard",
category: "Fitness",
quantity: 89,
},
{
id: "k14wgerj1",
name: "Sage Wieser",
category: "Accessories",
quantity: 77,
},
{
id: "fq4wgergq",
name: "Kris Marrier",
category: "Clothing",
quantity: 65,
},
{
id: "764wger11",
name: "Abel Maclead",
category: "Clothing",
quantity: 61,
},
{
id: "08ge885f",
name: "Mattie Poquette",
category: "Fitness",
quantity: 42,
},
{
id: "wg57erg2",
name: "Meaghan Garufi",
category: "Accessories",
quantity: 99,
},
{
id: "264w3erg2",
name: "Gladys Rim",
category: "Magalhaes",
quantity: 92,
},
];
const column = [
{
expander: true,
field: "",
header: "",
},
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "quantity",
header: "Quantity",
sortable: true,
sorter: (a, b) =>
Number(a.quantity) > Number(b.quantity) ? 1 : -1,
},
];
const expandedRows = ref([])
const handleExpandedRowsChange = (rows) => {
console.log(rows);
expandedRows.value = [...rows]
};
</script>
<template>
<TkTable
:columns.prop="column"
:data.prop="data"
dataKey="id"
paginationMethod="client"
:rowsPerPage="5"
:totalItems="data.length"
:expandedRows="expandedRows"
@tkExpandedRowsChange="(e) => handleExpandedRowsChange(e.detail)"
>
<div v-for="item, index in expandedRows" :slot="`expand-content-${item.id}`" :key="'expanded-row-' + index">
<div :style="{ display: 'flex', gap: '8px' }">content</div>
</div>
</TkTable>
</template>
Export File
The basic features of the TkTable component are demonstrated.
- React
- Vue
- Angular
const tableRef = useRef<HTMLTkTableElement>(null);
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
];
const exportOptions = [
{
label: "Pdf",
value: "pdf",
},
{
label: "Excel",
value: "excel",
},
{
label: "Csv",
value: "csv",
},
];
const handleItemClick = (e) => {
tableRef.current?.exportFile({
type: e.detail.value,
fileName: "custom_file_name",
} as ITableExportOptions);
};
return (
<div style={{ padding: "8px" }}>
<TkTable ref={tableRef} columns={column} data={basicData}>
<div slot="header-right">
<TkDropdown
options={exportOptions}
position="bottom-end"
onTkItemClick={handleItemClick}
>
<TkButton
slot="trigger"
label="Export"
icon="keyboard_arrow_down"
iconPosition="right"
type="outlined"
/>
</TkDropdown>
</div>
</TkTable>
</div>
);
<script setup>
import { TkTable, TkDropdown, TkButton } from '@takeoff-ui/vue'
import { ref } from 'vue';
const basicData = [
{
id: "f230fh0g3",
name: "Bamboo Watch",
category: "Accessories",
quantity: 24,
},
{
id: "nvklal433",
name: "Black Watch",
category: "Onyama",
quantity: 42,
},
{
id: "zz21cz3c1",
name: "Blue Band",
category: "Accessories",
quantity: 87,
},
{
id: "244wgerg2",
name: "Blue T-Shirt",
category: "Fitness",
quantity: 12,
},
{
id: "h456wer53",
name: "Bracelet",
category: "Clothing",
quantity: 45,
},
];
const column = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
];
const exportOptions = [
{
label: "Pdf",
value: "pdf",
},
{
label: "Excel",
value: "excel",
},
{
label: "Csv",
value: "csv",
},
];
const tableRef = ref()
const handleItemClick = (e) => {
tableRef.value?.$el.exportFile({
type: e.detail.value,
fileName: "custom_file_name",
});
};
</script>
<template>
<div :style="{ padding: '8px' }">
<TkTable ref="tableRef" :columns.prop="column" :data.prop="basicData">
<div slot="header-right">
<TkDropdown
:options.prop="exportOptions"
position="bottom-end"
@tkItemClick="handleItemClick"
>
<TkButton
slot="trigger"
label="Export"
icon="keyboard_arrow_down"
iconPosition="right"
type="outlined"
/>
</TkDropdown>
</div>
</TkTable>
</div>
</template>
Sticky Column
This feature allows the selected columns stay in their position in case of having multiple columns that extands the space.
- React
- Vue
- Angular
const tableRef = useRef<HTMLTkTableElement>(null);
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
fixed: "left",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
{
field: "startDate",
header: "Start Date",
},
{
field: "endDate",
header: "End Date",
},
{
field: "duration",
header: "Dration",
},
{
field: "place",
header: "Place",
},
{
field: "status",
header: "Status",
fixed: "right",
},
];
return (
<div style={{ padding: "8px" }}>
<TkTable ref={tableRef} cardTitle="Sticky" columns={column} data={stickyData}>
</TkTable>
</div>
);
<script setup>
import { TkTable, TkDropdown, TkButton } from '@takeoff-ui/vue'
import { ref } from 'vue';
const stickyData=[
{
id: "f230fh0g3",
name: "Bamboo Watch",
category: "Accessories",
quantity: 24,
startDate:"12.20",
endDate:"13.20",
duration:"60 minutes",
place:"Ankara",
status:"Onboard"
},
{
id: "nvklal433",
name: "Black Watch",
category: "Onyama",
quantity: 42,
startDate:"11.40",
endDate:"15.20",
duration:"220 minutes",
place:"Istanbul",
status:"Boarding"
},
{
id: "zz21cz3c1",
name: "Blue Band",
category: "Accessories",
quantity: 87,
startDate:"09.00",
endDate:"15.00",
duration:"360 minutes",
place:"Paris",
status:"Departed"
},
{
id: "244wgerg2",
name: "Blue T-Shirt",
category: "Fitness",
quantity: 12,
startDate:"07.30",
endDate:"14.20",
duration:"410 minutes",
place:"London",
status:"Arrived"
},
{
id: "h456wer53",
name: "Bracelet",
category: "Clothing",
quantity: 45,
startDate:"18.20",
endDate:"06.20",
duration:"720 minutes",
place:"Tokyo",
status:"Checked-in"
},
]
const column = [
{
field: "id",
header: "Id",
fixed: "left",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
{
field: "startDate",
header: "Start Date",
},
{
field: "endDate",
header: "End Date",
},
{
field: "duration",
header: "Dration",
},
{
field: "place",
header: "Place",
},
{
field: "status",
header: "Status",
fixed: "right",
},
];
</script>
<template>
<div :style="{ padding: '8px' }">
<TkTable ref="tableRef" cardTitle="Sticky" :columns.prop="column" :data.prop="stickyData">
</TkTable>
</div>
</template>
Sticky Header
This feature allows the table header to remain fixed at the top when scrolling through large datasets. To enable sticky header functionality, you must provide a height value through the containerStyle prop.
- React
- Vue
- Angular
const tableRef = useRef<HTMLTkTableElement>(null);
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
{
field: "place",
header: "Place",
},
{
field: "status",
header: "Status",
},
];
return (
<TkTable columns={column} data={stickyData} containerStyle={{height: "300px"}}>
</TkTable>
);
<script setup>
import { TkTable } from '@takeoff-ui/vue'
const column = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
{
field: "place",
header: "Place",
},
{
field: "status",
header: "Status",
fixed: "right",
},
];
</script>
<template>
<TkTable :columns.prop="column" :data.prop="stickyData" :containerStyle="{height: "300px"}">
</TkTable>
</template>
Column Arrangement
This feature allows the user to drag and drop the columns to the desired position.
- React
- Vue
- Angular
const [columns, setColumns] = useState<ITableColumn[]>([
{
field: 'id',
header: 'Id',
},
{
field: 'name',
header: 'Name',
},
{
field: 'category',
header: 'Category',
},
{
field: 'quantity',
header: 'Quantity',
},
]);
const [selectedColumns, setSelectedColumns] = useState<ITableColumn[]>(
columns.filter((item) => ['id', 'name'].includes(item.field)),
);
const handleDragStart = (e: React.DragEvent, index: number) => {
const parentElement = e.currentTarget.closest('div');
if (parentElement) {
e.dataTransfer.setData('text/plain', index.toString());
parentElement.classList.add('dragging');
e.dataTransfer.setDragImage(parentElement, 0, 0);
}
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
};
const handleDragEnd = (e: React.DragEvent) => {
const parentElement = e.currentTarget.closest('div');
if (parentElement) {
parentElement.classList.remove('dragging');
}
};
const handleDrop = (e: React.DragEvent, targetIndex: number) => {
e.preventDefault();
const sourceIndex = parseInt(e.dataTransfer.getData('text/plain'));
const newColumns = [...columns];
const [movedItem] = newColumns.splice(sourceIndex, 1);
newColumns.splice(targetIndex, 0, movedItem);
setColumns(newColumns);
setSelectedColumns(
newColumns.filter((item) =>
selectedColumns
.map((selectedCol) => selectedCol.field)
.includes(item.field),
),
);
};
return (
<div className="flex flex-col gap-2">
<TkPopover className="flex justify-end" position="bottom" trigger="click">
<TkButton
variant="neutral"
type="outlined"
slot="trigger"
label="Arrange Columns"
/>
<div slot="content">
<div className="flex flex-col gap-2">
{columns.map((col, index) => (
<div
key={col.field}
className="flex justify-between gap-2 py-2 px-3 hover:bg-gray-100 transition-colors"
>
<TkCheckbox
label={col.header}
value={
selectedColumns.findIndex(
(item) => item.field == col.field,
) > -1
}
onTkChange={(e) => {
if (e.detail) {
setSelectedColumns([...selectedColumns, col]);
} else {
setSelectedColumns(
selectedColumns.filter(
(item) => item.field != col.field,
),
);
}
}}
/>
<TkIcon
icon="drag_indicator"
variant="neutral"
draggable
onDragStart={(e) => handleDragStart(e, index)}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
onDrop={(e) => handleDrop(e, index)}
style={{ cursor: 'move' }}
/>
</div>
))}
</div>
</div>
</TkPopover>
<TkTable columns={selectedColumns} data={data} />
</div>
);
<script setup lang="ts">
import { ref } from 'vue';
import { ITableColumn } from '@takeoff-ui/core';
import {
TkTable,
TkCheckbox,
TkIcon,
TkPopover,
TkButton,
} from '@takeoff-ui/vue';
const columns = ref<ITableColumn[]>([
{ field: 'id', header: 'Id' },
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' },
]);
const selectedColumns = ref<ITableColumn[]>(
columns.value.filter((item) => ['id', 'name'].includes(item.field)),
);
const handleDragStart = (e: DragEvent, index: number) => {
const parentElement = (e.currentTarget as HTMLElement).closest('div');
if (parentElement) {
e.dataTransfer?.setData('text/plain', index.toString());
parentElement.classList.add('dragging');
e.dataTransfer?.setDragImage(parentElement, 0, 0);
}
};
const handleDragOver = (e: DragEvent) => {
e.preventDefault();
};
const handleDragEnd = (e: DragEvent) => {
const parentElement = (e.currentTarget as HTMLElement).closest('div');
if (parentElement) {
parentElement.classList.remove('dragging');
}
};
const handleDrop = (e: DragEvent, targetIndex: number) => {
e.preventDefault();
const sourceIndex = parseInt(e.dataTransfer?.getData('text/plain') || '0');
const newColumns = [...columns.value];
const [movedItem] = newColumns.splice(sourceIndex, 1);
newColumns.splice(targetIndex, 0, movedItem);
columns.value = newColumns;
selectedColumns.value = newColumns.filter((item) =>
selectedColumns.value
.map((selectedCol) => selectedCol.field)
.includes(item.field),
);
};
const handleCheckboxChange = (e: any, col: ITableColumn) => {
if (e.detail) {
selectedColumns.value = [...selectedColumns.value, col];
} else {
selectedColumns.value = selectedColumns.value.filter(
(item) => item.field !== col.field,
);
}
};
</script>
<template>
<div class="flex flex-col gap-2">
<TkPopover
style="display: flex; justify-content: flex-end"
position="bottom"
trigger="click"
>
<TkButton
variant="neutral"
type="outlined"
slot="trigger"
label="Arrange Columns"
/>
<div slot="content">
<div
style="display: flex; flex-direction: column; gap: 8px; padding: 8px"
>
<div
v-for="(col, index) in columns"
:key="col.field"
style="
display: flex;
justify-content: space-between;
gap: 8px;
padding: 8px;
transition: background-color 0.3s ease;
"
>
<TkCheckbox
:label="col.header"
:value="
selectedColumns.findIndex((item) => item.field === col.field) >
-1
"
@tk-change="(e) => handleCheckboxChange(e, col)"
/>
<TkIcon
icon="drag_indicator"
variant="neutral"
draggable="true"
@dragstart="(e) => handleDragStart(e, index)"
@dragover="handleDragOver"
@dragend="handleDragEnd"
@drop="(e) => handleDrop(e, index)"
style="cursor: move"
/>
</div>
</div>
</div>
</TkPopover>
<TkTable :columns.prop="selectedColumns" :data.prop="data" />
</div>
</template>
Size
This feature allows the user to use alternative sizes.
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
];
return (
<div style={{ padding: "8px" }}>
<TkTable columns={column} data={basicData} size="small"/>
</div>
);
<script setup>
import { TkTable } from '@takeoff-ui/vue'
const column = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
];
</script>
<template>
<div :style="{ padding: '8px' }">
<TkTable :columns.prop="column" :data.prop="basicData" size="small" />
</div>
</template>
<div style="padding: 8px">
<tk-table
[columns]="[
{ field: 'id', header: 'Id' },
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' }
]"
[data]="[
{ id: 1, name: 'Product A', category: 'Electronics', quantity: 12 },
{ id: 2, name: 'Product B', category: 'Books', quantity: 8 },
{ id: 3, name: 'Product C', category: 'Groceries', quantity: 20 }
]"
size="small"
/>
</div>
Empty Data Slot
This feature allows you to customize the content displayed when there are no data in the table.
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
];
const [data, setData] = useState([]);
return (
<div className="p-2">
<div style={{ marginBottom: '1rem', display: 'flex', gap: '0.5rem' }}>
<TkButton label="Load Data" onTkClick={() => setData(basicData)} className="mt-2" />
<TkButton label="Show Empty Data Slot" onTkClick={() => setData([])} className="mt-2" />
</div>
<TkTable columns={column} data={data}>
<div slot="empty-data" className="p-4 text-center text-gray-500">
No data found...
</div>
</TkTable>
</div>
);
<script setup>
import { TkButton, TkTable } from '@takeoff-ui/vue';
import { ref } from 'vue';
import { basicData } from './data';
const data = ref([]);
const columns = [
{
field: 'id',
header: 'Id',
},
{
field: 'name',
header: 'Name',
},
{
field: 'category',
header: 'Category',
},
{
field: 'quantity',
header: 'Quantity',
},
];
const loadData = () => {
data.value = basicData;
};
const showEmptySlot = () => {
data.value = [];
};
</script>
<template>
<div class="p-2">
<div style="margin-bottom: 1rem; display: flex; gap: 0.5rem">
<TkButton label="Load Data" @tk-click="loadData" class="mt-2" />
<TkButton label="Show Empty Data Slot" @tk-click="showEmptySlot" class="mt-2" />
</div>
<TkTable :columns.prop="columns" :data.prop="data">
<div slot="empty-data" class="p-4 text-center text-gray-500">No data found...</div>
</TkTable>
</div>
</template>
<div style="padding: 8px">
<div style="margin-bottom: 1rem; display: flex; gap: 0.5rem">
<tk-button label="Load Data" (tkClick)="loadData()" class="mt-2"></tk-button>
<tk-button label="Show Empty Data Slot" (tkClick)="showEmptySlot()" class="mt-2"></tk-button>
</div>
<tk-table [columns]="columns" [data]="data">
<div slot="empty-data" class="p-4 text-center text-gray-500">
No data found...
</div>
</tk-table>
</div>
Header and Footer Slot
This feature allows you to add custom header and footer rows within the table body (<tbody>). You can use the slot attribute to specify whether the row is a header or footer. This is useful for adding summary rows, additional information, or custom styling to specific sections of the table body.
- React
- Vue
- Angular
const column: ITableColumn[] = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
];
return (
<div style={{ padding: "8px" }}>
<TkTable columns={column} data={basicData}>
<tr slot="body-header" style={{ background: 'white', fontWeight: 'bold' }}>
<td style={{ padding: '0 8px' }}>Cell Sum Data</td>
<td colSpan={3} style={{ padding: '0 8px' }}>This is a custom header row in tbody</td>
</tr>
<tr slot="body-footer" style={{ background: 'white', fontWeight: 'bold' }}>
<td style={{ padding: '0 8px' }}>Cell Sum Data</td>
<td colSpan={3} style={{ padding: '0 8px' }}>This is a custom footer row in tbody</td>
</tr>
</TkTable>
</div>
);
<script setup>
import { TkTable } from '@takeoff-ui/vue'
const column = [
{
field: "id",
header: "Id",
},
{
field: "name",
header: "Name",
},
{
field: "category",
header: "Category",
},
{
field: "quantity",
header: "Quantity",
},
];
</script>
<template>
<div :style="{ padding: '8px' }">
<TkTable columns={column} data={basicData}>
<tr slot="body-header" style="background: white; font-weight: bold">
<td style="padding:0 8px">Cell Sum Data</td>
<td colSpan={3} style="padding:0 8px">This is a custom header row in tbody</td>
</tr>
<tr slot="body-footer" style="background: 'white'; font-weight: bold;">
<td style="padding:0 8px">Cell Sum Data</td>
<td colSpan={3} style="padding:0 8px">This is a custom footer row in tbody</td>
</tr>
</TkTable>
</div>
</template>
Grouped Rows
The TkTable component supports row grouping functionality that allows you to organize table data by specific column values. This feature provides visual grouping with headers showing group names and item counts, and works seamlessly with pagination, sorting, and selection.
Key Features
- Visual Group Headers: Automatically generated headers for each group showing the group value and count
- Pagination Support: Groups work correctly with both client-side and server-side pagination
- Selection Persistence: Row selections are maintained across grouping changes
- Sorting Integration: Grouped data respects existing sort configurations
- Event Handling: Listen to grouping changes with the
onTkGroupByChangeevent
Two Implementation Approaches
1. Controlled Grouping (State-driven) Use the groupBy prop with component
state for external control. This approach gives you full control over the
grouping state and allows integration with other application state management.
2. Uncontrolled Grouping (Method-driven)
Use imperative methods like groupByColumn() and clearGrouping() for internal
control. This approach is simpler when you don't need to manage grouping state
externally.
🎛️ Controlled Grouping (State-driven)
Current groupBy value: status
🔧 Uncontrolled Grouping (Method-driven)
Grouping is controlled internally via imperative methods. Use the buttons below to trigger grouping changes.
💡 Key Features:
- Controlled Grouping: Use the
groupByprop with React state for external control - Uncontrolled Grouping: Use
groupByColumn()andclearGrouping()methods - Event Handling: Listen to
onTkGroupByChangefor grouping state changes - Visual Grouping: Rows are visually grouped with headers showing group name and count
- Pagination Support: Grouping works seamlessly with both client and server-side pagination
- Selection Persistence: Row selection is maintained across grouping changes
- React
- Vue
- Angular
import { ITableColumn } from '@takeoff-ui/core';
import { TkButton, TkTable } from '@takeoff-ui/react';
import { useRef, useState } from 'react';
function GroupedRowsExample() {
const controlledTableRef = useRef<HTMLTkTableElement>(null);
const uncontrolledTableRef = useRef<HTMLTkTableElement>(null);
// Controlled table state
const [controlledGroupBy, setControlledGroupBy] = useState<string>('status');
const [controlledSelectedRows, setControlledSelectedRows] = useState<any[]>([]);
const [uncontrolledSelectedRows, setUncontrolledSelectedRows] = useState<any[]>([]);
// Sample data
const sampleData = [
{ id: 1, name: 'Website Redesign', status: 'In Progress', priority: 'High', department: 'Design' },
{ id: 2, name: 'API Development', status: 'In Progress', priority: 'High', department: 'Engineering' },
{ id: 3, name: 'Database Migration', status: 'Completed', priority: 'Critical', department: 'Engineering' },
{ id: 4, name: 'Marketing Campaign', status: 'Planning', priority: 'Medium', department: 'Marketing' },
// ... more data
];
const columns: ITableColumn[] = [
{ header: 'Project Name', field: 'name', sortable: true, searchable: true },
{ header: 'Status', field: 'status', sortable: true, searchable: true },
{ header: 'Priority', field: 'priority', sortable: true, searchable: true },
{ header: 'Department', field: 'department', sortable: true, searchable: true },
];
// Controlled grouping handlers
const handleControlledGrouping = (field: string) => {
setControlledGroupBy(field);
};
const clearControlledGrouping = () => {
setControlledGroupBy('');
};
// Uncontrolled grouping handlers (using imperative methods)
const handleUncontrolledGrouping = async (field: string) => {
await uncontrolledTableRef.current?.groupByColumn(field);
};
const clearUncontrolledGrouping = async () => {
await uncontrolledTableRef.current?.clearGrouping();
};
// Event handlers
const handleControlledSelectionChange = (e: any) => {
setControlledSelectedRows(e.detail);
};
const handleControlledGroupByChange = (e: any) => {
console.log('GroupBy changed to:', e.detail || 'none');
};
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
{/* Controlled Table */}
<div>
<h3>🎛️ Controlled Grouping (State-driven)</h3>
<p>Current groupBy: <strong>{controlledGroupBy || 'none'}</strong></p>
<div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
<TkButton onTkClick={() => handleControlledGrouping('status')} label="Group by Status" type="outlined" />
<TkButton onTkClick={() => handleControlledGrouping('priority')} label="Group by Priority" type="outlined" />
<TkButton onTkClick={() => handleControlledGrouping('department')} label="Group by Department" type="outlined" />
<TkButton onTkClick={clearControlledGrouping} label="Clear Grouping" type="text" />
</div>
<TkTable
ref={controlledTableRef}
dataKey="id"
cardTitle="Controlled Table"
rowsPerPage={8}
paginationMethod="client"
columns={columns}
data={sampleData}
selectionMode="checkbox"
selection={controlledSelectedRows}
onTkSelectionChange={handleControlledSelectionChange}
onTkGroupByChange={handleControlledGroupByChange}
groupBy={controlledGroupBy}
striped={true}
/>
</div>
{/* Uncontrolled Table */}
<div>
<h3>🔧 Uncontrolled Grouping (Method-driven)</h3>
<p>Grouping controlled internally via imperative methods</p>
<div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
<TkButton onTkClick={() => handleUncontrolledGrouping('status')} label="Group by Status" type="outlined" />
<TkButton onTkClick={() => handleUncontrolledGrouping('priority')} label="Group by Priority" type="outlined" />
<TkButton onTkClick={() => handleUncontrolledGrouping('department')} label="Group by Department" type="outlined" />
<TkButton onTkClick={clearUncontrolledGrouping} label="Clear Grouping" type="text" />
</div>
<TkTable
ref={uncontrolledTableRef}
dataKey="id"
cardTitle="Uncontrolled Table"
rowsPerPage={8}
paginationMethod="client"
columns={columns}
data={sampleData}
selectionMode="checkbox"
selection={uncontrolledSelectedRows}
onTkSelectionChange={handleUncontrolledSelectionChange}
onTkGroupByChange={handleUncontrolledGroupByChange}
striped={true}
/>
</div>
</div>
);
}
<script setup>
import { ITableColumn } from '@takeoff-ui/core';
import { TkButton, TkTable } from '@takeoff-ui/vue';
import { ref } from 'vue';
const controlledTableRef = ref(null);
const uncontrolledTableRef = ref(null);
// Controlled table state
const controlledGroupBy = ref('status');
const controlledSelectedRows = ref([]);
const uncontrolledSelectedRows = ref([]);
// Sample data
const sampleData = [
{ id: 1, name: 'Website Redesign', status: 'In Progress', priority: 'High', department: 'Design' },
{ id: 2, name: 'API Development', status: 'In Progress', priority: 'High', department: 'Engineering' },
{ id: 3, name: 'Database Migration', status: 'Completed', priority: 'Critical', department: 'Engineering' },
{ id: 4, name: 'Marketing Campaign', status: 'Planning', priority: 'Medium', department: 'Marketing' },
// ... more data
];
const columns = [
{ header: 'Project Name', field: 'name', sortable: true, searchable: true },
{ header: 'Status', field: 'status', sortable: true, searchable: true },
{ header: 'Priority', field: 'priority', sortable: true, searchable: true },
{ header: 'Department', field: 'department', sortable: true, searchable: true },
];
// Controlled grouping handlers
const handleControlledGrouping = (field) => {
controlledGroupBy.value = field;
};
const clearControlledGrouping = () => {
controlledGroupBy.value = '';
};
// Uncontrolled grouping handlers
const handleUncontrolledGrouping = async (field) => {
await uncontrolledTableRef.value?.groupByColumn(field);
};
const clearUncontrolledGrouping = async () => {
await uncontrolledTableRef.value?.clearGrouping();
};
// Event handlers
const handleControlledSelectionChange = (e) => {
controlledSelectedRows.value = e.detail;
};
const handleControlledGroupByChange = (e) => {
console.log('GroupBy changed to:', e.detail || 'none');
};
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 24px;">
<!-- Controlled Table -->
<div>
<h3>