We’ve done a lot of work up to this point on providing meta data for our cafes. Right now, to be honest, this data is pretty worthless. You can only see the data for each cafe if you visit the individual cafe. This is nice, but it’d be really slick if you could filter by the meta data and find cafes that fit what you are looking for. This is the first of a few tutorials on how to do this and we will begin by filtering the group of coffee shops on the home page. All of this filtering will be done through VueJS since we already have the data loaded. As our data grows, we will do some more server-side filtering through our API.

Here is the list of filters we will have:

  • Filter by text search
  • Select tags to filter display
  • Filter by roasters
  • Select brew methods available

This tutorial will be taking our first steps into using mixins. According to the Vue JS docs here Mixins — Vue.js:

Mixins are a flexible way to distribute reusable functionalities for Vue components

The way I like to think of mixins are pared down components that fit like puzzle pieces into full components. They can contain methods, computed variables and other variables that enhance your full component.

We will be using our filtering functionality in a few different scenarios so we will want to make a single method that performs the filter. These filters can get pretty large as well so it will be nice to have a re-usable method and separate file so our component code won’t get too large.

The way we will structure this tutorial is we will create each method that does the filtering through a mixin. Then we will build the specific component that allows the UI to be filtered. Finally, we will build a method on the cafe card that validates the visibility from the mixins. To communicate which filters are selected, we will rely heavily on the Event Bus which handles our emitting of events. We’ve used the event bus a little bit when we built our VueJS Tag component https://serversideup.net/vue-js-tag-input/ but will use it heavily for the filtering. Essentially it’s a way of communicating/passing data between components easily. In VueJS 1.x this was done through dispatching and broadcasting events. For more information check out: Components — Vue.js

Step 1: Add Text Filter Mixin

The text filter will simply take a term entered by the user and check it against a cafe. The method will accept a search term and a cafe object and we will see if the search term is in the cafe name, cafe location name, address, or city.

First, we will add the file /resources/assets/js/mixins/filters/CafeTextFilter.js. In this file we will add the following code:

export const CafeTextFilter = {
  methods: {
    processCafeTextFilter( cafe, text ){

    }
  }
}

What this does is create a constant that has a method to process the cafe text. The processCafeTextFilter( cafe, text ) method accepts a cafe and a piece of text.

Now we have to fill out the actual filtering. The cool thing is this is the same type of method for each search term. What we will be doing is converting every searchable key to lower case for consistency and then we will use the lower case version of the search term. This way we aren’t worried about capitalization. We also do a regex match to find the text anywhere in the string and our regex gets loaded with the search term.

Next we should fill in our method with the following code:

/*
  Only process if the text is greater than 0
*/
if( text.length > 0 ){
  /*
    If the name, location name, address, or city match the text that
    has been added, return true otherwise return false.
  */
  if( cafe.name.toLowerCase().match( '[^,]*'+text.toLowerCase()+'[,$]*' )
    || cafe.location_name.toLowerCase().match( '[^,]*'+text.toLowerCase()+'[,$]*' )
    || cafe.address.toLowerCase().match( '[^,]*'+text.toLowerCase()+'[,$]*' )
    || cafe.city.toLowerCase().match( '[^,]*'+text.toLowerCase()+'[,$]*' )
  ){
    return true;
  }else{
    return false;
  }
}else{
  return true;
}

What this does is if the cafe name, location_name, address, or city matches the text entered, then return true otherwise return false. We also only process the filter if there is text entered. Otherwise we return true which means that the cafe would show if that’s the only filter being processed.

Our final mixin should look like:

