Vuetify v-data-table drag and resizable columns together - vuejs2

I'm using vuetify v-data-table. I implement drag column with SortableJs and also column resizable.
When I drag a column, it works properly but after that the resize does not work anymore.
<div id="app">
<v-app id="inspire">
<v-data-table :headers="headers" :items="desserts"
sort-by="calories"
disable-sort
v-sortable-table="{onEnd:sortTheHeadersAndUpdateTheKey}"
:key="anIncreasingNumber" >
</v-data-table>
</v-app>
</div>
Here is my codepen
Thanks in advance.

As I can see here, the Vuetify team is not interested in doing this feature now.
Thank you for the Feature Request and interest in improving Vuetify. Unfortunately, this is not a functionality we are looking to implement now.
For now, you can check this CodepPen it will do the trick for you.
If you don't want to do it manually you could install an npm package called vue-columns-resizable-vuetify
<!-- Credit
Column Resize - Web Dev Tricks https://webdevtrick.com/resizable-table-columns/
Vuetify Datatable - Vuetify Example
https://vuetifyjs.com/en/components/data-tables/ -->
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="desserts"
class="elevation-1"
>
</v-data-table>
</v-app>
</div>
th, td {
border-right: 1px solid grey;
}
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
headers: [
{ text: 'Dessert (100g serving)', sortable: false, value: 'name'},
{ text: 'Calories', sortable: false, value: 'calories' },
{ text: 'Fat (g)', sortable: false, value: 'fat' },
{ text: 'Carbs (g)', sortable: false, value: 'carbs' },
{ text: 'Protein (g)', sortable: false, value: 'protein' },
{ text: 'Iron (%)', sortable: false, value: 'iron' },
],
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%',
},
],
}
},
})
var tables = document.getElementsByTagName('table');
for (var i=0; i<tables.length;i++){
resizableGrid(tables[i]);
}
function resizableGrid(table) {
var row = table.getElementsByTagName('tr')[0],
cols = row ? row.children : undefined;
if (!cols) return;
table.style.overflow = 'hidden';
var tableHeight = table.offsetHeight;
for (var i=0;i<cols.length;i++){
var div = createDiv(tableHeight);
cols[i].appendChild(div);
cols[i].style.position = 'relative';
setListeners(div);
}
function setListeners(div){
var pageX,curCol,nxtCol,curColWidth,nxtColWidth;
div.addEventListener('mousedown', function (e) {
curCol = e.target.parentElement;
nxtCol = curCol.nextElementSibling;
pageX = e.pageX;
var padding = paddingDiff(curCol);
curColWidth = curCol.offsetWidth - padding;
if (nxtCol)
nxtColWidth = nxtCol.offsetWidth - padding;
});
div.addEventListener('mouseover', function (e) {
e.target.style.borderRight = '2px solid #0000ff';
})
div.addEventListener('mouseout', function (e) {
e.target.style.borderRight = '';
})
document.addEventListener('mousemove', function (e) {
if (curCol) {
var diffX = e.pageX - pageX;
if (nxtCol)
nxtCol.style.width = (nxtColWidth - (diffX))+'px';
curCol.style.width = (curColWidth + diffX)+'px';
}
});
document.addEventListener('mouseup', function (e) {
curCol = undefined;
nxtCol = undefined;
pageX = undefined;
nxtColWidth = undefined;
curColWidth = undefined
});
}
function createDiv(height){
var div = document.createElement('div');
div.style.top = 0;
div.style.right = 0;
div.style.width = '5px';
div.style.position = 'absolute';
div.style.cursor = 'col-resize';
div.style.userSelect = 'none';
div.style.height = height + 'px';
return div;
}
function paddingDiff(col){
if (getStyleVal(col,'box-sizing') == 'border-box'){
return 0;
}
var padLeft = getStyleVal(col,'padding-left');
var padRight = getStyleVal(col,'padding-right');
return (parseInt(padLeft) + parseInt(padRight));
}
function getStyleVal(elm,css){
return (window.getComputedStyle(elm, null).getPropertyValue(css))
}
};

