Toggle button text on a loop VUE - vue.js

I have a loop with products, each with a product card.
I want to be able to toggle the button when clicked from Add to cart to Remove from cart.
The problem is all of the products buttons toggle at the same time, and I wan't ONLY the individual product card buttons to be toggled referencing each individual product.
In my HTML
<div v-for="product of products" :key="product.id">
<span class="btn btn-primary mt-5 modal-toggle-btn" #click="addGift(product, text, 'index')" v-show="!isAdded">Añadir a la box</span>
<span class="btn btn-primary mt-5 modal-toggle-btn" #click="removeGift(product, 'index')" v-show="isAdded">Quitar de la box</span>
</div>
Vue data
isAdded: false
My Vue methods
addGift(product, index){
this.campaign.selectedproducts.push({name: product.name });
this.isAdded = true
},
removeGift(product, index){
this. campaign.selectedproducts.splice(index, 1)
this.isAdded = false
},

My suggestion is to:
Divide the product buttons as an individual component.
Use addedIds as an array to store added product ids instead of isAdded boolean.
Communicate parent and child click events with Vue event handling.
Store clicked product id in to the addedProductId on click events.
Check against addedProductId to make sure a product was added or
not in child component.
Example:
ProductButtons.vue (child component)
<template>
<div>
<span class="btn btn-primary mt-5 modal-toggle-btn" #click="addGift" v-show="!isAdded">Añadir a la box</span>
<span class="btn btn-primary mt-5 modal-toggle-btn" #click="removeGift" v-show="isAdded">Quitar de la box</span>
</div>
</template>
<script>
export default {
name: "ProductButtons",
props: {
product: { type: Object, required: true },
addedIds: { type: Array, required: true },
},
computed: {
isAdded() {
return this.addedIds.indexOf(this.product.id) > -1;
},
},
methods: {
addGift(){
this.$emit('addGift', this.product);
},
removeGift(product){
this.$emit('addGift', this.product);
},
}
}
</script>
In Your HTML
<template v-for="product of products" :key="product.id">
<product-buttons :product="product" :addedIds="addedIds" #addGift="addGift" #removeGift="removeGift"></product-buttons>
</template>
Vue data
addedIds: []
Your Vue methods
addGift(product){
this.campaign.selectedproducts.push({name: product.name });
// save product id as an added id
const index = this.addedIds.indexOf(product.id);
if (index === -1) {
this.addedIds.push(product.id);
}
},
removeGift(product){
this.campaign.selectedproducts.splice(index, 1);
// remove product id
const index = this.addedIds.indexOf(product.id);
if (index > -1) {
this.addedIds.splice(index, 1);
}
},

Related

how to add and get items from an array in vue3?

so I am build a shop with a cart in it and I want to add products to the cart and view them in the cart after I added them. how can I fix my code? as I stumbled across the push method of JavaScript but for some reason it does not work for me. so, how can I add items to the cart and retrieve them later?
here is my code:
shop.vue
<template>
<div class="shop">
<h1>shop</h1>
<div class="products" v-for="item in items" :key="item.id">{{ item.name }}</div>
<button #click="addToCart">Add to Cart</button>
</div>
<div class="cart">
<h1>cart</h1>
<div class="cartitems" v-for="item in cart" :key="item.id">{{ item.name }} {{ item.price }}</div>
</div>
</template>
<script>
import Products from "../db.json"
export default {
name: "shop",
data() {
return {
items: Products,
cart: []
}
},
methods: {
addToCart() {
this.cart.push(this.items.id)
}
}
}
</script>
db.json as my "db" with the products
[
{
"id": 1,
"name": "iphone",
"price": 2000
},
{
"id": 2,
"name": "galaxy",
"price": 3000
}
]
addToCart() {
this.cart.push(this.items.id)
}
There is a typo (I assume). You want to add a certain item id to the cart. this.items is an array and does not have an id property.
You actually want to pass the id as an argument to the addToCart method:
<button #click="addToCart(item.id)">Add to Cart</button>
Then grab and add it to the cart:
addToCart(id) {
this.cart.push(id)
}
Update/ Edit:
You also need to place the <button> inside the v-for loop, otherwise it will not have access to the iteration scope:
<div class="products" v-for="item in items" :key="item.id">
{{ item.name }}
<button #click="addToCart(item.id)">Add to Cart</button>
</div>

