Vuetify data table in single-select mode, selecting one row selects all others - vue.js

I am trying to select one row in the table and emit the selected item.
Selecting one selects all but only the first encountered object is saved to the model (as selected variable).
Do you have any ideas, what i'm doing wrong?
<template>
<v-data-table
:headers="headers"
:items="items"
:search="search"
:loading="loading"
v-model="selected"
single-select
show-select
:options="{itemsPerPage:5}"
#item-selected="itemSelected"
>
<template v-slot:top>
<v-toolbar flat>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
></v-text-field>
</v-toolbar>
</template>
<template v-slot:item.name="{ item }">{{ item.name }}</template>
</v-data-table>
</template>
<script>
export default {
name: "variable-selector",
props: ["variables", "map", "index"],
data() {
return {
search: "",
selected: {},
loading: false,
items: [],
headers: [{ text: "Variable name", value: "name", sortable: true }]
};
},
methods: {
itemSelected(selection) {
if (selection.value) {
this.$emit("selected", selection.item); // it always emits var_2 object
} else {
this.$emit("selected", null);
}
},
updateItemsList(variables) {
this.items = Array.from(variables);
}
},
mounted() {
this.updateItemsList(this.variables);
},
watch: {
variables(newValue) {
this.loading = true;
this.updateItemsList(newValue);
this.loading = false;
}
}
};
</script>

Each object should be unique key value if u face error , you want manually tell each object is unique
just add
item-key="table_header_index"//or name
eg:
<v-data-table
:headers="headers"
:items="items"
show-select
item-key="table_header_index" <-------------------add this line
>
</v-data-table>

I had this issue and the realized my item-key="value" did not match any of the values in my header. Select one of your header values and it should work.

From the example in the docs I can see the following:
1.) selected should be an array, not an object
Selected holds all selected values. single-select property just determines if the length can be bigger than 1.
2.) if you use v-model you should not use #item-selected="itemSelected"
v-model is already 2 way binding. but you trigger an additional event and override the model (which should be an array) with an object or null
Solution
Make selected an array and remove #item-selected="itemSelected".
<template>
<v-data-table
:headers="headers"
:items="items"
:search="search"
:loading="loading"
v-model="selected"
single-select
show-select
:options="{itemsPerPage:5}"
>
<template v-slot:top>
<v-toolbar flat>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
></v-text-field>
</v-toolbar>
</template>
<template v-slot:item.name="{ item }">{{ item.name }}</template>
</v-data-table>
</template>
<script>
export default {
name: "variable-selector",
props: ["variables", "map", "index"],
data() {
return {
search: "",
selected: [],
loading: false,
items: [],
headers: [{ text: "Variable name", value: "name", sortable: true }]
};
},
methods: {
updateItemsList(variables) {
this.items = Array.from(variables);
}
},
mounted() {
this.updateItemsList(this.variables);
},
watch: {
variables(newValue) {
this.loading = true;
this.updateItemsList(newValue);
this.loading = false;
}
}
};
</script>

your data from back should have primary key. if it does it your table understand it; and if your data doesn't have primary key should write item-key for table.
good luck

Related

How do I capture the value of the prop in the text field?

