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 .value on doneCount. In the script, a computed value is a ref too, and dividing the ref itself gives NaN, 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 computed to 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> (the ul no longer needs v-else), and put the div with the class progress from 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.