Related

How to add pagination code to data table in vue js?

I'm utilizing v-data-table in a vue component, looking through the documentation it wasn't clear to me how to utilize built in pagination in the data table and how to invoke code for the pagination . I tried to look through some of the blogs but still not clear.
<template>
<v-data-table
:page="page"
:pageCount="numOfPages"
:headers="headers"
...
</template>
<script>
export default {
name: "DataTableSample",
data() {
return {
...
},
watch: {
options: {
handler() {
this.getData();
},
},
deep: true,
},
methods: {
load(){
//load data
...
},
mounted(){
this.getData();
},
};
</script>
Not sure what does this statement means it wasn't clear to me how to utilize built in pagination in the data table and how to invoke code for the pagination ?
But <v-data-table> providing pagination by default. Here is the demo :
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
selected: [ ],
headers: [
{
text: 'Dessert (100g serving)',
align: 'left',
sortable: false,
value: 'name',
},
{ text: 'Calories', value: 'calories' },
{ text: 'Fat (g)', value: 'fat' },
{ text: 'Carbs (g)', value: 'carbs' },
{ text: 'Protein (g)', value: 'protein' },
{ text: 'Iron (%)', value: 'iron' },
],
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%',
}
]
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.0.0-beta.8/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vuetify#2.0.0-beta.8/dist/vuetify.min.css"/>
<div id="app">
<v-app>
<v-data-table
id="mytable"
v-model="selected"
:headers="headers"
:items="desserts"
:items-per-page="5"
class="elevation-1"
item-key="name"
>
</v-data-table>
</v-app>
</div>

Vuetify data table v-model is not reactive to changes inside the table items

I have a Vuetify data table that is refreshed from the server every 5 seconds. It has selectable rows. If you select a row, then something in the data changes, the v-model array of selected items does not reflect the changes inside the row items. This codepen is a slightly modified version of a Vuetify example:
https://codepen.io/hobbeschild/pen/bGqGMQQ?editors=1010
Select the first row. At the top you will see the time in the selected item. Wait 5 seconds for the data to refresh. You will see that the selected item time does not match the row item time anymore.
Is there a way to ensure the v-model array contents reflect the new values in the items? I can think of a way to do this programmatically, but I have lots of tables like this and hope there is an easier way, with the table props perhaps.
HTML:
<div id="app">
<v-app id="inspire">
<div>selected = {{ selected[0] }}</div>
<div>
<v-data-table
v-model="selected"
show-select
item-key="name"
:headers="headers"
:items="desserts"
:options.sync="options"
:server-items-length="totalDesserts"
:loading="loading"
class="elevation-1"
></v-data-table>
</div>
</v-app>
</div>
JS:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
selected: [],
totalDesserts: 0,
desserts: [],
loading: true,
options: {},
headers: [
{
text: 'Dessert (100g serving)',
align: 'start',
sortable: false,
value: 'name',
},
{ text: 'Calories', value: 'calories' },
{ text: 'Fat (g)', value: 'fat' },
{ text: 'Carbs (g)', value: 'carbs' },
{ text: 'Protein (g)', value: 'protein' },
{ text: 'Iron (%)', value: 'iron' },
{ text: 'Time', value: 'time' },
],
reloadTimerId: Number,
}
},
watch: {
options: {
handler () {
this.getDataFromApi()
},
deep: true,
},
},
mounted () {
this.getDataFromApi();
this.reloadTimerId = setInterval(this.getDataFromApi, 5000);
},
methods: {
getDataFromApi () {
this.loading = true
this.fakeApiCall().then(data => {
this.desserts = data.items
this.totalDesserts = data.total
this.loading = false
})
},
/**
* In a real application this would be a call to fetch() or axios.get()
*/
fakeApiCall () {
return new Promise((resolve, reject) => {
const { sortBy, sortDesc, page, itemsPerPage } = this.options
let items = this.getDesserts()
const total = items.length
if (sortBy.length === 1 && sortDesc.length === 1) {
items = items.sort((a, b) => {
const sortA = a[sortBy[0]]
const sortB = b[sortBy[0]]
if (sortDesc[0]) {
if (sortA < sortB) return 1
if (sortA > sortB) return -1
return 0
} else {
if (sortA < sortB) return -1
if (sortA > sortB) return 1
return 0
}
})
}
if (itemsPerPage > 0) {
items = items.slice((page - 1) * itemsPerPage, page * itemsPerPage)
}
setTimeout(() => {
resolve({
items,
total,
})
}, 1000)
})
},
getDesserts () {
return [
{
name: 'Frozen Yogurt',
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
iron: '1%',
time: new Date(),
},
{
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%',
},
]
},
},
})
I do not think there is any "build-in" way to do what you want. Problem is model (selected in your code) holds references of selected objects from items/deserts array. If you replace items/deserts (with this.desserts = data.items) with completely new array containing completely new objects, this is what you get...
So doing this yourself is most certainly only way. Either:
Recreate selected whenever you replace items/deserts
this.selected = data.items.filter(i => this.selected.findIndex(item => item.name === i.name) > -1)
Or do not replace array and it's items - update existing objects with new data instead

