Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Loading Animations & Quantity Input

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Our 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!

Loading Animation

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.

Add to Cart Success State

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...

Hooking up the Quantity Input

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.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

This course is also built to work with Vue 3!

What JavaScript libraries does this tutorial use?

// 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
    }
}
userVoice