Related
I have a Request Form Component, and within this request form Component I have a Dropdown Menu Component, which I will link both below. All values in my table are pushed into an object upon hitting the Submit Button. However my dropdown selection is only being picked up by my console.log and not being pushed into the Object.
I'm not so familiar with Vue, so I'm not sure what direction to go in for fixing this. I'll attach the relevant (?) pieces of code below.
Parent Component:
<SelectComponent :selected="this.selected" #change="updateSelectedValue" />
export default {
fullScreen: true,
name: 'CcRequestForm',
mixins: [BaseForm],
name: "App",
components: {
SelectComponent,
},
data() {
return {
selected: "A",
};
},
props: {
modelName: {
default: 'CcRequest',
},
parentId: {
type: Number,
default: null,
},
},
mounted() {
this.formFields.requester.value = this.currentRequesterSlug;
},
destroyed() {
if (!this.modelId) return;
let request = this.currentCcRequest;
request.params = request.params.filter(p => p.id)
},
computed: {
...mapGetters(['ccTypesForRequests', 'currentRequesterSlug', 'currentCcRequest']),
ccTypesCollection() {
return this.ccTypesForRequests.map((x)=>[x.slug, this.t(`cc_types.${x.slug}`)]);
}
},
methods: {
addParam() {
this.addFormFields(['params'], {
slug: '',
name: '',
isRequired: true,
description: '',
typeSlug: '',
selected: ''
});
},
deleteParam(idx){
this.removeFormFields(['params', idx]);
},
restoreParam(idx){
this.restoreFormFields(['params', idx])
},
$newObject() {
return {
slug: '',
name: '',
isAbstract: false,
requester: '',
description: '',
status: 'inactive',
params: [],
selected: ''
};
},
$extraPrams() {
return {
parentId: this.parentId,
};
},
updateSelectedValue: function (newValue) {
this.selected = newValue;
},
},
watch: {
selected: function (val) {
console.log("value changed", val);
},
},
};
Child Component:
<script>
export default {
name: "SelectComponent",
props: {
selected: String,
},
computed: {
mutableItem: {
get: function () {
return this.selected;
},
set: function (newValue) {
this.$emit("change", newValue);
},
},
},
};
You have to define the emit property in the parent component, or else it won't know what to expect. That would look like:
<SelectComponent :selected="this.selected" #update-selected-value="updateSelectedValue" />
Check out this tutorial for more information: https://www.telerik.com/blogs/how-to-emit-data-in-vue-beyond-the-vuejs-documentation
To update selected property inside the object, in this constellation, you need to update object property manually upon receiving an event, inside of updateSelectedValue method. Other way could be creating a computed property, since it's reactive, wrapping "selected" property.
computed: {
selectedValue () {
return this.selected
}
}
And inside of object, use selectedValue instead of selected:
return {
...
selected: selectedValue
}
I am trying to build a Vuetify CRUD using AXIOS. I am able to do CRUD operations, but am not able to display the newly added data till the page is refreshed. When I select edit, I am not able to see the existing data while editing. The editable dialog box shows empty.
Please help me if to resolve this.
Here is my script
import Navbar from '../components/Navbar.vue'
import { getAPI } from '../axios-api'
// import DataService from "../services/DataService";
export default {
data: () => ({
dialog: false,
dialogDelete: false,
headers: [
{ text: 'ID', align: 'start', value: 'id',},
{ text: 'Length', value: 'size' },
{ text: 'Weight', value: 'weight' },
{ text: 'Actions', value: 'actions', sortable: false },
],
info: [],
editedIndex: -1,
editedItem: {
ID: '',
Weight: 0,
Length: 0,
},
defaultItem: {
name: '',
Weight: 0,
Length: 0,
},
}),
components:{
Navbar
},
computed: {
formTitle () {
return this.editedIndex === -1 ? 'New Item' : 'Edit Item'
},
},
methods: {
initialize () {
getAPI.get('calibration/sizeweight')
.then(response => {
this.info = response.data
})
},
editItem (item) {
this.editedIndex = this.info.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialog = true
},
deleteItem (item) {
this.editedIndex = this.info.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialogDelete = true
},
deleteItemConfirm () {
this.info.splice(this.editedIndex, 1)
this.closeDelete()
getAPI.delete('calibration/sizeweight/'+this.editedItem.id)
.then(response=>{
console.log(response)
})
},
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.info[this.editedIndex], this.editedItem)
getAPI.put('calibration/sizeweight/'+this.editedItem.ID,{id:this.editedItem.ID ,weight:this.editedItem.Weight,size:this.editedItem.Length})
.then(response=>{
console.log(response)
this.initialize()
})
} else {
this.info.push(this.editedItem)
getAPI.post('calibration/sizeweight',{id:this.editedItem.ID ,weight:this.editedItem.Weight,size:this.editedItem.Length})
.then(response=>{
console.log(response)
this.initialize()
})
.catch(error => { console.log(error.response.data)})
}
this.close()
},
},
watch: {
dialog (val) {
val || this.close()
},
dialogDelete (val) {
val || this.closeDelete()
},
},
created () {
this.initialize()
},
}
</script>
I have a postAsset action in my vuex store like so
async postAsset({dispatch}, asset) {
const f = await dispatch('srcToFile', asset);
asset[0].files.fileList = f;
const fileData = asset[0].files.fileList;
const detailData = asset[0].detail;
const fData = new FormData();
fData.append('Name', asset[0].name);
Object.keys(detailData).forEach((key) => {
fData.append(`Detail.${key}`, detailData[key]);
});
for (var i = 0; i < fileData.length; i++) {
fData.append('Files', fileData[i]);
}
await axios({
method: 'post',
url: 'https://localhost:5001/api/Assets',
data: fData,
headers: {
'Content-Type': undefined
}
})
.then(function(response) {
console.warn(response);
})
.catch(function(response) {
console.warn(response);
});
}
It is successfully posting to my api backend and to the database.
The issue that I am running into is that after I make the first post it posts the previous data and the new data I do not know why it is doing this. I did add await to the axios call but that just slowed it down it is still posting two times after the first and im sure if i keep posting it will continue to post the previous ones into the db again and again. Im at a loss as to what is going on so reaching out for some assistance to see if I can get this resolved.
examples of what it looks like in the db
does anyone have any advice for me so I can get this fixed? I should only be getting one item posted at a time that is the desired result. I have gone through my inputs and put in .prevent to stop them from clicking twice but I don't think it is that .. this is like it is saving the data and reposting it all at once each time I add a new record .
UPDATE:
the code that calls the action
populateAssets ({ dispatch }, asset) {
return new Promise((resolve) => {
assets.forEach((asset) => {
commit('createAsset', asset);
);
dispatch('postAsset', asset);
resolve(true);
});
},
the populate assets populates a list with a completed asset.
and asset is coming from the srcToFile method
that converts the files to a blob that I can post with
async srcToFile(context, asset) {
const files = asset[0].files.fileList;
let pmsArray = [];
for (let f = 0; f < files.length; f++) {
var data = files[f].data;
let name = files[f].name;
let mimeType = files[f].type;
await fetch(data)
.then(function(res) {
const r = res.arrayBuffer();
console.warn('resource ', r);
return r;
})
.then(function(buf) {
console.warn('buffer: ', [buf]);
let file = new File([buf], name, { type: mimeType });
pmsArray.push(file);
});
}
console.warn(pmsArray);
return pmsArray;
},
asset is an array from my add asset component
structure of asset
name: '',
detail: {
category: '',
manufacturer: '',
model: '',
serialNumber: '',
purchasePlace: '',
quantity: 1,
acquiredDate: '',
purchasePrice: '',
currentValue: '',
condition: '',
assetLocation: '',
retiredDate: '',
description: ''
},
files: {
fileList: []
}
hope this helps out some
the whole store file
import Vue from 'vue'
import Vuex from 'vuex'
import { states } from '../components/enums/enums'
import { getField, updateField } from 'vuex-map-fields'
import axios from 'axios'
Vue.use(Vuex);
const inventory = {
namespaced: true,
strict: true,
state: {
assets: {
items: []
},
categories: [],
manufacturers: [],
assetLocations: [],
conditions: ['New', 'Fair', 'Good', 'Poor']
},
getters: {
assetItems: state => state.assets.items,
getAssetById: (state) => (id) => {
return state.assets.items.find(i => i.id === id);
},
conditions: (state) => state.conditions,
categories: (state) => state.categories,
manufacturers: (state) => state.manufacturers,
assetLocations: (state) => state.assetLocations
},
mutations: {
createAsset (state, assets) {
state.assets.items.push(assets);
},
createCategories (state, category) {
state.categories.push(category);
},
createManufacturers (state, manufacturer) {
state.manufacturers.push(manufacturer);
},
createLocations (state, locations) {
state.assetLocations.push(locations);
}
},
actions: {
addToCategories ({ commit }, categories) {
commit('createCategories', categories);
},
addToManufacturers ({ commit }, manufacturers) {
commit('createManufacturers', manufacturers);
},
addToLocations ({ commit }, locations) {
commit('createLocations', locations);
},
populateAssets ({ dispatch }, asset) {
//return new Promise((resolve) => {
// assets.forEach((asset) => {
// commit('createAsset', asset);
// });
dispatch('postAsset', asset);
// resolve(true);
//});
},
addAsset ({ dispatch, /*getters*/ }, newAsset) {
//let assetCount = getters.assetItems.length;
//newAsset.id = assetCount === 0
// ? 1
// : assetCount++;
dispatch('populateAssets', [newAsset]);
},
async srcToFile(context, asset) {
const files = asset[0].files.fileList;
let pmsArray = [];
for (let f = 0; f < files.length; f++) {
var data = files[f].data;
let name = files[f].name;
let mimeType = files[f].type;
await fetch(data)
.then(function(res) {
const r = res.arrayBuffer();
console.warn('resource ', r);
return r;
})
.then(function(buf) {
console.warn('buffer: ', [buf]);
let file = new File([buf], name, { type: mimeType });
pmsArray.push(file);
});
}
console.warn(pmsArray);
return pmsArray;
},
async postAsset({ dispatch }, asset) {
const f = await dispatch('srcToFile', asset);
asset[0].files.fileList = f;
const fileData = asset[0].files.fileList;
const detailData = asset[0].detail;
const fData = new FormData();
fData.append('Name', asset[0].name);
Object.keys(detailData).forEach((key) => {
fData.append(`Detail.${key}`, detailData[key]);
});
for (var i = 0; i < fileData.length; i++) {
fData.append('Files', fileData[i]);
}
await axios({
method: 'post',
url: 'https://localhost:5001/api/Assets',
data: fData,
headers: { 'Content-Type': undefined }
})
.then(function(response) {
console.warn(response);
})
.catch(function(response) {
console.warn(response);
});
}
}
};
const maintenance = {
state: {
backup: []
},
strict: true,
getters: {},
mutations: {},
actions: {}
};
const assetProcessing = {
namespaced: true,
state: {
currentAsset: {
id: 0,
name: '',
detail: {
category: '',
manufacturer: '',
model: '',
serialNumber: '',
purchasePlace: '',
quantity: 1,
acquiredDate: '',
purchasePrice: '',
currentValue: '',
condition: '',
assetLocation: '',
retiredDate: '',
description: ''
},
files: {
fileList: []
}
},
filePosition: -1,
selectedItem: -1,
state: states.view,
isNewAsset: false
},
getters: {
getField,
getOpenAsset (state) {
return state.currentAsset
},
getSelectedAsset: (state, getters, rootState, rootGetters) => (id) => {
if (state.isNewAsset) return state.currentAsset
Object.assign(state.currentAsset, JSON.parse(JSON.stringify(rootGetters['inventory/getAssetById'](!id ? 0 : id))));
return state.currentAsset
},
appState: (state) => state.state,
getCurrentPosition (state) {
return state.filePosition
},
selectedAssetId: (state) => state.selectedItem
},
mutations: {
updateField,
setAsset (state, asset) {
Object.assign(state.currentAsset, asset)
},
setFiles (state, files) {
Object.assign(state.currentAsset.files, files)
},
newAsset (state) {
Object.assign(state.isNewAsset, true)
Object.assign(state.currentAsset, {
id: 0,
name: '',
detail: {
category: '',
manufacturer: '',
model: '',
serialNumber: '',
purchasePlace: '',
quantity: 1,
acquiredDate: '',
purchasePrice: '',
currentValue: '',
condition: '',
assetLocation: '',
retiredDate: '',
description: ''
},
files: {
fileList: []
}
})
},
updateSelectedItem (state, id) {
Vue.set(state, 'selectedItem', id);
},
updateState (state, newState) {
Vue.set(state, 'state', newState);
}
},
actions: {}
};
export const store = new Vuex.Store({
modules: {
inventory: inventory,
maintenance: maintenance,
assetProcessing
}
})
add asset is called when the user clicks the save button on the form
addAsset () {
this.$store.dispatch('inventory/addAsset', this.newAsset) <--- this calls add asset
this.$store.commit('assetProcessing/updateState', states.view);<-- this closes the window
},
So after much debugging we found that the eventbus was firing multiple times causing the excessive posting we added
beforeDestroy() {
eventBus.$off('passAssetToBeSaved');
eventBus.$off('updateAddActionBar');
},
to the AssetAdd.vue component and it eliminated the excessive posting of the asset.
I want to thank #phil for helping me out in this.
I'm having a weirdo syntax problem with declaring deep:true watchers on vuejs:
This is working fine, both watchers triggers when expected and so one:
watch: {
'focus': function () {
this.visible = this.focus.map(bool => bool)
},deep:true,
'map': function (newval, oldval) {
Vue.set(this.nmap, oldval, !this.nmap[oldval])
Vue.set(this.nmap, newval, !this.nmap[newval])
},
},
but if I try to add a new watcher with deep: true properties it outputs an error that says: Duplicate key 'deep'
watch: {
'focus': function () {
this.visible = this.focus.map(bool => bool)
},deep:true,
'map': function (newval, oldval) {
Vue.set(this.nmap, oldval, !this.nmap[oldval])
Vue.set(this.nmap, newval, !this.nmap[newval])
},
'selected': function (newval, oldval) {
console.log('Old val:')
console.log(oldval)
console.log('New val:')
console.log(newval)
},deep:true,
},
How I'm supposed to declare this new watcher over this array?
So from what you tell us, I assume that focus, map, and selected are data objects that might get changed. The following code would execute the function in handler once there is a change:
watch: {
focus: {
handler: function() {
this.visible = this.focus.map(bool => bool)
}, deep: true
},
map: function(newval, oldval) {
Vue.set(this.nmap, oldval, !this.nmap[oldval])
Vue.set(this.nmap, newval, !this.nmap[newval])
},
selected: {
handler: function(newval, oldval) {
console.log('Old val:')
console.log(oldval)
console.log('New val:')
console.log(newval)
}, deep: true,
}
}
Source: VueJS Docs
I noticed a bug with my vuetify server-side data table. When a data table contains no data and then I attempt to add a new one, it will be successful but it is not displayed in the table, I even checked my database. After I hit the refresh button of the browser, it now shows.
But when a data already exist (even just one), the newly added data automatically reflects in the table. I don't know what is causing this behavior and it only happens when there is no data. I think it has something to do with an empty table being rendered.
Template
<v-data-table
:headers="headers"
:items="tableData"
:editItem="this.editItem"
:deleteItem="this.deleteItem"
:options.sync="pagination"
:server-items-length="totalData"
:loading="loading"
dense
>
Script
export default {
data() {
return {
editedIndex: -1,
search: "",
loading: true,
pagination: {},
dialog: false,
valid: false,
validationErrors: '',
tableData: [],
totalData: 0,
departments: [],
tableItem: {
name: '',
departmend_id: '',
},
snackbar: {
enable: false,
name: '',
message: '',
},
headers: [
{ text: 'ID', value: 'id' },
{ text: 'Department Name', value: 'name' },
{ text: 'From Department', value: 'department' },
{ text: 'Created At', value: 'created_at' },
{ text: 'Action', value: 'action' },
],
rules: {
name: [
v => !!v || 'This field is required',
v => (v && v.length <= 50) || 'Field must be less than 50 characters'
]
}
}
},
watch: {
params: {
handler() {
this.getDataFromApi().then(data => {
this.tableData = data.items
this.totalData = data.total
})
},
deep: true
}
},
computed: {
params(nv) {
return {
...this.pagination,
query: this.search
}
},
formTitle() {
return this.editedIndex === -1 ? 'New Section' : this.tableItem.name
},
},
mounted() {
this.getDataFromApi().then(data => {
this.tableData = data.items
this.totalData = data.total
})
},
created() {
this.getDepartments()
},
methods: {
async getDataFromApi() {
this.loading = true
return new Promise((resolve, reject) => {
const { sortBy, sortDesc, page, itemsPerPage } = this.pagination
let search = this.search.trim().toLowerCase()
axios.get('/api/sections').then(res => {
let items = _.orderBy(res.data, ['created_at'], ['desc'])
const total = items.length
if (search) {
items = items.filter(item => {
return Object.values(item).join(",").toLowerCase().includes(search)
})
}
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(() => {
this.loading = false
resolve({
items,
total
})
}, 300)
})
})
},
async initialize() {
this.loading = true
let res = await axios.get('/api/sections')
this.tableData = _.orderBy(res.data, ['created_at'], ['desc'])
setTimeout(() => {
this.loading = false
}, 300)
},
async getDepartments() {
let res = await axios.get('/api/departments')
this.departments = _.orderBy(res.data, ['name'], ['asc'])
},
reset() {
this.$refs.form.reset()
},
close() {
this.dialog = false
this.$refs.form.reset()
setTimeout(() => {
this.editedIndex = -1
}, 300)
},
editItem(item) {
this.editedIndex = this.tableData.indexOf(item)
this.tableItem = Object.assign({}, item)
this.dialog = true
},
updateSnackbar(e) {
this.snackbar.enable = e
},
async deleteItem(item) {
let index = this.tableData.indexOf(item)
if(confirm('Are you sure you want to delete this item?') && this.tableData.splice(index, 1)) {
let res = await axios.delete('/api/sections/' + item.id)
this.snackbar.name = ''
this.snackbar.message = res.data.message
this.snackbar.enable = true
}
},
async save() {
if (this.editedIndex > -1) {
try {
Object.assign(this.tableData[this.editedIndex], this.tableItem)
let res = await axios.put('/api/sections/' + this.tableItem.id, this.tableItem)
this.snackbar.name = res.data.name
this.snackbar.message = res.data.message
} catch(error) {
if (error.response.status == 422){
this.validationErrors = error.response.data.errors
}
}
} else {
try {
let res = await axios.post('/api/sections', this.tableItem)
this.snackbar.name = res.data.name
this.snackbar.message = res.data.message
} catch(error) {
if (error.response.status == 422){
this.validationErrors = error.response.data.errors
}
}
}
await this.initialize()
this.snackbar.enable = true
this.close()
this.reset()
},
}
}
So after countless of trials, I have finally found the solution.
try {
let res = await axios.post('/api/departments', this.tableItem)
this.getDataFromApi().then(data => {
this.tableData = data.items
this.totalData = data.total
})
}