M
M
MSAFT2019-03-22 00:34:23
Vue.js
MSAFT, 2019-03-22 00:34:23

How to filter by multiple values ​​in VueJS?

The task is to filter the returned results by several values ​​for the already obtained result and reset the filter itself with the button.

Filter:

<h6>{{ trans('calculator.filter') }}</h6>

    <div class="row">
      <div class="col">
    <p>{{ trans('calculator.brand_filter') }}</p>
    <select v-model="brand" class="custom-select">
      <option v-for="brand in brands.sort()" :value="brand">{{ brand }}</option>
    </select>
      </div>

      <div class="col">
    <p>{{ trans('calculator.company_filter') }}</p>
    <select  v-model="company" class="custom-select">
      <option v-for="company in companies.sort()" :value="company">{{ company }}</option>
    </select></div>


      <div class="col">
    <p>{{ trans('calculator.country_filter') }}</p>
    <select v-model="country" class="custom-select">
      <option v-for="brand in countries.sort()" :value="brand">{{ brand }}</option>
    </select></div>

        <div class="col">
    <p>{{ trans('calculator.side_filter') }}</p>
    <select v-model="sidesFilter" class="custom-select">
      <option v-for="side in sides.sort()" :value="side">{{ side }}</option>
    </select></div>

          <div class="col">
    <p>{{ trans('calculator.rating_filter') }}</p>
    <select v-model="ratingFilter" class="custom-select" disabled>
      <option value="5" selected>5</option>
      <option value="4">4</option>
      <option value="3">3</option>
      <option value="2">2</option>
      <option value="1">1</option>
    </select></div>

      <a href="#">{{ trans('calculator.reset_filter') }}</a></div>


Results:
<div v-if="!profiles.length">
      <h4>{{ trans('calculator.no_results') }}</h4>
    </div>
    <div v-for="profile in filteredProfiles" class="row feed">
      <div class="col-3">
        <img alt="" :src="`${profile.company.logo}`" width="100%">
      </div>
      <div class="col-5 no-padding">
        <a :href="`/companies/${profile.company.id}`" target="_blank" class="title">{{profile.company.name}}</a>
        <div>
          <star-rating
            v-model="profile.company.rating || 0"
            read-only
            :show-rating="false"
            :star-size="16"
          />
          <span>{{(profile.company.workingHours.isOpen) ? trans('calculator.open') : trans('calculator.closed') }} | </span>
          <span>
            {{roundTime(profile.company.workingHours.today.from)}}
            -
            {{roundTime(profile.company.workingHours.today.to)}}</span>
        </div>
        <span>{{ trans('calculator.brand') }}: {{profile.brand.name}}<br>{{ trans('calculator.country') }}: {{profile.brand.country}}</span>
        <p class="text-muted">{{`${profile.sides}`}} {{ trans('calculator.profile_window') }}</p>
      </div>
      <div class="col-4">
        <h5>{{ trans('calculator.price') }}: {{Math.round(profile.price)}} </h5>
        <p>*{{ trans('calculator.pre_price') }}&nbsp;<i v-tooltip.top="trans('calculator.info_preprice')" class="fas fa-info-circle"></i></p>
        <br>
        <div class="responsive_results">
        <button
          class="button_calculator coolBeans"
          @click="selectProfile(profile)"
          data-toggle="modal"
          data-target="#orderModal"
        >
          {{ trans('calculator.order') }}
        </button></div>
      </div>
    </div>


Logics:
export default {
    props: ['profiles', 'onSend'],
    data() {
      return {
        isModalOpen: false,
        profile: {},
      }
    },
    computed:   {

      filteredProfiles() {
        return this.profiles.filter(
                n => n.brand.name === this.brand,
                n => n.company.name === this.company,
                n => n.brand.country === this.country,
                n => n.sides === this.side
      },

      brands() {
        return [...new Set(this.profiles.map(n => n.brand.name))];
      },
      companies() {
        return [...new Set(this.profiles.map(n => n.company.name))];
      },
      countries() {
        return [...new Set(this.profiles.map(n => n.brand.country))];
      },
      sides() {
        return [...new Set(this.profiles.map(n => n.sides))];
      },

    },

    methods: {

      roundTime(time) {
        const timeStr = time.split(':');
        timeStr.pop();
        return timeStr.join(':');
      },

      selectProfile(profile) {
        this.profile = profile;
      },

      orderProfile(profile) {
        $('#orderModal').modal('hide');
        this.onSend(profile);
      }
    }
  }


How to write the filteredProfiles logic so that using a filter to filter the shown result? And how to register a reset button
<a href="#">{{ trans('calculator.reset_filter') }}</a>

To reset the filter

Answer the question

In order to leave comments, you need to log in

1 answer(s)
0
0xD34F, 2019-03-22
@MSAFT

It's all difficult. Four selects are very similar, four computed properties are very similar. Too much copypasta. We need to simplify.
First of all, we will carefully look at all these similar pieces of code, find where they differ, and based on these differences, we will make a description of the filters:

filters: [
  { name: 'calculator.brand_filter', getter: obj => obj.brand.name, value: '' },
  { name: 'calculator.company_filter', getter: obj => obj.company.name, value: '' },
  { name: 'calculator.country_filter', getter: obj => obj.brand.country, value: '' },
  { name: 'calculator.side_filter', getter: obj => obj.sides, value: '' },
],

Then we will do the actual filtering, pass the data through the array of filters (if the filter value is set, filtering is performed, if not, we leave the data as it is):
computed: {
  filteredProfiles() {
    return this.filters.reduce((profiles, { value, getter }) => {
      return value
        ? profiles.filter(n => getter(n) === value)
        : profiles;
    }, this.profiles);
  },
},

Next, you need to give the user the ability to set filter values. Let's make a filter component:
Vue.component('filter-select', {
  props: [ 'name', 'items', 'value' ],
  template: `
<div>
  <p>{{ name }}<p>
  <select :value="value" @change="$emit('input', $event.target.value)">
    <option value="">-</option>
    <option v-for="n in items">{{ n }}</option>
  </select>
</div>`
});

And the method by which we will receive arrays of unique values ​​for selects:
unique(arr, getter) {
  return [...new Set(arr.map(getter))];
},

Finally, let's create the filters themselves:
<filter-select
  v-for="f in filters"
  v-model="f.value"
  :items="unique(profiles, f.getter)"
  :name="trans(f.name)"
></filter-select>

Also, let's not forget about resetting the filter values. So here is the filter reset method and the button that will call it when clicked:
resetFilters() {
  this.filters.forEach(n => n.value = '');
},

<button @click="resetFilters">{{ trans('calculator.reset_filter') }}</button>

A demo can be viewed here (yes, there are fewer filters than yours - well, you didn’t show your data, but I didn’t have enough imagination for more).

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question