After Vue.delete or $delete UI is not updating - vue.js

this is my first question on stackoverflow.
So, I try to delete a item from array, I see, that in Vue Dev Tools it was deleted, but UI not updating.
I become this array as response from Laravel API and send dynamic to Vue Component like this
...
<admin-panel :jurisdictions="{{ $jurisdictions }}"></admin-panel>
...
then in my AdminComponent I redirect to AdminHomeComponent with props like this
<template>
<router-view :jurisdictions="jurisdictions"></router-view>
</template>
...
props: ['jurisdictions'],
...
created() {
this.$router.push({ name: "AdminHomeComponent" }).catch(err => {});
},
...
In AdminHomeComponent I have props too and router link to another component JurisdictionsComponent like this
<template>
...
<router-link :to="{name: 'JurisdictionsComponent'}"> Jurisdictions</router-link>
...
</template>
<script>
...
props: ["jurisdictions"]
...
</script>
And then will fun, wenn in JurisdictionsComponent I add a new one, or editing old one it works, there are reactive, but if I try to delete one, it still be reactive and I see this in Vue Dev Tools, but I cann't unterstand, why UI not updating..
JurisdictionsComponent
<template>
<div class="w-100">
<div id="jurisdictionsContainer" ref="jurisdictionsContainer">
<div class="panel-heading d-flex justify-content-between">
<h3 class="panel-title">Jurisdictions</h3>
<div class="pull-right">
<button #click.prevent="$modal.show('create-edit-jurisdiction', {'action' : 'create'})">
<i class="fas fa-plus-square"/> Create new
</button>
</div>
</div>
<table class="table table-hover mt-2 rounded" id="jurisdictions-table">
<thead class="thead-dark ">
<tr>
<th>Title</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="jurisdictions-table-body">
here I make v-for
<tr v-if="jurisdictions !== null" v-for="(jurisdiction, index) in this.jurisdictions" v-bind:key="jurisdiction.id"
class="result clickable-row"
#click="show($event, jurisdiction)">
<td class="title">
{{ jurisdiction.title }}
</td>
<td class="position-relative">
<button #click="$modal.show('create-edit-jurisdiction', {'jurisdiction': jurisdiction, 'index': index, 'action' : 'edit'})">
<div class="not-clickable">Edit</div>
here a show a delete modal window, use can deside delete or not, my code from ModalDeleteComponent see below
</button>
<button #click="$modal.show('delete-jurisdiction', {'jurisdiction': jurisdiction, 'index': index})">
<div class="not-clickable">Delete</div>
<i class="fas fa-trash-alt not-clickable"/>
</button>
</tr>
</tbody>
<delete-jurisdiction #onDeleted="onClickDelete"/>
<create-edit-jurisdiction #create="onClickCreate" #edit="onClickEdit":errors="this.errors.createEdit"/>
</div>
</div>
</template>
<script>
export default {
name: "JurisdictionsComponent",
props: ["jurisdictions"],
data() {
return {
isAllSelected: false,
errors: {
createEdit: {},
addEvent: {}
},
}
},
methods: {
/**
* Create a new jurisdiction
*
* #param data form
*/
onClickCreate(data) {
axios.post("/admin-dashboard/jurisdictions", data.form)
.then(response => {
response.data.image === undefined ? response.data.image = null : response.data.image;
response.data.selected = false;
this.jurisdictions.push(response.data);
this.$modal.hide("create-edit-jurisdiction");
this.errors.createEdit = {}
})
.catch(errors => {
this.errors.createEdit = errors.response.data.errors;
});
Here a try to delete jurisdiction, it deletes from database, from props in Vue Dev Tools but not from UI
/**
* Delete jurisdiction request
*
* #param index
*/
onClickDelete(index) {
axios.delete("/admin-dashboard/jurisdictions/" + this.jurisdictions[index].id)
.then(() => {
this.$delete(this.jurisdictions, index);
this.$modal.hide("delete-jurisdiction");
})
.catch(errors => {
console.log(errors)
});
},
/**
* Edit a jurisdiction
*
* #param data form
*/
onClickEdit(data) {
axios.patch(this.jurisdictions[data.index].path, data.form)
.then(response => {
this.$set(this.jurisdictions, data.index, response.data);
this.$modal.hide("create-edit-jurisdiction");
this.errors.createEdit = {}
})
.catch(errors => {
this.errors.createEdit = errors.response.data.errors;
})
},
}
</script>
ModalDeleteComponent
<template>
<modal name="delete-jurisdiction" #before-open="beforeOpen" height="200" #before-close="beforeClose">
<div class="h-100">
<div v-if="jurisdiction !== null" class="p-4 mt-2">
<h3>Do you want really delete
<a :href="'/admin-dashboard/jurisdictions/'+jurisdiction.id"><strong>{{ jurisdiction.title }}</strong></a>
<span v-if="jurisdiction.events.length > 0">
with {{ jurisdiction.events.length }} {{ jurisdiction.events.length === 1 ? 'event' : "events"}}
</span>?
</h3>
</div>
<div class="bg-dark d-flex justify-content-around p-2 position-absolute w-100" style="bottom: 0">
<button class="btn btn-danger" #click="submitDelete">Delete</button>
<button class="btn btn-secondary" #click="$modal.hide('delete-jurisdiction')">Cancel</button>
</div>
</div>
</modal>
</template>
<script>
export default {
name: "ModalDeleteJurisdictionComponent",
data() {
return {
jurisdiction: null,
index: ""
}
},
methods: {
submitDelete() {
this.$emit('onDeleted', this.index);
},
beforeOpen (event) {
this.jurisdiction = event.params.jurisdiction;
this.index = event.params.index;
},
beforeClose(event) {
this.jurisdiction = null;
this.index = "";
}
}
}
</script>
I know, my question is too long, but if anyone tries to answer this, I will very happy))
I'm open to any contra questions. Sorry for my English