export const CafeTextFilter = {
  methods: {
    processCafeTextFilter( cafe, text ){
      /*
        Only process if the text is greater than 0
      */
      if( text.length > 0 ){
        /*
          If the name, location name, address, or city match the text that
          has been added, return true otherwise return false.
        */
        if( cafe.name.toLowerCase().match( '[^,]*'+text.toLowerCase()+'[,$]*' )
          || cafe.location_name.toLowerCase().match( '[^,]*'+text.toLowerCase()+'[,$]*' )
          || cafe.address.toLowerCase().match( '[^,]*'+text.toLowerCase()+'[,$]*' )
          || cafe.city.toLowerCase().match( '[^,]*'+text.toLowerCase()+'[,$]*' )
        ){
          return true;
        }else{
          return false;
        }
      }else{
        return true;
      }
    }
  }
}

This will now allow us to search for text in the attributes we want searched.

Step 2: Add Tags Filter Mixin

Now we need to add our tags filter mix-in.. This will allow us to filter shops by tag. It will be a little more complex since we will have to allow only tags that are already created to be filtered from the auto complete and to check to see if the cafe has the tags. Let’s begin!

First, create the /resources/assets/js/mixins/filters/CafeTagsFilter.js file and add the following code:

export const CafeTagsFilter = {
  methods: {
    processCafeTagsFilter( cafe, tags ){

    }
  }
}

Like our last mixin the method takes two parameters a cafe and tags array. We will be passing an array of tags that will be filtered through when processed.

Now let’s add the code to fill out the method:

/*
  If there are tags to be filtered, run the filter.
*/
if( tags.length > 0 ){
    /*
        Makes array of the tags for the cafe
    */
    var cafeTags = [];

    /*
    Make array of cafe tags this is what we will check to
    see contains a filter.
  */
  for( var i = 0; i < cafe.tags.length; i++ ){
    cafeTags.push( cafe.tags[i].tag );
  }

  /*
    Iterate over all of the tags being filtered.
  */
  for( var i = 0; i < tags.length; i++ ){
    /*
      If the tag is in the array of cafe tags then
      we return true.
    */
    if( cafeTags.indexOf( tags[i] ) > -1 ){
      return true;
    }
  }

  /*
    If we made it this far, then we return false because
    the cafe doesn't contain the tags
  */
  return false;
}else{
  return true;
}

First we check to see if there are tags to be filtered. If not we just return true disregarding the filter.

Next, we initialize an empty array of cafeTags. What this does is allow us to make an array of tags that belong to a cafe so we can search for a tag being filtered much easier. We then loop over every tag for the cafe and build the cafe tags array.

Now, we loop over the tags being filtered and check to see if the cafeTags contain the tag in the filter:

/*
  Iterate over all of the tags being filtered.
*/
for( var i = 0; i < tags.length; i++ ){
  /*
    If the tag is in the array of cafe tags then
    we return true.
  */
  if( cafeTags.indexOf( tags[i] ) > -1 ){
    return true;
  }
}

If it does, then we return true, otherwise we reach the bottom of the method and return false since the tags were not found in the filter.

Step 3: Add Is Roaster Filter Mixin

This filter should be pretty easy to implement. We will simply be checking if the cafe is a roaster or not which is a flag in the database for the cafe record.

First, add the following file: /resources/assets/js/mixins/filters/CafeIsRoasterFilter.js and add the following code:

export const CafeIsRoasterFilter = {
  methods: {
    processCafeIsRoasterFilter( cafe ){

    }
  }
}

All we have to do for this filter is check to see if the cafe is flagged as a roaster or not. To do that, add the following code:

/*
  Checks to see if the cafe is a roaster or not
*/
if( cafe.roaster == 1 ){
  return true;
}else{
  return false;
}

That’s all we need to do! We just check to see if the cafe is a roaster or not.

Step 4: Add Brew Methods Filter Mixin

This will be our last filter we implement right now. This will filter if the cafe has certain brew methods or not.

First, we need to add our mixin file at /resources/assets/js/mixins/filters/CafesBrewMethodsFilter.js and add the following code:

export const CafeBrewMethodsFilter = {
  methods: {
    processCafeBrewMethodsFilter( cafe, brewMethods ){

    }
  }
}

The functionality will similar to the tags filter. We will be displaying cafes that have brew methods selected by the user.

We then fill the filter method with the following code:

