how to add and get items from an array in vue3? - vue.js

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>

Related

How to change input value for individual object in an array using v-for in Vue?

I'm making a shopping cart. I want to show the price of each product based on their quantity.
I made the buttons to add or subtract the quantity of products. However the quantity changes in all products because they all share the same data. How can I do to change quantity for the specific product?
<div v-for="(cartProduct,index) in cartProducts" :key="index" class="px-3 py-2">
<div id="cartProducts">
<p>{{cartProduct.description}}</p>
<button #click="subtract" >-</button> <p>{{quantity}}</p> <button #click="add">+</button>
<p>$ {{cartProduct.price*quantity}}</p>
</div>
</div>
export default {
data(){
return{
quantity:1
}
},
methods:{
add(){
this.quantity++;
},
subtract(){
this.quantity--;
},
}
You have to do is, build an object for all products having quantity field, just like this
<template>
<div>
<div
v-for="(cartProduct, index) in cartProducts"
:key="index"
class="px-3 py-2"
>
<div id="cartProducts">
<p>{{ cartProduct.description }}</p>
<button #click="subtractProduct(index)">-</button>
<p>{{ cartProduct.quantity }}</p>
<button #click="addProduct(index)">+</button>
<p>$ {{ cartProduct.price * cartProduct.quantity }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
cartProducts: [
{
description: "product 1",
price: 100,
quantity: 0,
},
{
description: "product 2",
price: 100,
quantity: 0,
},
{
description: "product 3",
price: 100,
quantity: 0,
},
],
};
},
methods: {
addProduct(index) {
this.cartProducts[index].quantity++;
},
subtractProduct(index) {
if (this.cartProducts[index].quantity !== 0)
this.cartProducts[index].quantity--;
},
},
};
</script>

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.

Toggle button text on a loop VUE

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);
}
},

Send data from one component to another in vue

