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 SubscribeHere'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 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!
"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
}
}