I have a prop and currently am able to get the data of the prop, Am trying a way to capture the item of the prop when saving the form.
Is there a way where i can take the value and pass if in a hidden text-area and bind the data to the vmodel?
Any help I appreciate.
<v-dialog v-model="dialog" persistent max-width="800">
<template v-slot:activator="{ on }">
<v-btn dark v-on="on" color="primary" round> Make payment </v-btn>
</template>
<v-card>
<v-card-title class="headline primary">
<span class="white--text">Add a new Doctor Payment Record {{ queueId }}</span>
<v-btn icon dark #click.native="dialog = false" absolute right>
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<users-search
:leave-selected="true"
idOnly
label="Select Doctor"
#results="setDoctor"
>
</users-search>
<div class="row px-3">
<v-autocomplete
class="px-3 col-sm-8"
v-model="expense.bank"
v-if="banks.data"
:items="banks.data"
outline
chips
label="Select bank"
item-text="name"
item-value="id"
>
</v-autocomplete>
<v-text-field
class="px-3 col-sm-8"
outline
flat
v-model="expense.amount"
type="number"
#input="expense.percentage()"
required
label="Amount *"
persistent-hint
/>
</div>
<v-text-field
class="px-3"
outline
flat
v-model="expense.total_paid"
required
label="amount paid"
persistent-hint
/>
<v-text-field
class="px-3"
outline
flat
:value="setQueue"
v-model="expense.queueId"
required
:label=queueId
persistent-hint
/>
<v-alert :value="true" type="error" v-if="errors.any()">
<div v-html="errors.display()"></div>
</v-alert>
<v-layout row wrap>
<v-flex xs12>
<v-btn
color="success"
:loading="saveLoader"
#click="recordExpense()"
>save</v-btn
>
</v-flex>
</v-layout>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script>
import NewUser from "#finance/libs/users/NewUser";
import {mapActions, mapGetters} from "vuex";
export default {
props: [
'queueId'
],
data: () => ({
dialog: false,
expense: new NewUser(),
saveLoader: false,
}),
computed: {
...mapGetters({
banks: "getBanks",
}),
balance: function () {
return parseFloat(10);
},
submitted() {
return this.expense.form.submitted;
},
contaminated() {
return this.expense.form.errorDetected;
},
errors() {
return this.expense.form.errors;
},
},
watch: {
submitted(v) {
if (v) {
this.saveLoader = false;
}
},
contaminated() {
this.saveLoader = false;
},
},
methods: {
...mapActions({
fetchBanks: "setBanks",
}),
setDoctor(user) {
this.expense.doctor_id = user.id;
},
setQueue(){
console.log(this.queueId);
this.expense.queueId = this.queueId;
},
async recordExpense() {
this.saveLoader = true;
let response = await this.expense.saveExpense();
this.saveLoader = false;
if (response) {
this.dialog = false;
this.$emit("expenseCreated");
}
},
},
mounted() {
this.fetchBanks();
}
};
</script>
The prop queueId i also want to store it along with the user information from the form.
Try this one, it should work:
<template>
<textarea v-model="hiddenValue" :style="{ display: 'none' }"></textarea>
</template>
<script>
export default {
props: [ 'queueId' ],
data() {
return {
hiddenValue: this.queueId
}
}
}
</script>
In case you will no need the prop to be modified, please bind the texarea value to the prop directly:
<textarea hidden v-model="queueId" :style="{ display: 'none' }></textarea>

Dynamic editable columns with vuetify data table