Vuetify data table get selected item

I have a v-data-table that has Show-select,
I want to get the data of what items I selected, at least alert the first column value upon check
. Im searching everywhere. i cant find answer to my problem.
<div id="app">
<v-app id="inspire">
<v-data-table
:value="selected"
#input="enterSelect($event)"
:items-per-page="itemsPerPage"
:headers="headers"
:items="desserts"
item-key="name"
show-select
class="elevation-1"
>
</v-data-table>
<v-dialog>
<v-card>
</v-card>
</v-dialog>
</v-app>
</div>
js
new Vue({
el: '#app',
vuetify: new Vuetify(),
methods: {
enterSelect(values) {
if (values.length == this.itemsPerPage) {
alert('selected all')
}
}
},
data () {
return {
selected: [],
itemsPerPage: 10,
headers: [
{
text: 'Dessert (100g serving)',
align: 'left',
sortable: false,
value: 'name',
},
{ text: 'Calories', value: 'calories' },
{ text: 'Fat (g)', value: 'fat' },
{ text: 'Carbs (g)', value: 'carbs' },
{ text: 'Protein (g)', value: 'protein' },
{ text: 'Iron (%)', value: 'iron' },
],
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%',
},
],
}
},
})
https://codepen.io/CodingDeer/pen/QWLyaog?editors=1010
this is just an example of what I recently found (code not mine),
this code can detect if all checkbox selected
Use v-model instead of :value to allow modification of selected variable when you try to select some items. Then check the this.selected's length inside the enterSelect function.
<v-data-table
v-model="selected"
#input="enterSelect()"
...
>...</v-data-table>
methods: {
enterSelect() {
console.log(this.selected.map(e => e.name)); // logs all the selected items.
if (this.selected.length == this.itemsPerPage) {
alert('selected all')
}
}
}
For example you want to get the names of the rows, here's how to get them.
methods: {
enterSelect(values) {
var names = values.map(function(value){ return value.name })
console.log(names)
}
}

Vuetify v-data-table double click event does not work

I'm using a v-data-table.
According to official library, it has a dblclick:row event.
I tried to use it, but doesn't work.
(click:row event works fine.)
vuetify version is latest vuetify#2.3.8.
how can I use double click event in v-data-table?
here is my code sample.
(sorry for didn't write full code. I added. )
<div id="app">
<v-app id="inspire">
<v-card>
<v-data-table
:items="items"
:headers="headers"
dense
v-model="selected"
hide-default-footer
#click:row="clickRow"
#dblclick:row="dblclickRow"
>
</v-data-table>
</v-card>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
selected: [],
headers: [
{
text: "Dessert (100g serving)",
align: "start",
sortable: false,
value: "name"
},
{ text: "Calories", value: "calories" },
{ text: "Fat (g)", value: "fat" },
{ text: "Carbs (g)", value: "carbs" },
{ text: "Protein (g)", value: "protein" },
{ text: "Iron (%)", value: "iron" }
],
items: [
{
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%"
}
],
selected:[]
}),
methods: {
dblclickRow(){
console.log("rowDoubleClicked");
},
clickRow(){
// console.log("rowClicked");
}
}
})
Your functions should be under methods. See this link https://v2.vuejs.org/v2/guide/events.html#Method-Event-Handlers.
The event is not reaching your function