How to programmatically remove a rendered v-for element from the DOM when using a click event?

I am trying to use V-Animate-CSS to show a "deletion" animation when a delete button is pressed. I am struggling with trying to specify the exact element to delete programmatically within a v-for loop.
Let me explain:
I have the following vue <template> like so:
<div
v-for="x in divisionLangs"
:key="x.P_uID"
>
<button
type="button"
#click.prevent="deleteCard(x.P_uID)"
>
</button>
<transition name="bounce"
<div v-if="show" class="card-body">
<!-- card content is here -->
</div>
</transition>
My <script> section looks like so:
data() {
return {
show: true,
divisionLangs: []
}
}
deleteCard(id) {
this.show = !this.show
this.divisionLangs = this.divisionLangs.filter(x => x.P_uID !== id)
},
The data for the divisionLangs array looks like so:
[
{
P_uID: 789,
..blah...
},
{
P_uID: 889,
...blah...
}
]
How can I structure this code so only the matching card item is deleted from the rendered list and not ALL of the card items? What happens right now is that all of the cards are deleted on the deleteCard method.
You should define a child component for each item like below:
Parent component:
<tmplate>
<child-component v-for="x in divisionLangs" :key="x.P_uID" />
</template>
And in child component:
<button
type="button"
#click.prevent="deleteCard(x.P_uID)"
>
</button>
<transition name="bounce"
<div v-if="show" class="card-body">
<!-- card content is here -->
</div>
</transition>
And Script part of child component:
data() {
return {
show: true,
divisionLangs: []
}
}
deleteCard(id) {
this.show = !this.show
this.divisionLangs = this.divisionLangs.filter(x => x.P_uID !== id)
},

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.

Show on several elements in the same component in vuejs

Looping out a number of boxes within the same component in vuejs.
Each box has a button that reveals more text using v-on:click.
The problem is that all the boxes respond to this at the same time.
Is there a way to target the specific button being clicked if there are several buttons in a component?
Is there some way to isolate each button so they all dont activate at once?
<div class="filter-list-area">
<button #click="show =!show"> {{text}} </button>
<ul class="filter-list-item-area">
<li class="filter-list-item " v-for="(items, key) in packages">
<div>
<img class="fit_rating">
</div>
<div class="filter-list-item-info" >
<h3>{{items.partner_university}}</h3>
<p> Match: {{items.match_value}}</p>
<div v-for="(courses, key) in courses">
<transition name="courses">
<div class="courses2" v-show="show">
<p v-if="courses.Pu_Id === items.pu_Id">
{{courses.Course_name}}
</p>
</div>
</transition>
</div>
</div>
</li>
</ul>
</div>
</template>
<script>
import testdata from '../App.vue'
export default {
data (){
return{
text: 'Show Courses',
testFilter: 'Sweden',
show: false
}
},
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
testuni: Array,
list: Array,
packages: Array,
courses: Array
},
methods:{
afunction(){
console.log(this.show);
},
changeText(){
if(this.show){
this.text = 'Hide Courses'
}
else{
this.text = "Show Courses"
}
}
},
mounted() {
this.afunction();
},
watch: {
show:
function() {
this.afunction()
this.changeText()
}
},
}
EDIT: I've created this before you posted the code example, but you could use same principle:
In your data add showMoreText, which will be used to track if show more data should be shown.
I would agree with #anaximander that you should use child components here
Simple example how to show/hide
<template>
<div>
<div v-for="(box, index) in [1,2,3,4,5]">
<div>
Box {{ box }} <button #click="toggleMore(index)">More</button>
</div>
<div v-show="showMoreText[index]">
More about box {{ box }}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
showMoreText: {},
}
},
methods: {
toggleMore(index) {
/*
Adds a property to a reactive object, ensuring the new property is
also reactive, so triggers view updates.
https://vuejs.org/v2/api/#Vue-set
*/
this.$set(this.showMoreText, index, ! this.showMoreText[index])
}
}
}
</script>
This sounds like an ideal situation for a new child component, which will allow each instance of the new component to have its own separate state.
The child components can emit events to the parent, if cross-component communication is necessary.

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.