Hi I'm trying to send data from one component to another but not sure how to approach it.
I've got one component that loops through an array of items and displays them. Then I have another component that contains a form/input and this should submit the data to the array in the other component.
I'm not sure on what I should be doing to send the date to the other component any help would be great.
Component to loop through items
<template>
<div class="container-flex">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Name</p>
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in entries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry />
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
export default {
name: 'entry-list',
components: {
addEntry
},
data: function() {
return {
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
}
}
</script>
Component for adding / sending data
<template>
<div
class="entry-add"
v-bind:class="{ 'entry-add--open': addEntryIsOpen }">
<input
type="text"
name="addEntry"
#keyup.enter="addEntries"
v-model="newEntries">
</input>
<button #click="addEntries">Add Entries</button>
<div
class="entry-add__btn"
v-on:click="openAddEntry">
<span>+</span>
</div>
</div>
</template>
<script>
export default {
name: 'add-entry',
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push(this.newEntries);
this.newEntries = '';
},
openAddEntry() {
this.addEntryIsOpen = !this.addEntryIsOpen;
}
}
}
</script>
Sync the property between the 2:
<add-entry :entries.sync="entries"/>
Add it as a prop to the add-entry component:
props: ['entries']
Then do a shallow merge of the 2 and emit it back to the parent:
this.$emit('entries:update', [].concat(this.entries, this.newEntries))
(This was a comment but became to big :D)
Is there a way to pass in the key of name? The entry gets added but doesn't display because im looping and outputting {{ entry.name }}
That's happening probably because when you pass "complex objects" through parameters, the embed objects/collections are being seen as observable objects, even if you sync the properties, when the component is mounted, only loads first level data, in your case, the objects inside the array, this is performance friendly but sometimes a bit annoying, you have two options, the first one is to declare a computed property which returns the property passed from the parent controller, or secondly (dirty and ugly but works) is to JSON.stringify the collection passed and then JSON.parse to convert it back to an object without the observable properties.
Hope this helps you in any way.
Cheers.
So with help from #Ohgodwhy I managed to get it working. I'm not sure if it's the right way but it does seem to work without errors. Please add a better solution if there is one and I'll mark that as the answer.
I follow what Ohmygod said but the this.$emit('entries:update', [].concat(this.entries, this.newEntries)) didn't work. Well I never even need to add it.
This is my add-entry.vue component
<template>
<div
class="add-entry"
v-bind:class="{ 'add-entry--open': addEntryIsOpen }">
<input
class="add-entry__input"
type="text"
name="addEntry"
placeholder="Add Entry"
#keyup.enter="addEntries"
v-model="newEntries"
/>
<button
class="add-entry__btn"
#click="addEntries">Add</button>
</div>
</template>
<script>
export default {
name: 'add-entry',
props: ['entries'],
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push({name:this.newEntries});
this.newEntries = '';
}
}
}
</script>
And my list-entries.vue component
<template>
<div class="container-flex">
<div class="wrapper">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Competition Entries</p>
</div>
<div class="entries__header__search">
<input
type="text"
name="Search"
class="input input--search"
placeholder="Search..."
v-model="search">
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in filteredEntries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry :entries.sync="entries"/>
</div>
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
import pickWinner from '#/components/pick-winner.vue'
export default {
name: 'entry-list',
components: {
addEntry,
pickWinner
},
data: function() {
return {
search: '',
entries: [
{
name: 'Geoff'
},
{
name: 'Stu'
},
{
name: 'Craig'
},
{
name: 'Mark'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredEntries() {
if(this.search === '') return this.entries
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
</script>

How to pick v-model for Vue.js draggable when using a nested list

Here is my Vue
<div id="main">
<h1>Vue Dragable For</h1>
<div class="drag">
<ul>
<li v-for="category in data">
<draggable id="category" v-model="category" :move="checkMove" class="dragArea" :options="{group:'people'}">
<div v-for="item in category" style="border: 3px;">${ item.name }</div>
</draggable>
</li>
</ul>
</div>
</div>
<script>
var vm = new Vue({
el: "#main",
delimiters:['${', '}'],
data: {{ data | tojson | safe }},
methods:{
checkMove: function(evt){
console.log(evt.draggedContext.index);
console.log(evt.draggedContext.futureIndex);
console.log(evt.from.id);
console.log(evt.to.id);
axios.post('/categorize', {
'index': JSON.stringify(evt.draggedContext.index),
'futureIndex': JSON.stringify(evt.draggedContext.futureIndex),
'from':evt.to.id,
'to':evt.from.id,
});
return true
}
}
});
</script>
the data rendered in the template {{ data | tojson | safe }} just looks like:
{"data": {"uncategorized": [{"name": ""}, {"name": "first"}, {"name": "another"}]}}
Right now I am getting this error
You are binding v-model directly to a v-for iteration alias. This will not be able to modify the v-for source array because writing to the alias is like modifying a function local variable. Consider using an array of objects and use v-model on an object property instead.
so I don't think it it likes how I am using v-model. I am basing my code on this example: https://jsfiddle.net/dede89/32ao2rpm/ which uses raw son names in its v-model tags, but I cannot do that. So how should I do this?
Thanks!
I fixed this by changing my data object so that I can reference an object property on the v-model instead. Basically exactly what it said in the error. Specifically, it looks like:
<li v-for="(object,category) in data">
<h3>${ object.name }</h3>
<draggable v-bind:id="object.name"
v-model="object.items"
:id="'category-' + category"
:move="checkMove" class="dragArea"
:options="{group: 'items'}">
<div v-for="(item,key) in object.items" :id="item.id">
<a href="{{ url_for('view',itemid=${ item.id }) }}">
${ item.name }
</a>
</div>
</draggable>
</li>
with the data object like so:
{"data": {
"category2": {
"category_id": 2,
"items": [],
"name": "category2"
},
"category3": {
"category_id": 3,
"items": [],
"name": "category3"
},
"uncategorized": {
"category_id": 1,
"items": [
{ "id": 1, "name": "first!" }
],
"name": "uncategorized"
}
}}