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 SubscribeOur add to cart button works... though it's not very obvious. We click "Add to Cart" and... nothing happens! Sure, we can refresh and see that the shopping cart header is increasing... but come on! We need some fireworks when an item is added!
Find the data()
section of the component. Our first mission is to add a loading animation while the item is being added. To track that "state", create a new addToCartLoading
data set to false
by default.
... lines 1 - 69 | |
<script> | |
... lines 71 - 77 | |
export default { | |
name: 'ProductShow', | |
... lines 80 - 90 | |
data() { | |
return { | |
cart: null, | |
addToCartLoading: false, | |
... lines 95 - 96 | |
}; | |
}, | |
... lines 99 - 129 | |
}; | |
</script> | |
... lines 132 - 149 |
Then, inside of the method, put this.addToCartLoading = true
on top... and this.addToCartLoading = false
on the bottom.
Oh, but now we will need to make the AJAX call wait until it finishes. Add await
and then, of course, we need to make the function async
.
... lines 1 - 69 | |
<script> | |
... lines 71 - 77 | |
export default { | |
name: 'ProductShow', | |
... lines 80 - 90 | |
data() { | |
return { | |
cart: null, | |
addToCartLoading: false, | |
... lines 95 - 96 | |
}; | |
}, | |
... lines 99 - 118 | |
methods: { | |
async addToCart() { | |
this.addToCartLoading = true; | |
await addItemToCart(this.cart, { | |
... lines 123 - 125 | |
}); | |
this.addToCartLoading = false; | |
}, | |
}, | |
}; | |
</script> | |
... lines 132 - 149 |
I love it! Copy the new addToCartLoading
and head up to the template. Let's see.. right after the button, add an <i />
tag that's self-closing with v-show="addToCartLoading"
. Our project uses FontAwesome, so we can turn this into a loading animation with class="fas fa-spinner fa-spin"
.
<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"> | |
<div class="d-flex align-items-center justify-content-center"> | |
... lines 41 - 50 | |
<button | |
... lines 52 - 54 | |
> | |
Add to Cart | |
<i | |
v-show="addToCartLoading" | |
class="fas fa-spinner fa-spin" | |
/> | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
... lines 69 - 149 |
Yes, we do have a Loading
component, but this will look nicer in this spot. Back at the browser, we shouldn't even need to refresh. Yep! It instantly works and looks better.
But, hmm. I also want a user to get a happy feeling once the "add to cart" finishes. Back in the component, go find data
again: I want to be able to track whether or not an "add to cart" has finished successfully. Create a new piece of data for this called addToCartSuccess
set to false
.
... lines 1 - 73 | |
<script> | |
... lines 75 - 81 | |
export default { | |
name: 'ProductShow', | |
... lines 84 - 94 | |
data() { | |
return { | |
... lines 97 - 98 | |
addToCartSuccess: false, | |
... lines 100 - 101 | |
}; | |
}, | |
... lines 104 - 136 | |
}; | |
</script> | |
... lines 139 - 156 |
Now, in the method, after the AJAX call finishes, say this.addToCartSuccess = true
. Oh, and in case we add multiple things in the cart, when the method starts, make sure this is false
. So it starts false
, then goes to true
once we're done.
... lines 1 - 73 | |
<script> | |
... lines 75 - 81 | |
export default { | |
name: 'ProductShow', | |
... lines 84 - 94 | |
data() { | |
return { | |
... lines 97 - 98 | |
addToCartSuccess: false, | |
... lines 100 - 101 | |
}; | |
}, | |
... lines 104 - 123 | |
methods: { | |
async addToCart() { | |
this.addToCartLoading = true; | |
this.addToCartSuccess = false; | |
... lines 128 - 132 | |
this.addToCartLoading = false; | |
this.addToCartSuccess = true; | |
}, | |
}, | |
}; | |
</script> | |
... lines 139 - 156 |
Back up in the template, we're going to use this in a very similar way. Copy the <i>
tag, use addToCartSuccess
and, for the class
, use fas fa-check
for a cool, not-spinning, check mark.
<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"> | |
<div class="d-flex align-items-center justify-content-center"> | |
... lines 41 - 60 | |
<i | |
v-show="addToCartSuccess" | |
class="fas fa-check" | |
/> | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
... lines 73 - 156 |
Let's try it! Back on the page, click and... oh. That felt good. I feel this uncontrollable need to keep shopping...
Ok: when we call addItemToCart
, in addition to product
, there are two other pieces of data: color
- for products that come in multiple colors - and quantity
. Right now, both are hardcoded to boring values.
Let's fix quantity
first. What we need to do is basically bind this input
to a piece of data so that when the input changes, the data changes! And... we know how to do that!
Start by adding the new data key: quantity
with a default value of 1.
... lines 1 - 82 | |
export default { | |
name: 'ProductShow', | |
... lines 85 - 95 | |
data() { | |
return { | |
cart: null, | |
quantity: 1, | |
... lines 100 - 103 | |
}; | |
}, | |
... lines 106 - 138 | |
}; | |
</script> | |
... lines 141 - 158 |
Next, up in the template, Vue makes it beautifully simple: v-model="quantity"
.
Thanks Vue! Now, when the input changes, the quantity
data will change. And hmm, to be a bit fancier, I'll say v-model.number
.
<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"> | |
<div class="d-flex align-items-center justify-content-center"> | |
... lines 41 - 44 | |
<input | |
v-model.number="quantity" | |
... lines 47 - 49 | |
> | |
... lines 51 - 66 | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</template> | |
... lines 74 - 158 |
That cool trick will convert the string input value into a Number
type. Now we can use this below: instead of quantity: 1
, we want quantity: this.quantity
.
... lines 1 - 74 | |
<script> | |
... lines 76 - 82 | |
export default { | |
name: 'ProductShow', | |
... lines 85 - 125 | |
methods: { | |
async addToCart() { | |
... lines 128 - 129 | |
await addItemToCart(this.cart, { | |
... lines 131 - 132 | |
quantity: this.quantity, | |
}); | |
... lines 135 - 136 | |
}, | |
}, | |
}; | |
</script> | |
... lines 141 - 158 |
Head back to the browser. Nice! A moment ago, the input
was blank. Now, it starts at 1. Increase this to 3. We currently have 12 items in the cart. Hit "Add to Cart", refresh and... woo! 15!
Ok: the last missing piece is the product color. Well, this product doesn't come in multiple colors, so it's finished. But if you click "Furniture" and then the inflatable sofa, this does come in multiple colors. Next: we need to grab the selected color and send that in the Ajax call. And you know what else? Whenever we hit "Add to Cart", I'm tired of the header not updating until we refresh. Let's also fix that... which is a bit interesting because it lives outside of our Vue component.
"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
}
}