The Data table element does not support more than a simple text search filtering. This means that it only allows you to add a text field that filters rows whenever a cell partially contains the word you are looking for:
See the Pen Vuetify Example Pen by Front.id (@frontid) on CodePen.
But what happens when we want to put more filters as in this example?
What's happening here is the component does not natively support multiple filters. BUT it does allow us to customize the behavior of the only field available and we are going to use that functionality as a trojan horse to expand the filtering possibilities.
We know that the element <v-data-table> has the possibility to use a property ":filter" in which to indicate the "prop" that has to listen. This "prop" has to be a string, and when it changes, the table will internally apply a filtering using that word to show only the rows that meet the criteria.
We also know that the <v-data-table> element allows you to customize the search algorithm mentioned above using the ":custom-filter" attribute. To this attribute you will assign a method that you will have to create in the "methods" section of your component.
Finally we know that to change the value of the prop associated to :filter we have to add a component <v-text-field> and associate it to the prop through v-model="miprop".
As you have already inferred, the problem appears when you want to add more than one filter to the table because the table only allows as entry point to internal filtering the attribute :filter and can not be an array or object, so you can not go sending information from various fields such as a select, a date picker, etc..
I put a component with the implemented solution and explain it below.
<template>
<v-layout row wrap>
<v-flex xs6>
<v-text-field
append-icon="search"
label="Filter"
single-line
hide-details
@input="filterSearch"
></v-text-field>
</v-flex>
<v-flex xs6>
<v-select
:items="authors"
label="Author"
@change="filterAuthor"
></v-select>
</v-flex>
<v-flex xs12>
<v-data-table
:headers="headers"
:items="rows"
item-key="name"
:search="filters"
:custom-filter="customFilter"
>
<template slot="headers" slot-scope="props">
<tr>
<th v-for="header in props.headers" :key="header.text">
{{ header.text }}
</th>
</tr>
</template>
<template slot="items" slot-scope="props">
<tr>
<td>{{ props.item.name }}</td>
<td>{{ props.item.added_by }}</td>
</tr>
</template>
</v-data-table>
</v-flex>
</v-layout>
</template>
<script>
export default {
data: () => ({
filters: {
search: '',
added_by: '',
},
authors: ['Admin', 'Editor'],
headers: [
{
text: 'Names',
align: 'left',
value: 'name',
sortable: false
},
{
text: 'Item addad by',
value: 'added_by',
align: 'left',
sortable: false
}
],
rows: [
{
name: 'Marcelo Tosco',
added_by: 'admin'
},
{
name: 'Carlos Campos',
added_by: 'admin'
},
{
name: 'Luis Gonzalez',
added_by: 'Editor'
},
{
name: 'Keopx',
added_by: 'Editor'
},
{
name: 'Marco Marocchi',
added_by: 'Admin'
},
]
}),
methods: {
customFilter(items, filters, filter, headers) {
// Init the filter class.
const cf = new this.$MultiFilters(items, filters, filter, headers);
cf.registerFilter('search', function (searchWord, items) {
if (searchWord.trim() === '') return items;
return items.filter(item => {
return item.name.toLowerCase().includes(searchWord.toLowerCase());
}, searchWord);
});
cf.registerFilter('added_by', function (added_by, items) {
if (added_by.trim() === '') return items;
return items.filter(item => {
return item.added_by.toLowerCase() === added_by.toLowerCase();
}, added_by);
});
// Its time to run all created filters.
// Will be executed in the order thay were defined.
return cf.runFilters();
},
/**
* Handler when user input something at the "Filter" text field.
*/
filterSearch(val) {
this.filters = this.$MultiFilters.updateFilters(this.filters, {search: val});
},
/**
* Handler when user select some author at the "Author" select.
*/
filterAuthor(val) {
this.filters = this.$MultiFilters.updateFilters(this.filters, {added_by: val});
},
}
};
</script>
In this example you can see at the beginning two form elements that will be our filters. One is a text field that will filter rows based on the coincidence of the text in the "name" column and the second is a select that will filter rows based on the "author".
Pay attention to these elements. They don't have the v-model="miprop" reference you'd expect it to be if you wanted to use any of these fields as table filters. Instead we have put two events @change and @input with their respective methods.
In both methods comes into play a VUE plugin called $MultiFilters. This is a plugin that contains a very small class that only has 3 methods: updateFilters(), registerFilters() and runFilters().
NOTE: The installation of the plugin is discussed below. See section "Installing the MultiFilters plugin".
In the case of the methods that respond to events we are using updateFilters() to keep the prop "filters" updated with the last value obtained from each of the filters.
/**
* Handler when user input something at the "Filter" text field.
*/
filterSearch(val) {
this.filters = this.$MultiFilters.updateFilters(this.filters, {search: val});
},
/**
* Handler when user select some author at the "Author" select.
*/
filterAuthor(val) {
this.filters = this.$MultiFilters.updateFilters(this.filters, {added_by: val});
},
What happens when we update the prop "filters"? happens that having associated it to the table through the attribute :search="filters" is going to execute the internal search of the table. And as I said before, the prop should be a string if we want the filtering to work... so the next step is to intervene in the filtering using our own method defined in the <v-data-table> component through the attr :custom-filter="customFilter".
The first thing we did in the method we created to apply filtering is to instantiate $MultiFilters to have the two methods that are going to help us close the filtering cycle and then we use them.
This class, in addition to the methods, will take care of extracting from the prop "filters" the value that corresponds to each filter that we are going to use. Better a little code to exemplify it:
cf.registerFilter('search', function (searchWord, items) {
if (searchWord.trim() === '') return items;
return items.filter(item => {
return item.name.toLowerCase().includes(searchWord.toLowerCase());
}, searchWord);
});
cf.registerFilter('added_by', function (added_by, items) {
if (added_by.trim() === '') return items;
return items.filter(item => {
return item.added_by.toLowerCase() === added_by.toLowerCase();
}, added_by);
});
In each one of the cf.registerFilter() we are indicating on which value of filters we are going to act and the function that applies the filtering. The filtering itself is nothing of the other world. just a little bit of js and returns only the items that have passed the test.
For example in the first method we have indicated that for the value filters.search a function is executed that verifies row by row in the cell "name" if this one has inside its text some part that coincides with the filters.name.
And finally we launch cf.runFilters(); whose purpose is to launch each one of the filters in a sequential way. This detail is important because if the first filter that is applied returns 5 of the 100 elements of a table, the next filter will only evaluate those 5 elements.
Here I put the example already working. The first table is a more complex example that has a pair of datepickers and a log that shows the values that is taking the filtering. The second table is the one we have explained in this post.
The source code of these examples can be found HERE and the example can be seen directly from HERE.
The example repository already has it implemented but if you want to take this to your project, in addition to the component explained above you will need to install the Vue MultiFilters plugin.
/**
* Enabled v-data-table to have moire than one filter.
*/
class MultiFilters {
/**
* Constructor.
*
* @param items
* @param filters
* @param filter
* @param headers
*/
constructor(items, filters, filter, headers) {
this.items = items;
this.filter = filter;
this.headers = headers;
this.filters = filters;
this.filterCallbacks = {};
}
/**
* Updates filter values.
* @param filters filter´s object
* @param val JSON chunk to be updated.
* @returns {*}
*/
static updateFilters(filters, val) {
return Object.assign({}, filters, val);
}
/**
* Adds a new filter
* @param filterName The name of the filter from which the information will be extracted
* @param filterCallback The callback that will apply the filter.
*/
registerFilter(filterName, filterCallback) {
this.filterCallbacks[filterName] = filterCallback;
}
/**
* Run all filters.
* @returns {*}
*/
runFilters() {
const self = this;
let filteredItems = self.items;
Object.entries(this.filterCallbacks)
.forEach(([entity, cb]) => {
filteredItems = cb.call(self, self.filters[entity], filteredItems);
});
return filteredItems;
}
}
// Vue plugin.
const MultiFiltersPlugin = {
install(Vue, options) {
Vue.prototype.$MultiFilters = MultiFilters;
}
};
export default MultiFiltersPlugin;
import '@babel/polyfill'
import Vue from 'vue'
import './plugins/vuetify'
import MultiFiltersPlugin from './plugins/MultiFilters' // <-- THIS
import App from './App.vue'
Vue.config.productionTip = false;
Vue.use(MultiFiltersPlugin); // <-- THIS
new Vue({
render: h => h(App)
}).$mount('#app');
That's all there is to it. See you next time!
Comentarios
Amazing! I'll just overwrite some stuff to have a unique input for multiple fields and done. Thanks bro
cbd vape cbd gummies cbd vape
Cool ! This is what I was looking for a week. Thanks so much.
Nice article. Thanks a lot!
Hello, can date screening give a demo?
Of course. Is here https://frontid.github.io/vuetify-data-table-multi-filter/
cbd oil green roads cbd cbd oil cbd oil hemp oil cbd charlotte's web cbd oil
cbd coffee cbd oil most reputable cbd oil supplier pure cbd oil best cbd oil cbd oil
Vuetify 2.0 is now checking the prop type of the search prop. How would we go around that?
[Vue warn]: Invalid prop: type check failed for prop "search". Expected String with value "[object Object]", got Object
FOR VUETIFY 2.X CHECK THIS SAMPLE
https://frontid.github.io/vuetify-2-data-table-multi-filter/
SOURCE CODE HERE
I have tried using this to enable search through one column instead of the vuetify all columns search but i keep getting an error that the field i am search(in my case name) is undefined.
Could you make an article for searching through only one column?
Its not working on me. It says 'options' is defined but never used in '61 | install(Vue, options) {'.
It is possible to add Expanded row there?
For Vuetify 1.x multicolumn filters - lean and slicky CodePen Example
Hola,
No me funciona, no me devuelve el valor buscado en la tabla ( a pesar que está ahí)
Alguna idea?
Saludos
Tal vez tienes una versión superior de Vuetify? tal vez la V2? https://frontid.github.io/vuetify-2-data-table-multi-filter/
This works really well thanks a lot
Is MultiFilter work on filter by word and then filter on range, for example smaller than min and bigger than max?
Add new comment