N
Nuxt2mo ago
ipconfig

NuxtUI Paginated UTable and Select All

I have a paginated table and in v2 I used to just enable select and the table component would handle it all and return the selected rows as well as remember which rows were selected. Now that I'm using v3, it appears that functionality no longer exists and needs to be self implemented. What is the best way to handle pagination and select all with the new UTable component?
8 Replies
kapa.ai
kapa.ai2mo ago
Hi @ipconfig. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- You're right that the UTable component in Nuxt UI v3 handles row selection differently than in v2. In v3, you need to implement pagination and row selection manually, but the component provides the necessary tools to do this effectively. Implementing Pagination with Row Selection in UTable v3 The UTable component in Nuxt UI v3 is built on top of TanStack Table, which provides powerful APIs for pagination and row selection. Here's how to implement these features: 1. Basic Setup with Row Selection
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import { getPaginationRowModel } from '@tanstack/vue-table'
import type { TableColumn } from '@nuxt/ui'

const UCheckbox = resolveComponent('UCheckbox')

// Your data and columns definition
const data = ref([/* your data */])

const columns = [
// Add a select column with checkboxes
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value) =>
table.toggleAllPageRowsSelected(!!value),
ariaLabel: 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value) => row.toggleSelected(!!value),
ariaLabel: 'Select row'
})
},
// Your other columns
]

// Track row selection state
const rowSelection = ref({})

// Track pagination state
const pagination = ref({
pageIndex: 0,
pageSize: 5
})

// Reference to access table API
const table = useTemplateRef('table')
</script>
<template>
<div class="w-full space-y-4 pb-4">
<UTable
ref="table"
v-model:row-selection="rowSelection"
v-model:pagination="pagination"
:data="data"
:columns="columns"
:pagination-options="{
getPaginationRowModel: getPaginationRowModel()
}"
/>

<div class="flex justify-center border-t border-(--ui-border) pt-4">
<UPagination
:default-page="(table?.tableApi?.getState().pagination.pageIndex || 0) + 1"
:items-per-page="table?.tableApi?.getState().pagination.pageSize"
:total="table?.tableApi?.getFilteredRowModel().rows.length"
@update:page="(p) => table?.tableApi?.setPageIndex(p - 1)"
/>
</div>

<div class="px-4 py-3.5 text-sm">
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
</div>
</div>
</template>
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import { getPaginationRowModel } from '@tanstack/vue-table'
import type { TableColumn } from '@nuxt/ui'

const UCheckbox = resolveComponent('UCheckbox')

// Your data and columns definition
const data = ref([/* your data */])

const columns = [
// Add a select column with checkboxes
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value) =>
table.toggleAllPageRowsSelected(!!value),
ariaLabel: 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value) => row.toggleSelected(!!value),
ariaLabel: 'Select row'
})
},
// Your other columns
]

// Track row selection state
const rowSelection = ref({})

// Track pagination state
const pagination = ref({
pageIndex: 0,
pageSize: 5
})

// Reference to access table API
const table = useTemplateRef('table')
</script>
<template>
<div class="w-full space-y-4 pb-4">
<UTable
ref="table"
v-model:row-selection="rowSelection"
v-model:pagination="pagination"
:data="data"
:columns="columns"
:pagination-options="{
getPaginationRowModel: getPaginationRowModel()
}"
/>

<div class="flex justify-center border-t border-(--ui-border) pt-4">
<UPagination
:default-page="(table?.tableApi?.getState().pagination.pageIndex || 0) + 1"
:items-per-page="table?.tableApi?.getState().pagination.pageSize"
:total="table?.tableApi?.getFilteredRowModel().rows.length"
@update:page="(p) => table?.tableApi?.setPageIndex(p - 1)"
/>
</div>

