If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeI'm really happy with how our cart page is looking. Now we need to bring the quantity and remove button to life and make them work. First up is the quantity input.
As a reminder, each row is being rendered by the ShoppingCartItem
component... but the data for quantity
lives up on ShoppingCart
... inside the cart
data: each item has, among other things, a quantity. So this is what we ultimately need to update.
The input lives down here in the ShoppingCartItem
component. So, to communicate up to ShoppingCart
, we need to emit an event. No problem!
Over in cart-item
, find the input
and add @input=""
. So, whenever this value changes, call a new function: updateQuantity
:
<template> | |
<div :class="[$style.component, 'row', 'p-3']"> | |
... lines 3 - 15 | |
<div class="col-3"> | |
<input | |
... lines 18 - 21 | |
@input="updateQuantity" | |
/> | |
</div> | |
... lines 25 - 34 | |
</div> | |
</template> | |
... lines 37 - 87 |
Copy that, scroll to the bottom and... add a methods
key with an updateQuantity
function inside. This will be passed an event
object.
... lines 1 - 37 | |
<script> | |
... lines 39 - 40 | |
export default { | |
name: 'ShoppingCartItem', | |
... lines 43 - 56 | |
methods: { | |
updateQuantity(event) { | |
... lines 59 - 63 | |
}, | |
}, | |
}; | |
</script> | |
... lines 68 - 87 |
All we need to do here is emit an event. Do that with this.$emit()
and... call the event, how about, updateQuantity
. Though, I have to admit, I got a little lazy when I recorded this. Technically this event name is fine and will work.. but the Vue best practice is to use kebab-casing for event names. So this should be update-quantity
. Both work, but follow the convention... not lazy Ryan.
... lines 1 - 37 | |
<script> | |
... lines 39 - 40 | |
export default { | |
name: 'ShoppingCartItem', | |
... lines 43 - 56 | |
methods: { | |
updateQuantity(event) { | |
this.$emit('updateQuantity', { | |
... lines 60 - 62 | |
}); | |
}, | |
}, | |
}; | |
</script> | |
... lines 68 - 87 |
Anyways, for the event data, let's think. We really need to pass 2 things: something that identifies which cart item is being updated and what its new quantity should be.
For the first thing, the way we're identifying each item in the cart is via its product and color combination. So pass both: productId
set to this.item.product['@id']
, then colorId
set to basically the same thing... but first check to see if there is a color. If there is, pass this.item.color['@id']
, else pass null
:
... lines 1 - 37 | |
<script> | |
... lines 39 - 40 | |
export default { | |
name: 'ShoppingCartItem', | |
... lines 43 - 56 | |
methods: { | |
updateQuantity(event) { | |
this.$emit('updateQuantity', { | |
productId: this.item.product['@id'], | |
colorId: this.item.color ? this.item.color['@id'] : null, | |
... line 62 | |
}); | |
}, | |
}, | |
}; | |
</script> | |
... lines 68 - 87 |
You could also pass that id
property we added to each item... and then find the correct item using that... it's your call.
The other thing we need to pass is the quantity, which isn't stored as a piece data, but is available via event.target.value
:
... lines 1 - 37 | |
<script> | |
... lines 39 - 40 | |
export default { | |
name: 'ShoppingCartItem', | |
... lines 43 - 56 | |
methods: { | |
updateQuantity(event) { | |
this.$emit('updateQuantity', { | |
productId: this.item.product['@id'], | |
colorId: this.item.color ? this.item.color['@id'] : null, | |
quantity: event.target.value, | |
}); | |
}, | |
}, | |
}; | |
</script> | |
... lines 68 - 87 |
Beautiful! Let's check this in the browser. Go to the Vue dev tools: we can at least make sure the event emits correctly. And... perfect! When we click up or down, the updateQuantity
event does show up.
Check the "payload".. this contains the event object. Oh... that's not quite right: the color and product id's look good... but the quantity is a string. That makes sense: input values are strings... but we want a number.
This is exactly the reason why we sometimes use v-model.number
: it grabs the value, converts it into a number and then updates the data. We're not using v-model
here, but we can do that same conversion manually. Wrap the input value with parseFloat()
:
... lines 1 - 37 | |
<script> | |
... lines 39 - 40 | |
export default { | |
name: 'ShoppingCartItem', | |
... lines 43 - 56 | |
methods: { | |
updateQuantity(event) { | |
this.$emit('updateQuantity', { | |
... lines 60 - 61 | |
quantity: parseFloat(event.target.value), | |
}); | |
}, | |
}, | |
}; | |
</script> | |
... lines 68 - 87 |
Now when we change the quantity... yes! We have a number!
So: unfortunately, we can't just listen to the new updateQuantity
event from shopping-cart
... because shopping-cart
doesn't render cart-item
directly: it's rendered by the shopping cart list component.
But it's no problem! We just need to do one extra step: listen to the event in ShoppingCartList
and re-emit that event so that we can then listen to it from the top-level ShoppingCart
.
Open index.vue
and find where we render shopping-cart-item
: here it is. Add @updateQuantity
to listen to the new event. Normally, we set this to a method name... but we can also just... write code here! Re-emit the event with $emit()
... and I'll keep the same name - updateQuantity
. For the data... just use the same event! We have access to it here as $event
:
<template> | |
<div> | |
... lines 3 - 6 | |
<div v-if="items.length"> | |
... lines 8 - 20 | |
<shopping-cart-item | |
... lines 22 - 24 | |
@updateQuantity="$emit('updateQuantity', $event)" | |
/> | |
... lines 27 - 30 | |
</div> | |
</div> | |
</template> | |
... lines 34 - 64 |
Check this out in the Vue dev tools: now when we change the quantity... yes! There are two events, one emitted from each component.
Finally, we can listen to this event in ShoppingCart
and use it to update the data and save the new quantity back to the server. Let's do that next!
"Houston: no signs of life"
Start the conversation!
// package.json
{
"devDependencies": {
"@fortawesome/fontawesome-free": "^5.15.1", // 5.15.1
"@symfony/webpack-encore": "^0.30.0", // 0.30.2
"axios": "^0.19.2", // 0.19.2
"bootstrap": "^4.4.1", // 4.5.3
"core-js": "^3.0.0", // 3.6.5
"eslint": "^6.7.2", // 6.8.0
"eslint-config-airbnb-base": "^14.0.0", // 14.2.0
"eslint-plugin-import": "^2.19.1", // 2.22.1
"eslint-plugin-vue": "^6.0.1", // 6.2.2
"regenerator-runtime": "^0.13.2", // 0.13.7
"sass": "^1.29.0", // 1.29.0
"sass-loader": "^8.0.0", // 8.0.2
"vue": "^2.6.11", // 2.6.12
"vue-loader": "^15.9.1", // 15.9.4
"vue-template-compiler": "^2.6.11", // 2.6.12
"webpack-notifier": "^1.6.0" // 1.8.0
}
}