So, thanks oshell for a tipp. Ich have renamed in jurisdictions to dataJurisdictions and init in created() {this.dataJurisdictions = this.jurisdictions} as well. First of all I want to avoid duplication of data in components and work only with props, but nevertheless it works. Thanks a lot!

You are adding to jurisdictions, which is a prop.
this.jurisdictions.push(response.data);
However, you should either update the prop in the parent component, to trigger a prop change and re-render or assign the prop to the components data as initial value and then update data.
Changing prop in parent component can be done using $emit or by using Vuex.
Assigning data locally just needs a different value name.
this.localJurisdictions = this.jurisdictions
And for updating then use this new data value. (Use accordingly in template.)
this.localJurisdictions.push(response.data);

Related

Show button according to v-if condition in vue.js

I have two buttons in a page. One is Create phase and the other is Edit Phase.
I want to hide one button when the other button is available at same place. I have written my code like below:
<template>
<button
v-if="phaseData.phase_name == ''"
class="btn btn-primary"
>
Create Phase
</button>
<button v-else>Edit Phase</button>
</template>
<script>
import pagination from "../common/Pagination";
import Multiselect from "vue-multiselect";
import axios from "axios";
export default {
components: {
pagination,
Multiselect,
},
data() {
return {
phaseData: [],
showPhase: "",
};
},
methods:{
editProject: function (id) {
//const_this = this;
this.showPhaseModal = true;
this.axios
.get("http://45.114.85.18:8099/api/project/phase?project_id=" + id, {
headers: {
Authorization: "Bearer " + localStorage.getItem("access_token"),
},
})
.then((res) => {
this.phaseData = res.data.data;
this.showPhase = res.data.data[0];
});
},
}
But only the edit phase button is showing. The create phase button is not showing according to the condition. The backend is using node.js.
Updates:
<td class="text-center">
<div v-for="(data, index) in phaseData" :key="index">
<button
v-if="data[0].phase_name == ''"
class="btn btn-primary"
>
Create Phase
</button>
<button v-else>Edit Phase</button>
</div>
new Vue({
el: '#demo',
data() {
return {
phaseData: [{phase_name: 'aaa'}, {phase_name: ''}, {phase_name: 'bbb'}]
}
}
})
Vue.config.productionTip = false
Vue.config.devtools = false
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<table>
<tr v-for="(data, index) in phaseData" :key="index">
<td class="text-center" >
<p>{{data.phase_name}}</p>
<div>
<button
v-if="!data.phase_name"
class="btn btn-primary"
>
Create Phase
</button>
<button v-else>Edit Phase</button>
</div>
</td>
</tr>
</table>
</div>
this.phaseData is array , so you can try v-if="phaseData[0].phase_name == ''" or you can check v-if="showPhase.phase_name == ''"
Id data
data() {
return {
phaseData: null, // no value
showPhase: "",
};
},
In template check if value exist phaseData
<template>
<button v-if="phaseData" class="btn btn-primary">Create Phase</button>
<button v-else>Edit Phase</button>
</template>
rest code as it is.
Also check your conditions when to set phaseData

Removing specific object from array keeps removing last item

Here is what I have and I will explain it as much as I can:
I have a modal inside my HTML code as shown below:
<div id="favorites-modal-edit" class="modal">
<div class="modal-background"></div>
<div class="modal-card px-4">
<header class="modal-card-head">
<p class="modal-card-title">Favorites</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
<div class="container">
<div id="favorites-modal-edit-wrapper" class="columns is-multiline buttons">
<favorites-edit-component v-for="(favorite, index) in favorites_list" :key="favorite.id" :favorite="favorite" />
</div>
</div>
</section>
<footer class="modal-card-foot">
<button class="button" #click="addItem">
Add Item
</button>
</footer>
</div>
</div>
The id="favorites-modal-edit" is the Vue.js app, then I have the <favorites-edit-component /> vue.js component.
Here is the JS code that I have:
I have my favorites_list generated which is an array of objects as shown below:
const favorites_list = [
{
id: 1,
name: 'Horse',
url: 'www.example.com',
},
{
id: 2,
name: 'Sheep',
url: 'www.example2.com',
},
{
id: 3,
name: 'Octopus',
url: 'www.example2.com',
},
{
id: 4,
name: 'Deer',
url: 'www.example2.com',
},
{
id: 5,
name: 'Hamster',
url: 'www.example2.com',
},
];
Then, I have my vue.js component, which is the favorites-edit-component that takes in the #click="removeItem(this.index) which is coming back as undefined on the index.
Vue.component('favorites-edit-component', {
template: `
<div class="column is-half">
<button class="button is-fullwidth is-danger is-outlined mb-0">
<span>{{ favorite.name }}</span>
<span class="icon is-small favorite-delete" #click="removeItem(this.index)">
<i class="fas fa-times"></i>
</span>
</button>
</div>
`,
props: {
favorite: Object
},
methods: {
removeItem: function(index) {
this.$parent.removeItem(index);
},
}
});
Then I have the vue.js app that is the parent as shown below:
new Vue({
el: '#favorites-modal-edit',
// Return the data in a function instead of a single object
data: function() {
return {
favorites_list
};
},
methods: {
addItem: function() {
console.log('Added item');
},
removeItem: function(index) {
console.log(index);
console.log(this.favorites_list);
this.favorites_list.splice(this.favorites_list.indexOf(index), 1);
},
},
});
The problem:
For some reason, each time I go to delete a item from the list, it's deleting the last item in the list and I don't know why it's doing it, check out what is happening:
This is the guide that I am following:
How to remove an item from an array in Vue.js
The item keeps coming back as undefined each time the remoteItem() function is triggered as shown below:
All help is appreciated!
There is an error in your favorites-edit-component template, actually in vue template, when you want to use prop, data, computed, mehods,..., dont't use this
=> there is an error here: #click="removeItem(this.index)"
=> in addition, where is index declared ? data ? prop ?
you're calling this.$parent.removeItem(index); then in removeItem you're doing this.favorites_list.splice(this.favorites_list.indexOf(index), 1); this means that you want to remove the value equal to index in you array no the value positioned at the index
=> this.favorites_list[index] != this.favorites_list[this.favorites_list.indexOf(index)]
In addition, I would suggest you to modify the favorites-edit-component component to use event so it can be more reusable:
favorites-edit-component:
<template>
<div class="column is-half">
<button class="button is-fullwidth is-danger is-outlined mb-0">
<span>{{ favorite.name }}</span>
<span class="icon is-small favorite-delete" #click="$emit('removeItem', favorite.id)">
<i class="fas fa-times"></i>
</span>
</button>
</div>
</template>
and in the parent component:
<template>
...
<div id="favorites-modal-edit-wrapper" class="columns is-multiline buttons">
<favorites-edit-component
v-for="favorite in favorites_list"
:key="favorite.id"
:favorite="favorite"
#removeItem="removeItem($event)"
/>
</div>
...
</template>
<script>
export default {
data: function () {
return {
favorites_list: [],
};
},
methods: {
...
removeItem(id) {
this.favorites_list = this.favorites_list.filter((favorite) => favorite.id !== id);
}
...
},
};
I would restructure your code a bit.
In your favorites-edit-component
change your removeItem method to be
removeItem() {
this.$emit('delete');
},
Then, where you are using your component (in the template of the parent)
Add an event catcher to catch the emitted "delete" event from the child.
<favorites-edit-component v-for="(favorite, index) in favorites_list" :key="favorite.id" :favorite="favorite" #delete="removeItem(index)"/>
The problem you have right now, is that you are trying to refer to "this.index" inside your child component, but the child component does not know what index it is being rendered as, unless you specifically pass it down to the child as a prop.
Also, if you pass the index down as a prop, you must refer to it as "index" and not "this.index" while in the template.

[Vue warn]: Error in event handler for "update-user": "TypeError: Cannot set property 'name' of undefined"

I have 2 vue components one is a list users and the other is the details of a user.
What I'm trying to do is, if I have to update a user's details then I want it to automatically update.
The problem I'm getting is that I'm getting this error
[Vue warn]: Error in event handler for "update-user": "TypeError: Cannot set property 'name' of undefined"
Here is my code for the user details component
<template>
<div class="card">
<div class="card-header">
<span class="text-success" #click="updateUser">Save Changes</span>
</div>
<div class="card-body p-0">
<div class="card card-primary card-outline card-outline-tabs">
<div class="card-body">
<div class="form-group">
<label for="name">Full Name</label>
<input type="text" class="form-control" id="name" v-model="name">
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['emit', 'user'],
data() {
return {
name: null,
}
},
mounted() {
},
methods: {
updateUser() {
axios.put('/admin/users/'+this.user.id, this.name).then(response => {
this.emit.$emit('update-user', {
user: this.name
});
});
}
}
}
</script>
and my vue component with the list of users
<template>
<div class="card">
<div class="card-body>
<table class="table table-sm table-striped">
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr v-for="user in showUsers">
<td>{{user.name}}</td>
<td style="text-align: right;"><button class="btn btn-sm btn-outline-info" #click="detail(user)">Manage</button></td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
props: ['emit', 'users'],
data() {
return {
perPage: 10,
currentPage: 1,
}
},
computed: {
filterUsers() {
return (this.userFilter === '') ? this.users : this.users.filter(u => { return u.name.toLowerCase().indexOf(this.userFilter.toLowerCase()) > -1; });
},
showUsers() {
let start = (this.currentPage - 1) * this.perPage;
let end = start + this.perPage;
return this.filterUsers.slice(start, end);
}
},
methods: {
detail(user) {
this.emit.$emit('manage-user', { user: user });
}
},
mounted(){
this.emit.$on('update-user', payload => {
this.user.name = payload.user.name;
});
}
}
</script>
The error you met is caused by this.user doesn't exists in the context inside mounted() { this.emit.$on('update-user', ....}.
As my understanding, you'd like to update the name of a specific user, but in that context, you only have all users (this.users) inside mounted(), you have to filter out that specific user then apply the update.
Below is one solution:
If user.id is unique, you can emit user.id and user.name from updateUser, then in mounted, find the specific user by user.id, then update it.
updateUser() {
axios.put('/admin/users/'+this.user.id, this.name).then(response => {
this.emit.$emit('update-user', {
name: this.name, // new user.name
id: this.user.id // assuming user.id is unique
});
});
}
this.emit.$on('update-user', payload => {
this.users.find(user => user.id===payload.id).name = payload.name; // find the user then update its name
});