/*
  If there are brew methods to be filtered, run the filter.
*/
if( brewMethods.length > 0 ){
  /*
    Makes array of the brew methods for the cafe
  */
  var cafeBrewMethods = [];

  /*
    Makes array of brew methods tags so we can see if they
    are in the filter.
  */
  for( var i = 0; i < cafe.brew_methods.length; i++ ){
    cafeBrewMethods.push( cafe.brew_methods[i].method );
  }

  /*
    Iterate over all of the brew methods being filtered.
  */
  for( var i = 0; i < brewMethods.length; i++ ){
    /*
      If the tag is in the array of cafe tags then we return
      true.
    */
    if( cafeBrewMethods.indexOf( brewMethods[i] ) > -1 ){
      return true;
    }
  }

  /*
    If we made it this far, then we return false because the
    cafe doesn't contain the tags.
  */
  return false;

}else{
  return true;
}

We first check to see if there are any brew methods being filtered. If there are, then we continue otherwise return true and the filter won’t be processed.

Next, we build our brew methods array and compile all of the cafe’s brew methods. We then iterate over all of the brew methods selected to be filtered and if the cafe has the brew method we return true.

If we get to the end of the brew methods without returning true we return false since the cafe doesn’t contain one of the brew methods selected.

We’ve now completed our filter mixins. Next we will build our filter component. This will go on top of our /home page allowing the user to select what they want to see.

Step 5: Add Filter Component

This component will sit on top of our home screen allowing the users to select the filters of their choice for the coffee shop displays.

First we need to add our filter component to /resources/assets/js/components/cafes/CafeFilter.vue and then add our default component code:

<style lang="scss">

</style>

<template>
  <div id="cafe-filter">

  </div>
</template>

<script>
  export default {

  }
</script>

Step 6: Add Text Box for Searching

First we will need to add a text box for the text search to our template and a model to store the entry so add the following code to the template:

<div class="grid-container">
  <div class="grid-x grid-padding-x">
    <div class="large-6 medium-6 small-12 cell">
        <label>Search</label>
      <input type="text" v-model="textSearch" placeholder="Search"/>
    </div>
    <div class="large-6 medium-6 small-12 cell">

    </div>
  </div>
</div>

We also need to add the data for the model:

data(){
  return {
    textSearch: ''
  }
}

Step 7: Add Tags Input For Tag Filters

Now we will need to add a tags input so we can select tags. To do this, we will need to import the TagsInput component and the EventBus. The TagsInput component will allow us to select tags to filter by and the EventBus will allow us to listen not only to the tags input and allow us to respond to changes, but after we add the filters, allow us to communicate these filters with the cafe cards to determine if we should show or hide the card.

Our Javascript should now look like:

<script>
  /*
    Imports the tags input file
  */
  import TagsInput from '../global/forms/TagsInput.vue';

  /*
    Imports the Event Bus to pass updates.
  */
  import { EventBus } from '../../event-bus.js';

  export default {
    components: {
      TagsInput
    },

    data(){
      return {
        textSearch: ''
      }
    }
  }
</script>

and our template should look like:

<template>
  <div id="cafe-filter">
    <div class="grid-container">
      <div class="grid-x grid-padding-x">
        <div class="large-6 medium-6 small-12 cell">
            <label>Search</label>
          <input type="text" v-model="textSearch" placeholder="Search"/>
        </div>
        <div class="large-6 medium-6 small-12 cell">
          <tags-input v-bind:unique="'cafe-search'"></tags-input>
        </div>
      </div>
    </div>
  </div>
</template>

We bound the unique key for the tags input to be cafe-search this way we can listen to the changes from the tags input and store them locally in our data. We just need to add the tags array:

tags: []

to our data. Finally, we need to listen to the tags edited event and take the tags passed and bind them to the tags array that’s local:

mounted(){
  EventBus.$on('tags-edited', function( tagsEdited ){
    if( tagsEdited.unique == 'cafe-search' ){
      this.tags = tagsEdited.tags;
    }
  }.bind(this));
}

