Skip to content

Commit

Permalink
feat(VDataTable): backport filterMode prop from v3 (#17747)
Browse files Browse the repository at this point in the history
closes #11600
  • Loading branch information
KaelWD committed Jul 5, 2023
1 parent a85b85f commit 05c10a3
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 21 deletions.
1 change: 1 addition & 0 deletions packages/api-generator/src/locale/en/v-data-table.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"disablePagination": "Disables pagination completely",
"disableSort": "Disables sorting completely",
"expandIcon": "Icon used for expand toggle button.",
"filterMode": "Controls how how custom column filters are combined with the default filtering. Both modes only apply the default filter to columns not specified in `customKeyFilter`.\n\n- **union**: There is at least one match from the default filter, OR all custom column filters match.\n- **intersection**: There is at least one match from the default filter, AND all custom column filters match.",
"fixedHeader": "Fixed header to top of table. **NOTE:** Does not work in IE11",
"groupBy": "Changes which item property should be used for grouping items. Currently only supports a single grouping in the format: `group` or `['group']`. When using an array, only the first element is considered. Can be used with `.sync` modifier",
"groupDesc": "Changes which direction grouping is done. Can be used with `.sync` modifier",
Expand Down
52 changes: 41 additions & 11 deletions packages/vuetify/src/components/VDataTable/VDataTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
RowClassFunction,
RowStyleFunction,
DataTableItemProps,
DataTableFilterMode,
} from 'vuetify/types'

// Components
Expand Down Expand Up @@ -55,21 +56,39 @@ function searchTableItems (
search: string | null,
headersWithCustomFilters: DataTableHeader[],
headersWithoutCustomFilters: DataTableHeader[],
customFilter: DataTableFilterFunction
customFilter: DataTableFilterFunction,
filterMode: DataTableFilterMode,
) {
search = typeof search === 'string' ? search.trim() : null

return items.filter(item => {
// Headers with custom filters are evaluated whether or not a search term has been provided.
// We need to match every filter to be included in the results.
const matchesColumnFilters = headersWithCustomFilters.every(filterFn(item, search, defaultFilter))
if (filterMode === 'union') {
// If the `search` property is empty and there are no custom filters in use, there is nothing to do.
if (!(search && headersWithoutCustomFilters.length) && !headersWithCustomFilters.length) return items

// Headers without custom filters are only filtered by the `search` property if it is defined.
// We only need a single column to match the search term to be included in the results.
const matchesSearchTerm = !search || headersWithoutCustomFilters.some(filterFn(item, search, customFilter))
return items.filter(item => {
// Headers with custom filters are evaluated whether or not a search term has been provided.
if (headersWithCustomFilters.length && headersWithCustomFilters.every(filterFn(item, search, defaultFilter))) {
return true
}

// Otherwise, the `search` property is used to filter columns without a custom filter.
return (search && headersWithoutCustomFilters.some(filterFn(item, search, customFilter)))
})
} else if (filterMode === 'intersection') {
return items.filter(item => {
// Headers with custom filters are evaluated whether or not a search term has been provided.
// We need to match every filter to be included in the results.
const matchesColumnFilters = headersWithCustomFilters.every(filterFn(item, search, defaultFilter))

// Headers without custom filters are only filtered by the `search` property if it is defined.
// We only need a single column to match the search term to be included in the results.
const matchesSearchTerm = !search || headersWithoutCustomFilters.some(filterFn(item, search, customFilter))

return matchesColumnFilters && matchesSearchTerm
})
return matchesColumnFilters && matchesSearchTerm
})
} else {
return items
}
}