Getting part of the page to display updated data in vue

I'm using vue to create a page where I list all users and if I click on the edit button the details of that user then gets shown
next to the list.
What I'm trying to do is, if I update a user and click save then the user details in the list needs to change.
The problem I'm having is that I'm not able to get the details to change in the list after I've saved.
My vue
<template>
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-7">
<table class="table table-striped table-sm mt-2">
<thead>
<tr>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="user in displayAllUsers">
<td>{{ user.name }}</td>
<td>
<button class="btn btn-sm btn-success" #click="manageUser(user)">Edit</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-5" v-if="user != null">
<div class="card">
<div class="card-header">
<h4 class="card-title mb-0">Manage {{ user.name }}</h4>
</div>
<div class="card-body">
<table class="table">
<tr>
<th>Name</th>
<td>
<input type="text" v-model="user.name">
</td>
</tr>
</table>
</div>
<div class="card-footer">
<button #click="updateUser()"class="btn btn-success"><i class="fa fa-save"></i> Save</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
components: {
},
data: function () {
return {
users: [],
user: null
}
},
computed: {
displayAllUsers(){
return this.users;
}
},
methods: {
manageUser(user){
axios.get('/admin/user/'+user.id).then((response) => {
this.user = response.data.user;
});
},
updateUser(){
axios.put('/admin/user/'+this.user.id, {
name: this.user.name
}).then((response) => {
this.users = response.data.user;
});
}
},
mounted() {
axios.get('/admin/users').then((response) => {
this.users = response.data.users;
});
}
}
</script>
There are two possible solutions.
The first is to run this code at the end of the updateUser method:
axios.get('/admin/users').then((response) => {
this.users = response.data.users;
});
The second is to use a state manager like Vuex.
The first scenario will fetch again your users data from the remote API and will update your view with all your users.
With the second scenario, you will handle your application state way much better than just using the data attribute of your page module, but in the background, it is more or less the same as the first solution I suggest.
To update the current user only in the table you could do something like that at the end of the updateUser method:
let userIdx = -1;
for(let idx = 0, l = this.users.length; idx < l; idx++) {
if ( this.user.id === this.users[idx].id ) {
userIdx = idx;
break;
}
}
if ( -1 !== userIdx ) {
this.users[userIdx] = this.user;
this.user = {};
}
Other than your problem, it seems like you don't need this code:
computed: {
displayAllUsers(){
return this.users;
}
},
You could remove this code, and instead use this code in the HTML part:
<tr v-for="user in users">
For your updateUser function you could just return the modified user in the same format that you have for all the users in you user list and update the user by index. This is presuming that the user you want to update is in the users array to start with.
updateUser() {
axios.put('/admin/user/'+this.user.id, {
name: this.user.name
}).then((response) => {
const updatedUser = response.data.user;
// Find the index of the updated user in the users list
const index = this.users.findIndex(user => user.id === updatedUser.id);
// If the user was found in the users list update it
if (index >= 0) {
// Use vue set to update the array by index and force an update on the page
this.$set(this.users, index, updatedUser);
}
});
}
This could be a good starting point.
Unrelated Note:
You can add your mounted function code to its own method, for example
getUsers() {
axios.get('/admin/users').then((response) => {
this.users = response.data.users;
});
}
then
mounted() {
this.getUsers()
}
this makes it a little cleaner and easier if you ever need to get the users again (example: if you start having filters the user can change)
As it could get more complex vuex would be a great addition.