I am trying to make a vue data table component, I want to make it so that if you pass an array of column names, the values in those columns will be editable like in this example. I am currently trying to loop through the array prop passed to the component like as so...
<template
v-for="(column, index) in editableColumns"
v-slot:[getEditableColumn(column)]="props"
>
<v-edit-dialog
:return-value.sync="props.item.desk"
#save="save"
#cancel="cancel"
#open="open"
#close="close"
:key="index"
>
{{ props.item.desk }}
<template v-slot:input>
<v-text-field
v-model="props.item.desk"
label="Edit"
single-line
counter
></v-text-field>
</template>
</v-edit-dialog>
</template>
the get editable columns operates as such:
getEditableColumn(column) {
console.log(column);
return `item.${column}`;
},
it basically returns the value of the column I want to be editable as such item.columnName but the function never runs, I do have to mention that if I pass directly the name like this without a for loop it works, but I want this to work dynamically as I will use the table in multiple places with different column names, and I don't want to make them all columns editable editable.
Below I have attached the full code of the component.
<template>
<v-card>
<v-card-title>
{{ title || "" }}
<v-col>
<v-btn icon color="black" v-if="refresh">
<v-icon>mdi-refresh</v-icon>
</v-btn>
<v-btn icon color="black" v-if="exportExcel" #click="exportToXlsx">
<v-icon>mdi-microsoft-excel</v-icon>
</v-btn>
<v-btn icon color="black" v-if="exportPdf">
<v-icon>mdi-file-pdf-box</v-icon>
</v-btn>
<v-btn icon color="black" v-if="fontSizeControlls">
<v-icon>mdi-format-font-size-decrease</v-icon>
</v-btn>
<v-btn icon color="black" v-if="fontSizeControlls" #click="logSelected">
<v-icon>mdi-format-font-size-increase</v-icon>
</v-btn>
</v-col>
<v-spacer></v-spacer>
<v-col v-if="searchBar">
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Search"
outlined
dense
></v-text-field>
</v-col>
</v-card-title>
<v-data-table
#input="(selected) => $emit('selected', selected)"
#click:row="rowClickFunction"
v-model="selected"
:headers="headers"
:items="data"
:search="search"
:show-select="showSelect"
:single-select="singleSelect"
:height="height"
:items-per-page="itemsPerPage"
:item-key="itemKey"
dense
>
<!-- Pass template elements inside the component call to render custom components inside the table -->
<template
v-for="slot in Object.keys($scopedSlots)"
:slot="slot"
slot-scope="scope"
>
<slot :name="slot" v-bind="scope" />
</template>
<template
v-for="(column, index) in editableColumns"
v-slot:[getEditableColumn(column)]="props"
>
<v-edit-dialog
:return-value.sync="props.item.desk"
#save="save"
#cancel="cancel"
#open="open"
#close="close"
:key="index"
>
{{ props.item.desk }}
<template v-slot:input>
<v-text-field
v-model="props.item.desk"
label="Edit"
single-line
counter
></v-text-field>
</template>
</v-edit-dialog>
</template>
</v-data-table>
</v-card>
</template>
<script>
import { utils, writeFile } from "xlsx";
export default {
props: {
headers: Array,
data: Array,
title: String,
height: String,
itemsPerPage: Number,
itemKey: String,
searchBar: { tpye: Boolean, default: false },
rowClickFunction: {
type: Function,
default: () => {},
},
editableColumns: {
type: Array,
},
refresh: {
type: Boolean,
default: false,
},
exportExcel: {
type: Boolean,
default: false,
},
exportPdf: {
type: Boolean,
default: false,
},
fontSizeControlls: {
type: Boolean,
default: false,
},
singleSelect: {
type: Boolean,
default: false,
},
showSelect: {
type: Boolean,
default: false,
},
xlsxName: {
type: String,
default: "Sheet.xlsx",
},
},
data() {
return {
search: "",
selected: [],
dialog: false,
};
},
methods: {
exportToXlsx() {
const worksheet = utils.json_to_sheet(this.data);
const workbook = utils.book_new();
utils.book_append_sheet(workbook, worksheet, "Data");
writeFile(workbook, this.xlsxName);
},
getEditableColumn(column) {
console.log(column);
return `item.${column}`;
},
logSelected() {
console.log(this.selected);
},
logRow(row) {
console.log(row);
console.log(this.selected);
},
getSlotName(slot) {
return `${slot}.slotName`;
},
save() {
this.snack = true;
this.snackColor = "success";
this.snackText = "Data saved";
},
cancel() {
this.snack = true;
this.snackColor = "error";
this.snackText = "Canceled";
},
open() {
this.snack = true;
this.snackColor = "info";
this.snackText = "Dialog opened";
},
close() {
console.log("Dialog closed");
},
},
};
</script>
The slot name must be one of the headers element's value

using vuetify overlay in datatable

