Skip to content

Commit

Permalink
feat(VDataTable/Virtual): add support for sorting raw objects (#19048)
Browse files Browse the repository at this point in the history
closes #11226
  • Loading branch information
johnleider authored Jan 19, 2024
1 parent 8bcdf9d commit 0bba2f5
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 19 deletions.
105 changes: 105 additions & 0 deletions packages/docs/src/examples/v-data-table/prop-headers-sort-raw.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<template>
<v-data-table
:headers="headers"
:items="items"
></v-data-table>
</template>

<script setup>
function sortRaw (a, b) {
if (a.location < b.location) return -1
if (a.location > b.location) return 1
const dateA = a.constructed.split('-').pop().trim()
const dateB = b.constructed.split('-').pop().trim()
return dateA.localeCompare(dateB, undefined, { numeric: true, sensitivity: 'base' })
}
const headers = [
{ title: 'Name', key: 'name' },
{ title: 'Location', key: 'location', sortRaw },
{ title: 'Constructed', key: 'constructed' },
{ title: 'Description', key: 'description' },
]
const items = [
{ name: 'Great Pyramid of Giza', location: 'Egypt', constructed: '2584-2561 BC', description: 'The oldest and largest of the three pyramids in the Giza pyramid complex.' },
{ name: 'Hanging Gardens of Babylon', location: 'Iraq', constructed: '600 BC', description: 'An ascending series of tiered gardens, said to have been built in ancient Babylon.' },
{ name: 'Statue of Zeus at Olympia', location: 'Greece', constructed: '435 BC', description: 'A giant seated figure of Zeus, made by the sculptor Phidias.' },
{ name: 'Temple of Artemis at Ephesus', location: 'Turkey', constructed: '550 BC', description: 'A large temple dedicated to the goddess Artemis, one of the Seven Wonders of the Ancient World.' },
{ name: 'Mausoleum at Halicarnassus', location: 'Turkey', constructed: '351 BC', description: 'A tomb built for Mausolus, a satrap of the Persian Empire.' },
{ name: 'Colossus of Rhodes', location: 'Greece', constructed: '292-280 BC', description: 'A statue of the Greek sun-god Helios, erected in the city of Rhodes.' },
{ name: 'Lighthouse of Alexandria', location: 'Egypt', constructed: '280 BC', description: 'A lighthouse built by the Ptolemaic Kingdom on the island of Pharos.' },
{ name: 'Great Wall of China', location: 'China', constructed: '7th century BC - 1644 AD', description: 'A series of fortifications made of stone, brick, and other materials.' },
{ name: 'Petra', location: 'Jordan', constructed: '312 BC', description: 'A historical city known for its rock-cut architecture and water conduit system.' },
{ name: 'Taj Mahal', location: 'India', constructed: '1632-1653 AD', description: 'An ivory-white marble mausoleum on the south bank of the Yamuna river.' },
{ name: 'Machu Picchu', location: 'Peru', constructed: '1450 AD', description: 'An Incan citadel set high in the Andes Mountains.' },
{ name: 'Chichen Itza', location: 'Mexico', constructed: '600 AD', description: 'A large pre-Columbian archaeological site built by the Maya people.' },
{ name: 'Roman Colosseum', location: 'Italy', constructed: '70-80 AD', description: 'An oval amphitheatre in the centre of the city of Rome.' },
{ name: 'Stonehenge', location: 'United Kingdom', constructed: '3000 BC - 2000 BC', description: 'A prehistoric monument consisting of a ring of standing stones.' },
{ name: 'Angkor Wat', location: 'Cambodia', constructed: '12th century AD', description: 'The largest religious monument in the world, originally constructed as a Hindu temple.' },
{ name: 'Moai Statues of Easter Island', location: 'Chile', constructed: '1250-1500 AD', description: 'Monolithic human figures carved by the Rapa Nui people on Easter Island.' },
{ name: 'Hagia Sophia', location: 'Turkey', constructed: '537 AD', description: 'A former Greek Orthodox Christian patriarchal basilica, later an Ottoman imperial mosque and now a museum.' },
{ name: 'Alhambra', location: 'Spain', constructed: '13th century AD', description: 'A palace and fortress complex located in Granada.' },
{ name: 'Forbidden City', location: 'China', constructed: '1406-1420 AD', description: 'A palace complex in central Beijing, serving as the home of emperors and their households.' },
{ name: 'Christ the Redeemer', location: 'Brazil', constructed: '1922-1931 AD', description: 'An Art Deco statue of Jesus Christ in Rio de Janeiro.' },
{ name: 'Acropolis of Athens', location: 'Greece', constructed: '5th century BC', description: 'An ancient citadel located on a rocky outcrop above the city of Athens.' },
{ name: 'Terracotta Army', location: 'China', constructed: '246-206 BC', description: 'A collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China.' },
{ name: 'Parthenon', location: 'Greece', constructed: '447-438 BC', description: 'A former temple on the Athenian Acropolis, dedicated to the goddess Athena.' },
{ name: 'Tower of London', location: 'United Kingdom', constructed: '1078 AD', description: 'A historic castle located on the north bank of the River Thames in central London.' },
{ name: 'Neuschwanstein Castle', location: 'Germany', constructed: '1869-1886 AD', description: 'A 19th-century Romanesque Revival palace on a rugged hill above the village of Hohenschwangau.' },
]
</script>

<script>
export default {
data: () => ({
headers: [
{ title: 'Name', key: 'name' },
{
title: 'Location',
key: 'location',
sortRaw (a, b) {
if (a.location < b.location) return -1
if (a.location > b.location) return 1
const dateA = a.constructed.split('-').pop().trim()
const dateB = b.constructed.split('-').pop().trim()
return dateA.localeCompare(dateB, undefined, { numeric: true, sensitivity: 'base' })
},
},
{ title: 'Constructed', key: 'constructed' },
{ title: 'Description', key: 'description' },
],
items: [
{ name: 'Great Pyramid of Giza', location: 'Egypt', constructed: '2584-2561 BC', description: 'The oldest and largest of the three pyramids in the Giza pyramid complex.' },
{ name: 'Hanging Gardens of Babylon', location: 'Iraq', constructed: '600 BC', description: 'An ascending series of tiered gardens, said to have been built in ancient Babylon.' },
{ name: 'Statue of Zeus at Olympia', location: 'Greece', constructed: '435 BC', description: 'A giant seated figure of Zeus, made by the sculptor Phidias.' },
{ name: 'Temple of Artemis at Ephesus', location: 'Turkey', constructed: '550 BC', description: 'A large temple dedicated to the goddess Artemis, one of the Seven Wonders of the Ancient World.' },
{ name: 'Mausoleum at Halicarnassus', location: 'Turkey', constructed: '351 BC', description: 'A tomb built for Mausolus, a satrap of the Persian Empire.' },
{ name: 'Colossus of Rhodes', location: 'Greece', constructed: '292-280 BC', description: 'A statue of the Greek sun-god Helios, erected in the city of Rhodes.' },
{ name: 'Lighthouse of Alexandria', location: 'Egypt', constructed: '280 BC', description: 'A lighthouse built by the Ptolemaic Kingdom on the island of Pharos.' },
{ name: 'Great Wall of China', location: 'China', constructed: '7th century BC - 1644 AD', description: 'A series of fortifications made of stone, brick, and other materials.' },
{ name: 'Petra', location: 'Jordan', constructed: '312 BC', description: 'A historical city known for its rock-cut architecture and water conduit system.' },
{ name: 'Taj Mahal', location: 'India', constructed: '1632-1653 AD', description: 'An ivory-white marble mausoleum on the south bank of the Yamuna river.' },
{ name: 'Machu Picchu', location: 'Peru', constructed: '1450 AD', description: 'An Incan citadel set high in the Andes Mountains.' },
{ name: 'Chichen Itza', location: 'Mexico', constructed: '600 AD', description: 'A large pre-Columbian archaeological site built by the Maya people.' },
{ name: 'Roman Colosseum', location: 'Italy', constructed: '70-80 AD', description: 'An oval amphitheatre in the centre of the city of Rome.' },
{ name: 'Stonehenge', location: 'United Kingdom', constructed: '3000 BC - 2000 BC', description: 'A prehistoric monument consisting of a ring of standing stones.' },
{ name: 'Angkor Wat', location: 'Cambodia', constructed: '12th century AD', description: 'The largest religious monument in the world, originally constructed as a Hindu temple.' },
{ name: 'Moai Statues of Easter Island', location: 'Chile', constructed: '1250-1500 AD', description: 'Monolithic human figures carved by the Rapa Nui people on Easter Island.' },
{ name: 'Hagia Sophia', location: 'Turkey', constructed: '537 AD', description: 'A former Greek Orthodox Christian patriarchal basilica, later an Ottoman imperial mosque and now a museum.' },
{ name: 'Alhambra', location: 'Spain', constructed: '13th century AD', description: 'A palace and fortress complex located in Granada.' },
{ name: 'Forbidden City', location: 'China', constructed: '1406-1420 AD', description: 'A palace complex in central Beijing, serving as the home of emperors and their households.' },
{ name: 'Christ the Redeemer', location: 'Brazil', constructed: '1922-1931 AD', description: 'An Art Deco statue of Jesus Christ in Rio de Janeiro.' },
{ name: 'Acropolis of Athens', location: 'Greece', constructed: '5th century BC', description: 'An ancient citadel located on a rocky outcrop above the city of Athens.' },
{ name: 'Terracotta Army', location: 'China', constructed: '246-206 BC', description: 'A collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China.' },
{ name: 'Parthenon', location: 'Greece', constructed: '447-438 BC', description: 'A former temple on the Athenian Acropolis, dedicated to the goddess Athena.' },
{ name: 'Tower of London', location: 'United Kingdom', constructed: '1078 AD', description: 'A historic castle located on the north bank of the River Thames in central London.' },
{ name: 'Neuschwanstein Castle', location: 'Germany', constructed: '1869-1886 AD', description: 'A 19th-century Romanesque Revival palace on a rugged hill above the village of Hohenschwangau.' },
],
}),
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,15 @@ Unless you are using the **multi-sort** prop seen below, this array will almost
Using the **multi-sort** prop will enable you to sort on multiple columns at the same time.

<example file="v-data-table/prop-multi-sort" />

### Sort by raw

::: success

This feature was introduced in [v3.5.0 (Polaris)](/getting-started/release-notes/?version=v3.5.0)

:::

Using a *sortRaw* key in your headers object gives you access to all values on the item. This is useful if you want to sort by a value that is not displayed in the table or a combination of multiple values.

<example file="v-data-table/prop-headers-sort-raw" />
10 changes: 8 additions & 2 deletions packages/vuetify/src/components/VDataTable/VDataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,13 @@ export const VDataTable = genericComponent<new <T extends readonly any[], V>(
const { sortBy, multiSort, mustSort } = createSort(props)
const { page, itemsPerPage } = createPagination(props)

const { columns, headers, sortFunctions, filterFunctions } = createHeaders(props, {
const {
columns,
headers,
sortFunctions,
sortRawFunctions,
filterFunctions,
} = createHeaders(props, {
groupBy,
showSelect: toRef(props, 'showSelect'),
showExpand: toRef(props, 'showExpand'),
Expand All @@ -144,7 +150,7 @@ export const VDataTable = genericComponent<new <T extends readonly any[], V>(
const { toggleSort } = provideSort({ sortBy, multiSort, mustSort, page })
const { sortByWithGroups, opened, extractRows, isGroupOpen, toggleGroup } = provideGroupBy({ groupBy, sortBy })

const { sortedItems } = useSortedItems(props, filteredItems, sortByWithGroups, sortFunctions)
const { sortedItems } = useSortedItems(props, filteredItems, sortByWithGroups, sortFunctions, sortRawFunctions)
const { flatItems } = useGroupedItems(sortedItems, groupBy, opened)
const itemsLength = computed(() => flatItems.value.length)

Expand Down
10 changes: 8 additions & 2 deletions packages/vuetify/src/components/VDataTable/VDataTableVirtual.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,13 @@ export const VDataTableVirtual = genericComponent<new <T extends readonly any[],
const { groupBy } = createGroupBy(props)
const { sortBy, multiSort, mustSort } = createSort(props)

const { columns, headers, sortFunctions, filterFunctions } = createHeaders(props, {
const {
columns,
headers,
filterFunctions,
sortFunctions,
sortRawFunctions,
} = createHeaders(props, {
groupBy,
showSelect: toRef(props, 'showSelect'),
showExpand: toRef(props, 'showExpand'),
Expand All @@ -103,7 +109,7 @@ export const VDataTableVirtual = genericComponent<new <T extends readonly any[],
const { toggleSort } = provideSort({ sortBy, multiSort, mustSort })
const { sortByWithGroups, opened, extractRows, isGroupOpen, toggleGroup } = provideGroupBy({ groupBy, sortBy })

const { sortedItems } = useSortedItems(props, filteredItems, sortByWithGroups, sortFunctions)
const { sortedItems } = useSortedItems(props, filteredItems, sortByWithGroups, sortFunctions, sortRawFunctions)
const { flatItems } = useGroupedItems(sortedItems, groupBy, opened)

const allItems = computed(() => extractRows(flatItems.value))
Expand Down
31 changes: 18 additions & 13 deletions packages/vuetify/src/components/VDataTable/composables/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,9 @@ export function createHeaders (
) {
const headers = ref<InternalDataTableHeader[][]>([])
const columns = ref<InternalDataTableHeader[]>([])
const sortFunctions = ref<Record<string, DataTableCompareFunction>>()
const filterFunctions = ref<FilterKeyFunctions>()
const sortFunctions = ref<Record<string, DataTableCompareFunction>>({})
const sortRawFunctions = ref<Record<string, DataTableCompareFunction>>({})
const filterFunctions = ref<FilterKeyFunctions>({})

watchEffect(() => {
const _headers = props.headers ||
Expand Down Expand Up @@ -260,22 +261,26 @@ export function createHeaders (

const flatHeaders = parsed.headers.flat(1)

sortFunctions.value = flatHeaders.reduce((acc, header) => {
if (header.sortable && header.key && header.sort) {
acc[header.key] = header.sort
for (const header of flatHeaders) {
if (!header.key) continue

if (header.sortable) {
if (header.sort) {
sortFunctions.value[header.key] = header.sort
}

if (header.sortRaw) {
sortRawFunctions.value[header.key] = header.sortRaw
}
}
return acc
}, {} as Record<string, DataTableCompareFunction>)

filterFunctions.value = flatHeaders.reduce((acc, header) => {
if (header.key && header.filter) {
acc[header.key] = header.filter
if (header.filter) {
filterFunctions.value[header.key] = header.filter
}
return acc
}, {} as FilterKeyFunctions)
}
})

const data = { headers, columns, sortFunctions, filterFunctions }
const data = { headers, columns, sortFunctions, sortRawFunctions, filterFunctions }

provide(VDataTableHeadersSymbol, data)

Expand Down
18 changes: 16 additions & 2 deletions packages/vuetify/src/components/VDataTable/composables/sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,13 @@ export function useSort () {
return data
}

// TODO: abstract into project composable
export function useSortedItems <T extends Record<string, any>> (
props: { customKeySort: Record<string, DataTableCompareFunction> | undefined },
items: Ref<T[]>,
sortBy: Ref<readonly SortItem[]>,
sortFunctions?: Ref<Record<string, DataTableCompareFunction> | undefined>,
sortRawFunctions?: Ref<Record<string, DataTableCompareFunction> | undefined>,
) {
const locale = useLocale()
const sortedItems = computed(() => {
Expand All @@ -106,7 +108,7 @@ export function useSortedItems <T extends Record<string, any>> (
return sortItems(items.value, sortBy.value, locale.current.value, {
...props.customKeySort,
...sortFunctions?.value,
})
}, sortRawFunctions?.value)
})

return { sortedItems }
Expand All @@ -116,7 +118,8 @@ export function sortItems<T extends Record<string, any>> (
items: T[],
sortByItems: readonly SortItem[],
locale: string,
customSorters?: Record<string, DataTableCompareFunction>
customSorters?: Record<string, DataTableCompareFunction>,
customRawSorters?: Record<string, DataTableCompareFunction>,
): T[] {
const stringCollator = new Intl.Collator(locale, { sensitivity: 'accent', usage: 'sort' })

Expand All @@ -129,9 +132,20 @@ export function sortItems<T extends Record<string, any>> (

let sortA = getObjectValueByPath(a.raw, sortKey)
let sortB = getObjectValueByPath(b.raw, sortKey)
let sortARaw = a.raw
let sortBRaw = b.raw

if (sortOrder === 'desc') {
[sortA, sortB] = [sortB, sortA]
;[sortARaw, sortBRaw] = [sortBRaw, sortARaw]
}

if (customRawSorters?.[sortKey]) {
const customResult = customRawSorters[sortKey](sortARaw, sortBRaw)

if (!customResult) continue

return customResult
}

if (customSorters?.[sortKey]) {
Expand Down
1 change: 1 addition & 0 deletions packages/vuetify/src/components/VDataTable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type DataTableHeader = {

sortable?: boolean
sort?: DataTableCompareFunction
sortRaw?: DataTableCompareFunction
filter?: FilterFunction

children?: DataTableHeader[]
Expand Down

0 comments on commit 0bba2f5

Please sign in to comment.