What this does is when the tags have been edited, we check to see if the unique key that references our tags input matches, then we grab the tags from the tags input which is passed as the payload. We then bind those to the local tags.

A few updates I made to the /resources/assets/js/components/global/forms/TagsInput.vue. If you didn’t read the tutorial on making this input, check it out here https://serversideup.net/vue-js-tag-input/.

First, I changed the div.tags-input to display as a table so the element expands as we enter tags.

Second, I imported lodash:

import _ from 'lodash'

So we can use it to debounce the input and not send a request every key press. Our searchTags() method should look like:

/*
  Searches the API route for tags with the autocomplete.
*/
searchTags: _.debounce( function(e) {
  if( this.currentTag.length > 2 && !this.pauseSearch ){
    this.searchSelectedIndex = -1;
    axios.get( ROAST_CONFIG.API_URL + '/tags' , {
      params: {
        search: this.currentTag
      }
    }).then( function( response ){
      this.tagSearchResults = response.data;
    }.bind(this));
  }
}, 300),

This will slow down the requests to the API and make the input much more user friendly.

Step 8: Add Is Roaster Filter

We need to now add the input for the third input for our is roaster filter. This should simply be a toggle and in this case, we will use a checkbox.

First, add the variable for isRoaster to the data array like so and initialize it to false:

data(){
  return {
    textSearch: '',
    tags: [],
    isRoaster: false
  }
}

Now, we just need to add the checkbox to our template below the text field:

<div class="is-roaster-container">
  <input type="checkbox" v-model="isRoaster"/> <label>Is Roaster?</label>
</div>

Now we have a filter to see if the cafe is a roaster or not.

Step 9: Add Brew Methods Filter

Finally, we have to add the input for the brew methods filter. This will be styled a little bit differently since we will have a set amount of brew methods.

First, add the brewMethods array to the data:

data(){
  return {
    textSearch: '',
    tags: [],
    isRoaster: false,
    brewMethods: []
  }
},

Next, we need to import the computed data for the brew methods:

/*
  Loads the Vuex data we need such as brew methods
*/
computed: {
  cafeBrewMethods(){
    return this.$store.getters.getBrewMethods;
  },
},

I named the computed brew methods cafeBrewMethods since we already have a brewMethods variable.

Now we need to iterate over the brew methods and add a simple display.

Below our is roaster filter add the following for the template:

<div class="brew-methods-container">
  <div class="filter-brew-method" v-on:click="toggleBrewMethodFilter( method.method )" v-bind:class="{'active' : brewMethods.indexOf( method.method ) > -1 }" v-for="method in cafeBrewMethods">
    {{ method.method }}
  </div>
</div>

There’s a lot to this little piece of code so let’s break it down.

First, we are iterating over all of the brew methods and making a button to toggle. Next, the v-on:click="toggleBrewMethodFilter( method.method )" directive toggles whether or not the brew method should be included in the filter.

We need to shell out the method for this, so in our methods object on the CafeFilter.vue component add:

methods: {
  toggleBrewMethodFilter( method ){
    if( this.brewMethods.indexOf( method ) > -1 ){
      this.brewMethods.splice( this.brewMethods.indexOf( method ), 1 );
    }else{
      this.brewMethods.push( method );
    }
  }
}

What this method does is if the brew method is in the array, we remove it from the array by splicing the array at the index with a length of one. Otherwise we add the brew methods to the array.

Next in the template we are binding an active class if the brew method is in the brewMethods array:

v-bind:class="{'active' : brewMethods.indexOf( method.method ) > -1 }"

What this does is if the brew method has an index, we bind the class of active.

Now we have the functionality for the toggles, let’s add the CSS. So in our style tag add:

