I am using Vue, Vuex, and Vuetify to display courses in data-table and want in-line editing as a feature. Please see relevant component code below.
#Courses.vue
<template>
<v-data-table
:headers="headers"
:items="courses"
:search="search"
:loading="loading"
no-data-text="No Courses Available"
>
<template slot="items" slot-scope="props">
<tr>
<td>
<v-edit-dialog
:return-value.sync="props.item.title"
lazy
>
{{ props.item.title }}
<v-text-field
slot="input"
label="Enter New Course Title"
v-model="props.item.title"
single-line
counter
#keyup.enter="onUpdateCourse({ id: props.item.id, title: props.item.title})"
></v-text-field>
</v-edit-dialog>
</td>
<td>{{ props.item.category }}</td>
<td>{{ props.item.startDate | date }}</td>
<td>{{ props.item.endDate | date }}</td>
<td>{{ props.item.location }}</td>
</tr>
</template>
</v-data-table>
</template>
<script>
export default {
data() {
return {
headers: [** table headings **],
};
},
computed: {
courses() {
return this.$store.getters.courses;
},
},
methods: {
onUpdateCourse(itemToUpdate) {
debugger;
this.$store.dispatch('updateCourse', itemToUpdate);
},
},
};
</script>
It works; but I take issue with the fact that this approach alters the Vuex state directly: the only reason to dispatch(‘updateCourse’, itemToUpdate) is to update the db (firestore in this case). I would have thought it preferable to update the $store.state solely via the Vuex actions/mutations rather than have it syncing directly here.
So, my first question is: Should I take issue with this, and if not why not?
Because it was bugging me, I added a local copy of the Vuex courses array to the computed section:
localCopy() {
return this.courses.map(x => Object.assign({}, x));
},
and built the data-table using :items="localCopy". This allows me to update the courses from within the Vuex actions but the data table doesn’t update until I click somewhere else in the app.
So, my second question is: If this is the correct approach, how do I keep the reactivity of the component?
Thanks for any help.
(PS – When cutting and pasting code, or editing in the text box, it seems that some of the double quotes " are unfortunately getting converted to fancy quotes “. It is doubly unfortunate that I am dyslexic and although I have done my best to hunt them down, I literally can’t see them most of the time. Please don’t hate on me)
To not assign changes to props.item.title, do:
Remove the .sync in <v-edit-dialog :return-value="props.item.title".
Replace v-model with :value in <v-text-field :value="props.item.title".
As .sync has an implicit #return-value:update="props.item.title = $event" and v-model has an implicit #input="props.item.title = $event (roughly), the above alone (removing .sync and replacing v-model with :value) would stop title from being directly modified.
To make it being modified via dispatch also add an #input listener that calls the dispatch: #input="onUpdateCourse({ id: props.item.id, title: props.item.title})".
Finally, here's how your code should look like:
<td>
<v-edit-dialog
:return-value="props.item.title"
lazy
>
{{ props.item.title }}
<v-text-field
slot="input"
label="Enter New Course Title"
:value="props.item.title"
#input="onUpdateCourse({ id: props.item.id, title: props.item.title})"
single-line
counter
#keyup.enter="onUpdateCourse({ id: props.item.id, title: props.item.title})"
></v-text-field>
</v-edit-dialog>
</td>
Related
I am doing a project with VueJS and I need to the following:
Use an API & fetch a list of users in the home page.
When clicking on a user's button, I need to redirect to another component & output that user's details in that component (only the details of the user that I clicked).
Here is the table displaying the users info
<v-data-table hide-actions flat :headers="headers" :items="doctors">
<template v-slot:items="props">
<td>{{ props.index + 1 }}</td>
<td>{{ props.item.profile.full_name }}</td>
<td>{{ props.item.email }}</td>
<td>{{ props.item.username }}</td>
<td>{{ props.item.updated_at }}</td>
<td>
<v-btn outline small color="indigo" #click="view(props.item)">
<i class="fa fa-eye"></i> make payment
</v-btn>
</td>
</template>
<template v-slot:no-results>
<h6 class="grey--text">No data available</h6>
</template>
</v-data-table>
the view function is in the methods
view() {
window.open(`/finance/pay-doctors/${item.id}`, "_blank");
},
I have created a dynamic route
{ path: '/finance/pay-doctors/:id', component: DoctorDetails}
Am unable to create the DoctorDetails and show data
I recommend that you try with a router push initially.
Hence please use
<script>
export default {
methods: {
view(id) {
this.$router.push({ path: `/finance/pay-doctors/${item.id}` })
}
}
}
</script>
Make sure that your router is properly set + that you have the proper state thanks to your Vue devtools.
Also, be sure to have something to fetch the user's data on DoctorDetails.
Learning Vuetify and Vuetify (Loving both) but come across an issue I can't figure out so would appreciate someone sharing best practice please
I have created a component that contains a vuetify datatable and I pass the titles and items via props from a parent to the child, so far so good
The bit I can't figure out is that I want to loop through a specific field (the example I am using is item.calories as per the docs) and run a function over it.
I assume I pass the item.calories field as a prop but how do I then send that to a function
in my parent I pass to my DataTable component as follows
<DataTable
:headers="headers"
:content="content"
title="This is my data table title"
:myCalories="content.calories" <-- This bit is causing me the issue
/>
How in my DataTable component can I change the below to use the :myCalories prop, or is there a better approach I could consider?
<template v-slot:[`item.calories`]="{ item }">
<v-chip
:color="getColor(item.calories)"
dark
>
{{ item.calories }}
</v-chip>
</template>
My function
methods: {
getColor (calories) {
if (calories > 400) return 'red'
else if (calories > 200) return 'orange'
else return 'green'
},
},
I did wonder if I should run the function in the parent first and pass the result over but if you could advise on the best practice way to achieve the above it would be very much appreciated
Gibbo
I'm not sure it this will help but I had a similar problem and ended up using the body slot of the v-data-table and redefined my whole table.
This workaround could help you but this is certainly not the best approach to do it
<template v-slot:body="{ items, headers }">
<tbody>
<tr v-for="item in items" :key="item.name">
<td v-for="column in headers" :key="column.value">
<div v-if="column.value === 'calories'" :style="getColor(item.calories)">
{{ item[column.value] }}
</div>
<div v-else>
{{ item[column.value] }}
</div>
</td>
</tr>
</tbody>
</template>
Here is the codepen example https://codepen.io/taghurei-the-reactor/pen/YzaBNxw?editors=1111
Problem: I cannot add an expandable row in my data table. It is not showing.
Goal: I need to populate some data in my data table with an expandable row.
What I have tried: I have followed the documentation, here is the reproducible code that you can access on codepen. You can see after uncommenting the code which populates my Data table, the expandable row isn't visible anymore.
Code used to add expandable row:
<template v-slot:top>
<v-toolbar flat>
<v-toolbar-title>Data</v-toolbar-title>
<v-spacer></v-spacer>
<v-switch v-model="singleExpand" label="Single expand" class="mt-2"></v-switch>
</v-toolbar>
</template>
<template v-slot:expanded-item="{ headers, item }">
<td :colspan="headers.length">More info about {{ item.name }}</td>
</template>
Code used to add data in Data table:
<!-- <template v-slot:body="props">
<tr v-for="index in props.items" :key="index.Info">
<td id="table-data" v-for="header in headers" :key="header.Info" class="text-left">
{{ index.packetParsed[header.value] }}
</td>
</tr>
</template> -->
Note: Please note that I cannot change the structure of data as it is retrieved from the database.
as you have mentioned that the structure of data can't be changed
in the codepen which you have provided, there I saw data as
dataFromDatabase: [ { packetParsed: { Name: "Bill", Address: "NY", Contact: "1234", Info: "Mgr", }, packetParsed: { Name: "Tom", Address: "CA", Contact: "9876", Info: "Sr Mgr", }, }, ],
can you please explain why the data has an object which contains objects with the same key (Note: we can read only the last object if all the keys are same when you read )
as per the documentation of vuetify expandable table, items prop should be an array with unique item objects .
but you have provided an array with a single object( which has multiple objects with same key )
So I have a Vuetify data table with a lot of items, I need to display 50/100/150 items.
The problem, when I have 50 items it takes about 2 to 3 seconds to load the entire table. My request takes about 500ms to load and vue takes 3 seconds to render. When I have 150 items it takes about 10 seconds.
Is it possible to reduce render time to at least 5 seconds with 150 items?
I'm using this table: https://vuetifyjs.com/en/components/data-tables#data-tables
My table code:
<template>
<v-data-table
:headers="headers"
:items="desserts"
:items-per-page="5"
class="elevation-1"
:items-per-page="50"
:footer-props="rowsPerPageItems"
>
<TableRow :row="item" v-for="item in clientList" :key="item.id">
</v-data-table>
</template>
// my clientList is async response from server
import { mapGetters } from "vuex";
export default {
data(){
return {
rowsPerPageItems: [50, 100, 150]
}
},
computed: {
...mapGetters["clientList"] // this comes from vuex, I don't think it's relevant. It returns about 400 client info
}
}
<!-- My component TableRow -->
<template>
<tr>
<td>
<v-checkbox v-model="row.checked" color="primary"></v-checkbox>
</td>
<td>{{ row.data }}</td>
<td>{{ row.name }}</td>
<td>
<!-- This is a jQuery plugin, I need it to keep track of every key pressed -->
<Select2
:id="row.id.toString()"
:settings="{ minimumInputLength: 3, ajax: ajaxData, placeholder: labels.descriptionList[row.description] }"
#select="handleSelect2($event)"
/>
</td>
<td v-if="this.is_expense">
<v-select
:items="labels.categoryName"
:placeholder="labels.categoryList[row.category]"
#input="updateCashflow"
v-model="row.category"
dense
></v-select>
</td>
<td v-else></td>
<td>{{ row.amount }}</td>
</tr>
</template>
Remove that component and read v-data-table documentation.
You shouldn't add v-checkbox to make it rows selectable, use show-select prop instead with v-model on v-data-table
Use item.<name> slot to add custom components into cells
In my case, I really need components inside my table so I used
<v-select v-if="mountedComponent" :items="myList"/>
<span v-else>Some default value</span>
When page mounted I set mounted to true
export default {
name: "Test",
data(){
return {
mountedComponent: false,
myList:["item 1", "item 2"]
}
},
mounted(){
this.mountedComponent= true;
}
}
This solved my performance issue, going from 10 ~ 15 seconds delay to 100ms.
I have a v-select nested in a v-for for items..
So, basically, v-for item in items gives me a table containing all items.
In this table, I have a v-select containing it's own array of items pulled from a computed property (getter).
So, if I have 3 items, then I would have a 3 item table with 3 v-selects all containing the same dropdown options populated from the computed.
My issue is, when I select an option in one v-select, it populates in all of them.
How do I configure it to only populate the one particular v-select, and not all of them?
Surprisingly, every example I have researched, is either dealing with nested data, or something else not relevant here. But if this does happen to be a duplicate, I would be happy to be pointed in the right direction.
a bit of shorthand code for context:
<v-data-table :items="getItems">
<tr>
<td>{{ item.name }}</td>
<td> <v-select :items="availableSelections"/>
</tr>
</v-data-table>
get getItems() {
// returns iterated items
}
get availableSelections() {
// returns selection choices in an array
So, my intended behavior here is, if I have 3 items returned from getItems(), I would have 3 rows filled with 3 names and 3 v-selects, all with the same dropdown choices from availableSelections(). But when I select an option from a v-select from one row, it would not populate the other v-selects in other rows.
Do you want something like that:
<template>
<v-app>
<v-content>
<v-data-table :items="items">
<template v-slot:items="{ item }">
<td>{{ item.name }}</td>
<td>
<v-select :items="options" v-model="item.optionSelected" />
</td>
<td>{{ item.optionSelected }}</td>
</template>
</v-data-table>
</v-content>
</v-app>
</template>
<script>
export default {
name: 'app',
data: () => ({
items: [
{ name: 'Name 1', optionSelected: null },
{ name: 'Name 2', optionSelected: null },
{ name: 'Name 3', optionSelected: null },
],
options: ['Option 1', 'Option 2', 'Option 3'],
}),
};
</script>
In this example, each v-select don't change others components.