Product Template & Color Selector

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Time to build this product show page and make it shine! This will involve adding... mostly just a bunch of HTML that prints product data... so let's cheat! If you downloaded the course code, you should have a tutorial/ directory. Let me find mine. This holds a few files that we're going to use. Start by copying colors-service.js into assets/service/. We'll check that out in a minute.

import axios from 'axios';
/**
* Gets the color information from our database
*
* @return {Promise}
*/
export function fetchColors() {
return axios({
method: 'get',
url: '/api/colors',
});
}

Also copy color-selector.vue and put that into assets/components/.

<template>
<div :class="$style.component">
<span
v-for="(color) in colors"
:key="color['@id']"
:class="{ selected: color['@id'] === selectedIRI}"
:title="color.name"
:style="{ backgroundColor: `#${color.hexColor}` }"
@click="selectColor(color['@id'])"
/>
</div>
</template>
<script>
import { fetchColors } from '@/services/colors-service';
export default {
name: 'ColorSelector',
data() {
return {
colors: [],
selectedIRI: null,
};
},
async created() {
this.colors = (await fetchColors()).data['hydra:member'];
},
methods: {
selectColor(iri) {
this.selectedIRI = iri;
this.$emit('color-selected', iri);
},
},
};
</script>
<style lang="scss" module>
... lines 38 - 54
</style>

Okay: we haven't talked about it yet, but some products come in several colors. This means that the user will need to choose which color they want when they add that product to their cart. This simple component loads all of the colors inside its created function. Yep, there is literally an API endpoint that returns information - like hex colors - for every possible color in the system.

It then renders each color via different color swatches - little boxes. When a user clicks on one, the component emits - via this selectColor() method - a color-selected event. That's going to be super handy later. Also, when it loops over the colors, it uses a dynamic selected class so we can see which color is currently selected.

So it's pretty straightforward, but this is also a pretty cool, reusable component! Though, it doesn't contain anything new to us.

For the rest of the product-show component, go into the template and, after the v-if, I'll paste a bunch of HTML! You can copy this from the code block on this page. Oh, I've got some extra whitespace.

<template>
<div>
<loading v-if="loading" />
<div v-if="product">
<title-component :text="product.name" />
</div>
<div
v-if="product"
:class="$style.product"
class="row"
>
<div class="col-4 pt-3">
<img
class="d-block"
:src="product.image"
:alt="product.name"
>
<div class="p-2">
<small>brought to you by </small>
<small
class="d-inline"
v-text="product.brand"
/>
</div>
</div>
<div class="col-8 p-3">
<div v-text="product.description" />
<div class="row mt-4 align-items-center">
<div class="col-4">
Price: <strong>${{ price }}</strong>
</div>
<div class="col-8 p-3">
<div class="d-flex align-items-center justify-content-center">
<color-selector
v-if="product.colors.length !== 0"
/>
<input
class="form-control mx-3"
type="number"
min="1"
>
<button
class="btn btn-info btn-sm"
>
Add to Cart
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
... lines 63 - 113

Then... all the way at the bottom, after the script, I'll also paste a small style tag.

... lines 1 - 96
<style lang="scss" module>
@import '~styles/components/light-component';
.product {
@include light-component;
img {
max-width:100%;
max-height:100%;
}
input {
width: 60px;
}
}
</style>

Scroll back up to the template code that we just pasted. There's nothing too special: there's a new div that only renders v-if there is a product. Inside, we render the image and other data like brand and description.

Near the bottom, there is also an input for the quantity... but this is non-functional. What I mean is: it's not bound to a piece of data and it doesn't have any listeners on it. It's just... an input. Below that is a button that also doesn't do anything yet.

The Color Selector

The most interesting part might be that we're rendering the <color-selector> component. If this product comes in multiple colors, we render the color selector.

Oh, but we haven't imported this yet. Down here, import ColorSelector from @/components/color-selector... and put that inside components.

... lines 1 - 63
<script>
import { fetchOneProduct } from '@/services/products-service';
import ColorSelector from '@/components/color-selector';
... lines 67 - 69
export default {
name: 'ProductShow',
components: {
ColorSelector,
... lines 74 - 75
},
... lines 77 - 95
};
</script>
... lines 98 - 115

One other special thing we do in the template is print a price computed property... which we don't have yet. Open product-card. This already has a computed price property that formats the price before rendering it. Copy that, go to product-show, find computed and paste.

... lines 1 - 63
<script>
import formatPrice from '@/helpers/format-price';
... lines 66 - 70
export default {
name: 'ProductShow',
... lines 73 - 89
computed: {
/**
* Returns a formatted price for the product
* @returns {string}
*/
price() {
return formatPrice(this.product.price);
},
},
... lines 99 - 105
};
</script>
... lines 108 - 125

Thanks to PhpStorm, when I pasted that, it added the formatPrice import for me. Awesome!

But... I've been noticing that my Encore build is super mad! What's up?

export getColors was not found in @/services/colors-service.

Ah, I bet that's my fault! Open the colors-service.js file. Yep! The export is called fetchColors, but in color-selector.vue, I'm using getColors(). I'll change that and fix the file in the tutorial/ directory so you don't have this issue.

Now the build is happy. It's pretty awesome that Webpack won't let you get away with a mistake like that.

Ok: with any luck, the page should work. When we move over... yes! I'll refresh to be sure. We have an image, title, description and formatted price! Beautiful!

To see an example of the color selector, go to Furniture and click this big cool Inflatable Sofa that's great for office productivity. Boom! A neat lil' color selector with a border on the active element. And even though we're not listening to it yet, if you go to the Vue dev tools and click "Events"... each time I click a color, it dispatches a color-selected event. We're going to take advantage of that soon.

Ok! I think we're ready to start adding stuff to our cart! Let's talk about the cart API 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
    }
}