/* @vue/component */
Expand Down Expand Up @@ -112,6 +131,10 @@ export default mixins(
type: Function,
default: defaultFilter,
} as PropValidator<typeof defaultFilter>,
filterMode: {
type: String,
default: 'intersection',
} as PropValidator<DataTableFilterMode>,
itemClass: {
type: [String, Function],
default: () => '',
Expand Down Expand Up @@ -229,7 +252,14 @@ export default mixins(
this.widths = Array.from(this.$el.querySelectorAll('th')).map(e => e.clientWidth)
},
customFilterWithColumns (items: any[], search: string) {
return searchTableItems(items, search, this.headersWithCustomFilters, this.headersWithoutCustomFilters, this.customFilter)
return searchTableItems(
items,
search,
this.headersWithCustomFilters,
this.headersWithoutCustomFilters,
this.customFilter,
this.filterMode
)
},
customSortWithHeaders (items: any[], sortBy: string[], sortDesc: boolean[], locale: string) {
return this.customSort(items, sortBy, sortDesc, locale, this.columnSorters)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -957,11 +957,34 @@ describe('VDataTable.ts', () => {
expect(wrapper.html()).toMatchSnapshot()
})

// https://github.com/vuetifyjs/vuetify/issues/11600
it('should return rows from columns that match custom filters', async () => {
const wrapper = mountFunction({
propsData: {
items: testItems,
filterMode: 'union',
headers: [
{ text: 'Dessert (100g serving)', align: 'left', value: 'name' },
{ text: 'Calories', value: 'calories', filter: value => value === 159 },
{ text: 'Fat (g)', value: 'fat' },
{ text: 'Carbs (g)', value: 'carbs' },
{ text: 'Protein (g)', value: 'protein' },
{ text: 'Iron (%)', value: 'iron' },
],
},
})

wrapper.setProps({ search: 'eclair' })
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalCurrentItems).toHaveLength(2)
})

// https://github.com/vuetifyjs/vuetify/issues/11179
it('should return rows from columns that exclusively match custom filters', async () => {
const wrapper = mountFunction({
propsData: {
items: testItems,
filterMode: 'intersection',
headers: [
{ text: 'Dessert (100g serving)', align: 'left', value: 'name' },
{ text: 'Calories', value: 'calories', filter: value => value === 159 },
Expand All @@ -973,6 +996,11 @@ describe('VDataTable.ts', () => {
},
})

wrapper.setProps({ search: 'eclair' })
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalCurrentItems).toHaveLength(0)

wrapper.setProps({ search: 'frozen' })
await wrapper.vm.$nextTick()
expect(wrapper.vm.internalCurrentItems).toHaveLength(1)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2874,18 +2874,18 @@ exports[`VDataTable.ts should hide group button when column is not groupable 1`]
<div role="button"
aria-haspopup="listbox"
aria-expanded="false"
aria-owns="list-747"
aria-owns="list-763"
class="v-input__slot"
>
<div class="v-select__slot">
<label for="input-747"
<label for="input-763"
class="v-label theme--light"
style="left: 0px; position: absolute;"
>
Sort by
</label>
<div class="v-select__selections">
<input id="input-747"
<input id="input-763"
readonly="readonly"
type="text"
aria-readonly="false"
Expand Down Expand Up @@ -3424,7 +3424,7 @@ exports[`VDataTable.ts should hide group button when column is not groupable 1`]
<div role="button"
aria-haspopup="listbox"
aria-expanded="false"
aria-owns="list-751"
aria-owns="list-767"
class="v-input__slot"
>
<div class="v-select__slot">
Expand All @@ -3433,7 +3433,7 @@ exports[`VDataTable.ts should hide group button when column is not groupable 1`]
10
</div>
<input aria-label="Rows per page:"
id="input-751"
id="input-767"
readonly="readonly"
type="text"
aria-readonly="false"
Expand Down Expand Up @@ -11354,18 +11354,18 @@ exports[`VDataTable.ts should respect mustSort property on options 1`] = `
<div role="button"
aria-haspopup="listbox"
aria-expanded="false"
aria-owns="list-731"
aria-owns="list-747"
class="v-input__slot"
>
<div class="v-select__slot">
<label for="input-731"
<label for="input-747"
class="v-label theme--light"
style="left: 0px; position: absolute;"
>
Sort by
</label>
<div class="v-select__selections">
<input id="input-731"
<input id="input-747"
readonly="readonly"
type="text"
aria-readonly="false"
Expand Down Expand Up @@ -11504,7 +11504,7 @@ exports[`VDataTable.ts should respect mustSort property on options 1`] = `
<div role="button"
aria-haspopup="listbox"
aria-expanded="false"
aria-owns="list-735"
aria-owns="list-751"
class="v-input__slot"
>
<div class="v-select__slot">
Expand All @@ -11513,7 +11513,7 @@ exports[`VDataTable.ts should respect mustSort property on options 1`] = `
10
</div>
<input aria-label="Rows per page:"
id="input-735"
id="input-751"
readonly="readonly"
type="text"
aria-readonly="false"
Expand Down
2 changes: 2 additions & 0 deletions packages/vuetify/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ export interface DataTableHeader<T extends any = any> {
sort?: DataTableCompareFunction<T>
}

export type DataTableFilterMode = 'union' | 'intersection'

export type DataItemsPerPageOption = (number | {
text: string
value: number
Expand Down

0 comments on commit 05c10a3

Please sign in to comment.