Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/api-generator/src/locale/en/DataTable-group.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"props": {
"groupBy": "Defines the grouping of the table items.",
"groupKey": "Custom function to generate group IDs. Receives `{ key, value, parentKey }` where `parentKey` is `null` for top-level groups. Useful when group values contain special characters or are non-string types.",
"openAllGroups": "Opens all groups by default. Individual groups can still be toggled closed by the user.",
"opened": "An array of group IDs that should be open. Supports two-way binding with `v-model:opened`.",
"pageBy": "Controls how pagination counts items.\n- **item** paginates by individual items,\n- **auto** paginates by top-level groups and falls back to regular items if **group-by** is empty,\n- **any** paginates by both items and groups combined"
}
}
26 changes: 25 additions & 1 deletion packages/docs/src/data/new-in.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,13 @@
},
"VDataIterator": {
"props": {
"initialSortOrder": "3.11.0"
"groupKey": "4.1.0",
"initialSortOrder": "3.11.0",
"openAllGroups": "4.1.0",
"opened": "4.1.0"
},
"events": {
"update:opened": "4.1.0"
}
},
"VDataTable": {
Expand All @@ -130,12 +136,18 @@
"expandIcon": "3.10.0",
"groupCollapseIcon": "3.10.0",
"groupExpandIcon": "3.10.0",
"groupKey": "4.1.0",
"headerProps": "3.5.0",
"initialSortOrder": "3.11.0",
"openAllGroups": "4.1.0",
"opened": "4.1.0",
"pageBy": "3.12.0",
"showFirstLastPage": "4.1.0",
"sortIcon": "3.12.0"
},
"events": {
"update:opened": "4.1.0"
},
"slots": {
"group-summary": "3.10.0"
}
Expand All @@ -146,11 +158,17 @@
"expandIcon": "3.10.0",
"groupCollapseIcon": "3.10.0",
"groupExpandIcon": "3.10.0",
"groupKey": "4.1.0",
"initialSortOrder": "3.11.0",
"openAllGroups": "4.1.0",
"opened": "4.1.0",
"pageBy": "3.12.0",
"showFirstLastPage": "4.1.0",
"sortIcon": "3.12.0"
},
"events": {
"update:opened": "4.1.0"
},
"slots": {
"group-summary": "3.10.0"
}
Expand All @@ -161,9 +179,15 @@
"expandIcon": "3.10.0",
"groupCollapseIcon": "3.10.0",
"groupExpandIcon": "3.10.0",
"groupKey": "4.1.0",
"initialSortOrder": "3.11.0",
"openAllGroups": "4.1.0",
"opened": "4.1.0",
"sortIcon": "3.12.0"
},
"events": {
"update:opened": "4.1.0"
},
"slots": {
"group-summary": "3.10.0"
}
Expand Down
103 changes: 103 additions & 0 deletions packages/docs/src/examples/v-data-iterator/prop-grouping.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<template>
<div>
<div class="d-flex ga-4 mb-4 align-center">
<v-switch v-model="openAll" label="Open all groups" hide-details></v-switch>
<v-btn size="small" variant="tonal" @click="opened = []">Close all</v-btn>
</div>

<v-data-iterator
v-model:opened="opened"
:group-by="[{ key: 'category' }]"
:group-key="({ value }) => value"
:items="items"
:items-per-page="-1"
:open-all-groups="openAll"
>
<template v-slot:default="{ groupedItems, toggleGroup, isGroupOpen }">
<v-row>
<template v-for="groupOrItem in groupedItems" :key="groupOrItem.type === 'group' ? groupOrItem.id : groupOrItem.raw.name">
<v-col v-if="groupOrItem.type === 'group'" cols="12">
<v-card
variant="tonal"
@click="toggleGroup(groupOrItem)"
>
<v-card-title class="d-flex align-center">
<v-icon
:icon="isGroupOpen(groupOrItem) ? 'mdi-chevron-down' : 'mdi-chevron-right'"
class="me-2"
></v-icon>
{{ groupOrItem.value }}
<v-chip class="ms-2" size="small" variant="outlined">
{{ groupOrItem.items.length }}
</v-chip>
</v-card-title>
</v-card>
</v-col>

