Reputation: 1251
I'm trying to create an element in vue.js, so that when I update my cart it will show a warning with the item added/updated to cart. So if I add a new car, it would show that last car added.
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 3}
]
to
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 3},
{ name: 'Mustang', quantity: 1}
]
will show
<div>
You have 1 x Mustang in Cart
</div>
But if I update the quantity of a car that was already in the cart, it will show that last car updated.
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 3}
]
to
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 4}
]
will show
<div>
You have 4 x Toyota in Cart
</div>
So far I made it work based in this answer
new Vue({
el: '#app',
data: {
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 3}
]
}
});
Vue.component('car-component', {
props: ["car"],
data: function() {
return {
lastAdded:''
}
},
template: `
<div>
You have {{lastAdded.quantity}} x {{lastAdded.name}} in Cart
</div>`,
watch: {
car: {
handler: function(newValue) {
this.lastAdded = newValue;
},
deep: true
}
}
});
html
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<body>
<div id="app">
<p>Added to Cart:</p>
<car-component :car="car" v-for="car in cars"></car-component>
</div>
</body>
The point is that now it just detects when a object is already in the cart and changes quantity, but not when there is a new car added. I tried to play with another watcher, but it didn't work. Thanks in advance!
Upvotes: 1
Views: 3759
Reputation: 138196
You could pass the entire cars[]
array to <car-component>
, and allow the component to determine which element of cars[]
to display a message about:
In car-component
, add a prop
(typed for safety) to hold the passed-in cars[]
:
Vue.component('car-component', { // ... props: { cars: Array }, }
Add two data properties:
* `car` - the current car.
* `copyOfCars` - the last known copy of `cars[]`, used to determine which array element has changed. *Note: While watchers are provided both the old and new values of the watched property, the old value does not actually indicate the previous value for arrays of objects.*
Vue.component('car-component', {
//...
data() {
return {
car: {},
copyOfCars: undefined, // `undefined` because we don't need it to be reactive
};
},
}
Define a method (e.g., named findActiveCar
) that determines which element in a given cars[]
is most recently "active" (newly added or modified).
Vue.component('car-component', { // ... methods: { /** * Gets the newest/modified car from the given cars */ findActiveCar(newCars) { if (!newCars || newCars.length === 0) return {};
let oldCars = this.copyOfCars;
// Assume the last item of `newCars` is the most recently active
let car = newCars[newCars.length - 1];
// Search `newCars` for a car that doesn't match its last copy in `oldCars`
if (oldCars) {
for (let i = 0; i < Math.min(newCars.length, oldCars.length); i++) {
if (newCars[i].name !== oldCars[i].name
|| newCars[i].quantity !== oldCars[i].quantity) {
car = newCars[i];
break;
}
}
}
this.copyOfCars = JSON.parse(JSON.stringify(newCars));
return car;
}
}
}
Define a watcher on the cars
property that sets car
to the new/modified item from findActiveCar()
.
Vue.component('car-component', {
// ...
watch: {
cars: {
handler(newCars) {
this.car = this.findActiveCar(newCars);
},
deep: true, // watch subproperties of array elements
immediate: true, // run watcher immediately on this.cars[]
}
},
}
Vue.component('car-component', {
props: {
cars: Array,
},
data() {
return {
car: {},
copyOfCars: undefined,
}
},
template: `<div>You have {{car.quantity}} x {{car.name}} in Cart</div>`,
watch: {
cars: {
handler(newCars) {
this.car = this.findActiveCar(newCars);
},
deep: true,
immediate: true,
}
},
methods: {
findActiveCar(newCars) {
if (!newCars || newCars.length === 0) return {};
let oldCars = this.copyOfCars;
let car = newCars[newCars.length - 1];
if (oldCars) {
for (let i = 0; i < Math.min(newCars.length, oldCars.length); i++) {
if (newCars[i].name !== oldCars[i].name
|| newCars[i].quantity !== oldCars[i].quantity) {
car = newCars[i];
break;
}
}
}
this.copyOfCars = JSON.parse(JSON.stringify(newCars));
return car;
}
}
});
new Vue({
el: '#app',
data: () => ({
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 3}
]
}),
methods: {
addCar() {
this.cars.push({
name: 'Mustang', quantity: 1
})
}
}
})
<script src="https://unpkg.com/[email protected]"></script>
<div id="app">
<h1>Added to Cart</h1>
<button @click="addCar">Add car</button>
<ul>
<li v-for="(car, index) in cars" :key="car.name + index">
<span>{{car.name}} ({{car.quantity}})</span>
<button @click="car.quantity++">+</button>
</li>
</ul>
<car-component :cars="cars" />
</div>
Upvotes: 1
Reputation: 376
hmm how would I do this?
seems to me we have an array of objects and we are tracking the most recently added or modified object. Sure.
So, I think I'd want to only track the recently modified object and render that.
first the html:
<div id="app">
<p>Added to Cart:</p>
<car-component :car="latestCar"></car-component>
</div>
and the vue instance:
new Vue({
el: '#app',
data: {
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 3}
],
latestCar: {}
},
methods: {
updateLatestCar(car) {
this.latestCar = car;
//call this method from any other method where updates take place
//so if would be called from your addCar method and your updateCar method
//(which I assume exist even though they are not shown in your code)
}
}
});
Vue.component('car-component', {
props: ["car"],
data: function() {
return {
lastAdded:''
}
},
template: `
<div>
You have {{lastAdded.quantity}} x {{lastAdded.name}} in Cart
</div>`,
watch: {
car: {
handler: function(newValue) {
this.lastAdded = newValue;
},
deep: true
}
}
});
If you are modifying your array of objects via some method that is external to the Vue instance then that will require some additional thought.
But it seems like for this you'd have some methods in the Vue instance methods block like this:
addCar(car) {
this.cars.push(car);
this.updateLatestCar(car);
},
updateCar(index, car) {
this.cars[index] = car;
this.updateLatestCar(car);
}
Upvotes: 2