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 SubscribeWe've successfully extracted most of the markup and functionality for the "add to cart" controls into this new component. Our template still references a few undefined methods, but thanks to the props and data we added to the component, all the variables should now be defined. And that means, we should be able to get this to render.
Back in index.vue
, let's import this puppy: import CartAddControls
from and I'll be lazy and just use ./cart-add-controls
because they live in the same directory. Add this to components
...
... lines 1 - 53 | |
<script> | |
... lines 55 - 59 | |
import CartAddControls from './cart-add-controls'; | |
... line 61 | |
export default { | |
name: 'ProductShow', | |
components: { | |
... lines 65 - 66 | |
CartAddControls, | |
}, | |
... lines 69 - 106 | |
}; | |
</script> | |
... lines 109 - 122 |
Then head up to the template: <cart-add-controls
. Pass this the props it needs: :product
set to product
and then allowAddToCart
. This should be true if the cart
is done loading.
As a reminder, this component uses the get-shopping-cart
mixin, which means it has a data called cart
. We can use that here: allowAddToCart
if cart !== null
.
<template> | |
<div> | |
... lines 3 - 8 | |
<div | |
... lines 10 - 12 | |
> | |
... lines 14 - 30 | |
<div class="col-8 p-3"> | |
... lines 32 - 33 | |
<div class="row mt-4 align-items-center"> | |
... lines 35 - 38 | |
<div class="col-8 p-3"> | |
<cart-add-controls | |
... line 41 | |
:product="product" | |
:allow-add-to-cart="cart !== null" | |
... lines 44 - 45 | |
/> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
... lines 53 - 122 |
Next add :add-to-cart-loading
set to the addToCartLoading
data - which also comes from the mixin - and :add-to-cart-success
set to addToCartSuccess
.
<template> | |
<div> | |
... lines 3 - 8 | |
<div | |
... lines 10 - 12 | |
> | |
... lines 14 - 30 | |
<div class="col-8 p-3"> | |
... lines 32 - 33 | |
<div class="row mt-4 align-items-center"> | |
... lines 35 - 38 | |
<div class="col-8 p-3"> | |
<cart-add-controls | |
... line 41 | |
:product="product" | |
:allow-add-to-cart="cart !== null" | |
:add-to-cart-loading="addToCartLoading" | |
:add-to-cart-success="addToCartSuccess" | |
/> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
... lines 53 - 122 |
Done! Let's try it out! Move over to the browser and... yes! It renders! Well, yes, it does render, but Vue is totally mad because our template references some undefined methods. Time to fix those!
The first is updateSelectedColor
. Go into the original component and find this method. This updates this.selectedColorId
... which no longer lives in this component anyways. Copy the method, delete it, go to cart-add-controls
, scroll down, add a new methods
section and paste.
... lines 1 - 32 | |
<script> | |
... lines 34 - 35 | |
export default { | |
name: 'ProductCartAddControls', | |
... lines 38 - 64 | |
methods: { | |
updateSelectedColor(iri) { | |
this.selectedColorId = iri; | |
}, | |
}, | |
}; | |
</script> | |
... lines 72 - 80 |
Thanks to this, when the color-selector
component emits its color-selected
event, we call this method and it updates the selectedColorId
data.
The last undefined method we're referencing is addToCart()
, which also lives in the parent index.vue
component. This one is a bit more complex. We can't just copy the method and move it... because it calls this.addProductToCart()
. If I hold Command or Ctrl and click that method, it comes from the mixin.
And, more importantly, adding an item to the cart modifies the cart
data... and so that logic should happen inside the component that holds that data, which is index.vue
.
In other words, this is another classic situation where we need to emit an event from a child component so that its parent component can listen to that event and update some data.
And... that's actually awesome! Emitting an event from a component is a great way to make that component generic and reusable. Anyone using this will be able to do whatever logic they want when the "add to cart" button is pressed.
Down in methods, add addToCart()
. But now, instead of modifying some data, say this.$emit()
to emit an add-to-cart
event. Pass this the two things that our component needs to communicate: quantity
set to this.quantity
and selectedColorId
set to this.selectedColorId
.
... lines 1 - 32 | |
<script> | |
... lines 34 - 35 | |
export default { | |
name: 'ProductCartAddControls', | |
... lines 38 - 64 | |
methods: { | |
... lines 66 - 68 | |
addToCart() { | |
this.$emit('add-to-cart', { | |
quantity: this.quantity, | |
selectedColorId: this.selectedColorId, | |
}); | |
}, | |
}, | |
}; | |
</script> | |
... lines 78 - 86 |
Finally, back in index.vue
, when we include cart-add-controls
, we can listen to this: @add-to-cart=
and then call the addToCart()
method that we already have... we'll just need to tweak it slightly.
<template> | |
<div> | |
... lines 3 - 8 | |
<div | |
... lines 10 - 12 | |
> | |
... lines 14 - 30 | |
<div class="col-8 p-3"> | |
... lines 32 - 33 | |
<div class="row mt-4 align-items-center"> | |
... lines 35 - 38 | |
<div class="col-8 p-3"> | |
<cart-add-controls | |
... lines 41 - 45 | |
@add-to-cart="addToCart" | |
/> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
... lines 54 - 119 |
Head down to find addToCart()
. This will now receive the event
object that we're sending to the add-to-cart
event: with quantity
and selectedColorId
. Instead of adding an event
argument and reading those keys off of it, let's put our fancy hat on and use object destructuring: {}
then quantity
and selectedColorId
. Remove the this
from both of those variables below.
... lines 1 - 54 | |
<script> | |
... lines 56 - 62 | |
export default { | |
name: 'ProductShow', | |
... lines 65 - 98 | |
methods: { | |
addToCart({ quantity, selectedColorId }) { | |
this.addProductToCart(this.product, selectedColorId, quantity); | |
}, | |
}, | |
}; | |
</script> | |
... lines 106 - 119 |
Phew! We moved a lot of stuff around to get this working... but we now have a beautiful component that receives the props it needs, manages the data it needs and emits an event when it needs to.
So let's try it! Move over and do a full refresh just in case. We have 12 items in the cart right now. Add 3 blue couches and... yea! The loading animation was right, the check box shows and the cart header updated! And when we go to the cart page, it's there!
Next: let's leverage our hard work to add the featured product sidebar.
"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
}
}