This is a project to display food recipes, and several operations can be performed on the recipe, such as modifying the recipe, deleting a recipe, adding a recipe, but when modifying a recipe, I modify the data and press the Save button (as in the picture), but the modifications are not saved and I do not know why.
How can i solve the problem?
This is the form for modifying a recipe and from here the new data is passed to the recipe.
EditRecipesDetails:
<template>
<v-row justify="center">
<v-dialog v-model="editDialog" persistent max-width="500px" height="400px">
<template v-slot:activator="{ on, attrs }">
<v-btn
fab
accent
color="primary"
dark
v-bind="attrs"
v-on="on"
class="ml-70"
>
<v-icon>
mdi-pencil
</v-icon>
</v-btn>
</template>
<v-card>
<v-layout>
<v-flex xs12>
<v-card-title>
<span class="headline"> Edit Recipe</span>
</v-card-title>
</v-flex>
</v-layout>
<v-layout>
<v-flex xs12>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12">
<v-text-field
name="title"
label="Title"
id="title"
v-model="editedTitle"
required
></v-text-field>
<v-text-field
name="description"
label="Description"
id="description"
v-model="editedDescription"
multi-line
required
></v-text-field>
</v-col>
</v-row>
</v-container>
</v-card-text>
</v-flex>
</v-layout>
<v-divider></v-divider>
<v-layout>
<v-flex xs12>
<v-card-actions>
<!-- <v-spacer></v-spacer> -->
<v-btn
class="green--text darken-1"
text
#click="editDialog = false"
>
Close
</v-btn>
<v-btn
class="green--text darken-1"
text
#click="onSaveChanges = false"
>
Save
</v-btn>
</v-card-actions>
</v-flex>
</v-layout>
</v-card>
</v-dialog>
</v-row>
</template>
<script>
export default {
props: ["recipe"],
data() {
return {
editDialog: false,
editedTitle: this.recipe.title,
editedDescription: this.recipe.description,
};
},
methods: {
onSaveChanges() {
if (
this.editedTitle.trim() === "" ||
this.editedDescription.trim() === ""
) {
return;
}
this.editDialog = false;
const stringifiedData = JSON.stringify(
this.$store.dispatch("updateRecipeData", {
id: this.recipe.id,
title: this.editedTitle,
description: this.editedDescription,
})
);
// console.log("S: ", stringifiedData);
localStorage.setItem("updateRecipe", stringifiedData);
console.log(
"We got : ",
JSON.parse(localStorage.getItem("updateRecipe"))
);
},
},
};
</script>
Here the button to edit the recipe is displayed.
And when you press this button, the modification form in the image appears.
Recipe:
<template>
<v-container>
<v-layout row wrap v-if="loading">
<v-flex xs12 class="text-xs-center">
<v-progress-circular
indeterminate
class="primary--text"
:width="7"
:size="70"
>
</v-progress-circular>
</v-flex>
</v-layout>
<v-layout row wrap v-else>
<v-flex x12>
<v-card>
<!-- <v-card-title> -->
<v-card-text>
<h4 class="btn-style mt-4 mb-4 font">
{{ recipe.title }}
</h4>
<template v-if="true">
<v-spacer></v-spacer>
<app-edit-recipe-details :recipe="recipe"></app-edit-recipe-details>
</template>
<v-img height="530px" :src="recipe.imageUrl" class="mb-4"></v-img>
<div class="btn-style mb-6">
{{ recipe.description }}
</div>
<div v-for="ing in recipe.ingredients" :key="ing.id">
{{ ing.Name }} {{ ing.Quantity }}
<v-btn class="green darken-1 color mb-5 ml-4 mr-4 pl-50">
<v-icon class="green darken-1 btn-style">mdi-plus</v-icon>
</v-btn>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<!-- <v-flex xs5 sm4 md2> -->
<v-btn class=" mb-4 mr-4">
<v-icon left class=" pl-4 ">
mdi-pen
</v-icon>
<!-- </v-btn> -->
</v-btn>
<!-- </v-flex> -->
</v-card-actions>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
import { mapGetters } from "vuex";
export default {
props: ["id"],
computed: {
recipe() {
const loadedRecipe = this.$store.getters.loadedRecipe(this.id);
console.log("We loaded a recipe with value : ", loadedRecipe);
return loadedRecipe;
},
ingredient() {
return this.$store.getters.loadedingredient(this.id);
},
loading(){
return this.$store.getters.loading
},
...mapGetters([
//Here we put Getter
"loadedRecipe",
"loadedingredient",
])
}
};
</script>
<style scoped>
.btn-style {
color: #43a047;
}
.color {
color: #fafafa;
}
.deleteColorIcon {
color: #e53935;
}
.font {
font-size: 30px;
}
</style>
Here the functions for modifying the recipe data are written.
store:
import Vue from "vue";
import Vuex from "vuex";
import image1 from "../assets/img/image4.jpg";
import image2 from "../assets/img/image2.jpg";
import image3 from "../assets/img/image3.jpg";
import image4 from "../assets/img/insta2.jpg";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
loadedingredients: [
{ id: "1", Name: "Sugar", Quantity: "5kg" },
{ id: "2", Name: "Sugar", Quantity: "5kg" },
{ id: "3", Name: "Sugar", Quantity: "5kg" },
],
loadedRecipes: [
{
imageUrl: image3,
id: "3",
title: "Homemade Burger",
description:
"Small plates, salads & sandwiches - an intimate setting with 12 indoor seats plus patio
seating.."
},
{
imageUrl: image1,
id: "1",
title: "Cake",
description:
"Small plates, salads & sandwiches - an intimate setting with 12 indoor seats plus patio
seating.."
},
{
imageUrl: image4,
id: "4",
title: "Salad",
description:
"Small plates, salads & sandwiches - an intimate setting with 12 indoor seats plus patio
seating.."
},
{
imageUrl: image2,
id: "2",
title: "Kabseh",
description:
"Small plates, salads & sandwiches - an intimate setting with 12 indoor seats plus patio
seating."
},
],
user: [
{ name: "Hiba", email: "Hiba69#gmail.com", password: "123442321325" },
],
loading: false,
},
actions: {
updateRecipeData({commit},payload){
commit('setloading',true)
const updateObj = {}
if(payload.title){
updateObj.title == payload.title
}
if(payload.description){
updateObj.description == payload.description
}
commit('updateRecipe', payload)
localStorage.setItem('updateRecipe',this.loadedRecipes)
}
},
// like setters
mutations: {
updateRecipe(state,payload){
const recipe = state.loadedRecipes.find(recipe=>{
return recipe.id == payload.id
})
if(payload.title){
recipe.title = payload.title
}
if(payload.description){
recipe.description = payload.description
}
}
},
getters: {
loadedRecipes: (state) => {
return state.loadedRecipes.sort((RecipeA, RecipeB) => {
return RecipeA.id > RecipeB.id;
}).map(aRec => {
aRec['ingredients'] = [...state.loadedingredients];
return aRec;
})
},
loadedRecipe: (state) => {
return (recipeId) => {
return state.loadedRecipes.find((recipe) => {
return recipe.id === recipeId;
})
};
},
isLoggedIn: (state) => {
return state.isLoggedIn;
},
loading (state) {
return state.loading
},
},
});
<v-btn
class="green--text darken-1"
text
#click="onSaveChanges = false"
>
Save
</v-btn>
You are setting your method onSaveChanges to false when you should be calling the function. Modify #click="onSaveChanges = false" to #click="onSaveChanges" and you will be good to go
Related
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>
I'm passing an array to a component but the component sees the array as undefined. Here is the parent calling the component...
<FileList ref="files" class="ma-3 pa-0" :passFiles="true" :passedFiles="header.files"></FileList>
Vue devtools sees the array, it is valid. As seen in the screenshot below:
Yet in my created hook in the controller, it shows this.passedFiles as undefined. (this.passFiles, however, shows correctly as true.)
created(){
console.log(this.passFiles,this.passedFiles); //this.passedFiles shows as undefined
window.addEventListener("resize", this.onResize);
},
I dumped the array right before it gets sent to the component, and it is there, see screenshot:
I tried this just to make sure, and it gets passed to the array fine:
:passedFiles="[{0: '1'}]"
I'm pulling my hair out here. Here is the full component, it is long but it shows you everything
<template>
<div>
<div class="text-center pa-10" v-show="loading">
<v-progress-circular
:size="35"
:width="3"
color="primary"
indeterminate
></v-progress-circular>
</div>
<v-data-table
v-show="!loading"
:headers="showActions ? headers : headersRead"
:items="orderedFiles"
:items-per-page="paginate"
:footer-props="{'items-per-page-options':[paginate, 15, 30, 50, 100, -1]}"
:hide-default-footer="oneFileOnly"
class="elevation-1 custom-rounded-box ma-0 pa-0"
ref="aWidth"
:style="forceHeight&&$vuetify.breakpoint.mdAndUp ? 'height:'+forceHeight+'px;' : ''"
>
<template slot="no-data">
<div>There are currently no files here</div>
</template>
<template v-slot:item.description="{item, index}">
<v-row
no-gutters
style="flex-wrap: nowrap;"
>
<v-col
cols="12"
md="11"
class="flex-grow-0 flex-shrink-0"
>
<v-tooltip bottom v-if="item.description">
<template v-slot:activator="{ on, attrs }">
<a
v-if="item.gdoc"
style="text-decoration: none; color: orange;"
v-bind="attrs"
v-on="on"
#click.prevent="gdocDialog = true;editingFile = item"
class="d-block text-truncate"
:style="$vuetify.breakpoint.mdAndUp ? 'max-width:'+aWidth+'px;' : 'max-width:'+bWidth+'px;'"
>
{{item.description}}
</a>
<a
v-else
:href="'/getFile?id='+item.id"
style="text-decoration: none; color: orange;"
v-bind="attrs"
v-on="on"
class="d-block text-truncate"
:style="$vuetify.breakpoint.mdAndUp ? 'max-width:'+aWidth+'px;' : 'max-width:'+bWidth+'px;'"
>
{{item.description}}
</a>
</template>
<span>{{item.file_name_original}}</span>
</v-tooltip>
<div v-else>
<a
v-if="item.gdoc"
style="text-decoration: none; color: orange;"
#click.prevent="gdocDialog = true;editingFile = item"
class="d-block text-truncate"
:style="$vuetify.breakpoint.mdAndUp ? 'max-width:'+aWidth+'px;' : 'max-width:'+bWidth+'px;'"
>
{{item.file_name_original}}
</a>
<a
v-else
:href="'/getFile?id='+item.id"
style="text-decoration: none; color: orange;"
class="d-block text-truncate"
:style="$vuetify.breakpoint.mdAndUp ? 'max-width:'+aWidth+'px;' : 'max-width:'+bWidth+'px;'"
>
{{item.file_name_original}}
</a>
</div>
</v-col>
<v-col
cols="12"
md="1"
style="min-width: 30px; max-width: 30px;"
class="flex-grow-1 flex-shrink-0"
v-show="$vuetify.breakpoint.mdAndUp"
>
<v-edit-dialog
:return-value.sync="item.description"
#save="editFileInline()"
#open="inlineEditOpen(item, index)"
v-if="showActions"
>
<template v-slot:input>
<v-text-field
ref="inline_file"
v-model="editingFile.description"
label="Edit"
single-line
counter
></v-text-field>
</template>
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-icon
small
class="ml-2"
color="orange"
v-bind="attrs"
v-on="on"
width="100%"
>
mdi-pencil
</v-icon>
</template>
<span>Edit the file description</span>
</v-tooltip>
</v-edit-dialog>
</v-col>
</v-row>
</template>
<template v-slot:item.icon="{ item }">
<v-icon
:color="item.icon_color"
>
{{item.icon}}
</v-icon>
</template>
<template v-slot:item.uploaded="{ item }">
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<div v-bind="attrs"
v-on="on">
{{item.date_difference}}
</div>
</template>
<span>
<v-avatar
size="26px"
class="mr-2"
>
<img
:src="'/img/profile-pictures/'+item.user.profile_photo_thumb"
>
</v-avatar>
{{item.pretty_date}} by {{item.user.full_name}}</span>
</v-tooltip>
</template>
<template v-slot:item.actions="{item}" v-if="showActions">
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-icon
small
color="red"
#click="showDeleteDialog(item)"
v-bind="attrs"
v-on="on"
>
mdi-delete
</v-icon>
</template>
<span>Delete</span>
</v-tooltip>
</template>
</v-data-table>
<!-- Upload modal -->
<v-dialog
v-model="fileUploadDialog"
max-width="500px"
width="500px"
:transition="transitionSiteWide()"
persistent
v-if="showActions"
>
<v-card>
<v-progress-linear
indeterminate
color="yellow darken-2"
v-show="fileUploadProcess"
></v-progress-linear>
<v-toolbar
dark
class="primary"
dense
elevation="0"
>
<v-icon class="mr-2">mdi-cloud-upload</v-icon>
<v-toolbar-title class="text">Upload File(s)</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
<v-card-text class="pt-3" v-show="!fileUploadGDoc">
<template>
<v-file-input
small-chips
:label="!oneFileOnly ? 'Upload multiple files by clicking here' : 'Click here to upload a file'"
type="file"
ref="files"
:accept="acceptedFiles()"
#change="onFilePicked()"
:key="componentKey"
show-size
counter
:multiple="!oneFileOnly"
:rules="!oneFileOnly ? rules : rulesSingle"
></v-file-input>
</template>
</v-card-text>
<v-card-text class="pt-3" v-show="fileUploadGDoc">
<v-text-field
ref="gdoc_description"
v-model="gdoc_description"
label="Description"
:rules="gdoc_description_rules"
prepend-icon="mdi-pencil"
></v-text-field>
<v-text-field
ref="gdoc_link"
v-model="gdoc_link"
label="Link to your Google Document"
:rules="gdoc"
prepend-icon="mdi-google-drive"
></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
text
#click="ok"
>
Close
</v-btn>
<v-btn
class="primary"
text
v-show="fileUploadButton"
#click="uploadFiles()"
:loading="fileUploadProcess"
>
Upload
</v-btn>
<v-btn
class="primary"
text
v-show="gdocValidated()"
#click="uploadFiles()"
:loading="fileUploadProcess"
>
Attach
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Delete dialog -->
<v-dialog
v-if="showActions"
v-model="deleteFileConfirm"
max-width="400px"
:transition="transitionSiteWide()"
>
<v-card elevation="0">
<v-progress-linear
indeterminate
color="yellow darken-2"
v-show="deleteFileLoading"
></v-progress-linear>
<v-toolbar
dark
class="primary"
dense
elevation="0"
>
<v-icon class="mr-2">mdi-text-box-minus</v-icon>
<v-toolbar-title class="text">{{editingFile.description ? editingFile.description : editingFile.file_name_original}}</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
<v-card-text class="pb-0">
<v-container>
<p>Are you sure you want to delete this file?</p>
<p>{{editingFile.description ? editingFile.description : editingFile.file_name_original}}
will be removed from the system.</p>
</v-container>
</v-card-text>
<v-card-actions>
<v-btn
text
#click="deleteFileConfirm = false"
>
Close
</v-btn>
<v-spacer></v-spacer>
<v-btn
class="primary"
text
#click="deleteSet()"
>
Yes, delete this file
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Gdoc dialog -->
<v-dialog
v-model="gdocDialog"
max-width="1000px"
width="100%"
:transition="transitionSiteWide()"
>
<v-card elevation="0">
<v-toolbar
dark
color="teal"
dense
elevation="0"
>
<v-icon class="mr-2">mdi-google-drive</v-icon>
<v-toolbar-title class="text">{{editingFile.description ? editingFile.description : editingFile.file_name_original}}</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
<v-card-text class="pa-0">
<iframe ref="gdocIframe" :src="editingFile.file_name_original" :style="'height:'+iframeHeight+'px;width:100%;border:0'"></iframe>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
text
#click="gdocDialog = false"
>
Close
</v-btn>
<v-btn
class="primary"
text
link
#click="openGdoc(editingFile.file_name_original);"
>
Open in new window
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
export default {
props: {
type: String,
id: [Number, String],
paginate: Number,
fileUploadDialog: Boolean,
fileUploadGDoc: Boolean,
ok: Function,
showActions: Boolean,
forceHeight: { type: Number, default: 0 },
oneFileOnly: Boolean,
passFiles: Boolean,
passedFiles: Array,
},
data () {
return {
headers: [
{
text: '',
align: 'start',
sortable: false,
value: 'icon',
width: '20px'
},
{ text: 'Description', value: 'description', sortable: false, },
{ text: 'Uploaded', value: 'uploaded', width: '150px' },
{ text: 'Actions', value: 'actions', sortable: false, width: '80px', align: 'center' },
],
headersRead: [
{
text: '',
align: 'start',
sortable: false,
value: 'icon',
width: '20px'
},
{ text: 'Description', value: 'description', sortable: false, },
{ text: 'Uploaded', value: 'uploaded', width: '150px' },
],
rules: [
files => !files || !files.some(file => file.size > 20_097_152) || 'Each file cannot exceed 20mb',
],
rulesSingle: [
],
gdoc: [
(value) => !!value || "Required",
(value) => this.isURL(value) || "URL is not valid",
],
gdoc_description_rules: [
(value) => !!value || "Required",
],
files: [],
fileUploadButton: false,
deleteFileConfirmLoading: false,
fileUpload: false,
fileUploadProcess: false,
componentKey: 0,
postFormData: new FormData(),
editingFile: {
description: null,
index: null,
file_name_original: null,
},
deleteFileConfirm : false,
deleteFileLoading: false,
gdoc_link: null,
gdoc_description: null,
gdocDialog: false,
iframeHeight: 0,
aWidth: 0,
loading: true,
}
},
computed:{
orderedFiles: function () {
return _.orderBy(this.files, 'created_at', 'desc')
},
},
watch: {
files: {
immediate: false,
handler(){
if(this.files){
this.total = this.files.length;
this.$emit('totalFiles', this.total);
}else{
this.total = 0;
}
},
},
id: {
immediate: false,
handler(){
this.getFiles()
},
}
},
methods: {
async getFiles(){
this.loading = true;
await axios
.get('/app/files/getFiles?id=' + this.id + '&type=' + this.type)
.then(response => {
if (response.data.length) {
this.files = response.data;
this.$emit('totalFiles', this.files.length);
this.resize();
this.loading = false;
} else {
this.files = [];
this.$emit('totalFiles', 0);
this.loading = false;
}
})
.catch(error => {
this.majorError();
})
.finally()
},
onFilePicked(e) {
this.postFormData = new FormData();
if(this.$refs.files.validate()){
this.fileUploadButton = true;
}
this.postFormData = new FormData();
for(let key in event.target.files){
if(key >= 0){
this.postFormData.append( 'files[]', event.target.files[key]);
}
}
},
async uploadFiles(){
this.fileUploadProcess = true;
this.postFormData.append('type', this.type);
this.postFormData.append('id', this.id);
this.postFormData.append('gdoc', this.fileUploadGDoc);
this.postFormData.append('gdoc_link', this.gdoc_link);
this.postFormData.append('gdoc_description', this.gdoc_description);
const res = await this.callApi('post', '/app/files/uploadFiles', this.postFormData);
if(res.status===200){
this.componentKey++; //reset trick
this.snackbar(res.data.msg,res.data.type);
this.ok();
this.fileUploadProcess = false;
this.gdoc_link = null;
this.gdoc_description = null;
this.$refs.gdoc_link.reset()
this.$refs.gdoc_description.reset()
if(res.data.files){
for (const file of res.data.files) {
this.files.push(file);
}
}
this.resize();
this.fileUploadButton = false;
}else{
this.fileUploadProcess = false;
this.snackbar(res.data.msg, res.data.type);
}
},
inlineEditOpen (item) {
let obj = { ...item, editingIndex: this.files.indexOf(item) }
this.editingFile = obj;
},
async editFileInline(){
const file = Object.assign({}, this.editingFile); //Turn array into object for laravel
const res = await this.callApi('post', '/app/files/updateFile',
{file: file});
if(res.status===201){
this.files[this.editingFile.editingIndex].description = this.editingFile.description;
this.snackbar(this.editingFile.description + " has been edited successfully", 'success');
this.resize();
}else{
if(res.status===422){
for(let i in res.data.errors) {
this.snackbar(res.data.errors[i][0], 'error');
}
}else{
this.snackbar("There has been an error, we don't have any more information for you", 'error');
}
}
},
showDeleteDialog(file){
this.deleteFileConfirm = true;
let obj = { ...file, index: this.files.indexOf(file)}
this.editingFile= obj;
},
async deleteSet(){
this.deleteFileLoading = true;
const res = await this.callApi('post', '/app/files/deleteFile', this.editingFile);
if(res.status===200){
this.files.splice(this.editingFile.index, 1);
this.snackbar("File deleted successfully", 'success');
this.deleteFileConfirm = false;
}else{
if(res.status===422){
this.snackbar(res.data.msg, 'error');
}
}
this.deleteFileLoading = false;
},
gdocValidated(){
if(this.gdoc_link&&this.$refs.gdoc_link.validate()&&this.gdoc_description){
return true;
}
},
openGdoc(url){
window.open(url, '_blank').focus();
},
onResize() {
this.iframeHeight = window.innerHeight - 220;
if(this.showActions){
this.aWidth = this.$refs.aWidth.$el.clientWidth - 355;
this.bWidth = this.$refs.aWidth.$el.clientWidth - 150;
}else{
this.aWidth = this.$refs.aWidth.$el.clientWidth - 270;
this.bWidth = this.$refs.aWidth.$el.clientWidth - 65;
}
},
resize(){
setTimeout(() => window.dispatchEvent(new Event('resize')), 1);
},
},
async mounted(){
if(this.passFiles){
this.files = this.passedFiles;
//console.log(this.passedFiles,this.files)
this.loading = false;
}else{
this.getFiles();
}
this.onResize();
this.resize();
},
created(){
console.log(this.passFiles,this.passedFiles); //this.passedFiles shows as undefined
window.addEventListener("resize", this.onResize);
},
destroyed(){
window.removeEventListener("resize", this.onResize);
this.editingFile = null;
},
}
</script>
What am I missing here?
I using example from documentation datatable with crud options.
How to change "New" on-click function?
What is v-slot:activator="{ on, attrs }"?
I need to call api with vuex dispatch.
I want that before edit dialog is opened row will immediately created with ID parameter inside from api.
I added #click, to make call. Is it correct?
<template>
<v-data-table
:headers="headers"
:items="PIATConsignments"
:footer-props="footerProps"
class="elevation-1"
>
<template v-slot:top>
<v-toolbar
flat
>
<v-toolbar-title>Items</v-toolbar-title>
<v-divider
class="mx-4"
inset
vertical
></v-divider>
<v-spacer></v-spacer>
<v-dialog
v-model="dialogEdit"
scrollable
max-width="800px"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
color="primary"
dark
class="mb-2"
v-bind="attrs"
v-on="on"
#click="newItem"
>
New
</v-btn>
</template>
<v-card>
<v-card-title>
<span class="text-h5">{{ formTitle }}</span>
</v-card-title>
<v-card-text style="height: 600px;">
<v-container>
<v-row>
<v-col
cols="12"
sm="12"
md="6"
>
<v-autocomplete
v-model="editedItem.DepartureCountryDetails"
:items="countries"
item-text="ShortCountryName"
label="Departure"
class="input-group--focused"
v-bind:readonly="isReadonly"
return-object
></v-autocomplete>
</v-col>
<v-col
cols="12"
sm="12"
md="6"
>
<v-autocomplete
v-model="editedItem.DestinationCountryDetails"
:items="countries"
item-text="ShortCountryName"
label="Destination"
class="input-group--focused"
v-bind:readonly="isReadonly"
return-object
></v-autocomplete>
</v-col>
</v-row>
<v-row>
<v-col
cols="12"
sm="12"
md="6"
>
<v-text-field
v-model="editedItem.CAInvoiceValueAmount"
label="Amount"
></v-text-field>
</v-col>
<v-col
cols="12"
sm="12"
md="6"
>
<v-text-field
v-model="editedItem.UnifiedGrossMassMeasure"
label="Weight"
></v-text-field>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="blue darken-1"
text
#click="close"
>
Close
</v-btn>
<v-btn
color="blue darken-1"
text
#click="save"
>
Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="dialogDelete" max-width="500px">
<v-card>
<v-card-title class="text-h5">Do you want to delete item?</v-card-title>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click="closeDelete">Нет</v-btn>
<v-btn color="blue darken-1" text #click="deleteItemConfirm">Да</v-btn>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.actions="{ item }">
<v-icon
small
class="mr-2"
#click="editItem(item)"
>
mdi-pencil
</v-icon>
<v-icon
small
#click="deleteItem(item)"
>
mdi-delete
</v-icon>
</template>
</v-data-table>
</template>
<script>
import modelPIATConsignment from '#/models/form/consignment/piat/PIATConsignment'
import DepartureCountryDetails from './DepartureCountryDetails.vue'
export default {
name: 'PIATConsignmentDetailsTable',
components: {
DepartureCountryDetails,
},
props: {
consignmentForm: { Object },
},
data: function() {
return {
dialogEdit: false,
dialogDelete: false,
headers: [
{ text: 'Departure', value: 'DepartureCountryDetails.ShortCountryName' },
{ text: 'Destination', value: 'DestinationCountryDetails.ShortCountryName' },
{ text: 'Amount', value: 'CAInvoiceValueAmount' },
{ text: 'Weight', value: 'UnifiedGrossMassMeasure' },
{ text: 'Actions', value: 'actions' },
],
footerProps: {
'disable-items-per-page': true,
'items-per-page-options': [],
'items-per-page-all-text': '',
},
PIATConsignments: this.consignmentForm.PIATConsignmentDetails,
editedIndex: -1,
editedItem: modelPIATConsignment,
defaultItem: modelPIATConsignment,
}
},
computed: {
formTitle () { return this.editedIndex === -1 ? 'New' : 'Edit' },
countries() { return this.$store.getters['dict/countries'] },
},
watch: {
dialogEdit (val) { val || this.close() },
dialogDelete (val) { val || this.closeDelete() },
},
methods: {
editItem (item) {
this.editedIndex = this.PIATConsignments.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialogEdit = true
},
deleteItem (item) {
this.editedIndex = this.PIATConsignments.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialogDelete = true
},
deleteItemConfirm () {
this.PIATConsignments.splice(this.editedIndex, 1)
this.closeDelete()
},
close () {
this.dialogEdit = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
closeDelete () {
this.dialogDelete = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
save () {
if (this.editedIndex > -1) {
Object.assign(this.PIATConsignments[this.editedIndex], this.editedItem)
}
else {
this.PIATConsignments.push(this.editedItem)
}
this.close()
},
}
}
</script>
After hour trying.
Yes. Adding #click event on the button is right way.
After this just need manipulate with "current" row data.
methods: {
...
newItem() {
this.$store.dispatch('our-dispatch-method')
.then(res => {
res.id // For example we created element in our api and response is new ID.
// Now push to the table item list editedItem and set the index
this.PIATConsignments.push(this.editedItem)
this.editedIndex = this.PIATConsignments.indexOf(this.editedItem)
// Set your ID from api inside editedItem
this.editedItem.id = res.id;
})
}
...
}
I'm using vuetify datatable and I fill the table with API- GET Request via Axios.
The problem is when I click Edit Button on the right the placeholder of the textboxes overlap with the selected rows' values.
The issue can be easily understandable from the picture.
Also, when I delete the default headers i.e., (Carbs (g) and Protein (g)) from the code, the actions column is disappearing.. (Picture 2)
Finally, here is the City.vue
<template>
<v-data-table
:headers="headers"
:items="cities"
sort-by="Id"
class="elevation-1"
>
<template v-slot:top>
<v-toolbar
flat
>
<v-toolbar-title>CITY CRUD</v-toolbar-title>
<v-divider
class="mx-4"
inset
vertical
></v-divider>
<v-spacer></v-spacer>
<v-dialog
v-model="dialog"
max-width="500px"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
color="primary"
dark
class="mb-2"
v-bind="attrs"
v-on="on"
>
New City
</v-btn>
</template>
<v-card>
<v-card-title>
<span class="text-h5">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col
cols="12"
sm="6"
md="4"
>
<v-text-field
v-model="editedItem.Id"
label="Id"
></v-text-field>
</v-col>
<v-col
cols="12"
sm="6"
md="4"
>
<v-text-field
v-model="editedItem.Text"
label="Text"
></v-text-field>
</v-col>
<v-col
cols="12"
sm="6"
md="4"
>
<v-text-field
v-model="editedItem.Description"
label="Description"
></v-text-field>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="blue darken-1"
text
#click="close"
>
Cancel
</v-btn>
<v-btn
color="blue darken-1"
text
#click="save"
>
Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="dialogDelete" max-width="500px">
<v-card>
<v-card-title class="text-h5">Are you sure you want to delete this item?</v-card-title>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click="closeDelete">Cancel</v-btn>
<v-btn color="blue darken-1" text #click="deleteItemConfirm">OK</v-btn>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:[`item.actions`]="{ item }">
<v-icon
small
class="mr-2"
#click="editItem(item)"
>
mdi-pencil
</v-icon>
<v-icon
small
#click="deleteItem(item)"
>
mdi-delete
</v-icon>
</template>
<template v-slot:no-data>
<v-btn
color="primary"
#click="initialize"
>
Reset
</v-btn>
</template>
</v-data-table>
</template>
<script>
import axios from 'axios'
export default {
data: () => ({
dialog: false,
dialogDelete: false,
headers: [
{
text: 'Id',
align: 'start',
sortable: false,
value: 'Id',
},
{ text: 'Text', value: 'Text' },
{ text: 'Description', value: 'Description' },
{ text: 'Carbs (g)', value: 'carbs' },
{ text: 'Protein (g)', value: 'protein' },
{ text: 'Actions', value: 'actions', sortable: false },
],
cities: [],
editedIndex: -1,
editedItem: {
Id: 0,
Text: '',
Description: '',
},
defaultItem: {
Id: 0,
Text: '',
Description: '',
},
}),
watch: {
dialog (val) {
val || this.close()
},
dialogDelete (val) {
val || this.closeDelete()
},
},
created () {
this.initialize()
},
methods: {
initialize () {
axios.get("https://localhost:44377/city")
.then((response)=>{
this.cities=response.data;
});
},
editItem (item) {
this.editedIndex = this.cities.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialog = true
},
deleteItem (item) {
this.editedIndex = this.cities.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialogDelete = true
},
deleteItemConfirm () {
this.cities.splice(this.editedIndex, 1)
this.closeDelete()
},
close () {
this.dialog = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
closeDelete () {
this.dialogDelete = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
save () {
if (this.editedIndex > -1) {
Object.assign(this.cities[this.editedIndex], this.editedItem)
} else {
this.cities.push(this.editedItem)
}
this.close()
},
},
}
</script>
I would like to kindly ask you about Vuetify and its Data Table. I am using Vuex for state managing. When I open the page Vuex loads all the data from API. It works perfectly. Then I have the Data Table which also works (update, add, delete), but only one column does not... Data Table is filled with GET_CENTERS and the "one column" is an object (center.customer). When I change center.customer.name state is updated, database is updated, but the value in Data Table is not. (I have tried to just create a list of customers on same page and this list changes if :key is set).
CentersTable
<template>
<div>
<v-data-table
:headers="headers"
:items="GET_CENTERS"
sort-by="name"
class="elevation-1"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>My CRUD</v-toolbar-title>
<v-divider
class="mx-4"
inset
vertical
></v-divider>
<div class="flex-grow-1"></div>
<v-dialog v-model="dialog" max-width="500px">
<template v-slot:activator="{ on }">
<v-btn color="primary" dark class="mb-2" v-on="on">Nový Záznam</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="6">
<v-text-field v-model="editedItem.name" label="Název"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="6">
<v-text-field v-model="editedItem.address" label="Adresa"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="6">
<v-text-field v-model="editedItem.city" label="Město"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="6">
<v-text-field v-model="editedItem.zip" label="PSČ"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="6">
<v-text-field v-model="editedItem.email" label="Email"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="6">
<v-text-field v-model="editedItem.phone" label="Telefon"></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="12">
<v-combobox
v-model="editedItem.customer"
:items="GET_CUSTOMERS"
:search-input.sync="search"
item-text="name"
item-value="_id"
:hide-selected="hideSelected"
label="Zákazník"
persistent-hint
:clearable="clearable"
return-object
>
<template v-if="noData" v-slot:no-data>
<v-list-item>
<v-list-item-content>
<v-list-item-title>
No results matching "<strong>{{ search }}</strong>". Press <kbd>enter</kbd> to create a new one
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
</v-combobox>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<div class="flex-grow-1"></div>
<v-btn color="blue darken-1" text #click="close">Cancel</v-btn>
<v-btn color="blue darken-1" text #click="save">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.action="{ item }">
<v-icon
small
class="mr-2"
#click="editItem(item)"
>
edit
</v-icon>
<v-icon
small
#click="deleteItem(item)"
>
delete
</v-icon>
</template>
</v-data-table>
<template>
<v-list-item v-for="item in GET_CUSTOMERS" :key="item._id">
<v-list-item-content>{{item}}</v-list-item-content>
</v-list-item>
</template>
</div>
</template>
<script>
import {mapActions, mapGetters} from 'vuex'
export default {
name: "CentersTable",
data: () => ({
dialog: false,
editedItem: {},
headers: [
{
text: 'Název',
align: 'left',
sortable: true,
value: 'name',
},
{
text: 'Adresa',
sortable: true,
value: 'address',
},
{
text: 'Město',
sortable: true,
value: 'city',
},
{
text: 'PSČ',
sortable: true,
value: 'zip',
},
{
text: 'Email',
sortable: true,
value: 'email',
},
{
text: 'Telefon',
sortable: true,
value: 'phone',
},
{
text: 'Zákazník',
sortable: true,
value: 'customer.name',
},
{ text: 'Akce', value: 'action', sortable: false, align: 'right' },
],
search: null,
chips: true,
multiple: true,
hideSelected: true,
noData: true,
clearable: false,
}),
watch: {
dialog (val) {
val || this.close()
},
},
computed: {
formTitle() {
return this.editedItem._id === undefined ? 'Nový Záznam' : 'Upravit Záznam'
},
...mapGetters([
'GET_CUSTOMERS',
'GET_CENTERS'
]),
},
methods: {
...mapActions([
'createCenter',
'updateCenter',
'deleteCenter',
]),
editItem (item) {
this.editedItem = Object.assign({}, item)
this.dialog = true
},
deleteItem (item) {
confirm('Opravdu chcete smazat tento záznam?') && this.deleteCenter(item).then(() => {
this.$noty.success('Záznam byl smazán')
}).catch(() => {
this.$noty.alert('Při mazání záznamu došlo k chybě')
})
},
close () {
this.dialog = false
this.editedItem = {}
setTimeout(() => {
}, 300)
},
save () {
if (this.editedItem._id === undefined) {
this.createCenter(this.editedItem).then(() => {
this.$noty.success('Nový zákazník byl vytvořen')
}).catch(() => {
this.$noty.alert('Při vytváření zákazníka došlo k chybě')
})
} else {
this.updateCenter(this.editedItem).then(() => {
this.$noty.success('Záznam byl upraven')
}).catch(() => {
this.$noty.alert('Při ukládání záznamu došlo k chybě')
})
}
this.close()
},
}
}
</script>
<style scoped>
</style>
And Vuex store
import axios from "axios";
import Vue from "vue";
const state = {
customers: []
};
const mutations = {
SET_CUSTOMERS(state, payload) {
state.customers = payload;
},
UPDATE_CUSTOMER(state, payload) {
const customer = state.customers.findIndex(x => x._id === payload._id);
Vue.set(state.customers, customer, payload);
},
ADD_CUSTOMER(state, payload) {
state.customers.push(payload);
},
REMOVE_CUSTOMER(state, payload) {
const customer = state.customers
.map(customer => customer._id)
.indexOf(payload);
state.customers.splice(customer, 1);
}
};
const actions = {
loadCustomers({ commit }) {
axios.get("/api/customers").then(data => {
commit("SET_CUSTOMERS", data.data);
});
},
updateCustomer({ commit }, payload) {
axios.put(`/api/customers/${payload._id}`, payload).then(response => {
commit("UPDATE_CUSTOMER", response.data);
});
},
createCustomer({ commit }, payload) {
axios.post("/api/customers", payload).then(data => {
commit("ADD_CUSTOMER", data.data);
});
},
deleteCustomer({ commit }, payload) {
axios.delete(`/api/customers/${payload._id}`).then(() => {
commit("REMOVE_CUSTOMER", payload);
});
}
};
const getters = {
GET_CUSTOMERS: state => state.customers
};
export default {
state,
mutations,
actions,
getters
};