<style lang="scss">
  @import '[email protected]/abstracts/_variables.scss';

  div.filter-brew-method{
    display: inline-block;
    height: 35px;
    text-align: center;
    border: 1px solid #ededed;
    border-radius: 5px;
    padding-left: 10px;
    padding-right: 10px;
    padding-top: 5px;
    padding-bottom: 5px;
    margin-right: 10px;
    margin-top: 10px;
    cursor: pointer;
    color: $dark-color;
    font-family: 'Josefin Sans', sans-serif;

    &.active{
      border-bottom: 4px solid $primary-color;
    }
  }
</style>

This will give a nice button feel to our filters for brew methods. Now we need to start passing our filters through events so we know what to show and what to hide.

Step 10: Send Filter Events

Every time a filter gets adjusted we need to send a filter event with the selected filters. This is so we can listen to what the user has selected and apply the filters accordingly.

First, let’s add our listeners. In our component in the watch section add the following code:

watch: {
  textSearch(){

  },

  tags(){

  },

  isRoaster(){

  },

  brewMethods(){

  }
},

Now whenever one of our filter variables changes, we are watching and can fire an event that says “hey, the user selected a different filter”.

In each of the methods, add the call to this.updateFilterDisplay(); and add the method:

updateFilterDisplay(){

}

In this method we will emit out through our EventBus any changes to the filters so add the following code:

EventBus.$emit('filters-updated', {
  text: this.textSearch,
  tags: this.tags,
  roaster: this.isRoaster,
  brew_methods: this.brewMethods
});

Now whenever one of our filters is updated an event is sent out that other components can listen to and determine whether or not a cafe should be displayed or not.

Step 11: Show and Hide Filter Container

This is a simple UX feature that makes for a nice display in case the user doesn’t want to show the filters and just see all of the cafes to browse.

First, add a variable that determines if the filters should be shown or hidden in the data():

show: true

Next, under the id="cafe-filter" element, make sure the contain is shown only if the show is true:

<div id="cafe-filter">
    <div class="grid-container" v-show="show">
....

This will default to true and the filters will be shown. Next, add a container that will show the toggle below the container that contains the filters like so:

<div class="grid-container">
  <div class="grid-x grid-padding-x">
    <span class="show-filters" v-on:click="show = !show">{{ show ? 'Hide Filters ↑' : 'Show Filters ↓' }}</span>
  </div>
</div>

What this will do is when clicked, toggle the show and hide of the filters by adjusting the boolean.

The style for this feature is here:

span.show-filters{
  display: block;
  margin: auto;
  color: $dark-color;
  font-family: 'Josefin Sans', sans-serif;
  cursor: pointer;
  font-size: 14px;
}

It’s a really simple feature that cleans up the UI and gives control back to the user on the data they want to see.

Step 12: Add Cafe Filter Component to Top Of Cafes Page

We just need to add our new filter component to the top of our home page. To do this, open /resources/assets/js/pages/Home.vue and include the filter component:

import CafeFilter from '../components/cafes/CafeFilter.vue';

Next, we need to include the components in the components object in the page like so:

components: {
  CafeCard,
  Loader,
  CafeFilter
},

Finally, we will add our cafe filter below our link to add a cafe:

<div class="grid-container">
  <div class="grid-x">
    <div class="large-12 medium-12 small-12 columns">
      <router-link :to="{ name: 'newcafe' }">+ Add Cafe</router-link>
    </div>
  </div>
</div>

<cafe-filter></cafe-filter>

...

This will display the filter component we just made on our new cafe page.

I also added a quick style for the add cafe button to clean up the design:

<style lang="scss">
  @import '[email protected]/abstracts/_variables.scss';

  div#home{
    a.add-cafe-button{
      float: right;
      display: block;
      margin-top: 10px;
      margin-bottom: 10px;
      background-color: $dark-color;
      color: white;
      padding-top: 5px;
      padding-bottom: 5px;
      padding-left: 10px;
      padding-right: 10px;
    }
  }
</style>

then added the add-cafe-button class to the router link:

<router-link :to="{ name: 'newcafe' }" class="add-cafe-button">+ Add Cafe</router-link>

Step 13: Add Check Visibility Check To Cafe Card