So I have a datatable of images that I want to expand in an overlay tag on click.
To do that, I created an array for each column of the table and I mapped it to its corresponding image.
Here's the code :
<template>
<v-app>
<app-navbar />
<v-main>
<div class="text-center">
<h3>
test {{ $route.params.name }}, {{ $route.query.status }},{{
$route.query.tag
}}
</h3>
</div>
<v-data-table
:headers="headers"
:items="imagesref"
:items-per-page="5"
class="elevation-1"
>
<template v-slot:[`item.index`]="{ index }">
{{index+1}}
</template>
<template v-slot:[`item.ref`]="{ index }">
<v-img :src="imagesref[index]" max-width="750" max-height="750" #click="expref[index] = !expref[index]"/>
<v-overlay :value="expref[index]"><v-img :src="imagesref[index]" max-width="1300" max-height="900" #click="expref[index] = !expref[index]"/> </v-overlay>
</template>
<template v-slot:[`item.test`]="{ index }">
<v-img :src="imagestest[index]" max-width="750" max-height="750" #click="exptest[index] = !exptest[index]"/>
<v-overlay :value="exptest[index]"><v-img :src="imagestest[index]" max-width="1300" max-height="900" #click="exptest[index] = !exptest[index]"/> </v-overlay>
</template>
<template v-slot:[`item.res`]="{ index }">
<v-img :src="imagesresult[index]" max-width="750" max-height="750" #click="expres[index] = !expres[index]"/>
<v-overlay :value="expres[index]"><v-img :src="imagesresult[index]" max-width="1300" max-height="900" #click="expres[index] = !expres[index]"/> </v-overlay>
</template>
<template #[`item.Scrubber`]="{ index }">
<nuxt-link :to="{ path: 'scrubber', query: { imageref: imagesref[index],imagetest:imagestest[index],imageres:imagesresult[index] }}">Show Scrubber</nuxt-link>
</template>
</v-data-table>
</v-main>
</v-app>
</template>
<script>
import appNavbar from "../../../components/appNavbar.vue"
import axios from "axios"
export default {
components: { appNavbar },
name: "App",
data() {
return {
expref:[],
exptest:[],
expres:[],
items: [],
imagesref: [],
imagestest: [],
imagesresult: [],
headers: [
{ text: 'index',value: 'index',sortable:false},
{ text: 'Imagesref', value: 'ref',sortable:false },
{ text: 'Imagestest', value: 'test',sortable:false },
{ text: 'Imagesresult', value: 'res',sortable:false },
{ text: 'Scrubber', value: 'Scrubber',sortable:false },
]
}
},
async created() {
try {
const res = await axios.get(`http://localhost:3004/tests`, {
params: { name: this.$route.params.name },
})
this.items = res.data
this.imagesref = res.data[0].refimages
this.imagestest = res.data[0].testimages
this.imagesresult = res.data[0].resultimages
for (let i of this.imagesref){
this.expref.push(false);
this.exptest.push(false);
this.expres.push(false);
}
} catch (error) {
console.log(error)
}
}
}
</script>
<style scoped>
</style>
When I tested it, after I click on the image the corresponding variable in the array changes its value to true but the overlay is not getting displayed but somehow when I change the value manually on devtools it works.Does someone have any idea what's going on and how can i make it work ?

Vue - Dynamically add specific v-model name with v-for, not just an index

