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 SubscribeIn cart-item.vue
, when the quantity changes, we're emitting an updateQuantity
event. But... it occurred to me that we're passing too much data in the event... or really, we're passing more data than we need to.
... 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 |
Think about it: both productId
and colorId
come from this.item
... which is passed to us as a prop... which means that our parent component already knows the productId
and colorId
that this component instance is tied to. Passing these back to our parent in the event data is... redundant!
... 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: parseFloat(event.target.value), | |
}); | |
}, | |
}, | |
}; | |
</script> | |
... lines 68 - 87 |
On a philosophical level, all we should need to pass to updateQuantity
is the new quantity.
This is a tiny detail, but let's clean this up. Copy both productId
and colorId
and then delete them from the event data.
... lines 1 - 37 | |
<script> | |
... lines 39 - 40 | |
export default { | |
name: 'ShoppingCartItem', | |
... lines 43 - 56 | |
methods: { | |
updateQuantity(event) { | |
this.$emit('updateQuantity', { | |
quantity: parseFloat(event.target.value), | |
}); | |
}, | |
}, | |
}; | |
</script> | |
... lines 66 - 85 |
Now go to index.vue
. When we emit the event here, we do need to include productId
and colorId
so that ShoppingCart
knows which item's quantity is being updated. We can add that info right here: call $emit()
and this time pass an object. Paste those keys - productId
and colorId
- change this.item
to item
... and then make sure to continue including quantity
set to $event.quantity
.
<template> | |
<div> | |
... lines 3 - 6 | |
<div v-if="items.length"> | |
... lines 8 - 20 | |
<shopping-cart-item | |
... lines 22 - 24 | |
@updateQuantity="$emit('updateQuantity', { | |
productId: item.product['@id'], | |
colorId: item.color ? item.color['@id'] : null, | |
quantity: $event.quantity, | |
})" | |
/> | |
... lines 31 - 34 | |
</div> | |
</div> | |
</template> | |
... lines 38 - 68 |
That's it! It worked fine before... and it works fine now... but this feels better.
The one thing that is not working when we change the quantity is the cart header: it's still not updating. But we do have logic to handle this. Open shopping-cart.vue
and find the updateQuantity()
method.
We know that this component uses a mixin: get-shopping-cart.js
. And that holds the code to update the shopping cart count in the header. We run this after adding a new product to the cart.
Ok then: if we isolated this into its own method, we could call that from inside of shopping-cart.vue
when the quantity is updated. Let's do it! But wait, let's go even further. We have this nice mixin... whose job is to hold cart data and methods related to that data. So it makes sense to also include a method here to handle everything related to updating an item's quantity.
Check it out: in the mixin, add a new method called updateProductQuantity()
with 3 arguments for the 3 pieces of info this needs: productId
, colorId
and quantity
.
... lines 1 - 7 | |
export default { | |
... lines 9 - 18 | |
methods: { | |
... lines 20 - 39 | |
updateProductQuantity(productId, colorId, quantity) { | |
... line 41 | |
}, | |
}, | |
}; |
Now go to shopping-cart
, copy the updateCartItemQuantity()
function that updates the quantity and makes the AJAX call, and paste it here. When I did that, PhpStorm automatically added the import on top for me... though I don't love that syntax.
import { | |
... lines 2 - 4 | |
updateCartItemQuantity, | |
} from '@/services/cart-service'; | |
export default { | |
... lines 9 - 18 | |
methods: { | |
... lines 20 - 39 | |
updateProductQuantity(productId, colorId, quantity) { | |
updateCartItemQuantity(this.cart, productId, colorId, quantity); | |
}, | |
}, | |
}; |
Anyways, now that we have this method, we can call it from our shopping-cart
component. Remove the console.log()
and just say this.updateProductQuantity()
passing the same arguments, except that we don't need to pass this.cart
: the mixin already has that.
... lines 1 - 22 | |
<script> | |
... lines 24 - 30 | |
export default { | |
name: 'ShoppingCart', | |
... lines 33 - 75 | |
methods: { | |
... lines 77 - 82 | |
updateQuantity({ productId, colorId, quantity }) { | |
this.updateProductQuantity(productId, colorId, quantity); | |
}, | |
}, | |
}; | |
</script> | |
... lines 89 - 99 |
Sweet! Just a little bit of reorganization so that more cart-related logic lives in the mixin. And if we try it... yup! It's not broken. We're awesome!
Thanks to this, updating the cart header will be even easier. Start by copying the header logic and scrolling down so we can create a new method. Call it updateCartHeaderTotal()
and paste!
... lines 1 - 7 | |
export default { | |
... lines 9 - 44 | |
updateCartHeaderTotal() { | |
document.getElementById('js-shopping-cart-items') | |
.innerHTML = getCartTotalItems(this.cart).toString(); | |
}, | |
}, | |
}; |
Now, very simply, at the end of addProductToCart()
call this.updateCartHeaderTotal()
and... repeat that in updateProductQuantity
: this.updateCartHeaderTotal()
.
Oh, and we don't need to do this, but I'm going to add an await
and then make the method async
. This will now wait for that quantity AJAX call to finish and then update the quantity in the header.
... lines 1 - 7 | |
export default { | |
... lines 9 - 18 | |
methods: { | |
async addProductToCart(product, selectedColorId, quantity) { | |
... lines 21 - 35 | |
this.updateCartHeaderTotal(); | |
}, | |
... line 38 | |
async updateProductQuantity(productId, colorId, quantity) { | |
await updateCartItemQuantity(this.cart, productId, colorId, quantity); | |
this.updateCartHeaderTotal(); | |
}, | |
... lines 44 - 48 | |
}, | |
}; |
Let's try it! I'll do a full page refresh to be safe. The cart header says 13. Increase a quantity... boom! Everything updates - including the cart header.
Next: let's hook up the last part of the cart functionality: the remove from cart button.
"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
}
}