This is where we tie everything together. Now that we have our filters displaying we need to load the tags when we load the cafes and adjust the CafeCard.vue to show or hide based on the filters. The processing of showing and hiding the cafe will be done through our mixins.

Our first step will be to open the app/Http/Controllers/API/CafesController.php file and add the request to load the tags with the cafes. We only need the tag field so our getCafes() method should look like:

/*
|-------------------------------------------------------------------------------
| Get All Cafes
|-------------------------------------------------------------------------------
| URL:            /api/v1/cafes
| Method:         GET
| Description:    Gets all of the cafes in the application
*/
public function getCafes(){
  $cafes = Cafe::with('brewMethods')
                ->with(['tags' => function( $query ){
                  $query->select('tag');
                }])
                ->get();

  return response()->json( $cafes );
}

Now we load the tags with each cafe so we can filter easier.

Next, we need to open /resources/assets/js/components/cafes/CafeCard.vue. This is where most of our functionality will take place.

First, let’s include our mixins so import them on the top of the component like so:

import { CafeIsRoasterFilter } from '../../mixins/filters/CafeIsRoasterFilter.js';
import { CafeBrewMethodsFilter } from '../../mixins/filters/CafeBrewMethodsFilter.js';
import { CafeTagsFilter } from '../../mixins/filters/CafeTagsFilter.js';
import { CafeTextFilter } from '../../mixins/filters/CafeTextFilter.js';

Then, like components, we need to add them to a mixins array within the CafeCard.vue component. We need to add the array like this:

mixins: [
  CafeIsRoasterFilter,
  CafeBrewMethodsFilter,
  CafeTagsFilter,
  CafeTextFilter
]

Now we have our methods imported through our mixins. Each method can now be called from within our component.

Next we need to import the event bus to our CafeCard.vue so we can listen to the user’s filter interactions.

/*
  Imports the Event Bus to listen to filter updates
*/
import { EventBus } from '../../event-bus.js';

When the component is mounted, we need to register a listener to listen for filter updates. We need to add the following code to the CafeCard.vue:

mounted(){
  EventBus.$on('filters-updated', function( filters ){
    this.processFilters( filters );
  }.bind(this));
}

What this does is when the filters have been updated, we will call a local method called processFilters() and pass the filters sent from the filter component through our mixin methods.

Let’s add our processFilters() method:

methods: {
  processFilters( filters ){

  }
}

In here, we will call each of our mixin methods to determine if we should show or hide the card. Before we construct the method that will validate by a mixin, let’s quick add the show/hide variable to our data:

data(){
  return {
    show: true
  }
},

Now let’s add our attribute to the template so it shows and hides based off of the variable:

<div class="large-3 medium-3 small-6 cell" v-show="show">

We are now ready to implement our processFilters() method. When I was writing this method, it got a little complex, so I’ll show the code first and explain what I did:

processFilters( filters ){
  /*
    If no filters are selected, show the card
  */
  if( filters.text == ''
    && filters.tags.length == 0
    && filters.roaster == false
    && filters.brew_methods.length == 0 ){
      this.show = true;
  }else{
    /*
      Initialize flags for the filtering
    */
    var textPassed = false;
    var tagsPassed = false;
    var brewMethodsPassed = false;
    var roasterPassed = false;


    /*
      Check if the roaster passes
    */
    if( filters.roaster && this.processCafeIsRoasterFilter( this.cafe ) ){
      roasterPassed = true;
    }else if( !filters.roaster ){
      roasterPassed = true;
    }

    /*
      Check if text passes
    */
    if( filters.text != '' && this.processCafeTextFilter( this.cafe, filters.text ) ){
      textPassed = true;
    }else if( filters.text == '' ){
      textPassed = true;
    }

    /*
      Check if brew methods passes
    */
    if( filters.brew_methods.length != 0 && this.processCafeBrewMethodsFilter( this.cafe, filters.brew_methods ) ){
      brewMethodsPassed = true;
    }else if( filters.brew_methods.length == 0 ){
      brewMethodsPassed = true;
    }

    /*
      Check if tags passes
    */
    if( filters.tags.length != 0 && this.processCafeTagsFilter( this.cafe, filters.tags ) ){
      tagsPassed = true;
    }else if( filters.tags.length == 0 ){
      tagsPassed = true;
    }

    /*
      If everything passes, then we show the Cafe Card
    */
    if( roasterPassed && textPassed && brewMethodsPassed && tagsPassed ){
      this.show = true;
    }else{
      this.show = false;
    }
  }
}

