go to part...
Displaying the Cart Items
Let's code the displaying of cart items first so that we could see the products being added right away as we implement the feature. This is actually going to be pretty easy, just a simple loop. Update the Cart.vue
template section like this:
<template>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link" href="">
Cart ({{ $store.state.cartCount }})
</a>
<div v-if="$store.state.cart.length > 0" class="navbar-dropdown is-boxed is-right">
<a v-for="item in $store.state.cart"
:key="item.id"
class="navbar-item"
href=""
>
{{ item.title }} x{{ item.quantity }} - ${{ item.totalPrice }}
</a>
<a class="navbar-item" href="">
Total: ${{ totalPrice }}
</a>
<hr class="navbar-divider">
<a class="navbar-item" href="">
Checkout
</a>
</div>
<div v-else class="navbar-dropdown is-boxed is-right">
<a class="navbar-item" href="">
Cart is empty
</a>
</div>
</div>
</template>
This is again just standard Vue stuff. Of course we won't see the results just yet. Let's move on.
Adding Products to the Cart
To add an item to the cart we need to modify the cart
state of our Vuex store. To modify a state we should use a Vuex mutation
. Mutations are pretty much like setters for the states. Let's add a mutation to add an item to the cart inside the store.js
file.
let store = {
state: {
cart: [],
cartCount: 0,
},
mutations: {
addToCart(state, item) {
console.log(item.title);
}
}
};
Note that mutations always receive a state
object as the first argument. For now we're just going to log the passed item's title. Let's go back to the ProductsList.vue
component. Find the <td>
element with the Add to Cart
text in the template and replace it with a button:
<td>
<button class="button is-success"
@click="addToCart(item)">Add to Cart</button>
</td>
Then add the addToCart
method below inside the script
section. Inside it we'll commit
a Vuex state mutation passing along a product object.
methods: {
addToCart(item) {
this.$store.commit('addToCart', item);
}
}
Refresh the page and you should see the title of a product in your browser's console every time you click on a Add to Cart
button near it. Back to the addToCart
mutation inside the store.js
file. The only thing we need to do is to push the item to the cart
array and increment the cartCount
counter.
mutations: {
addToCart(state, item) {
state.cart.push(item);
state.cartCount++;
}
}
This code is enough for you to see the items being added to the cart and the counter go up, no additional actions are required. Cool, right? Although, it's not gonna look the way it is supposed to right now. You'll also get errors in your browser's console. This is because we've referenced quantity
and totalPrice
properties when displaying cart items and our products don't have those by default. These are the further changes required to the mutation:
addToCart(state, item) {
let found = state.cart.find(product => product.id == item.id);
if (found) {
found.quantity ++;
found.totalPrice = found.quantity * found.price;
} else {
state.cart.push(item);
Vue.set(item, 'quantity', 1);
Vue.set(item, 'totalPrice', item.price);
}
state.cartCount++;
}
So we're checking if the product to be added is already in the cart. If it is - we're just incrementing its quantity and recalculating the total price. If it's not - we're pushing it to the cart
array and then manually adding the missing properties via Vue.set()
so that Vue can watch those properties in the future.
Try adding products to the cart again. Try adding the same product multiple times. Now you'll see quantity and total price for each individual item. The only thing left is to calculate the total price of the whole cart. To do this we'll just add a computed property to Cart.vue
.
computed: {
totalPrice() {
let total = 0;
for (let item of this.$store.state.cart) {
total += item.totalPrice;
}
return total.toFixed(2);
}
}
Removing Products from the Cart
Now let's handle item removal. First, open Cart.vue
. Let's add a remove button near each item in the template.
<a v-for="item in $store.state.cart"
:key="item.id"
class="navbar-item"
href=""
>
<span class="removeBtn"
title="Remove from cart"
@click.prevent="removeFromCart(item)">X</span>
{{ item.title }} x{{ item.quantity }} - ${{ item.totalPrice }}
</a>
Add a bit of styling to the button at the end of the component file.
<style>
.removeBtn {
margin-right: 1rem;
color: red;
}
</style>
Finally, add the removeFromCart
method which will simply call a Vuex mutation.
methods: {
removeFromCart(item) {
this.$store.commit('removeFromCart', item);
}
}
Now move to store.js
and let's create the missing mutation.
removeFromCart(state, item) {
let index = state.cart.indexOf(item);
if (index > -1) {
let product = state.cart[index];
state.cartCount -= product.quantity;
state.cart.splice(index, 1);
}
}
That was really simple. If the item is in the cart, we're subtracting the total products count in the cart and then removing the item from the cart
array.
Persisting the Cart
The only problem we have left is the cart gets wiped out every time we refresh the page or move to another page. To persist the cart we can use HTML5 local storage. Add this new mutation to the store.js
file.
saveCart(state) {
window.localStorage.setItem('cart', JSON.stringify(state.cart));
window.localStorage.setItem('cartCount', state.cartCount);
}
Now we have to commit this mutation at the end of both addToCart
and removeFromCart
mutations like this:
this.commit('saveCart');
At last, let's read these value at every page load. First, add these two lines at the very beginning of the store.js
file:
let cart = window.localStorage.getItem('cart');
let cartCount = window.localStorage.getItem('cartCount');
And then let's change the store's state initialization to this:
state: {
cart: cart ? JSON.parse(cart) : [],
cartCount: cartCount ? parseInt(cartCount) : 0,
},
That's it! The cart data won't be lost on page reload now, and it should even stay there if you close your browser.
Checkout
The only thing left for you to do is to send the cart data to the server. You can easily do this with axios
, for example like this:
let data = {
cart: JSON.stringify(this.$store.state.cart)
}
axios.post('/your-checkout-endpoint', data);
Then you can parse the data on the Laravel (PHP) side like this:
$cart = json_decode(request('cart'));
Don't accept this data as is though, as everything coming from the frontend should be double-checked. It's better to extract an array of product ids and then fetch the rest of the product data from the database, this way you'll be sure the data is not compromised.
All the materials at voerro are absolutely free and are worked on in the author's spare time. If you found any of the tutorials helpful, please consider supporting the project. Thank you!