Exclude disabled item in V-Data-Table's select-all [VUETIFY]

I have v-data-table with disabled item and want to exclude it when I trigger the select-all in header.data-table-select slot. Also applied :readonly but still get checked.
<template v-slot:item.data-table-select="{ item, isSelected, select }">
<v-simple-checkbox
:value="isSelected"
:readonly="item.name == 'Frozen Yogurt'"
:disabled="item.name == 'Frozen Yogurt'"
#input="select($event)"
></v-simple-checkbox>
</template>
Also looked at the docs and found this header.data-table-select slot but only gives me this options:
{
props: {
value: boolean
indeterminate: boolean
},
on: {
input: (value: boolean) => void
}
}
Is there any way of handling selected items in v-data-table?
Here's the live code: https://d4et5.csb.app/
EDITED
CodeSandBox: https://codesandbox.io/s/festive-haze-d4et5
It is possible to remove disabled item form a select all in datatable
I've added a new key "disabled" in items array
Here is the working codepen:
https://codepen.io/chansv/pen/mdJMvJr?editors=1010
<div id="app">
<v-app id="inspire">
<v-data-table
v-model="selected"
:headers="headers"
:items="desserts"
item-key="name"
show-select
class="elevation-1"
#toggle-select-all="selectAllToggle"
>
<template v-slot:item.data-table-select="{ item, isSelected, select }">
<v-simple-checkbox
:value="isSelected"
:readonly="item.disabled"
:disabled="item.disabled"
#input="select($event)"
></v-simple-checkbox>
</template>
</v-data-table>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
selected: [],
disabledCount: 0,
headers: [
{
text: 'Dessert (100g serving)',
align: 'start',
sortable: false,
value: 'name',
},
{ text: 'Calories', value: 'calories' },
{ text: 'Fat (g)', value: 'fat' },
{ text: 'Carbs (g)', value: 'carbs' },
{ text: 'Protein (g)', value: 'protein' },
{ text: 'Iron (%)', value: 'iron' },
],
desserts: [
{
name: 'Frozen Yogurt',
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
iron: '1%',
disabled: true,
},
{
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%',
disabled: true,
},
{
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%',
},
],
}
},
methods: {
selectAllToggle(props) {
if(this.selected.length != this.desserts.length - this.disabledCount) {
this.selected = [];
const self = this;
props.items.forEach(item => {
if(!item.disabled) {
self.selected.push(item);
}
});
} else this.selected = [];
}
},
created() {
const self = this;
this.desserts.map(item => {
if (item.disabled) self.disabledCount += 1
})
}
})
On tops chans' answer. if you have pagination with your datatable, and you have multiple pages of data, the answer probably won't work for you. instead of calculating disabledCount i calculate if selected items length is equal to all available items length
selectAllToggle(props) {
let availableItems = props.items.filter(item => !item.isDisabled)
if (this.selected.length !== availableItems.length) {
this.selected = availableItems;
} else {
this.selected = [];
}
}
Just One simple change will work. You have to change the value attribute v-simple-checkbox.
<template v-slot:item.data-table-select="{ item, isSelected, select }">
<v-simple-checkbox
:value="(item.name !== 'Frozen Yogurt') && isSelected"
:readonly="item.name == 'Frozen Yogurt'"
:disabled="item.name == 'Frozen Yogurt'"
#input="select($event)"
></v-simple-checkbox>
</template>