FIltering results based on several Multiselect boxes, vue
Asked Answered
M

1

7

I have a vue component where I'm currently successfully showing results from a data object, and I've also successfully created several multiselect boxes. My issue is filtering.

I know how I can set a single value from the multiselect and compare it (using v-if) in order to show certain results in an HTML div, but I'm completely lost now on how to do proper filtering based on multiple Multiselects (especially since several of them allow multiple options that store the values in arrays)

I'm putting my snippet below, but how can I properly make it so that I can filter results based on all values in the corresponding v-models for the multiselects, while making sure that if "All stores" or "All areas" is selected, it allows all values for that selection?

-- In other words, if the user doesn't ake a selection and the multiselect is left on the placeholder, all values for that select would be allowed to show in the DOM (based on other filters first)

new Vue({
  el: "#app",
  components: {Multiselect: window.VueMultiselect.default},
  data: {
    selectedOutput: '',
    selectedAreas:[],
    selectedStores: [],
    selectedCategories: [],
    selectedShifts: [],
    shifts: [
      {id: 1, name: "First"},
      {id: 2, name: "Second"}
    ],
    categories: [
      {id: 1, name: "electronics"},
      {id: 1, name: "home"},
      {id: 1, name: "auto"},
    ],
    outputOptions: [
      {id:1, name: "Sold"},
      {id:2, name: "Purchased"}
    ],
    areas: [
        {value: 1, name: "East"},
        {value: 1, name: "West"},
    ],
    stores: [
        {value: 1, name: "One"},
        {value: 2, name: "Two"}
    ],
    workNumbers: [
        {
          "Adam": {
            "name": "Adam",
            "title": "Manager",
            "shift": "First",
            "category": "electronics",
            "area" : "East",
            "store": "One",
            "sold": 140,
            "purchased": 15
          },
          "Ben": {
            "name": "Ben",
            "title": "Manager",
            "shift": "First",
            "category": "electronics",
            "area" : "East",
            "store": "One",
            "sold": 225,
            "purchased": 77
          },
          "Suzie": {
            "name": "Suzie",
            "title": "Manager",
            "shift": "Second",
            "category": "home",
            "area" : "West",
            "store": "Two",
            "sold": 124,
            "purchased": 55
          },
          "Reg": {
            "name": "Reg",
            "title": "Manager",
            "shift": "Second",
            "category": "home",
            "area" : "West",
            "store": "Two",
            "sold": 66,
            "purchased": 36
          },
          "Kelly": {
            "name": "Kelly",
            "title": "Manager",
            "shift": "Second",
            "category": "home",
            "area" : "West",
            "store": "Two",
            "sold": 55,
            "purchased": 2
          },
        }
    ]
  },
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-multiselect/3.0.0-alpha.2/vue-multiselect.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/vue-multiselect/3.0.0-alpha.2/dist/vue-multiselect.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="uk-width-2-10" style="position: relative !important;">
    <multiselect
      v-model="selectedOutput"
      :options="outputOptions"
      :multiple="false"
      :close-on-select="true"
      label="name"
      track-by="name"
      @input="checkOutput"
    ></multiselect>
</div>
<div class="uk-width-2-10" style="position:relative;">
    <multiselect
      v-model="selectedShifts"
      :options="shifts"
      :multiple="true"
      :close-on-select="true"
      placeholder="All shifts"
      label="name"
      track-by="name"
    ></multiselect>
</div>
<div class="uk-width-2-10" style="position: relative !important;">
    <multiselect
      v-model="selectedCategories"
      :options="categories"
      :multiple="true"
      :close-on-select="true"
      placeholder="All categories"
      label="name"
      track-by="id"
    ></multiselect>
</div>
<div class="uk-width-2-10" style="position: relative !important;">
    <multiselect
      v-model="selectedAreas"
      :options="areas"
      :multiple="true"
      :close-on-select="true"
      placeholder="All areas"
      label="name"
      track-by="name"
    ></multiselect>
</div>
<div class="uk-width-2-10" style="position: relative !important;">
    <multiselect
      v-model="selectedstores"
      :options="stores"
      :multiple="true"
      :close-on-select="true"
      placeholder="All stores"
      label="name"
      track-by="value"
    ></multiselect>
</div>

    <table>
    <tbody v-if="selectedOutput.name === 'Sold'">
      <tr v-for="(value, employee) in workNumbers"  :key="employee">
        <!-- this is where I need a condition to show based on filters, I believe-->
        <td>{{name}} - {{sold}}</td>
      </tr>
    </tbody>
    <tbody v-else-if="selectedOutput.name === 'Purchased'">
      <tr v-for="(value, employee) in workNumbers"  :key="employee">
        <!-- this is where I need a condition to show based on filters, I believe-->
        <td>{{name}} - {{purchased}}</td>
      </tr>
    </tbody>
    </table>
</div>

UPDATE:

Now I've moved to another table which is similar but it's looping around different objects in order to create a different model that is also passed through a function for a modal.

<tbody v-if="selectedOutput.name === 'Cubes'">
  <tr v-for="(value, employee) in workNumbers"  :key="employee">
    <td v-for="date in dates" :key="date" >
      <div v-for="(dateSpecificData, dateValue) in value.dates" :key="dateValue" @click="showModal(dateSpecificData)"   :style="'background: ' + (dateSpecificData.unavailable > 0 ? '#f7a7a3' : '#a8f0c6')">
        <div v-if="dateValue == date ">
          @{{dateSpecificData.sold}}
        </div>
      </div>
    </td>
  </tr>
</tbody>
Maximamaximal answered 7/10, 2021 at 12:9 Comment(0)
S
4

You can define a computed-property that returns the filtered list according to the params:

new Vue({
  el: "#app",
  components: { Multiselect: window.VueMultiselect.default },
  data: () => ({
    selectedOutput: '',
    outputOptions: [ {id:1, name: "Sold"}, {id:2, name: "Purchased"} ],
    selectedShifts: [],
    shifts: [ {id: 1, name: "First"}, {id: 2, name: "Second"} ],
    selectedCategories: [],
    categories: [ {id: 1, name: "electronics"}, {id: 2, name: "home"}, {id: 3, name: "auto"} ],
    selectedAreas:[],
    areas: [ {value: 1, name: "East"}, {value: 1, name: "West"} ],
    selectedStores: [],
    stores: [ {value: 1, name: "One"}, {value: 2, name: "Two"} ],
    workNumbers: [
      {
        "Adam": { "name": "Adam", "title": "Manager", "shift": "First", "category": "electronics", "area" : "East", "store": "One", "sold": 140, "purchased": 15 },
        "Ben": { "name": "Ben", "title": "Manager", "shift": "First", "category": "home", "area" : "West", "store": "Two", "sold": 225, "purchased": 77 },
        "Suzie": { "name": "Suzie", "title": "Manager", "shift": "Second", "category": "electronics", "area" : "East", "store": "One", "sold": 124, "purchased": 55 },
        "Reg": { "name": "Reg", "title": "Manager", "shift": "Second", "category": "home", "area" : "West", "store": "Two", "sold": 66, "purchased": 36 },
        "Kelly": { "name": "Kelly", "title": "Manager", "shift": "Second", "category": "auto", "area" : "West", "store": "Two", "sold": 55, "purchased": 2 }
      }
    ]
  }),
  methods: {
    filtedSelectedHelper(arr = [], val) {
      return arr.length ? arr.some(({ name }) => name === val) : true;
    }
  },
  computed: {
    filteredWorkNumbers () {
      const ouput = this.selectedOutput;
      const filteredList = 
        this.workNumbers
          .flatMap(Object.values)
          .filter(({ shift, category, area, store }) => 
            this.filtedSelectedHelper(this.selectedShifts, shift) &&
            this.filtedSelectedHelper(this.selectedCategories, category) &&
            this.filtedSelectedHelper(this.selectedAreas, area) &&
            this.filtedSelectedHelper(this.selectedStores, store) 
          );
       return !this.selectedOutput 
         ? filteredList.map(({ name, sold, purchased }) => 
             `${name} - ${sold} - ${purchased}`
           )
         : this.selectedOutput.name === "Sold"
           ? filteredList.map(({ name, sold }) => 
               `${name} - ${sold}`
             )
           : filteredList.map(({ name, purchased }) => 
               `${name} - ${purchased}`
             )
    }
  }
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]"></script>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/vue-multiselect.min.css">

<div id="app">
  <div class="uk-width-2-10" style="position: relative !important;">
    <multiselect
      v-model="selectedOutput"
      :options="outputOptions"
      :multiple="false"
      :close-on-select="true"
      label="name"
      track-by="name"
    ></multiselect>
  </div>
  <div class="uk-width-2-10" style="position:relative;">
    <multiselect
      v-model="selectedShifts"
      :options="shifts"
      :multiple="true"
      :close-on-select="true"
      placeholder="All shifts"
      label="name"
      track-by="name"
    ></multiselect>
  </div>
  <div class="uk-width-2-10" style="position: relative !important;">
    <multiselect
      v-model="selectedCategories"
      :options="categories"
      :multiple="true"
      :close-on-select="true"
      placeholder="All categories"
      label="name"
      track-by="id"
    ></multiselect>
  </div>
  <div class="uk-width-2-10" style="position: relative !important;">
    <multiselect
      v-model="selectedAreas"
      :options="areas"
      :multiple="true"
      :close-on-select="true"
      placeholder="All areas"
      label="name"
      track-by="name"
    ></multiselect>
  </div>
  <div class="uk-width-2-10" style="position: relative !important;">
    <multiselect
      v-model="selectedStores"
      :options="stores"
      :multiple="true"
      :close-on-select="true"
      placeholder="All stores"
      label="name"
      track-by="value"
    ></multiselect>
  </div>
  <table>
    <tbody>
      <tr v-for="str in filteredWorkNumbers" :key="str"><td>{{str}}</td></tr>
    </tbody>
  </table>
</div>

Resources:

https://v2.vuejs.org/v2/guide/computed.html

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

Sinuate answered 9/10, 2021 at 22:19 Comment(10)
Thank you for this, it does work on that table and the resources you attached help me clear up a few things. However, if you see my update, I have a similar table in the app that is building a different model by looping around dates as well, and then passing the object in a function call so I can't apply the same filter in this case (in computed). Would there be a way to do the filter specifically on the inner-most looop so that it only shows the valid records but it would keep the convention of how it's looping in the html and passing into the function call?Maximamaximal
I realize that's different then my original question, I'm just curious if there's an alternate way to call it for this similar tableMaximamaximal
@Maximamaximal glad this helped :) I can't give you a complete answer unless I see the data schemas, but I advise you to do the logic and computations in the computed property so it returns an array ready for rendering. Feel free to link a demo if you want with more explanations.Sinuate
@Majed_Badawi this is what I've been able to put together, it's a similar table but it loops differently as the data structure has dates that are being loooped as well, and I'm wanting to send the whole object at the end into a function that would show it in a modal jsfiddle.net/h3dLbv1jMaximamaximal
Maybe it's possible to replicate the whole loop in my HTML into a computed function like you said?Maximamaximal
Is this what you want: Given the new structure of data for the array elements, you want to show extra columns for every row for dates, if a date represents a key of the object, show its sold colored according to its unavailable, and calls showModal with this date's object?Sinuate
That's correct, yesMaximamaximal
Which I have achieved, but I want to try and add that filtering to it that wayMaximamaximal
How should the filtering work now as a record can have multiple objects?Sinuate
I'm thinking of it mainly as just showing anything that contains a matching value from the dropdowns. So any record that contains a match of store, etc. would show regardlessMaximamaximal

© 2022 - 2024 — McMap. All rights reserved.