<v-col v-else cols="12" md="4" sm="6">
<v-card>
<v-card-title>{{ groupOrItem.raw.name }}</v-card-title>
<v-card-subtitle>{{ groupOrItem.raw.origin }}</v-card-subtitle>
<v-card-text>{{ groupOrItem.raw.calories }} cal</v-card-text>
</v-card>
</v-col>
</template>
</v-row>
</template>
</v-data-iterator>
</div>
</template>

<script setup>
import { ref } from 'vue'

const opened = ref([])
const openAll = ref(false)

const items = [
{ name: 'Frozen Yogurt', calories: 159, category: 'Dairy', origin: 'USA' },
{ name: 'Ice cream sandwich', calories: 237, category: 'Dairy', origin: 'USA' },
{ name: 'Cheese', calories: 402, category: 'Dairy', origin: 'France' },
{ name: 'Butter', calories: 717, category: 'Dairy', origin: 'France' },
{ name: 'Eclair', calories: 262, category: 'Pastry', origin: 'France' },
{ name: 'Cupcake', calories: 305, category: 'Pastry', origin: 'USA' },
{ name: 'Croissant', calories: 231, category: 'Pastry', origin: 'France' },
{ name: 'Baklava', calories: 334, category: 'Pastry', origin: 'Turkey' },
{ name: 'Oreo', calories: 160, category: 'Cookie', origin: 'USA' },
{ name: 'Macaron', calories: 404, category: 'Cookie', origin: 'France' },
{ name: 'Biscotti', calories: 410, category: 'Cookie', origin: 'Italy' },
{ name: 'KitKat', calories: 518, category: 'Candy', origin: 'UK' },
{ name: 'Snickers', calories: 488, category: 'Candy', origin: 'USA' },
{ name: 'Haribo', calories: 340, category: 'Candy', origin: 'Germany' },
]
</script>

<script>
export default {
data: () => ({
opened: [],
openAll: false,
items: [
{ name: 'Frozen Yogurt', calories: 159, category: 'Dairy', origin: 'USA' },
{ name: 'Ice cream sandwich', calories: 237, category: 'Dairy', origin: 'USA' },
{ name: 'Cheese', calories: 402, category: 'Dairy', origin: 'France' },
{ name: 'Butter', calories: 717, category: 'Dairy', origin: 'France' },
{ name: 'Eclair', calories: 262, category: 'Pastry', origin: 'France' },
{ name: 'Cupcake', calories: 305, category: 'Pastry', origin: 'USA' },
{ name: 'Croissant', calories: 231, category: 'Pastry', origin: 'France' },
{ name: 'Baklava', calories: 334, category: 'Pastry', origin: 'Turkey' },
{ name: 'Oreo', calories: 160, category: 'Cookie', origin: 'USA' },
{ name: 'Macaron', calories: 404, category: 'Cookie', origin: 'France' },
{ name: 'Biscotti', calories: 410, category: 'Cookie', origin: 'Italy' },
{ name: 'KitKat', calories: 518, category: 'Candy', origin: 'UK' },
{ name: 'Snickers', calories: 488, category: 'Candy', origin: 'USA' },
{ name: 'Haribo', calories: 340, category: 'Candy', origin: 'Germany' },
],
}),
methods: {
groupKey ({ value }) {
return value
},
},
}
</script>
38 changes: 31 additions & 7 deletions packages/docs/src/examples/v-data-table/prop-grouping.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
<template>
<v-data-table
:group-by="groupBy"
:headers="headers"
:items="desserts"
:sort-by="sortBy"
item-value="name"
></v-data-table>
<div>
<div class="d-flex ga-4 mb-4 align-center flex-wrap">
<v-switch v-model="openAll" label="Open all groups" hide-details></v-switch>
<v-btn size="small" variant="tonal" @click="opened = []">Close all</v-btn>
</div>

