Skip to content

Commit

Permalink
fix(VDataTable): make custom filter function more user friendly (#7885)
Browse files Browse the repository at this point in the history
also added docs example of custom-filter and header filtering
  • Loading branch information
nekosaur authored and johnleider committed Jul 20, 2019
1 parent 9f12df7 commit 132ac8e
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 36 deletions.
18 changes: 18 additions & 0 deletions packages/api-generator/src/helpers/merge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const deepmerge = require('deepmerge')

function arrayMerge (a, b) {
const arr = a.slice()
for (let i = 0; i < b.length; i++) {
const found = a.findIndex(item => item.name === b[i].name)
if (found >= 0) {
arr[found] = deepmerge(a[found], b[i])
} else {
arr.push(b[i])
}
}
return arr
}

module.exports = function merge (a, b) {
return deepmerge(a, b, { arrayMerge })
}
17 changes: 2 additions & 15 deletions packages/api-generator/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,13 @@ const Vue = require('vue')
const Vuetify = require('vuetify')
const fs = require('fs')
const map = require('./helpers/map')
const deepmerge = require('deepmerge')
const deepmerge = require('./helpers/merge')

const hyphenateRE = /\B([A-Z])/g
function hyphenate (str) {
return str.replace(hyphenateRE, '-$1').toLowerCase()
}

function arrayMerge (a, b) {
const arr = a.slice()
for (let i = 0; i < b.length; i++) {
const found = a.findIndex(item => item.name === b[i].name)
if (found >= 0) {
arr[found] = deepmerge(a[found], b[i])
} else {
arr.push(b[i])
}
}
return arr
}

Vue.use(Vuetify)

function parseFunctionParams (func) {
Expand Down Expand Up @@ -153,7 +140,7 @@ for (const name in installedComponents) {
let options = parseComponent(component)

if (map[kebabName]) {
options = deepmerge(options, map[kebabName], { arrayMerge })
options = deepmerge(options, map[kebabName])
}

components[kebabName] = options
Expand Down
8 changes: 4 additions & 4 deletions packages/api-generator/src/maps/v-data-iterator.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { DataEvents, DataProps, DataDefaultScopedSlotProps } = require('./v-data')
const { DataFooterPageTextScopedProps } = require('./v-data-footer')

const DataIteratorProps = [
const DataIteratorProps = DataProps.concat([
{ name: 'value', source: 'v-data-iterator' },
{ name: 'singleSelect', source: 'v-data-iterator' },
{ name: 'expanded', source: 'v-data-iterator' },
Expand All @@ -12,14 +12,14 @@ const DataIteratorProps = [
{ name: 'noDataText', source: 'v-data-iterator' },
{ name: 'hideDefaultFooter', source: 'v-data-iterator' },
{ name: 'footerProps', source: 'v-data-iterator' },
].concat(DataProps)
])

const DataIteratorEvents = [
const DataIteratorEvents = DataEvents.concat([
{ name: 'input', source: 'v-data-iterator', value: 'any[]' },
{ name: 'update:expanded', source: 'v-data-iterator', value: 'any[]' },
{ name: 'item-selected', source: 'v-data-iterator', value: '{ item: any, value: boolean }' },
{ name: 'item-expanded', source: 'v-data-iterator', value: '{ item: any, value: boolean }' },
].concat(DataEvents)
])

const DataIteratorSlots = [
{ name: 'loading', source: 'data-iterator' },
Expand Down
11 changes: 8 additions & 3 deletions packages/api-generator/src/maps/v-data-table.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { DataDefaultScopedSlotProps, DataOptions, DataProps } = require('./v-data')
const deepmerge = require('../helpers/merge')
const { DataDefaultScopedSlotProps, DataOptions } = require('./v-data')
const { DataIteratorEvents, DataIteratorProps, DataIteratorItemScopedProps } = require('./v-data-iterator')
const { DataFooterPageTextScopedProps } = require('./v-data-footer')

Expand Down Expand Up @@ -86,13 +87,17 @@ const DataTableSlots = [

module.exports = {
'v-data-table': {
props: [
props: deepmerge(DataIteratorProps, [
{
name: 'headers',
type: 'TableHeader[]',
example: TableHeader,
},
].concat(DataIteratorProps).concat(DataProps),
{
name: 'customFilter',
default: '(value: any, search: string | null, item: any): boolean',
},
]),
slots: DataTableSlots,
events: DataTableEvents,
},
Expand Down
1 change: 1 addition & 0 deletions packages/docs/src/data/pages/components/DataTables.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"simple/footer-props",
"intermediate/slots",
"intermediate/expand",
"intermediate/custom-filter",
"intermediate/customize-header",
"intermediate/customize-rows",
"intermediate/customize-footer",
Expand Down
151 changes: 151 additions & 0 deletions packages/docs/src/examples/data-tables/intermediate/custom-filter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<template>
<div>
<v-data-table
:headers="headers"
:items="desserts"
item-key="name"
class="elevation-1"
:search="search"
:custom-filter="filterOnlyCapsText"
>
<template v-slot:top>
<v-text-field v-model="search" label="Search (UPPER CASE ONLY)" class="mx-4"></v-text-field>
</template>
<template v-slot:body.append>
<tr>
<td></td>
<td>
<v-text-field v-model="calories" type="number" label="Less than"></v-text-field>
</td>
<td colspan="4"></td>
</tr>
</template>
</v-data-table>
</div>
</template>

<script>
export default {
data () {
return {
search: '',
calories: '',
desserts: [
{
name: 'Frozen Yogurt',
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
iron: '1%',
},
{
name: 'Ice cream sandwich',
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
iron: '1%',
},
{
name: 'Eclair',
calories: 262,
fat: 16.0,
carbs: 23,
protein: 6.0,
iron: '7%',
},
{
name: 'Cupcake',
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
iron: '8%',
},
{
name: 'Gingerbread',
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
iron: '16%',
},
{
name: 'Jelly bean',
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
iron: '0%',
},
{
name: 'Lollipop',
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0,
iron: '2%',
},
{
name: 'Honeycomb',
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
iron: '45%',
},
{
name: 'Donut',
calories: 452,
fat: 25.0,
carbs: 51,
protein: 4.9,
iron: '22%',
},
{
name: 'KitKat',
calories: 518,
fat: 26.0,
carbs: 65,
protein: 7,
iron: '6%',
},
],
}
},
computed: {
headers () {
return [
{
text: 'Dessert (100g serving)',
align: 'left',
sortable: false,
value: 'name',
},
{
text: 'Calories',
value: 'calories',
filter: value => {
if (!this.calories) return true
return value < parseInt(this.calories)
},
},
{ text: 'Fat (g)', value: 'fat' },
{ text: 'Carbs (g)', value: 'carbs' },
{ text: 'Protein (g)', value: 'protein' },
{ text: 'Iron (%)', value: 'iron' },
]
},
},
methods: {
filterOnlyCapsText (value, search, item) {
return value != null &&
search != null &&
typeof value === 'string' &&
value.toString().toLocaleUpperCase().indexOf(search) !== -1
},
},
}
</script>
4 changes: 4 additions & 0 deletions packages/docs/src/lang/en/components/DataTables.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@
"virtualized": {
"header": "### Virtualized rows",
"desc": "Using the `virtual-rows` prop will virtualize the rendering of the rows, which can increase the performance of the data-table when you have a large number of rows. Be aware that you can not use the `body`, `body.prepend` or `body.append` slots with this prop."
},
"custom-filter": {
"header": "### Custom filtering",
"desc": "You can override the default filtering used with `search` prop by supplying a function to the `custom-filter` prop. If you need to customize the filtering of a specific column, you can supply a function to the `filter` property on header items. The signature is `(value: any, search: string | null, item: any): boolean`. This function will always be run even if `search` prop has not been provided. Thus you need to make sure to exit early with a value of `true` if filter should not be applied."
}
},
"props": {
Expand Down
25 changes: 15 additions & 10 deletions packages/vuetify/src/components/VDataTable/VDataTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,34 @@ import MobileRow from './MobileRow'
import ripple from '../../directives/ripple'

// Helpers
import { deepEqual, getObjectValueByPath, compareFn, getPrefixedScopedSlots, getSlot, defaultFilter } from '../../util/helpers'
import { deepEqual, getObjectValueByPath, compareFn, getPrefixedScopedSlots, getSlot, defaultFilter, FilterFn } from '../../util/helpers'
import { breaking } from '../../util/console'

function filterFn (item: any, search: string | null) {
function filterFn (item: any, search: string | null, filter: FilterFn) {
return (header: TableHeader) => {
const value = getObjectValueByPath(item, header.value)
return header.filter ? header.filter(value, search, item) : defaultFilter(value, search)
return header.filter ? header.filter(value, search, item) : filter(value, search, item)
}
}

function searchTableItems (
items: any[],
search: string | null,
headersWithCustomFilters: TableHeader[],
headersWithoutCustomFilters: TableHeader[]
headersWithoutCustomFilters: TableHeader[],
customFilter: FilterFn
) {
let filtered = items
search = typeof search === 'string' ? search.trim() : null
if (search) {
filtered = items.filter(item => headersWithoutCustomFilters.some(filterFn(item, search)))
if (search && headersWithoutCustomFilters.length) {
filtered = items.filter(item => headersWithoutCustomFilters.some(filterFn(item, search, customFilter)))
}

return filtered.filter(item => headersWithCustomFilters.every(filterFn(item, search)))
if (headersWithCustomFilters.length) {
filtered = filtered.filter(item => headersWithCustomFilters.every(filterFn(item, search, defaultFilter)))
}

return filtered
}

/* @vue/component */
Expand Down Expand Up @@ -82,8 +87,8 @@ export default VDataIterator.extend({
},
customFilter: {
type: Function,
default: searchTableItems,
} as PropValidator<(items: any[], search: string, exclusiveHeaders: TableHeader[], nonExclusiveHeaders: TableHeader[]) => any[]>,
default: defaultFilter,
} as PropValidator<typeof defaultFilter>,
},

data () {
Expand Down Expand Up @@ -170,7 +175,7 @@ export default VDataIterator.extend({
this.widths = Array.from(this.$el.querySelectorAll('th')).map(e => e.clientWidth)
},
customFilterWithColumns (items: any[], search: string) {
return this.customFilter(items, search, this.headersWithCustomFilters, this.headersWithoutCustomFilters)
return searchTableItems(items, search, this.headersWithCustomFilters, this.headersWithoutCustomFilters, this.customFilter)
},
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 @@ -17,7 +17,6 @@ export interface TableHeader {
class?: string | string[]
width?: string | number
filter?: (value: any, search: string | null, item: any) => boolean
filterExclusive?: boolean
sort?: compareFn
}

Expand Down
6 changes: 3 additions & 3 deletions packages/vuetify/src/util/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,9 +419,9 @@ export function sortItems (
})
}

export type FilterFn = (value: any, search: string, item: any) => boolean
export type FilterFn = (value: any, search: string | null, item: any) => boolean

export function defaultFilter (value: any, search: string | null) {
export function defaultFilter (value: any, search: string | null, item: any) {
return value != null &&
search != null &&
typeof value !== 'boolean' &&
Expand All @@ -433,7 +433,7 @@ export function searchItems (items: any[], search: string) {
search = search.toString().toLowerCase()
if (search.trim() === '') return items

return items.filter(item => Object.keys(item).some(key => defaultFilter(getObjectValueByPath(item, key), search)))
return items.filter(item => Object.keys(item).some(key => defaultFilter(getObjectValueByPath(item, key), search, item)))
}

/**
Expand Down

0 comments on commit 132ac8e

Please sign in to comment.