vuetify
vuetify copied to clipboard
feat(VDataTable): add slot for group header columns
Description
This resolves #18278 by adding [`group.${string}`]
slots to VDataTable
.
Added a set of slots [`group.${string}`]
to v-data-table much like [`item.${string}`]
, which will allow people to override what is rendered for the corresponding column in a group header. The slot will be forwarded the item
prop from VDataTableGroupHeaderRow allowing users to take advantage of group depth, key, and all the other things which data-table-group
and group-header
slots have access to.
Playground:
<template>
<v-app>
<v-container>
<v-chip-group v-model="groupBy" multiple filter>
<v-chip :value="{ key: 'city', order: 'asc' }">
City
</v-chip>
<v-chip :value="{ key: 'sport', order: 'asc' }">
Sport
</v-chip>
</v-chip-group>
<v-data-table
:group-by="groupBy"
:headers="headers"
:items="sports"
:items-per-page="20"
>
<template #[`group.active`]="{ item }">
{{ getItemsRecursive(item).reduce((a, v) => v.raw.active + a, 0) }}
</template>
<template #[`group.inactive`]="{ item }">
{{ getItemsRecursive(item).reduce((a, v) => v.raw.inactive + a, 0) }}
</template>
</v-data-table>
</v-container>
</v-app>
</template>
<script setup lang="ts">
import { Group } from '@/labs/VDataTable/composables/group'
import { SortItem } from '@/labs/VDataTable/composables/sort'
import { DataTableHeader } from '@/labs/VDataTable/types'
import { ref } from 'vue'
const groupBy = ref<readonly SortItem[]>([{ key: 'sport', order: 'asc' }, { key: 'city', order: 'asc' }])
type Team = {
name: string
city: string
sport: string,
active: number
inactive: number
}
const getItemsRecursive = (group:Group) => {
let items = group.items
while (items[0].items) {
items = items.flatMap((v:Group) => v.items)
}
return items
}
const headers: DataTableHeader[] = [
{
title: 'Name',
align: 'start',
key: 'name',
},
{
title: 'City',
align: 'start',
key: 'city',
},
{
title: 'Sport',
align: 'start',
key: 'sport',
},
{ title: 'Active Players', key: 'active', align: 'end' },
{ title: 'Inactive Players', key: 'inactive', align: 'end' },
]
const sports: Team[] = [
{
name: 'Celtics',
city: 'Boston',
sport: 'Basketball',
active: 10,
inactive: 5,
},
{
name: 'Lakers',
city: 'Los Angeles',
sport: 'Basketball',
active: 15,
inactive: 3,
},
{
name: '49ers',
city: 'San Francisco',
sport: 'Football',
active: 12,
inactive: 4,
},
{
name: 'Giants',
city: 'San Francisco',
sport: 'Baseball',
active: 16,
inactive: 2,
},
{
name: 'Warriors',
city: 'San Francisco',
sport: 'Basketball',
active: 14,
inactive: 4,
},
{
name: 'Sharks',
city: 'San Jose',
sport: 'Hockey',
active: 17,
inactive: 3,
},
{
name: 'Raiders',
city: 'Las Vegas',
sport: 'Football',
active: 11,
inactive: 5,
},
{
name: 'Golden Knights',
city: 'Las Vegas',
sport: 'Hockey',
active: 19,
inactive: 1,
},
{
name: 'Heat',
city: 'Miami',
sport: 'Basketball',
active: 16,
inactive: 2,
},
{
name: 'Marlins',
city: 'Miami',
sport: 'Baseball',
active: 14,
inactive: 4,
},
{
name: 'Dolphins',
city: 'Miami',
sport: 'Football',
active: 12,
inactive: 4,
},
{
name: 'Lightning',
city: 'Tampa Bay',
sport: 'Hockey',
active: 18,
inactive: 2,
},
{
name: 'Rays',
city: 'Tampa Bay',
sport: 'Baseball',
active: 15,
inactive: 3,
},
{
name: 'Buccaneers',
city: 'Tampa Bay',
sport: 'Football',
active: 14,
inactive: 2,
},
{
name: 'Timberwolves',
city: 'Minnesota',
sport: 'Basketball',
active: 11,
inactive: 5,
},
{
name: 'Twins',
city: 'Minnesota',
sport: 'Baseball',
active: 17,
inactive: 1,
},
{
name: 'Bruins',
city: 'Boston',
sport: 'Hockey',
active: 20,
inactive: 4,
},
{
name: 'Red Sox',
city: 'Boston',
sport: 'Baseball',
active: 15,
inactive: 3,
},
{
name: 'Yankees',
city: 'New York',
sport: 'Baseball',
active: 17,
inactive: 5,
},
{
name: 'Mets',
city: 'New York',
sport: 'Baseball',
active: 13,
inactive: 2,
},
{
name: 'Dodgers',
city: 'Los Angeles',
sport: 'Baseball',
active: 18,
inactive: 1,
},
{
name: 'Angels',
city: 'Los Angeles',
sport: 'Baseball',
active: 14,
inactive: 4,
},
]
</script>
EDIT: Update the playground section to use better example
Assuming some docs are needed to pull this PR. Let me know if that is the case.
Assuming some docs are needed to pull this PR. Let me know if that is the case.
https://github.com/vuetifyjs/vuetify/blob/dfafe8946afd2ed340a6c2d29c88383b320d13a9/packages/api-generator/src/locale/en/VDataTable.json#L39 and probably some others, you'd need to build vuetify and api-generator, run the docs and find missing slot descriptions in the api of data table components
<template #group.category="{ item }">
Is this supposed to be group.dairy
? Doesn't make much sense to show dairy in the category column.
This example also doesn't really work with nested groups ([{ key: 'dairy' }, { key: 'category' }]
), {{ item.value }}
puts each group value in the same column without extra checks. Do you have a better example of how this might be used?
If it's just for showing the grouped value we probably need to be doing this by default anyway:
Yeah, I have a more specific example. I'll write it up sometime today and post the code here with some screen shots
Here is a better example @KaelWD. Im noticing some definite pain points with the current implementation, such as not having access to previous the entire nested groupBy keys, and having to do stuff the getItemsRecursive
(in Playground.vue below)
Playground.vue
<template>
<v-app>
<v-container>
<v-chip-group v-model="groupBy" multiple filter>
<v-chip :value="{ key: 'city', order: 'asc' }">
City
</v-chip>
<v-chip :value="{ key: 'sport', order: 'asc' }">
Sport
</v-chip>
</v-chip-group>
<v-data-table
:group-by="groupBy"
:headers="headers"
:items="sports"
:items-per-page="20"
>
<template #[`group.active`]="{ item }">
{{ getItemsRecursive(item).reduce((a, v) => v.raw.active + a, 0) }}
</template>
<template #[`group.inactive`]="{ item }">
{{ getItemsRecursive(item).reduce((a, v) => v.raw.inactive + a, 0) }}
</template>
</v-data-table>
</v-container>
</v-app>
</template>
<script setup lang="ts">
import { Group } from '@/labs/VDataTable/composables/group'
import { SortItem } from '@/labs/VDataTable/composables/sort'
import { DataTableHeader } from '@/labs/VDataTable/types'
import { ref } from 'vue'
const groupBy = ref<readonly SortItem[]>([{ key: 'sport', order: 'asc' }, { key: 'city', order: 'asc' }])
type Team = {
name: string
city: string
sport: string,
active: number
inactive: number
}
const getItemsRecursive = (group:Group) => {
let items = group.items
while (items[0].items) {
items = items.flatMap((v:Group) => v.items)
}
return items
}
const headers: DataTableHeader[] = [
{
title: 'Name',
align: 'start',
key: 'name',
},
{
title: 'City',
align: 'start',
key: 'city',
},
{
title: 'Sport',
align: 'start',
key: 'sport',
},
{ title: 'Active Players', key: 'active', align: 'end' },
{ title: 'Inactive Players', key: 'inactive', align: 'end' },
]
const sports: Team[] = [
{
name: 'Celtics',
city: 'Boston',
sport: 'Basketball',
active: 10,
inactive: 5,
},
{
name: 'Lakers',
city: 'Los Angeles',
sport: 'Basketball',
active: 15,
inactive: 3,
},
{
name: '49ers',
city: 'San Francisco',
sport: 'Football',
active: 12,
inactive: 4,
},
{
name: 'Giants',
city: 'San Francisco',
sport: 'Baseball',
active: 16,
inactive: 2,
},
{
name: 'Warriors',
city: 'San Francisco',
sport: 'Basketball',
active: 14,
inactive: 4,
},
{
name: 'Sharks',
city: 'San Jose',
sport: 'Hockey',
active: 17,
inactive: 3,
},
{
name: 'Raiders',
city: 'Las Vegas',
sport: 'Football',
active: 11,
inactive: 5,
},
{
name: 'Golden Knights',
city: 'Las Vegas',
sport: 'Hockey',
active: 19,
inactive: 1,
},
{
name: 'Heat',
city: 'Miami',
sport: 'Basketball',
active: 16,
inactive: 2,
},
{
name: 'Marlins',
city: 'Miami',
sport: 'Baseball',
active: 14,
inactive: 4,
},
{
name: 'Dolphins',
city: 'Miami',
sport: 'Football',
active: 12,
inactive: 4,
},
{
name: 'Lightning',
city: 'Tampa Bay',
sport: 'Hockey',
active: 18,
inactive: 2,
},
{
name: 'Rays',
city: 'Tampa Bay',
sport: 'Baseball',
active: 15,
inactive: 3,
},
{
name: 'Buccaneers',
city: 'Tampa Bay',
sport: 'Football',
active: 14,
inactive: 2,
},
{
name: 'Timberwolves',
city: 'Minnesota',
sport: 'Basketball',
active: 11,
inactive: 5,
},
{
name: 'Twins',
city: 'Minnesota',
sport: 'Baseball',
active: 17,
inactive: 1,
},
{
name: 'Bruins',
city: 'Boston',
sport: 'Hockey',
active: 20,
inactive: 4,
},
{
name: 'Red Sox',
city: 'Boston',
sport: 'Baseball',
active: 15,
inactive: 3,
},
{
name: 'Yankees',
city: 'New York',
sport: 'Baseball',
active: 17,
inactive: 5,
},
{
name: 'Mets',
city: 'New York',
sport: 'Baseball',
active: 13,
inactive: 2,
},
{
name: 'Dodgers',
city: 'Los Angeles',
sport: 'Baseball',
active: 18,
inactive: 1,
},
{
name: 'Angels',
city: 'Los Angeles',
sport: 'Baseball',
active: 14,
inactive: 4,
},
]
</script>
Noticing some things immediately and wondering if I should fix. https://github.com/vuetifyjs/vuetify/blob/dfafe8946afd2ed340a6c2d29c88383b320d13a9/packages/api-generator/src/locale/en/VDataTable.json#L39 is missing "[`column.${string}`]"
slot, and the description for "[`column.${string}`]"
in VDataTableVirtual.json is not the clear, if I am understanding the code correctly. Looks like the column prop is for rendering a custom cell in the header, not complete custom rendering of a column, which doesn't even make sense given how data tables are oragnized.
Should I address these things in this PR or would that be extending the scope of the PR too far. Let me know
Would it be possible to sort the headers also based on columns first followed by grouped rows. Essentially it’s a multi sort.
Would it be possible to sort the headers also based on columns first followed by grouped rows. Essentially it’s a multi sort.
I'm a little confused, what exactly do you mean? Data tables support multi-sort already, and sorting while rows are grouped also works quite well
In your example screen shot you have a sum of all the active players for each sport e.g Baseball. What I would like to know is if the groups themselves can be sorted first with this calculated value ( e.g decscending order - highest numbers of total players in the group to lowest number of total players )
Hmm yeah if you want sorting the sum would have to be done through the groupBy object somehow, can't sort slot contents.
Actually my groups sort for some reasons when i sort by clicking the headers so I was suprised when I was testing the examples that the entire group did not move. I will try reproduce on playground. I’m not sure if this is a feature or a bug. Because the functionality of being able to sort the entire group itself is what I want.
Edit: looks the reason I was able to achieve sorting by group was because of the groupBy object { key: 'sport', order: 'asc' } If I remove the order pair and only have key e.g { key: 'sport' } then the groups will reorder.
Yeah that'll sort the groups by the grouped value (sport name in alphabetical order), there's no way to sort by count or some other aggregate though. With no defined order it'll go by order of first appearance in the data after non-group sorting.
Any hint on which files I should look or how you would go about implementing this ? , I want to try experiment and get something working , a feature request and PR hopefully.
Thanks
packages/vuetify/src/labs/VDataTable/composables/group.ts
I'd probably do it on headers actually, something like this maybe?
{
title: 'Active Players',
key: 'active',
align: 'end',
aggregate: ({ group }) => group.items.reduce((a, v) => v.raw.active + a, 0)
}
Yeah, this makes the most sense to me. By defining an aggregate function for each column, the slot can be passed the aggregated value along with the rest of the context, and thus the slot is just for overriding default rendering like with normal columns. This addresses the awkwardness of the recursive sum that I did in my example, and so I think this is the best move. In a sense to a slightly separate issue to what this PR is addressing, is this PR is just about the slot itself, but I feel like it is related closely enough to be worth addressing in this PR as well.
On a side note, I'm going to continue working in this PR I'm going to need a little bit of direction as this is ending up requiring some knowledge of the framework's inner workings that I don't quite have
Until such a time as the aggregate issue is addressed this feels complete. I addressed all docs I could find, so I think this can be merged. I performed all the checks I could find in the npm scripts, but if I'm missing something I'm happy to address it.
Please resolve merge conflicts, ty.
Hey, I haven't looked at this PR in a while. Like I mentioned in my previous comment, the aggregation API could in a certain way make this PR obsolete. I'm happy to update it if it's still a desired feature.