Featured Sidebar

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

Here's our mission: to render a featured product on the cart sidebar with fully-functional "add to cart" controls. OoooOoOo.

Start by going to the asset/services/ directory and opening products-service.js. This file was included in the starting code for the tutorial and it already has a function called fetchFeaturedProducts. This returns a Promise that resolves to an Axios response that contains an array of products that our API says should be "featured". Phew!

... lines 1 - 22
export function fetchFeaturedProducts() {
return axios.get('/api/products', {
params: { featured: 1 },
});
}
... lines 28 - 57

Now go into shopping-cart.vue: the component that renders the entire cart page. Here's what I'm thinking: when this component is created, we make an AJAX request to fetch the featured products. We'll then find the first featured product in that collection, set it onto a new piece of data on this component and render it up here in the sidebar.

Let's do it!

Start by adding the new piece of data: featuredProduct set to null.

... lines 1 - 26
<script>
... lines 28 - 34
export default {
name: 'ShoppingCart',
... lines 37 - 42
data() {
return {
... lines 45 - 46
featuredProduct: null,
};
},
... lines 50 - 102
};
</script>
... lines 105 - 115

To make the AJAX request, let's create a method and then call that method from created. How about: loadFeaturedProducts. Inside, say const featuredProducts = and execute that fetchFeaturedProducts() function we just saw. Make sure to auto-complete this so PhpStorm adds the import on top.

fetchFeaturedProducts returns a Promise whose data resolves to a response. So what we really want to do here is say await and then this will be an Axios response. Put that in parentheses... including around the await... then grab the data key from the response... and hydra:member from the data. That's the field on the JSON that actually holds the collection of products. Oh, and we, of course, now need to make the method async.

... lines 1 - 26
<script>
... line 28
import { fetchFeaturedProducts, fetchProductsById } from '@/services/products-service';
... lines 30 - 34
export default {
name: 'ShoppingCart',
... lines 37 - 81
methods: {
... lines 83 - 92
async loadFeaturedProduct() {
const featuredProducts = (await fetchFeaturedProducts()).data['hydra:member'];
... lines 95 - 100
},
},
};
</script>
... lines 105 - 115

After this, because we only need a single featured product, I'll do a little sanity check: if featureProducts.length === 0, just return. We're not going to code too much for this case. Finish this with this.featuredProduct = featuredProducts[0].

... lines 1 - 26
<script>
... line 28
import { fetchFeaturedProducts, fetchProductsById } from '@/services/products-service';
... lines 30 - 34
export default {
name: 'ShoppingCart',
... lines 37 - 81
methods: {
... lines 83 - 92
async loadFeaturedProduct() {
const featuredProducts = (await fetchFeaturedProducts()).data['hydra:member'];
if (featuredProducts.length === 0) {
return;
}
this.featuredProduct = featuredProducts[0];
},
},
};
</script>
... lines 105 - 115

Sweet! Wait... why is ESLint mad? Ah, it wants me to be cooler than I am and use array destructuring. Ok then! Put this.featuredProducts in an array and set that to featuredProducts.

... lines 1 - 26
<script>
... lines 28 - 34
export default {
name: 'ShoppingCart',
... lines 37 - 81
methods: {
... lines 83 - 92
async loadFeaturedProduct() {
... lines 94 - 99
[this.featuredProduct] = featuredProducts;
},
},
};
</script>
... lines 105 - 115

That will accomplish the same thing. Now we can copy the method name, head up to created, and call it! Make sure you put it above the this.colors line: we don't want our code to await for that to finish before fetching the featured product. In this order, both AJAX requests will effectively start at the same moment.

Ok! To make sure this is working, find your shiny browser, refresh and go to the Vue Dev Tools. On Components, find ShoppingCart. Let's see... there! We do have a featuredProduct data.

The New cart-sidebar Component

The sidebar itself is going to be complex enough that I think we should put it into its own component. If you downloaded the course code, you should have a tutorial/ directory with a cart-sidebar.vue file inside.

Copy that into assets/components/shopping-cart/.

<template>
<div :class="[$style.component, 'p-3', 'mb-5']">
<h5 class="text-center">
Featured Product!
</h5>
<img
class="d-block"
:src="featuredProduct.image"
:alt="featuredProduct.name"
>
<div class="pt-3">
<h6>
{{ featuredProduct.name }}
${{ price }}
</h6>
</div>
</div>
</template>
<script>
import formatPrice from '@/helpers/format-price';
export default {
name: 'ShoppingCartSidebar',
props: {
featuredProduct: {
type: Object,
required: true,
},
},
computed: {
price() {
return formatPrice(this.featuredProduct.price);
},
},
};
</script>
<style lang="scss" module>
@import '~styles/components/light-component.scss';
.component :global {
@include light-component;
img {
max-width:100%;
max-height:100%;
}
}
</style>

There's nothing fancy here: it has a featuredProduct required prop... and then it renders its data! Let's use this inside shopping-cart. You know the drill: go above the component and import CardSidebar from @/components/shopping-cart/cart-sidebar. Add that to components...

... lines 1 - 31
<script>
... lines 33 - 38
import CartSidebar from '@/components/shopping-cart/cart-sidebar';
export default {
name: 'ShoppingCart',
components: {
... lines 44 - 46
CartSidebar,
},
... lines 49 - 109
};
</script>
... lines 112 - 122

... then scroll up so we can use it. Let's see: make the aside not be self-closing... then put <cart-sidebar inside with a v-if set to featuredProduct.

<template>
<div :class="[$style.component, 'container-fluid']">
<div class="row">
<aside class="col-xs-12 col-lg-3">
<cart-sidebar
v-if="featuredProduct"
... line 7
/>
</aside>
... lines 10 - 26
</div>
</div>
</div>
</template>
... lines 31 - 122

I'm just coding defensively in case there is no feature product for some reason. The one prop we need to pass is :featuredProduct set to our featuredProduct data.

<template>
<div :class="[$style.component, 'container-fluid']">
<div class="row">
<aside class="col-xs-12 col-lg-3">
<cart-sidebar
v-if="featuredProduct"
:featured-product="featuredProduct"
/>
</aside>
... lines 10 - 26
</div>
</div>
</div>
</template>
... lines 31 - 122

Awesomesauce! Move over and... there it is! The feature product is our beloved Inflatable Sofa!

The last step is to make this functional by importing and using the cart-add-controls component. Let's do that next!

Leave a comment!

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