I want to loop over the colorMenus array and bind my v-model to the already defined data elements headerColor and checkboxColor
I have this simplified code:
<v-card
v-for="(colorMenu, index) in colorMenus"
:key="index"
>
<v-row>
<v-col>
<p class="font-weight-bold text-subtitle-2 mt-4">{{ colorMenu.title }}</p>
</v-col>
<v-col cols="8">
<v-text-field
v-model="myModels.color[index]"
v-mask="mask"
hide-details
class=""
solo
></text-field>
</v-col>
</v-row>
</v-card>
And my data looks like this:
export default {
data() {
return {
headerColor: '#1976D2FF',
checkboxColor: '#1976D2FF',
myModels: {
color: ['headerColor', 'checkboxColor']
},
colorMenus: [
{
title: 'HEADER:',
},
{
title: 'CHECKBOX:',
}
]
}
},
What's weird is I can get this, the model names, but they have # in front?
I think that's because you've defined strings inside of that color array. You should refer to these items like this:
myModels: {
color: [this.headerColor, this.checkboxColor]
},
I hope this helps. in a v-for for some reason I cannot access the name from an array, but I can if I access the data by key from ANOTHER object. No idea why, but it worked! Here is the fixed code:
<v-card
v-for="(colorMenu, index) in colorMenus"
:key="index"
>
<v-row>
<v-col>
<p class="font-weight-bold text-subtitle-2 mt-4">{{ colorMenu.title }}</p>
</v-col>
<v-col cols="8">
<v-text-field
v-model="myModels[colorMenu.type]"
v-mask="mask"
hide-details
class=""
solo
></text-field>
</v-col>
</v-row>
</v-card>
And then my data:
export default {
data() {
return {
headerColor: '#1976D2FF',
checkboxColor: '#1976D2FF',
myModels: {
headerColor: '#1976D2FF',
checkboxColor: '#1976D2FF',
},
colorMenus: [
{
title: 'HEADER:',
type: 'headerColor'
},
{
title: 'CHECKBOX:',
type: 'checkboxColor'
}
]
}
},
How about using computed property? This works, you can try it out here
https://codesandbox.io/s/optimistic-herschel-7q4u54?file=/src/App.vue
data() {
return {
headerColor: "#1976D2FF",
checkboxColor: "#1976D2FF",
};
},
computed: {
myModels() {
return [this.headerColor, this.checkboxColor];
},
},

Vuetify insert action button in data-table and get row data

Environment:
vue#^2.6.10:
vuetify#^2.1.0
I want to use v-data-table to show search results and add evaluate button in each row in the v-data-table.
Unfortunately I have two issues:
Evaluate buttons are not shown
I don't know how to get row data of pushed button
What do I need to change?
Template
<v-data-table
:headers="headers"
:items="search_result"
>
<template slot="items" slot-scope="row">
<td>{{row.item.no}}</td>
<td>{{row.item.result}}</td>
<td>
<v-btn class="mx-2" fab dark small color="pink">
<v-icon dark>mdi-heart</v-icon>
</v-btn>
</td>
</template>
</v-data-table>
Script
data () {
return {
headers: [
{ text: 'no', value: 'no' },
{ text: 'result', value: 'result' },
{ text: 'good', value: 'good'},
],
// in real case initial search_result = [], and methods: search function inject below data
search_result: [{no: 0, result: 'aaa'}, {no:2, result: 'bbb'],
}
},
slot name used to "replace the default rendering of a row" is item, not items
Add wrapping <tr> into slot template
Just add #click="onButtonClick(row.item) to v-btn and create method onButtonClick
<v-data-table :headers="headers" :items="search_result">
<template v-slot:item="row">
<tr>
<td>{{row.item.no}}</td>
<td>{{row.item.result}}</td>
<td>
<v-btn class="mx-2" fab dark small color="pink" #click="onButtonClick(row.item)">
<v-icon dark>mdi-heart</v-icon>
</v-btn>
</td>
</tr>
</template>
</v-data-table>
methods: {
onButtonClick(item) {
console.log('click on ' + item.no)
}
}
Note..
...solution above is replacing default row rendering with your own so expect some of the v-data-table features to not work (didn't try but I expect row selection, grouping, in place editing etc. will be broken). If that's problem for you, here is alternative solution:
Add one more column to your headers definition: { text: "", value: "controls", sortable: false }
Do not override item slot (row rendering). Override item.controls slot instead. Notice "controls" is the same as in column definition - we are overriding just rendering of "controls" column
Everything else is same
<v-data-table :headers="headers" :items="search_result">
<template v-slot:item.controls="props">
<v-btn class="mx-2" fab dark small color="pink" #click="onButtonClick(props.item)">
<v-icon dark>mdi-heart</v-icon>
</v-btn>
</template>
</v-data-table>
In my case the solution of Michal was throwing the following exception
The solution was using slot and slot-scope
<template>
<v-data-table
:headers="headers"
:items="items"
class="elevation-1"
>
<template slot="item.delete" slot-scope="props">
<v-btn class="mx-2" icon #click="() => delete(props.item)">
<v-icon dark>mdi-delete</v-icon>
</v-btn>
</template>
</v-data-table>
</template>
<script>
export default {
data() {
return {
headers: [
// Dynamic headers
{
text: 'Name',
value: 'name'
},
{
text: '',
// Row to replace the original template
value: 'delete'
},
],
items: [
{
id: 1,
name: 'A'
},
{
id: 2,
name: 'B'
}
]
};
},
methods: {
delete(item) {
this.items = this.items.filter((d) => d.id !== item.id);
},
},
};
</script>