Extend Vue function in component to display ID

I have a Vue component and I am using internalValue to access the value attribute. How would I extend this to also get the ID?
ie internalValue = value, id
I have tried but I don't know how to add this inside the internalValue function. I've even tried to only get the ID by changing all instances of value to id but it still spits out the value.
I'd be happy to have them as one ie value, id or access them like data.value and data.id
Initialise Vue
new Vue({
el: '#topic',
data: {
selectedTopic: null
}
});
Use Component
<div class="form-group" id="topic">
<topic v-model="selectedTopic"></topic>
</div>
Register Component
Vue.component('topic', require('./components/Topicselect.vue'));
Component
<template>
<div>
<label v-for="topic in topics" class="radio-inline radio-thumbnail">
<input type="radio" v-model="internalValue" name="topics_radio" :id="topic.id" :value="topic.name">
<span class="white-color lg-text font-regular text-center text-capitalize">{{ topic.name }}</span>
</label>
<ul class="hidden">
<li>{{ internalValue }}</li>
</ul>
</div>
</template>
<script>
export default {
props: ['value'],
data () {
return {
internalValue: this.value,
topics: []
}
},
mounted(){
axios.get('/vuetopics').then(response => this.topics = response.data);
},
watch: {
internalValue(v){
this.$emit('input', v);
console.log('Topicselect: the value is ' + this.internalValue);
}
}
}
</script>
Use the selected topic as your value. Basically, eliminate internalValue altogether, and just emit the topic associated with any given radio button when it's clicked. This will satisfy v-model, since it listens to input events (unless you customize it).
export default {
props: ['value'],
data () {
return {
topics: []
}
},
methods:{
selectValue(topic){
this.$emit('input', topic)
}
},
mounted(){
axios.get('/vuetopics').then(response => this.topics = response.data);
}
})
And your template
<template>
<div>
<label v-for="topic in topics" class="radio-inline radio-thumbnail">
<input type="radio" #click="selectValue(topic)" name="topics_radio" :id="topic.id" :value="topic.name" :checked="value && topic.id == value.id">
<span class="white-color lg-text font-regular text-center text-capitalize">{{ topic.name }}</span>
</label>
<ul class="hidden">
<li>{{ value }}</li>
</ul>
</div>
</template>
This will set selectedTopic in your Vue to a topic, which is something like
{
id: 2,
name: "some topic"
}
based on how you use it in your template.
Working example.