Vue.js - v-for indicies mixed up? - vue.js

What do I have: components structure
<Games> // using v-for - iterate all games
--<Game> // using v-for - iterate all players
----<Player 1>
------<DeleteWithConfirmation>
----<Player 2>
------<DeleteWithConfirmation>
----<Player 3>
------<DeleteWithConfirmation>
----...
<DeleteWithConfirmation> implementation: two clicks are required for deleting game property.
<template>
<div>
<button #click="incrementDelete"
v-html="deleteButtonHTML"></button>
<button v-if="deleteCounter === 1" #click="stopDeleting">
<i class="undo icon"></i>
</button>
</div>
</template>
<script>
export default {
name: 'DeleteWithConfirmation',
data() {
return {
deleteCounter: 0
}
},
computed: {
deleteButtonHTML: function () {
if (this.deleteCounter === 0)
return '<i class="trash icon"></i>'
else
return 'Are you sure?'
}
},
methods: {
incrementDelete() {
this.deleteCounter++
if (this.deleteCounter === 2) {
//tell parent component that deleting is confirmed.
//Parent call AJAX than.
this.$emit('deletingConfirmed')
this.stopDeleting()
}
else if (this.deleteCounter > 2)
this.stopDeleting()
},
stopDeleting() {
Object.assign(this.$data, this.$options.data())
}
}
}
</script>
My problem: seems like indicies are mixed up:
Before deleting 4th player was on "Are you sure state" (deleteCounter === 1), but after deleting it went to initial state (deleteCounter === 0). Seems like 3rd component state haven't updated its deleteCounter, but its data (player's name was updated anyway).
After successfull deleting <Games> component data is fetched again.