<div class="px-4 py-3.5 text-sm">
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
</div>
</div>
</template>
2. Key Components Explained 1. Row Selection: Use v-model:row-selection to bind to a reactive object that tracks selected rows. The checkbox in the header toggles selection for all rows on the current page. 2. Pagination: Use v-model:pagination to bind to a reactive object that tracks pagination state. The pagination-options prop is used to enable pagination with the getPaginationRowModel() function from TanStack Table. 3. UPagination Component: This component is used to display pagination controls. It's connected to the table's pagination state. 4. Table Reference: Use useTemplateRef to get access to the table's API, which provides methods for working with selection and pagination. This approach allows you to maintain selection state across pages, as the rowSelection ref will keep track of all selected rows, not just those on the current page. For more details and examples, you can refer to the Nuxt UI Table documentation and pagination examples. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@346449255759872010> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
ipconfig
ipconfigOP2mo ago
@kapa.ai How do I handle filtering data via api and persisting the checkbox?
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To handle filtering data via API while persisting checkbox selections in a Nuxt UI Table, you need to manage both the filtered data state and the selection state separately. Here's how to implement this: Managing API Filtering with Persistent Checkbox Selection When working with server-side filtering, you need to: 1. Track selected rows independently from the current page data 2. Maintain selection state when data changes due to filtering 3. Properly sync the UI with both states Implementation Approach
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UCheckbox = resolveComponent('UCheckbox')

// Your data fetching logic
const data = ref([])
const isLoading = ref(false)

// Track row selection with a separate object using row IDs as keys
const rowSelection = ref({})

// Column filters state
const columnFilters = ref([])

// Fetch data with filters
async function fetchData(filters) {
isLoading.value = true
try {
// Replace with your actual API call
const response = await fetch('/api/your-endpoint', {
method: 'POST',
body: JSON.stringify({ filters })
})
data.value = await response.json()
} catch (error) {
console.error('Error fetching data:', error)
} finally {
isLoading.value = false
}
}

// Watch for filter changes to refetch data
watch(columnFilters, (newFilters) => {
fetchData(newFilters)
}, { deep: true })

// Initial data fetch
onMounted(() => {
fetchData(columnFilters.value)
})
// Define columns with selection column
const columns = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value) =>
table.toggleAllPageRowsSelected(!!value),
ariaLabel: 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value) => row.toggleSelected(!!value),
ariaLabel: 'Select row'
})
},
// Your other columns
]

const table = useTemplateRef('table')
</script>

<template>
<div class="flex flex-col flex-1 w-full">
<!-- Filter inputs -->
<div class="flex px-4 py-3.5 border-b border-(--ui-border-accented)">
<UInput
:model-value="table?.tableApi?.getColumn('email')?.getFilterValue() as string"
class="max-w-sm"
placeholder="Filter emails..."
@update:model-value="table?.tableApi?.getColumn('email')?.setFilterValue($event)"
/>
</div>

<!-- Table with row selection -->
<UTable
ref="table"
v-model:row-selection="rowSelection"
v-model:column-filters="columnFilters"
:data="data"
:columns="columns"
:loading="isLoading"
/>

<!-- Selection summary -->
<div class="px-4 py-3.5 border-t border-(--ui-border-accented) text-sm">
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
</div>
</div>
</template>
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UCheckbox = resolveComponent('UCheckbox')

// Your data fetching logic
const data = ref([])
const isLoading = ref(false)

// Track row selection with a separate object using row IDs as keys
const rowSelection = ref({})

// Column filters state
const columnFilters = ref([])

// Fetch data with filters
async function fetchData(filters) {
isLoading.value = true
try {
// Replace with your actual API call
const response = await fetch('/api/your-endpoint', {
method: 'POST',
body: JSON.stringify({ filters })
})
data.value = await response.json()
} catch (error) {
console.error('Error fetching data:', error)
} finally {
isLoading.value = false
}
}

// Watch for filter changes to refetch data
watch(columnFilters, (newFilters) => {
fetchData(newFilters)
}, { deep: true })

// Initial data fetch
onMounted(() => {
fetchData(columnFilters.value)
})
// Define columns with selection column
const columns = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value) =>
table.toggleAllPageRowsSelected(!!value),
ariaLabel: 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value) => row.toggleSelected(!!value),
ariaLabel: 'Select row'
})
},
// Your other columns
]

const table = useTemplateRef('table')
</script>

<template>
<div class="flex flex-col flex-1 w-full">
<!-- Filter inputs -->
<div class="flex px-4 py-3.5 border-b border-(--ui-border-accented)">
<UInput
:model-value="table?.tableApi?.getColumn('email')?.getFilterValue() as string"
class="max-w-sm"
placeholder="Filter emails..."
@update:model-value="table?.tableApi?.getColumn('email')?.setFilterValue($event)"
/>
</div>

