When i use the vuetify data datable group header feature and
when the top group checkbox is selected, i want the checkboxes below it to be selected as well.
Here is the code.
https://codepen.io/ersin-g-ven-/pen/LYdeWor?editors=1010
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="desserts"
item-key="name"
sort-by="name"
group-by="category"
class="elevation-1"
show-select
show-group-by
>
<template v-slot:[`group.header`]="{items}">
<th colspan="2">
<v-checkbox
v-model="selected"
:label="items[0].category"
></v-checkbox>
</th>
</template>
</v-data-table>
</v-app>
</div>
Just came across your post. I think the combination of v-treeview with selection-type="leaf" can do the trick.
Check the documentation on: https://vuetifyjs.com/en/api/v-treeview/#props-selection-type
and see how it works: https://vuetifyjs.com/en/components/treeview/#selection-type
Using of the "v-model" with "watch" has worked for me:
<template v-slot:group.header="{group, items}">
<th colspan="4">
<v-checkbox
:label="items[0].category"
:value="group"
v-model="selected_groups"
>
</v-checkbox>
</th>
</template>
...
data() {
...
selected: [],
selected_groups: []
}
watch: {
selected_groups(node_new, node_old) {
if(node_new.length < node_old.length) {
let dif = node_old.filter(x => !node_new.includes(x))[0]
this.selected = this.selected.filter(item => { return !Object.values(item).includes(dif) })
} else if (node_new.length > node_old.length) {
this.items_arr.forEach(item => {
if(Object.values(item).includes(node_new.at(-1))) {
this.selected.push(item)
}
})
} else if (node_new.length == node_old.length) {
return this.selected
}
},
selected(selected_new, selected_old) {
if(selected_new.length < selected_old.length) {
let dif_NODE = selected_old.filter(x => !selected_new.includes(x))[0][this.groupable_col]
let arr_NODEs = []
selected_new.forEach(item => { arr_NODEs.push(item[this.groupable_col]) })
if(!arr_NODEs.includes(dif_NODE)) {
this.selected_groups= this.selected_groups.filter(item => item != dif_NODE)
}
} else if(selected_new.length > selected_old.length) {
if(!this.selected_groups.includes(selected_new.at(-1)[this.groupable_col])) {
this.selected_groups.push(selected_new.at(-1)[this.groupable_col])
}
}
}
}
Related
I need replace a button when it's clicked because I need that button to have 3 states (entrada, salida and registrado),I already have those buttons but they only work when reloading the page, I just want it to render without reload, I add my code for a better explanation
vuetify-data-table
<td>
<v-btn color="success" v-if="item.check_in == null && item.check_out == null"
v-on:click="entrada(item)">
Entrada</v-btn>
<v-btn color="error" v-else-if="item.check_out == null && item.check_in !== null"
v-on:click="salida(item)">
Salida</v-btn>
<v-btn v-else disabled>Registrado</v-btn>
</td>
You can play out with the buttons with in the <v-data-table> without any page refresh.
Live Demo :
new Vue({
el: '#app',
data () {
return {
headers: [
{
text: 'Check-In',
value: 'checkin'
},
{ text: 'Check-Out', value: 'checkout' },
{ text: 'Actions', value: 'action' }
],
details: [
{
checkin: null,
checkout: null,
action: null
},
{
checkin: 1,
checkout: null,
action: null
},
{
checkin: 1,
checkout: 3,
action: null
}
]
}
},
methods: {
entrada(index) {
this.details.forEach((obj, i) => {
if (i === index) {
obj.checkin = 2
}
})
},
salida(index) {
this.details.forEach((obj, i) => {
if (i === index) {
obj.checkin = null,
obj.checkout = null
}
})
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.7/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vuetify#1.5.7/dist/vuetify.min.css"/>
<div id="app">
<v-app id="inspire">
<div>
<v-data-table
:headers="headers"
:items="details"
class="elevation-1"
>
<template v-slot:items="{item, index}">
<td>{{ item.checkin }}</td>
<td>{{ item.checkout }}</td>
<td>
<v-btn color="success" v-if="item.checkin == null && item.checkout == null"
v-on:click="entrada(index)">
Entrada</v-btn>
<v-btn color="error" v-else-if="item.checkout == null && item.checkin !== null"
v-on:click="salida(index)">
Salida</v-btn>
<v-btn v-else disabled>Registrado</v-btn>
</td>
</template>
</v-data-table>
</div>
</v-app>
</div>
Whenever I save the first item in my store it won't show up in frontend, only my buttons do.
After the first row has been added and I refresh I can add more rows, which do show up.
I tried making a clientIndex because I thought it maybe it didn't update because of "field.id", but that changed nothing.
Can anyone tell what I can do to fix it?
My index.js
state: {
clientfields: [],
clients: [],
},
mutations: {
setClientFields(state, fields) {
state.clientfields = fields;
},
setClients(state, clients) {
state.clients = clients;
},
deleteClient(state, client) {
state.clients.splice(state.clients.indexOf(client), 1);
},
addClient(state, client) {
state.clients.unshift(client);
},
actions: {
fetchClients({ state, commit }) {
axios.get('http://localhost:8000/api/clients')
.then(response => {
console.log(response.data.data);
commit("setClients", response.data.data)
commit("setClientFields", Object.keys(state.clients[0]))
console.log(Object.keys(state.clients));
})
},
deleteClient({ commit }, clientdata) {
axios.delete("http://localhost:8000/api/clients/" + clientdata.id)
commit("deleteClient", clientdata)
},
saveClient({ commit }, clientdata) {
let promise;
if (clientdata.id != null) {
promise = axios.put("http://localhost:8000/api/clients/" + clientdata.id, clientdata)
console.log(clientdata)
} else {
console.log(clientdata)
promise = axios.post("http://localhost:8000/api/clients/", clientdata)
}
promise.then(function (response) {
if (clientdata.id != null) {
commit("saveClient", clientdata)
} else {
commit("addClient", response.data.data)
}
})
.catch(function (error) {
console.log(error.response);
commit('error', error.response)
})
},
}
My Component
<template>
<div class="app" >
<v-btn tile elevation="4" style="margin-left: 20px margin-top: 20px;" v-if="! showEdit" class="btn btn-blue left" v-on:click="saveClient">Nieuwe Client</v-btn>
<table class="table-auto">
<thead>
<tr class="md:border-t-2 border-collapse border border-gray-600 ...">
<th class="px-4 py-2 ">Edit</th>
<th class="px-4 py-2 ">Delete</th>
<th class="px-4 py-2 allow-overflow" v-for="field in fields" :key="field.id">{{ field.replace("_"," ") }}</th>
</tr>
</thead>
<tr class="md:border-t-2 border-collapse border border-gray-600 ..." v-for='(clientdata, clientIndex) in clients' :key="clientIndex">
<td class="px-4 py-2 font-medium">
<v-btn class="ocz" v-on:click="updClient(clientdata)">
Edit
</v-btn>
</td>
<td class="px-4 py-2 font-medium">
<template>
<v-dialog
v-model="confirm"
width="350"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
color="red"
v-bind="attrs"
v-on="on"
>
Delete
</v-btn>
</template>
<v-card>
<v-card-title class="text-h5">
ARE YOU SURE YOU <br> WANT TO DELETE THIS?
</v-card-title>
<v-card-actions>
<v-btn
color="grey"
text
#click="confirm = false"
>
Cancel
</v-btn>
<v-btn
color="red"
text
#click="delClient(clientdata); confirm = false;"
>
Delete
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
</td>
<td class="px-4 py-2 font-medium" v-for="field in fields" :key="field.id">{{ clientdata[field] }}</td>
</tr>
<tr v-if="!clients.length" >
<td class="px-4 py-2 font-medium" style="opacity: 0.6;">No data to be shown.</td>
</tr>
</table>
<transition name="fade">
<div class="modal" v-if="dialog">
<div class="modal-content">
<EditModal :client="selClient" v-show="showEdit" #close="closeEdit"/>
</div>
</div>
</transition>
</div>
</template>
<script>
import EditModal from '#/components/Clients/EditClients.vue'
export default{
dialog: false,
overlay: true,
components: {
EditModal,
},
data () {
return {
selClient: {},
showEdit: false,
confirm: false,
clientIndex: 100,
}
},
created () {
this.$store.dispatch('fetchClients')
},
computed: {
clients() {
return this.$store.state.clients;
},
fields() {
return this.$store.state.clientfields;
}
},
methods: {
closeEdit: function() {
this.showEdit = false;
this.dialog = false;
},
delClient: function(clientdata) {
this.clientIndex--;
this.$store.dispatch('deleteClient', clientdata)
},
updClient: function(clientdata) {
this.selClient = clientdata;
this.showEdit = true;
this.dialog = true;
this.clientIndex++;
this.$store.dispatch('saveClient', clientdata)
},
saveClient: function(clientdata) {
this.showEdit = true;
this.dialog = true
this.clientIndex++;
this.$store.dispatch('saveClient', clientdata);
},
createClient(){
this.selClient = {};
}
}
}
</script>
I Fixed it, in my index I just committed my field data on save so my table gets filled and shows data.
promise.then(function (response) {
if (clientdata.id != null) {
commit("saveClient", clientdata)
} else {
commit("addClient", response.data.data)
}
! added my commit here to fill fields :) !
})
In order to achieve for multi search function we add a search field at each header with v-slot where we define the header name directly. For example:
<template v-slot:header.NAME="{ header }">
{{ header.text }}
<v-menu offset-y :close-on-content-click="false">
<template v-slot:activator="{ on, attrs }">
<v-text-field v-bind="attrs" v-on="on"
v-model="nameSearch"
class="pa"
type="text"
></v-text-field>
</template>
</v-menu>
</template>
Now I have around 50 headers for my data table. My idea is to render dynamically. I tried with v-for but neither it renders and or give any error. Code below:
<template v-for="(x, index) in this.headers" slot="headers" slot-scope="props">
{{ props.x.text }}
<v-menu offset-y :close-on-content-click="false" :key="index">
<template v-slot:activator="{ on, attrs }">
<v-text-field v-bind="attrs" v-on="on"
v-model="searchObj"
class="pa"
type="text"
></v-text-field>
</template>
</v-menu>
</template>
what did i missed here ? or it is completely wrong way i went ?
Below is the full code:
<template>
<v-app class="main-frame">
<v-main>
<v-data-table
:headers="headers"
:items="filteredData"
>
<template v-slot:header.NAME="{ header }">
{{ header.text }}
<v-menu offset-y :close-on-content-click="false">
<template v-slot:activator="{ on, attrs }">
<v-text-field v-bind="attrs" v-on="on"
v-model="nameSearch"
class="pa"
type="text"
></v-text-field>
</template>
</v-menu>
</template>
<template v-slot:header.DEPARTMENT="{ header }">
{{ header.text }}
<v-menu offset-y :close-on-content-click="false">
<template v-slot:activator="{ on, attrs }">
<v-text-field v-bind="attrs" v-on="on"
v-model="departmentSearch"
class="pa"
type="text"
></v-text-field>
</template>
</v-menu>
</template>
</v-data-table>
</v-main>
</v-app>
</template>
<script>
import axios from "axios";
}
export default {
name: 'Home',
data() {
return {
/* Table data */
headers: []
allData: [],
/* Column Search */
nameSearch: '',
departmentSearch: ''
}
},
computed: {
filteredData() {
let conditions = [];
if (this.nameSearch) {
conditions.push(this.filterName);
}
if (this.departmentSearch) {
conditions.push(this.filterDepartment);
}
if (conditions.length > 0) {
return this.allData.filter((data) => {
return conditions.every((condition) => {
return condition(data);
})
})
}
return this.allData;
}
},
mounted () {
this.getAllData()
},
methods: {
getAllData() {
this.allData = []
axios
.get("http://localhost:5001/"}
.then(res => {
if (res.status === 200) {
if (res.data === 0) {
console.log("Something is wrong !!!")
} else {
this.allData = res.data["allData"]
this.headers = res.data["allDataHeaders"]
}
}
}).catch(error => {
console.log(error);
})
},
filterName(item) {
return item.NAME.toLowerCase().includes(this.nameSearch.toLowerCase())
},
filterDepartment(item) {
return item.DEPARTMENT.toLowerCase().includes(this.departmentSearch.toLowerCase())
},
}
}
</script>
I have found the solution myself:
<template v-for="(header, i) in headers" v-slot:
[`header.${header.value}`]="{ }">
<div #click.stop :key="i">
{{ header.text }}
<v-text-field :key="i"
v-model="multiSearch[header.value]"
class="pa"
type="text"
:placeholder="header.value"
prepend-inner-icon="mdi-magnify"
></v-text-field>
</div>
</template>
In data, I have an empty object called:
searchObj: {}
and in computed method, i have the following method:
filteredData() {
if (this.searchObj) {
return this.allData.filter(item => {
return Object.entries(this.searchObj).every(([key, value]) => {
return (item[key] || '').toLowerCase().includes(value.toLowerCase())
})
})
} else {
return this.allData
}
},
Full solution below:
<template>
<v-app class="main-frame">
<v-main>
<v-data-table
:headers="headers"
:items="filteredData"
>
<template v-for="(header, i) in headers" v-slot:
[`header.${header.value}`]="{ }">
{{ header.text }}
<div #click.stop :key="i">
<v-text-field :key="i"
v-model="multiSearch[header.value]"
class="pa"
type="text"
:placeholder="header.value"
prepend-inner-icon="mdi-magnify"
></v-text-field>
</div>
</template>
</v-data-table>
</v-main>
</v-app>
</template>
<script>
import axios from "axios";
}
export default {
name: 'Home',
data() {
return {
/* Table data */
headers: []
allData: [],
searchObj: {},
}
},
computed: {
filteredData() {
if (this.searchObj) {
return this.allData.filter(item => {
return Object.entries(this.searchObj).every(([key, value]) => {
return (item[key] ||'').toLowerCase().includes(value.toLowerCase())
})
})
} else {
return this.allData
}
},
},
mounted () {
this.getAllData()
},
methods: {
getAllData() {
this.allData = []
axios
.get("http://localhost:5001/"}
.then(res => {
if (res.status === 200) {
if (res.data === 0) {
console.log("Something is wrong !!!")
} else {
this.allData = res.data["allData"]
this.headers = res.data["allDataHeaders"]
}
}
}).catch(error => {
console.log(error);
})
},
}
}
</script>
Vuetify v-data-table supports several types of slots: v-slot:body, v-slot:item and v-slot:item.<name>.
We have been using v-slot:item.<name> extensively, as these provides a flexible way to style and process content in individual columns AND allow the table headers to be programmatically changed.
I'd like to add draggability to my v-data-table rows and have got this working using Vue.Draggable.
However the draggable component requires use of the v-data-table v-slot:body i.e. taking control of the full body of the table and thereby losing the flexibility of v-slot:item.<name>.
Is there a way these two components can be used together and provide v-slot:item.<name> support?
I have created a DataTableRowHandler component which allows v-slot:item.<name> support.
This is placed inside the draggable component, inserts the table <tr> element and feeds off the same "headers" array to insert <td> elements and v-slot:item.<name> entries. If no v-slot:item.<name> is defined then the cell value is output, in the same way that v-data-table works.
Here is the example component usage:
<v-data-table
ref="myTable"
v-model="selected"
:headers="headers"
:items="desserts"
item-key="name"
class="elevation-1"
>
<template v-slot:body="props">
<draggable
:list="props.items"
tag="tbody"
:disabled="!allowDrag"
:move="onMoveCallback"
:clone="onCloneCallback"
#end="onDropCallback"
>
<data-table-row-handler
v-for="(item, index) in props.items"
:key="index"
:item="item"
:headers="headers"
:item-class="getClass(item)"
>
<template v-slot:item.lock="{ item }">
<v-icon #click="item.locked = item.locked ? false : true">{{
item.locked ? "mdi-pin-outline" : "mdi-pin-off-outline"
}}</v-icon>
</template>
<template v-slot:item.carbs="{ item }">
{{ item.carbs }}
<v-icon>{{
item.carbs > 80
? "mdi-speedometer"
: item.carbs > 45
? "mdi-speedometer-medium"
: "mdi-speedometer-slow"
}}</v-icon>
</template>
</data-table-row-handler>
</draggable>
</template>
</v-data-table>
Here is the DataTableRowHandler component code
<template>
<tr :class="getClass">
<td v-for="(header, index) in headers" :key="index">
<slot :item="item" :name="columnName(header)">
<div :style="getAlignment(header)">
{{ getNonSlotValue(item, header) }}
</div>
</slot>
</td>
</tr>
</template>
<script>
export default {
name: "DataTableRowHandler",
components: {},
props: {
itemClass: {
type: String,
default: "",
},
item: {
type: Object,
default: () => {
return {};
},
},
headers: {
type: Array,
default: () => {
return [];
},
},
},
data() {
return {};
},
computed: {
getClass() {
return this.itemClass;
}
},
methods: {
columnName(header) {
return `item.${header.value}`;
},
getAlignment(header) {
const align = header.align ? header.align : "right";
return `text-align: ${align}`;
},
getNonSlotValue(item, header) {
const val = item[header.value];
if (val) {
return val;
}
return "";
},
},
};
</script>
An example of it's use is in this codesandbox link
I checked the source code of VDataTable and found that the content in tbody is generated by genItems().
The following example is implemented with functional components and is fully compatible with v-slot:item*:
<v-data-table ref="table" ...>
<template #body="props">
<draggable
v-if="$refs.table"
tag="tbody"
:list="props.items"
>
<v-nodes :vnodes="$refs.table.genItems(props.items, props)" />
</draggable>
</template>
<template #item.name="{ item }">
...
</template>
</v-data-table>
Here is the definition of the VNodes component:
components: {
VNodes: {
functional: true,
render: (h, ctx) => ctx.props.vnodes,
}
}
I'm new to vuejs + vuetify. I am using a datepicker to filter a datatable. The filtering part is working, however, when I clear the field the table does not return to original form. I tested using the html's input-date and worked perfectly. What am I doing wrong?
This is the datepicker component:
<template>
<v-menu
ref="menu"
v-model="menu"
:close-on-content-click="false"
transition="scale-transition"
offset-y
max-width="290"
min-width="290"
>
<template v-slot:activator="{ on }">
<v-text-field
clearable
dense
color="#15C98A"
outlined
v-model="computedDataFormatada"
label="data inicio"
append-icon="mdi-calendar-outline"
readonly
v-on="on"
></v-text-field>
</template>
<v-date-picker
v-model="date"
#input="dataEscolhida"
locale="pt"
color="#15C98A"
no-title
></v-date-picker>
</v-menu>
</template>
<script>
export default {
name:'DataPicker',
props:['label'],
data() {
return {
date: null,
menu: false,
};
},
computed: {
computedDataFormatada () {
return this.formataData(this.date)
},
},
methods: {
dataEscolhida() {
this.$emit('selecionado',this.computedDataFormatada);
this.menu = false;
},
formataData(date) {
if (!date) return null;
const [year, month, day] = date.split("-");
return `${day}/${month}/${year}`;
}
}
};
</script>
This is the way I'm filtering:
<v-data-table
fixed-header
single-select
item-key="id"
:headers="headers"
:items="customFilter"
:items-per-page="5"
class="elevation-1"
></v-data-table>
computed:{
customFilter(){
let startDate = this.dataInicioFiltro;
let endDate = this.dataFimFiltro;
return _.filter(this.grupos, (function (info) {
let date = info.data;
if ( (_.isNull(startDate) && _.isNull(endDate))||(_.isEmpty(startDate) && _.isEmpty(endDate)))
return true;
else if((!_.isEmpty(startDate) && _.isEmpty(endDate)))
return date ===startDate;
else if((_.isEmpty(startDate) && !_.isEmpty(endDate)))
return date ===endDate;
else
return (date >= startDate && date <= endDate);
}))
}
}
And this is how I'm using the component in the view:
<data-filtro label="data inicial" #selecionado="dataInicioFiltro=$event" />
I'm also using the lib lodash.
Ok everyone, I finally solved it out! Just have to use the event click:clear