You don't need a delete counter for achieving this. On the contrary, it makes it hard to understand your code. Just use a boolean like this:
<template>
<div>
<button #click="clickButton"
<template v-if="confirmation">
<i class="trash icon"></i>
</template>
<template v-else>
Are you sure?
</template>
</button>
<button v-if="confirmation" #click="confirmation = false">
<i class="undo icon"></i>
</button>
</div>
</template>
<script>
export default {
name: 'DeleteWithConfirmation',
data() {
return {
confirmation: false
}
},
methods: {
clickButton() {
if (!this.confirmation) {
this.confirmation = true;
} else {
this.$emit('deleting-confirmed');
}
}
}
</script>
The parent could then be looking e.g. like this:
<div class="row" v-if="showButton">
[...]
<delete-with-confirmation #deleting-confirmed="showButton = false">
</div>

One of the answers was deleted, I wish I could mention the initial author, but I don't remeber his username, so (changed a bit):
incrementDelete() {
if (this.deleteCounter === 1) { // 1 because there is "post-increment" at the end of the fucntion
this.deletingProgress = true
this.$emit('deletingConfirmed')
this.stopDeleting()
}
else if (this.deleteCounter > 1) // 1 because there is "post-increment" at the end of the fucntion
this.stopDeleting()
this.deleteCounter++ // "post-increment"
},
ssc-hrep3 answer is more clean (and lightweight) than mine approach, link.

Related

How to fire an event in mount in Vuejs

I have a sidebar that you can see below:
<template>
<section>
<div class="sidebar">
<router-link v-for="(element, index) in sidebar" :key="index" :to="{ name: routes[index] }" :class='{active : (index==currentIndex) }'>{{ element }}</router-link>
</div>
<div class="sidebar-content">
<div v-if="currentIndex === 0">
Profile
</div>
<div v-if="currentIndex === 1">
Meine Tickets
</div>
</div>
</section>
</template>
<script>
export default {
mounted() {
EventBus.$on(GENERAL_APP_CONSTANTS.Events.CheckAuthentication, () => {
this.authenticated = authHelper.validAuthentication();
});
console.log()
this.checkRouter();
},
data(){
return {
currentIndex:0,
isActive: false,
sidebar: ["Profile", "Meine Tickets"],
routes: ["profile", "my-tickets"],
authenticated: authHelper.validAuthentication(),
}
},
computed: {
getUser() {
return this.$store.state.user;
},
},
methods: {
changeSidebar(index) {
this.object = this.sidebar[index].products;
this.currentIndex=index;
},
checkRouter() {
let router = this.$router.currentRoute.name;
console.log(router);
if(router == 'profile') {
this.currentIndex = 0;
} else if(router == 'my-tickets') {
this.currentIndex = 1;
}
},
},
}
</script>
So when the link is clicked in the sidebar, the route is being changed to 'http://.../my-account/profile' or 'http://.../my-account/my-tickets'. But the problem is currentIndex doesn't change therefore, the content doesn't change and also I cannot add active class into the links. So how do you think I can change the currentIndex, according to the routes. Should I fire an event, could you help me with this also because I dont know how to do it in Vue. I tried to write a function like checkRouter() but it didn't work out. Why do you think it is happening? All solutions will be appreciated.
So if I understand correctly, you want currentIndex to be a value that's based on the current active route? You could create it as a computed property:
currentIndex: function(){
let route = this.$router.currentRoute.name;
if(router == 'profile') {
return 0;
} else if(router == 'my-tickets') {
return 1;
}
}
I think you could leverage Vue's reactivity a lot more than you are doing now, there's no need for multiple copies of the same element, you can just have the properties be reactive.
<div class="sidebar-content">
{{ sidebar[currentIndex] }}
</div>
Also, you might consider having object be a computed property, something like this:
computed: {
getUser() {
return this.$store.state.user;
},
object() {
return this.sidebar[currentIndex].products;
}
},
Just use this.$route inside of any component template. Docs .You can do it simple without your custom logic checkRouter() currentIndex. See simple example:
<div class="sidebar-content">
<div v-if="$route.name === 'profile'">
Profile
</div>
<div v-if="$route.name === 'my-tickets'">
Meine Tickets
</div>
</div>

How to pass props to input value in Vuejs

I have a parent component as a Cart. Here I defined quantity and I want to pass this quantity to the child component's input value which is Counter. So here how I am passing it and here is my parent component, Cart:
<Counter quantity="item.quantity"/>
And here is my child component, Counter:
<template>
<div id="counter">
<button class="minus" #click="countDown"><i :class="quantity == 0 ? 'disabled' : ''" class="fas fa-minus"></i></button>
<div class="count-number"><input class="counter-content" type="number" v-model="quantity"></div>
<button class="plus" #click="countUp"><i class="fas fa-plus"></i></button>
</div>
</template>
<script>
export default {
props: {
quantity: Number
},
methods: {
countUp() {
this.quantity++;
},
countDown() {
if(this.quantity > 0) {
this.quantity--;
}
},
}
}
</script>
I am quite new in Vue, so maybe I am doing something wrong when I pass the props. So could you help me about this?
Try (with the : colon sign)
<Counter :quantity="item.quantity"/>
Before you were just passing the string "item.quanity"
I see you're modifying your prop directly:
countUp() {
this.quantity++;
},
countDown() {
if(this.quantity > 0) {
this.quantity--;
}
},
This is not how you do it in Vue. You need to use two way binding.
countUp() {
this.$emit('input', this.quantity+1)
}
countDown() {
this.$emit('input', this.quantity-1)
}
and in your parent component:
<Counter :quantity="item.quantity" #input="(payload) => {item.quantity = payload}"/>
By the way, the Vue styleguide recommends to use multi-word component names: https://v2.vuejs.org/v2/style-guide/#Multi-word-component-names-essential (Cart = bad, MyCart = good)
We cannot change the value that we get from props, so I created a variable and put props there when mounting
Try it
<Counter :quantity="item.quantity"/>
and
<template>
<div id="counter">
<button class="minus" #click="countDown"><i :class="sum == 0 ? 'disabled' : ''" class="fas fa-minus"></i></button>
<div class="count-number"><input class="counter-content" type="number" v-model="sum"></div>
<button class="plus" #click="countUp"><i class="fas fa-plus"></i></button>
</div>
</template>
<script>
export default {
props: {
quantity: Number
},
data: () => ({
sum: 0
}),
mounted() {
this.sum = this.quantity;
},
methods: {
countUp() {
this.sum++;
},
countDown() {
if(this.sum > 0) {
this.sum--;
}
},
}
}
</script>

Update properties of component in Vue.js

I've been searching for the answer 2nd day. But still couldn't find solution.
I have the modal window template. And the main page template from where I need to update modal window size by clicking on the button (span). Shortly it's like this for HTML:
<template id="modal">
<div>
<div :class="'modal-' + size">
...
</div>
</div>
</template>
<template id="list">
<div>
<span #click="onDetails">
Show Details
</span>
</div>
<modal size="md" #showdetails="showdetails();" ref="modal">
...
</modal>
</template>
And for JS:
Vue.component("modal", {
template: "#modal",
props: {
size: {
type: String,
default: ""
}
},
methods: {
onDetails() {
this.$emit("showdetails")
}
}
})
var List = Vue.extend({
template: "#list",
methods: {
showDetails() {
if(this.$refs.modal.size == "md") {
this.$refs.modal.size = "lg"
}
<additional code here>
}
}
})
When I'm accessing this.$refs.modal.size for read - it's OK. When I'm just changing it from showDetails - OK if only this action in the function. When I'm put something else instead of - size is not updating.
For example:
this.$refs.modal.size = "lg" - will work
this.$refs.modal.theme = "danger"; this.$refs.modal.size = "lg" - neither of them are updating
What am I doing wrong?
You need to assign the attribute value of Size by the javascript method setAttribute . Example : this.$refs.modal.setAttribute('size', 'lg')
There is a working demo below:
new Vue({
el: '#app',
methods: {
showdetails() {
console.log(this.$refs.modal.getAttribute('size'));
this.$refs.modal.setAttribute('size', 'lg')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.15/vue.js"></script>
<div id='app'>
<button size="md" #click="showdetails" ref="modal">Click</button>
</div>

How to had a conditional render with a v-if in nuxt.js

I have an application that displays companies and their documents.
I would like to display a main title if no application is selected, and display the content of the application and removing the main title if an application is selected.
<template>
<div class="container">
<div v-if="docsAppDisplayed === false">
<Logo />
<h1 class="title">
<em class="text-red-900 font-bold">D</em>ocs<em class="text-yellow-700 font-bold">C</em>loud<em class="text-purple-900 font-bold">M</em>anager
</h1>
</div>
<div>
<DocumentationCard />
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data () {
return {
docsAppDisplayed: false
}
},
computed: {
...mapGetters(['applications', 'selectedApp', 'documentations', 'selectedDoc'])
},
async mounted () {
await this.$store.dispatch('getApplications', 'getDocumentations')
},
methods: {
selectApplications (id) {
this.$store.dispatch('selectedApp', id)
this.docsAppDisplayed = true
}
}
}
</script>
I don't know why my method doesn't work. An idea, please?
I specify that I have a sidebar that returns my company names and that when I click on it I can access their documents. My problem only concerns the display of the (logo + title) or my DocumentCard.
This never seems to get invoked by your component.
selectApplications (id) {
this.$store.dispatch('selectedApp', id)
this.docsAppDisplayed = true
}
Where is id coming from? Is it a route parameter, like https://example.com/myroute/1234 where 1234 is the id? Without knowing much about your code, my guess is, you're looking to do something like this:
export default {
data () {
return {
docsAppDisplayed: false
}
},
computed: {
...mapGetters(['applications', 'selectedApp', 'documentations', 'selectedDoc'])
},
async mounted () {
await this.$store.dispatch('getApplications', 'getDocumentations')
selectApplications(this.$route.params.id) // THE BIG CHANGE
},
methods: {
selectApplications (id) {
this.$store.dispatch('selectedApp', id)
this.docsAppDisplayed = true
}
}
}
You also might want to add a v-else to the div wrapping <DocumentationCard />.
<template>
<div class="container">
<div v-if="!docsAppDisplayed">
<Logo />
<h1 class="title">
<em class="text-red-900 font-bold">D</em>ocs<em class="text-yellow-700 font-bold">C</em>loud<em class="text-purple-900 font-bold">M</em>anager
</h1>
</div>
<div v-else>
<DocumentationCard />
</div>
</div>
</template>

Vue-draggable limit list to 1 element

Here I am trying to implement cloning an element from rankings list and put it in either of the two lists (list1 & list2). Everything seems to be working, I am able to drag and put but it looks like binding does not work as the two lists are not affected, because the watchers do not run when I drag an element to a list. Also, the clone function does not print the message to the console. I was using this example as a reference.
<template>
<div>
<div>
<div>
<draggable
#change="handleChange"
:list="list1"
:group="{ name: 'fighter', pull: false, put: true }"
></draggable>
</div>
<div>
<draggable
#change="handleChange"
:list="list2"
:group="{ name: 'fighter', pull: false, put: true }
></draggable>
</div>
</div>
<div>
<div v-for="wc in rankings" :key="wc.wclass">
<Card>
<draggable :clone="clone"
:group="{ name: 'fighter', pull: 'clone', put: false }"
>
<div class="cell" v-for="(fighter, idx) in wc.fighters" :key="fighter[0]">
<div class="ranking">
{{ idx + 1 }}
</div>
<div class="name">
{{ fighter[0] }}
</div>
</div>
</draggable>
</Card>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import draggable from "vuedraggable";
export default {
components: {
draggable
},
data() {
return {
rankings: [],
list1: [],
list2: []
};
},
methods: {
getRankingLabel(label) {
if (!label || label == "NR") return 0;
if (label.split(" ").indexOf("increased") !== -1) return 1;
if (label.split(" ").indexOf("decreased") !== -1) return -1;
},
clone({ id }) {
console.log("cloning");
return {
id: id + "-clone",
name: id
};
},
handleChange(event) {
console.log(event);
}
},
watch: {
// here I am keeping the length of both lists at 1
list1: function(val) {
console.log(val); // nothing prints, as the watcher does not run
if (val.length > 1) {
this.fighter_one.pop();
}
},
list2: function(val) {
console.log(val); // nothing prints, as the watcher does not run
if (val.length > 1) {
this.fighter_two.pop();
}
}
},
created() {
axios
.get("http://localhost:3000")
.then(res => {
this.rankings = res.data;
})
.catch(err => {
console.log(err);
});
}
};
</script>
<style>
</style>
As others have noted in the comments, your problem is likely related the <draggable> tag not containing either a :list prop or v-model.
With that said, you can limit the size of a list to 1 by calling the splice(1) method on the list in the #change event.
<draggable :list="list1" group="fighter" #change="list1.splice(1)">
{{ list1.length }}
</draggable>