<!-- Table with row selection -->
<UTable
ref="table"
v-model:row-selection="rowSelection"
v-model:column-filters="columnFilters"
:data="data"
:columns="columns"
:loading="isLoading"
/>

<!-- Selection summary -->
<div class="px-4 py-3.5 border-t border-(--ui-border-accented) text-sm">
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
</div>
</div>
</template>
Key Concepts 1. Separate State Management: The rowSelection ref maintains the selection state independently from the data, using row IDs as keys. 2. API Integration: The fetchData function handles API calls with the current filters, and the watch on columnFilters triggers refetching when filters change. 3. Two-way Binding: Using v-model:row-selection and v-model:column-filters to keep the UI in sync with your state. 4. Loading State: The isLoading ref is used to show a loading state during API calls. This approach works well because the TanStack Table (which powers UTable) maintains selection state based on row IDs, not array positions. When data changes due to filtering, rows with the same IDs will remain selected. For more details on column filtering with UTable, you can refer to the Nuxt UI Table documentation on column filters. If you need to handle more complex scenarios where the API returns different row IDs after filtering, you might need additional logic to map between your selection state and the new data. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@346449255759872010> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
ipconfig
ipconfigOP2mo ago
@kapa.ai Is there a way to switch from using row index to row.getValue('id') to manage selected records?
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Yes, you can definitely switch from using row indices to using row IDs for managing selected records in the UTable component. This is actually the recommended approach, especially when working with server-side filtering or pagination, as it provides more stable selection tracking. Using Row IDs for Selection Management The TanStack Table (which powers UTable) supports using row IDs instead of indices through its row selection API. Here's how to implement this:
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UCheckbox = resolveComponent('UCheckbox')

// Your data with unique IDs
const data = ref([
{
id: '4600',
date: '2024-03-11T15:30:00',
amount: 594
},
// more data...
])

// Define a row selection state object that will use IDs as keys
const rowSelection = ref({})

// Define your columns with a selection column
const columns = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value) =>
table.toggleAllPageRowsSelected(!!value),
ariaLabel: 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value) => row.toggleSelected(!!value),
ariaLabel: 'Select row'
})
},
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
// other columns...
]
// Configure the table options to use 'id' as the row ID
const tableOptions = {
state: {
rowSelection: rowSelection.value
},
enableRowSelection: true,
// This is the key part - tell the table to use the 'id' field for row identification
getRowId: (row) => row.id
}
</script>

<template>
<UTable
v-model:row-selection="rowSelection"
:data="data"
:columns="columns"
:options="tableOptions"
/>
</template>
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'

const UCheckbox = resolveComponent('UCheckbox')

// Your data with unique IDs
const data = ref([
{
id: '4600',
date: '2024-03-11T15:30:00',
amount: 594
},
// more data...
])

// Define a row selection state object that will use IDs as keys
const rowSelection = ref({})

// Define your columns with a selection column
const columns = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value) =>
table.toggleAllPageRowsSelected(!!value),
ariaLabel: 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value) => row.toggleSelected(!!value),
ariaLabel: 'Select row'
})
},
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
// other columns...
]
// Configure the table options to use 'id' as the row ID
const tableOptions = {
state: {
rowSelection: rowSelection.value
},
enableRowSelection: true,
// This is the key part - tell the table to use the 'id' field for row identification
getRowId: (row) => row.id
}
</script>

<template>
<UTable
v-model:row-selection="rowSelection"
:data="data"
:columns="columns"
:options="tableOptions"
/>
</template>
The key part of this implementation is the getRowId function in the table options, which tells TanStack Table to use the id field from your data objects as the unique identifier for each row. This approach has several advantages: 1. Selection state persists even when rows change position due to sorting or pagination 2. Works correctly with server-side filtering where row indices might change 3. Makes it easier to track specific records across UI updates You can see examples of this approach in the Nuxt UI Table documentation for row selection, where the selection state is managed using row IDs rather than indices. When implementing this with server-side data, you'll want to ensure each record has a stable, unique ID that doesn't change between API calls, so the selection state remains consistent. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@346449255759872010> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?