The last piece of functionality we miss is the ability to filter bills by category.

We want to click a category name in the NavBar component, and the table and chart should only display that category’s bills.

And we’ll add an ‘All’ button that is the default and will let us go back to show all the bills instead of filtering by one category.

Let’s do it!

I’m going to first restructure the App component a little bit, by:

  • adding an activeCategory property to the state
  • creating an activeBills computed property that will only return the bills associated with the currently selected categories (or all if none is selected)
  • passing activeBills to the BillsTable and Chart components, instead of bills so they will show the filtered bills.

The first change is easy:

export default {
  name: 'app',
  data() {
    return {
      bills: [],
      categories: [],
      shouldShowAddCategory: false,
      shouldShowAddBill: false,
      activeCategory: '',
    }
  },
  //...
}

Let’s create the activeBills computed property. We filter the bills by activeCategory, and we sort them by date in reverse chronological order (newest first):

export default {
  name: 'app',
  //...
  computed: {
    activeBills() {
      return this.bills
        .filter((bill) =>
          this.activeCategory ? bill.category === this.activeCategory : true
        )
        .sort((a, b) => (new Date(a.date) < new Date(b.date) ? 1 : -1))
    },
  },
}

Now we pass that to the child components, instead of bills:

<div class="w-1/2 bg-gray-200">
  <BillsTable
    :bills="activeBills"
    v-on:triggerShowAddBill="triggerShowAddBill"
    v-on:removeBill="removeBill"
  />
</div>
<div class="w-1/2 bg-gray-400 pt-4 pl-4 text-2xl">
  <Chart :bills="activeBills" />
</div>

Now we’re ready to go in the NavBar component and do our edits there. We’ll get back to the App component later to handle a few events we’ll pass it.

Here’s the NavBar component at the moment, before any change:

<template>
  <ul class="list-reset inline flex justify-center border-b-4 mb-0">
    <li
      class="p-4 inline bg-gray-200 hover:bg-gray-400 uppercase font-black cursor-pointer"
      v-for="category in categories"
      :key="category"
    >
      {{category}}
    </li>

    <li
      class="p-4 inline bg-gray-200 hover:bg-gray-400 uppercase font-black cursor-pointer"
      @click="triggerShowAddCategory"
    >
      ➕
    </li>
  </ul>
</template>

<script>
  export default {
    props: ['categories'],
    methods: {
      triggerShowAddCategory() {
        this.$emit('triggerShowAddCategory')
      },
    },
  }
</script>

First thing, let’s accept another prop, activeCategory:

props: ['categories', 'activeCategory'],

We’ll use that in the v-for loop to apply the class bg-gray-600 if a category is active. This class is part of Tailwind and will apply a darker background. We use :class because it’s dynamic. Vue will merge class and :class for us.

Also, when we click a category we call the setActiveCategory method, passing the category name:

<li
  class="p-4 inline hover:bg-gray-400 uppercase font-black cursor-pointer"
  :class="[ activeCategory === category ? 'bg-gray-600' : 'bg-gray-200' ]"
  v-for="category in categories"
  :key="category"
  @click="setActiveCategory(category)"
>
  {{category}}
</li>

Before iterating on categories, let’s add an “All” button:

<li
  class="p-4 inline hover:bg-gray-400 uppercase font-black cursor-pointer"
  :class="[ activeCategory === '' ? 'bg-gray-600' : 'bg-gray-200' ]"
  @click="clearActiveCategory()"
>
  All
</li>

when this is clicked, it calls the clearActiveCategory method.

Let’s implement those 2 methods we defined:

methods: {
  //...
  clearActiveCategory() {
    this.$emit('clearActiveCategory')
  },
  setActiveCategory(category) {
    this.$emit('setActiveCategory', category)
  }
}

as you can see, we let the parent component handle them, App. So let’s get back to the App component, and let’s add a v-on directive for those events:

<NavBar
  :categories="categories"
  :activeCategory="activeCategory"
  v-on:clearActiveCategory="clearActiveCategory"
  v-on:setActiveCategory="setActiveCategory"
  v-on:triggerShowAddCategory="triggerShowAddCategory"
/>

and let’s implement them:

clearActiveCategory() {
  this.activeCategory = ''
},
setActiveCategory(category) {
  this.activeCategory = category
}

Now whenever we click a category, we ask App to update the activeCategory state property, and that’s automatically passed down again to the NavBar component to let it highlight the category, and Vue is also updating the activeBills computed property so our BillsTable and Chart components only show the active category amounts.

See the app in the current state on CodeSandbox.


Go to the next lesson