Building an Online Store with Vue CLI — Part 5
Computed property practice
Part 1 | Part 2 | Part 3 | Part 4 | Part 5:
Previously we implemented “add to cart” functionality and a CounterBadge component that indicates to users when their action was successful. Overall, we’ve covered a lot of concepts so far, including:
- Component design/reuse
- Computed properties
- Methods
- Template syntax
- Vuex state management (getters, actions, and mutations)
- V-if and v-for directives
Before going any further, I thought it might be nice to flesh out some of the existing pages with more features/functionality using things we’ve already learned. In this segment, we’ll add computed properties to Cart.vue to calculate a user’s total.
Step 1: Gather requirements
Imagine we’ve been given the following mockups from a designer:
Based on the designs, the requirements for the “total” section (the grey area on the right side of the above mockups) include six fields that will rely on a variety of data properties (values that only this component has to know or care about) and computed properties (values that will change depending on what data we’re given and what actions the user takes). Parenthesis below denote the names for these computed properties we’ll be making in the future. The requirements are as follows:
- The first field should count the number of items (“cartItemsCount”) in the user’s cart and combine their prices (“itemsSubtotal”)
- The second field should include a select element for choosing a shipping option (“selectedShippingOption”). The element should display a list of possible choices (“shippingOptionsArray”), each of which consists of a text description of the speed and a numeric rate:
shippingOptionsArray: [
{
text: 'One day',
rate: 20,
},
{
text: 'Two days',
rate: 15,
},
{
text: 'Three to five days',
rate: 10,
},
{
text: 'One week or more',
rate: 5,
},
]
3. The third field should add up “itemsSubtotal” and the rate from “selectedShippingOption”. We’ll call this value “subtotal”.
4. The fourth field should apply a certain sales tax percentage to “subtotal”, in this case 6%. We’ll make this field flexible by not hardcoding the sales tax value, though. Instead, it will be customizable. To do so, we’ll need a “salesTax” property, which will be a decimal of some sort, and a “salesTaxApplied” property, which will be the product of “subtotal” times “salesTax”.
5. The fifth field should add “subtotal” and “salesTaxApplied” together. We’ll call this value “total”.
6. The last field is a button that should take users to a page where they could complete their payment.
One final note from the mockups: if the user hasn’t selected a shipping option, all fields except the first one should be empty, consisting of three dashes instead of a number. Once a user makes a choice, all fields should populate with the appropriate computed value.
Next we’ll create the above computed properties and their associated template markup.
Requirement #1: cartItemsCount and itemsSubtotal
The first field should count the number of items (“cartItemsCount”) in the user’s cart and combine their prices (“itemsSubtotal”)
In Cart.vue, we already have one computed property, “cartItems”, which is an array of all the items in the user’s cart. Let’s use this to calculate the new properties we need. Underneath “cartItems” add:
cartItemsCount() {
return this.cartItems.length;
},
This gives us the number of items in the user’s cart.
Below “cartItemsCount” add:
itemsSubtotal() {
return this.cartItems.reduce((total, item) => total + item.price, 0);
},
If you are unfamiliar with the reduce method, you can read about it here. Basically it’s a concise way of “reducing” array items to a single value, in our case: the sum of all cart items’ prices.
Next add the HTML and CSS markup to use these computed properties and display them nicely on the page (lines 26–33 and 95–113 below):
Requirement #2: selectedShippingOption and shippingOptionsArray
The second field should include a select element for choosing a shipping option (“selectedShippingOption”). The element should display a list of possible choices (“shippingOptionsArray”), each of which consists of a text description of the shipping speed and a numeric rate.
Both of these pieces of information are only relevant to Cart.vue, so we can store them as data properties on the component. To do so, create a “data” option like so (it’s a function that returns an object, and the object is what holds the pieces of data we want to use, lines 56–78):
Add two data properties: “selectedShippingOption” (line 58), and “shippingOptionsArray” (lines 59–76). “selectedShippingOption” stores an empty string when the component first renders because a user hasn’t selected an option yet. “shippingOptionsArray” has 4 options, each with a text description and a numeric rate (the dollar amount).
Next, let’s look more closely at how these properties will get used in the template. A normal HTML <select> element looks like this:
<select>
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="mercedes">Mercedes</option>
<option value="audi">Audi</option>
</select>
It contains a set of options, each with a text display value that the user sees, and a “value” attribute that the browser uses behind the scenes to represent the user’s choice.
In Vue templates, it works much the same way…with a few additions. We want Vue to update “selectedShippingOption” when the user makes a choice. The way we do that is with a special attribute called “v-model” on the <select> tag. Next, we add a disabled option that displays some default text for when the page loads and the user hasn’t made a choice yet. Finally, we use v-for to loop through our “shippingOptionsArray” data property to create the possible options for the user. Every option needs a “:key” because we’re using v-for and a “:value”, which for us will be the rate for each option. To construct the user-facing text for each option, we string together each option’s text and rate properties:
<select v-model="selectedShippingOption">
<option disabled value="">Please select an option</option>
<option
v-for="option in shippingOptionsArray"
:key="option.text"
:value="option.rate">
{{ option.text }} (${{ option.rate }})
</option>
</select>
Added to the whole template, it looks like this (lines 32–43):
We can then check to make sure everything is wired correctly by using Chrome as our browser and one of its extensions, Vue DevTools. When a site uses Vue, you can open up Chrome’s dev tools (option-command i on a Mac), and there will be a “Vue” tab on the far right. Open that up and find the Cart component in your App:
At first, “selectedShippingOption” will be an empty string. But when you make a shipping option selection, it should change to that option’s rate, as seen above.
Requirement #3: itemsSubtotal
The third field should add up “itemsSubtotal” and the rate from “selectedShippingOption”. We’ll call this value “subtotal”.
This computed property is pretty straight forward (lines 12–17 below):
If “selectedShippingOption” is not an empty string, it will return the sum of “itemsSubtotal” plus “selectedShippingOption”, both of which need to be converted to numbers because they got coerced into strings at some point. If “selectedShippingOption” is still an empty string, the function will return a string of three dashes.
The markup in the template is similar to the first field we added. After the <li> that wraps our <select> element, add (lines 11–14):
<li class="total-section__item">
<p class="total-section__item__label">Subtotal</p>
<p>{{ subtotal }}</p>
</li>
Requirement #4: salesTax, salesTaxPercentage, and salesTaxApplied
The fourth field should apply a certain sales tax percentage to “subtotal”, in this case 6%. This sales tax decimal should be customizable, not hardcoded (“salesTax”). The field should also display the sales tax as a percentage (“salesTaxPercentage”) and how much sales tax was applied to the order (“salesTaxApplied”) by multiplying “subtotal” by “salesTax”.
“salesTax” can be stored as a data property in our Cart component with a decimal value of 0.06 (line 9 below). “salesTaxPercentage” should be a computed property that return the percentage equivalent of “salesTax” (note the use of template literals to create the desired “6%” string on line 16). “salesTaxApplied”, another computed property, should behave similarly to “subtotal” in that it should only return a numeric value if “selectedShippingOption” is not an empty string. Note: line 20 uses the “toFixed()” method, which returns a number with 2 decimal places.
Add the following HTML after the subtotal
list item:
<li class="total-section__item">
<p class="total-section__item__label">
Tax ({{ salesTax * 100 }}%)
</p>
<p>{{ salesTaxApplied }}</p>
</li>
Requirement #5: total
The fifth field should add “subtotal” and “salesTaxApplied” together, if the user has selected a shipping option. If not, return a string with three dashes. We’ll call this computed property “total”.
Add the computed property:
total() {
if (this.selectedShippingOption) {
return Number(this.subtotal)
+ Number(this.salesTaxApplied);
}
return '---';
},
Add the new template markup:
<li class="total-section__item">
<p class="total-section__item__label">Total</p>
<p>{{ total }}</p>
</li>
Requirement #6: “Check out” button
The last field is a button that should take users to a page where they could complete their payment.
There is no computed property needed for this requirement, just template markup and a new CSS class (lines 12–16, and 25–27):
Outside the total-section-list <ul>, add a check-out <button>. As a precaution against a user clicking it when they haven’t selected a shipping option, I made the button disabled until they do so. So if “selectingShippingOption” is falsey (because it’s an empty string), the button will be disabled.
Here’s the complete Cart.vue file:
In the next article, we’ll create pages for displaying gender-specific items in our store. See you then! 👋