<pre class="mb-4 pa-2 bg-surface-variant rounded text-body-2">opened: {{ opened }}</pre>

<v-data-table
v-model:opened="opened"
:group-by="groupBy"
:group-key="groupKey"
:headers="headers"
:items="desserts"
:open-all-groups="openAll"
:sort-by="sortBy"
item-value="name"
></v-data-table>
</div>
</template>

<script setup>
import { ref } from 'vue'

const opened = ref([])
const openAll = ref(false)

const sortBy = ref([{ key: 'name', order: 'asc' }])
const groupBy = ref([{ key: 'category', order: 'asc' }, { key: 'status', order: 'asc' }])

const groupKey = ({ key, value, parentKey }) => `${parentKey}/${key}:${value}`

const headers = [
{ key: 'data-table-group', title: 'Category' },
{
Expand Down Expand Up @@ -91,6 +108,8 @@
<script>
export default {
data: () => ({
opened: [],
openAll: false,
sortBy: [{ key: 'name', order: 'asc' }],
groupBy: [{ key: 'category', order: 'asc' }, { key: 'status', order: 'asc' }],
headers: [
Expand Down Expand Up @@ -166,5 +185,10 @@
},
],
}),
methods: {
groupKey ({ key, value, parentKey }) {
return `${parentKey}/${key}:${value}`
},
},
}
</script>
8 changes: 8 additions & 0 deletions packages/docs/src/pages/en/components/data-iterators.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ The following code snippet is an example of a basic `v-data-iterator` component:

The following are a collection of examples that demonstrate more advanced and real world use of the `v-data-iterator` component.

### Props

#### Grouping

Use the **group-by** prop to group items, and **v-model:opened** to control which groups are open. The **group-key** prop allows customizing group IDs, and **open-all-groups** opens all groups by default.

<ExamplesExample file="v-data-iterator/prop-grouping" />

### Slots

The `v-data-iterator` component has 4 main slots
Expand Down
14 changes: 12 additions & 2 deletions packages/vuetify/src/components/VDataIterator/VDataIterator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,15 @@ export const VDataIterator = genericComponent<new <T> (
'update:sortBy': (value: any) => true,
'update:options': (value: any) => true,
'update:expanded': (value: any) => true,
'update:opened': (value: string[]) => true,
'update:currentItems': (value: any) => true,
},

setup (props, { slots }) {
const groupBy = useProxiedModel(props, 'groupBy')
const openedModel = useProxiedModel(props, 'opened')
const openAllGroups = toRef(() => props.openAllGroups)
const groupKeyFn = toRef(() => props.groupKey)
const search = toRef(() => props.search)

const { items } = useDataIteratorItems(props)
Expand All @@ -119,10 +123,16 @@ export const VDataIterator = genericComponent<new <T> (
const { page, itemsPerPage } = createPagination(props)

const { toggleSort } = provideSort({ initialSortOrder, sortBy, multiSort, mustSort, page })
const { sortByWithGroups, opened, extractRows, isGroupOpen, toggleGroup } = provideGroupBy({ groupBy, sortBy })
const {
sortByWithGroups,
opened,
extractRows,
isGroupOpen,
toggleGroup,
} = provideGroupBy({ groupBy, sortBy, opened: openedModel, openAllGroups })

const { sortedItems } = useSortedItems(props, filteredItems, sortByWithGroups, { transform: item => item.raw })
const { flatItems } = useGroupedItems(sortedItems, groupBy, opened, false)
const { flatItems } = useGroupedItems(sortedItems, groupBy, opened, false, isGroupOpen, groupKeyFn)

const manualPagination = toRef(() => !isEmpty(props.itemsLength))
const itemsLength = toRef(() => manualPagination.value ? Number(props.itemsLength) : flatItems.value.length)
Expand Down
13 changes: 10 additions & 3 deletions packages/vuetify/src/components/VDataTable/VDataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,12 @@ export const VDataTable = genericComponent<new <T extends readonly any[], V>(
'update:options': (value: any) => true,
'update:groupBy': (value: any) => true,
'update:expanded': (value: any) => true,
'update:opened': (value: string[]) => true,
'update:currentItems': (value: any) => true,
},

setup (props, { attrs, slots }) {
const { groupBy } = createGroupBy(props)
const { groupBy, opened, openAllGroups, groupKey } = createGroupBy(props)
const { initialSortOrder, sortBy, multiSort, mustSort } = createSort(props)
const { page, itemsPerPage } = createPagination(props)
const { disableSort } = toRefs(props)
Expand All @@ -162,7 +163,13 @@ export const VDataTable = genericComponent<new <T extends readonly any[], V>(
})

const { toggleSort } = provideSort({ initialSortOrder, sortBy, multiSort, mustSort, page })
const { sortByWithGroups, opened, extractRows, isGroupOpen, toggleGroup } = provideGroupBy({ groupBy, sortBy, disableSort })
const {
sortByWithGroups,
opened: openedGroups,
extractRows,
isGroupOpen,
toggleGroup,
} = provideGroupBy({ groupBy, sortBy, disableSort, opened, openAllGroups })

const { sortedItems } = useSortedItems(props, filteredItems, sortByWithGroups, {
transform: item => ({ ...item.raw, ...item.columns }),
Expand Down Expand Up @@ -195,7 +202,7 @@ export const VDataTable = genericComponent<new <T extends readonly any[], V>(
const { paginatedItems } = usePaginatedItems({ items, startIndex, stopIndex, itemsPerPage })
return { paginatedItems, pageCount, setItemsPerPage, prevPage, nextPage, setPage }
},
group: items => useGroupedItems(items, groupBy, opened, () => !!slots['group-summary']),
group: items => useGroupedItems(items, groupBy, openedGroups, () => !!slots['group-summary'], isGroupOpen, groupKey),
})

const paginatedItemsWithoutGroups = computed(() => extractRows(paginatedItems.value))
Expand Down
12 changes: 9 additions & 3 deletions packages/vuetify/src/components/VDataTable/VDataTableServer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ export const VDataTableServer = genericComponent<new <T extends readonly any[],
'update:options': (options: any) => true,
'update:expanded': (options: any) => true,
'update:groupBy': (value: any) => true,
'update:opened': (value: string[]) => true,
},

setup (props, { attrs, slots }) {
const { groupBy } = createGroupBy(props)
const { groupBy, opened, openAllGroups, groupKey } = createGroupBy(props)
const { initialSortOrder, sortBy, multiSort, mustSort } = createSort(props)
const { page, itemsPerPage } = createPagination(props)
const { disableSort } = toRefs(props)
Expand All @@ -84,11 +85,16 @@ export const VDataTableServer = genericComponent<new <T extends readonly any[],

const { toggleSort } = provideSort({ initialSortOrder, sortBy, multiSort, mustSort, page })

const { opened, isGroupOpen, toggleGroup, extractRows } = provideGroupBy({ groupBy, sortBy, disableSort })
const {
opened: openedGroups,
isGroupOpen,
toggleGroup,
extractRows,
} = provideGroupBy({ groupBy, sortBy, disableSort, opened, openAllGroups })

const { pageCount, setItemsPerPage, prevPage, nextPage, setPage } = providePagination({ page, itemsPerPage, itemsLength })

const { flatItems } = useGroupedItems(items, groupBy, opened, () => !!slots['group-summary'])
const { flatItems } = useGroupedItems(items, groupBy, openedGroups, () => !!slots['group-summary'], isGroupOpen, groupKey)

const { isSelected, select, selectAll, toggleSelect, someSelected, allSelected } = provideSelection(props, {
allItems: items,
Expand Down
Loading
Loading