First, we check to see if any filters are selected, that would be this chunk of code:

/*
  If no filters are selected, show the card
*/
if( filters.text == ''
  && filters.tags.length == 0
  && filters.roaster == false
  && filters.brew_methods.length == 0 ){
    this.show = true;

If there are no filters selected, we want to show the cafe card. The user hasn’t filtered anything so we want to display everything.

Next we narrow down our four filters. First we initialize booleans if each type passes or not. We initialize these to false:

/*
  Initialize flags for the filtering
*/
var textPassed = false;
var tagsPassed = false;
var brewMethodsPassed = false;
var roasterPassed = false;

Next we go through each of our filters using our mixins. The first is the check to see if the roaster is selected or not.

/*
  Check if the roaster passes
*/
if( filters.roaster && this.processCafeIsRoasterFilter( this.cafe ) ){
  roasterPassed = true;
}else if( !filters.roaster ){
  roasterPassed = true;
}

We check to see if it’s on and if the cafe passes the roaster filter. Otherwise if it’s off, then we say it’s passed. The reason we do this is it disregards the filter. At the end of all of our checks, we see if all of the filters are on to show or hide the cafe. This really allows the user to narrow down the results.

Next, we check to see if the text passes:

/*
  Check if text passes
*/
if( filters.text != '' && this.processCafeTextFilter( this.cafe, filters.text ) ){
  textPassed = true;
}else if( filters.text == '' ){
  textPassed = true;
}

We only check if the text has data entered. If there is no text entered, then we say text has passed and disregard the filter.

Next, we check if the brew methods pass:

/*
  Check if brew methods passes
*/
if( filters.brew_methods.length != 0 && this.processCafeBrewMethodsFilter( this.cafe, filters.brew_methods ) ){
  brewMethodsPassed = true;
}else if( filters.brew_methods.length == 0 ){
  brewMethodsPassed = true;
}

Once again we check if the user has selected a brew method or not. If they haven’t then we disregard the brew methods filter.

Finally, we check the tags filter:

/*
  Check if tags passes
*/
if( filters.tags.length != 0 && this.processCafeTagsFilter( this.cafe, filters.tags ) ){
  tagsPassed = true;
}else if( filters.tags.length == 0 ){
  tagsPassed = true;
}

Like the other filters, we ensure the user has selected a filter before moving forward otherwise we disregard the filter and set the tags passed to true.

One thing to notice is we are calling the mixins using the this prefix as if we defined the method in the component itself. This is the way mixin methods work to provide local access to a mixin method.

The final piece to our filtering is to check and make sure everything passes to show the CafeCard. This functionality is taken care of by this piece of code:

/*
  If everything passes, then we show the Cafe Card
*/
if( roasterPassed && textPassed && brewMethodsPassed && tagsPassed ){
  this.show = true;
}else{
  this.show = false;
}

What this does is ensure that all of the methods pass before determining if we show the card or not.

Conclusion

Filtering can get pretty complex, but with VueJS and the reactive functionality, the UX is totally worth it. By utilizing the EventBus and creating mixins we are able to separate functionality into smaller pieces so the code is reusable throughout our app. In the next tutorial we will work on filtering some of the Google Map data using the same mixing except showing and hiding the markers on the map.

As with every tutorial, let me know if any questions arise and be sure to reach out if they do. Also, all of the source code is readily available to look at and check out on GitHub: https://github.com/serversideup/roastandbrew