El elemento Data table no soporta mas que un filtrado simple de búsqueda de texto. Esto traducido quiere decir que solo permite añadirle un text field que filtre filas siempre que alguna celda contenga parcialmente la palabra que estás buscando:
See the Pen Vuetify Example Pen by Front.id (@frontid) on CodePen.
¿Pero que pasa cuando queremos poner mas filtros como en este ejemplo?
Pues pasa que el componente no soporta de forma nativa multiples filtros. PERO si nos permite customizar el comportamiento del único field disponible y vamos a usar esa funcionalidad como caballo de troya para expandir las posibilidades de filtrado.
Sabemos que el elemento <v-data-table> tiene la posibilidad de usar una propiedad ":filter" en la que indicar la "prop" que tiene que escuchar. Dicha "prop" tiene que ser un string, y cuando cambie, la tabla internamente va a aplicar un filtrado usando esa palabra para mostrar solo las filas que cumplan con el criterio.
También sabemos que el elemento <v-data-table> permite personalizar el algoritmo de la búsqueda mencionada anteriormente mediante el atributo ":custom-filter". A él le asignas un método que tendrás que crear en la sección de "methods" de tu componente.
Finalmente sabemos que para ir cambiando el valor del prop asociado a :filter tenemos que añadir un componente <v-text-field> y asociarlo a la prop mediante v-model="miprop".
Como ya habrás inferido, el problema aparece cuando quieres añadir mas de un filtro a la tabla ya que la tabla solo permite como punto de entrada al filtrado interno el atributo :filter y no puede ser un array u objeto, por lo que no puedes ir enviando información desde campos varios como puede ser un select, un date picker, etc.
Les pongo un componente con la solución implementada y la explico mas abajo.
<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>
En este ejemplo puedes ver al inicio dos elementos de formulario que van a ser nuestros filtros. Uno es un text field que va a filtrar filas basándose en la coincidencia del texto de la columna "name" y el segundo es un select que filtrará filas basándose en el "author".
Presta atención a estos elementos. No tienen la referencia v-model="miprop" que se esperaría que estuviera si quieres usar alguno de estos campos como filtros de la tabla. En su lugar hemos puesto dos eventos @change y @input con sus respectivos métodos.
En los dos métodos entra en juego un plugin VUE llamado $MultiFilters. Este es un plugin que contiene una clase muy pequeña que solo tiene 3 métodos: updateFilters(), registerFilters() y runFilters().
NOTA: La instalación del plugin se comenta mas abajo. Ver sección "Instalar el plugin MultiFilters".
En el caso de los métodos que responden a los eventos estamos usando updateFilters() para mantener la prop "filters" actualizada con el ultimo valor obtenido de cada uno de los filtros
/**
* 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});
},
¿Que pasa cuando actualizamos la prop "filters"? pues pasa que al haberla asociado a la tabla mediante el atributo :search="filters" se va a ejecutar la búsqueda interna de la tabla. Y como ya comenté antes, la prop debería ser un string si queremos que el filtrado funcione... por eso el siguiente paso es intervenir en el filtrado usando nuestro propio método definido en el componente v-data-table mediante el attr :custom-filter="customFilter".
Lo primero que hicimos en el método que creamos para aplicar el filtrado es instanciar $MultiFilters para disponer de los dos métodos que nos van a ayudar a cerrar el ciclo de filtrado y acto seguidos los usamos.
Esta clase ademas de los métodos, se encargará de extraer de la prop "filters" el valor que le corresponda a cada filtrado que vayamos a usar. Mejor un poco de código para ejemplificarlo:
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);
});
En cada uno de los cf.registerFilter() estamos indicando sobre qué valor de filters vamos a actuar y la función que aplica el filtrado. El filtrado en si no es nada del otro mundo. solo un poco de js y se devuelven solo los items que hayan superado el test.
Por ejemplo en el primer método hemos indicado que para el valor filters.search se ejecute una función que verifique fila a fila en la celda "name" si esta tiene dentro de su texto alguna parte que coincida con el filters.name.
Y finalmente lanzamos cf.runFilters(); cuya finalidad es lanzar cada uno de los filtrados de forma secuencial. Es importante este detalle ya que si el primer filtro que se aplique devuelve 5 de los 100 elementos de una tabla, el siguiente filtro solo va a evaluar esos 5 elementos.
Aquí les pongo el ejemplo ya funcionando. La primer tabla es un ejemplo mas complejo que tiene un par de datepickers y un log que va mostrando los valores que va tomando el filtrado. La segunda tabla es la que hemos explicado en este post.
El código fuente de estos ejemplos los puedes encontrar AQUÍ y el ejemplo puedes verlo directamente desde AQUÍ.
El repositorio de ejemplo ya lo tiene implementado pero si quieres llevarte esto a tu proyecto, ademas del componente explicado mas arriba vas a necesitar instalar el plugin de VUE MultiFilters
/**
* 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');
Eso es todo. Hasta la próxima!
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?
Agregar nuevo comentario