Progress
A tracker should show at a glance how your day is going: 2 / 5 done today, a bar that fills up as you check habits off, and some praise once you've done them all.
All of that is made from habits, so it's a job for computed values, as in the lesson Computed Values and Watchers. Vue recomputes them whenever a habit changes, and the page changes with them:
const doneCount = computed(() => {
return habits.value.filter((habit) => habit.done).length;
});
const percent = computed(() => {
if (habits.value.length === 0) {
return 0;
}
return Math.round((doneCount.value / habits.value.length) * 100);
});
const allDone = computed(() => {
return habits.value.length > 0 && doneCount.value === habits.value.length;
});
doneCount is how many habits are done, and percent is how much that is of all of them, in percent: 2 of 5 is 40. Math.round() rounds it to a whole number (1 of 3 would be 33.333…). Without the if, a list with no habits would divide by zero, and in JavaScript, 0 / 0 is NaN, “not a number”. allDone is true when every habit is done, but not when there are no habits at all.
The bar is two elements: a gray strip with the class bar, and a green one inside it with the class fill, both styled in style.css. The green one is as wide as the percentage. The width changes, so it goes in :style, as in the lesson Template Syntax, and since CSS needs a unit, the number gets '%':
<div class="progress">
<p>{{ doneCount }} / {{ habits.length }} done today</p>
<div class="bar">
<div class="fill" :style="{ width: percent + '%' }"></div>
</div>
<p v-if="allDone">All done for today! 🎉</p>
</div>
The progress belongs to the loaded page, so it should show only together with the list, under v-else. But only one element can have v-else, so wrap both in <template v-else>, as in the lesson Conditions and Lists: it groups them without adding an element to the page.
Typical mistakes:
doneCount / habits.value.length, without.valueondoneCount. In the script, a computed value is a ref too, and dividing the ref itself givesNaN, so the bar stays empty.width: percent, without'%'. The browser ignores a width without a unit, and the bar doesn't grow.
Instructions
- 1
Add
computedto the import from"vue", and write the three computed values from above under the refs. In the template, wrap the list in<template v-else>(theulno longer needsv-else), and put thedivwith the classprogressfrom above at the start of the<template v-else>, for now with just its first paragraph. Check a habit off:1 / 5 done today. - 2
Add the bar and the praise to the
div, as above. Check two habits off, and the bar fills to 40%. Check them all off, and the page praises you.