0

I am using a tabbed interface built with Bootstrap-Vue. When clicking on one of these tabs I'm using v-for to render some data in an accordion/dropdown which is displayed as a grid.

Currently, I have a tab that shows all results which works fine when using the following (Note: prizesData is just where I'm storing response.data after I have called in with axios):

<div class="col-sm-12 col-md-6 col-lg-3 w-100 prize-dropdown-wrapper pb-5"
 v-for="(prize, name, index) in prizesData"
 :key="index"
>
<!--REMAINDER OF MARKUP GOES HERE-->
</div>

My JSON structure looks something like this, except I have 43 results in total. Every value in each object will change, but I want to use prizeType1Name and prizeType2Name to check if they contain the strings "CTW", "IW" or "OG":

{
  "1": [
      {
         "prizeTitle": "",
         "prizeImage": "",
         "prizeFirstLineText": "",
         "prizeType1": {
            "Image": "",
            "Line1": "",
            "Line2": ""
         },
         "prizeType2": {
            "Image": "",
            "Line1": "",
            "Line2": ""
         },
         "prizeTermsText": "",
         "prizeType1Name": "CTW",
         "prizeType2Name": ""
      }
   ],
  "2": [
      {
         "prizeTitle": "",
         "prizeImage": "",
         "prizeFirstLineText": "",
         "prizeType1": {
            "Image": "",
            "Line1": "",
            "Line2": ""
         },
         "prizeType2": {
            "Image": "",
            "Line1": "",
            "Line2": ""
         },
         "prizeTermsText": "",
         "prizeType1Name": "IW",
         "prizeType2Name": ""
      }
   ]
}

To access either of these values I am using:

this.prizesData[name][0].prizeType1 or this.prizesData[name][0].prizeType2

I'm having difficulty understanding how to filter my results. For example, if I click on the tab named (CTW), I want to show only the results that contain "CTW" in the JSON file. Or if the tab named "IW" is clicked then the results should be for the objects that contain "IW".

What I have tried:

Added filteredPrizes to store filtered prizes as an object to access later

data() {
    return {
      prizesData: {},
      filteredPrizes: {},
    };
  }

I tried to use filter() on the existing prizesData object I received from the JSON file, I added this in the same method where I'm fetching the JSON file.

getPrizesInfo() {
      const self = this;
      //Get Error Validation JSON
      axios
        .get("/data/prizes.json")
        .then(response => (this.prizesData = response.data));

      this.filteredPrizes = this.prizesData.filter(prize =>
        this.prizesData[name][0].prizeType1.includes("CTW")
      );
    }

What I'm expecting: I have 4 tabs, 1st one works fine (Shows all). The other 3 need to show only filtered results depending on if "CTW" "IW" or "OG" exist in either prizeType1Name or prizeType2Name.

ImranR
  • 516
  • 2
  • 13
  • 30
  • 2
    have you tried using a computed property? – Our_Benefactors Feb 05 '20 at 15:43
  • I did initially try that, but I wasn't sure how to structure it or what to return exactly – ImranR Feb 05 '20 at 15:48
  • Do you have control over the JSON? It seems to me that it would make more sense for it to be an Array instead of an Object. That would make it easier to iterate to get the filtering you want. – seantunwin Feb 05 '20 at 16:12
  • I agree, unfortunately, I have to use the JSON as it is as – ImranR Feb 05 '20 at 16:14
  • @ImranR you should play around with computed properties. This is what they were designed for. It shouldn't take much experimentation before you understand the concept. I might have some time later tonight to post an example. – Our_Benefactors Feb 05 '20 at 19:36
  • @Our_Benefactors Yes I think computed properties are the best way to approach this also. I wasn't sure how to use it initially but I've figured it out. I'll post my solution later on today when I get the chance as it might help others also. – ImranR Feb 06 '20 at 10:14

3 Answers3

1

I am not sure I understood correctly, but you can try:

new Vue({
  el: '#app',

  data() {
    return {
      myData: {
        prizeTitle: '',
        prizeImage: '',
        prizeFirstLineText: '',
        prizeType1: {
          Image: 'CTW',
          Line1: 'CTW',
          Line2: 'CTW'
        },
        prizeType2: {
          Image: 'IW',
          Line1: 'IW',
          Line2: 'IW'
        },
        prizeTermsText: '',
        prizeType1Name: 'CTW',
        prizeType2Name: 'IW'
      },
      prizesData: {}
    }
  },

  methods: {
    filter (criteria) {
      // getting keyName with value equals to criteria
      const key = 
          Object.keys(this.myData)
            .filter(key => this.myData[key] === criteria)
              .pop().replace('Name', '')

      this.prizesData = this.myData[key]
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
  <button @click="filter('CTW')">CTW</button>
  <button @click="filter('IW')">IW</button>
  <pre>{{ prizesData }}</pre>
</div>
Adam Orłowski
  • 4,268
  • 24
  • 33
Christian Carrillo
  • 2,751
  • 2
  • 9
  • 15
  • How can I adapt this so I'm only checking prizeType1Name or prizeType2Name contains the criteria? I'm currently getting "TypeError: Cannot read property 'replace' of undefined." – ImranR Feb 05 '20 at 16:04
  • i don't sure how is your data structure for all cases – Christian Carrillo Feb 05 '20 at 16:08
  • The data structure of my JSON file, is exactly how I have shown in my question, except there are 43 instead of the 2 I have shown. 'prizeType1Name' and 'prizeType2Name' will either say 'CTW', 'IW' or 'OG'. If they are on the CTW tab on the user interface then I just want the ones that show 'CTW' in 'prizeType1Name' or 'prizeType2Name' to be displayed. To access either of these I use: this.prizesData[name][0].prizeType1 or this.prizesData[name][0].prizeType2 – ImranR Feb 05 '20 at 16:12
  • @ImranR I suggest putting a re-worded version of this comment in the question, particularly that you have 2 keys to evaluate. – seantunwin Feb 05 '20 at 16:14
1
  • Have your filteredPrizes be a comupted property
  • Create an Array of Objects for your criteria (may have refactor the idea to suit your needs or be more dynamic)
// Inside data
filterCriteria: [
  { name: 'prizeType1Name', value: 'CTW' },
  { name: 'prizeType2Name', value: 'CTW' }
];
  • Create a function to return an Array of Objects
    • create an empty Array to push our selections into
    • loop through the prizes Object by keys
    • inside the loop, iterate over the critera
    • assign the selection to the Array, provided it doesn't already exist
    • return the Array
// Inside methods
getFilteredPrizes(prizes, criteria) {
  selected = [];

  Object.keys(prizes).forEach(a => {
    criteria.forEach(b => {
      if (prizes[a][0][b.name] === b.value && !selected.contains(prizes[a][0])) {
        selected.push(prizes[a][0]);
      }
    });
   });

   return selected;
}

new Vue({
  el: '#app',
  data() {
    return {
      prizesData: {
        "1": [
            {
               "prizeTitle": "",
               "prizeImage": "",
               "prizeFirstLineText": "",
               "prizeType1": {
                  "Image": "",
                  "Line1": "",
                  "Line2": ""
               },
               "prizeType2": {
                  "Image": "",
                  "Line1": "",
                  "Line2": ""
               },
               "prizeTermsText": "",
               "prizeType1Name": "CTW",
               "prizeType2Name": ""
            }
         ],
        "2": [
          {
             "prizeTitle": "",
             "prizeImage": "",
             "prizeFirstLineText": "",
             "prizeType1": {
                "Image": "",
                "Line1": "",
                "Line2": ""
             },
             "prizeType2": {
                "Image": "",
                "Line1": "",
                "Line2": ""
             },
             "prizeTermsText": "",
             "prizeType1Name": "IW",
             "prizeType2Name": ""
          }
       ]
      },
      filterCriteria: [
        { name: 'prizeType1Name', value: 'CTW' },
        { name: 'prizeType2Name', value: 'CTW' }
      ]
    }
  },
  computed: {
    filteredPrizes() {
      return this.getFilteredPrizes(this.prizesData, this.filterCriteria);
    }
  },
  methods: {
    getFilteredPrizes(prizes, criteria) {
      selected = [];

      Object.keys(prizes).forEach(a => {
        criteria.forEach(b => {
          if (prizes[a][0][b.name] === b.value && !selected.includes(prizes[a][0])) {
            selected.push(prizes[a][0]);
          }
        });
      });

      return selected;
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
  <h3>Filtered Prizes</h3>
  <pre>{{ filteredPrizes }}</pre>
</div>
seantunwin
  • 1,698
  • 15
  • 15
0

As @seantunwin suggested, I changed my JSON from an object to an array - required some tidying up and formatting but made life easier in the long run.

So my new structure now looks like this:

[

{

"id": 1,

"prizeTitle": "",

"prizeImage": "",

"prizeFirstLineText": "",

"prizeType1": {

"Image": "",

"Line1": "",

"Line2": ""

},

"prizeType2": {

"Image": "",

"Line1": "",

"Line2": ""

},

"prizeTermsText": "",

"prizeType1Name": "CTW",

"prizeType2Name": ""

}

]

I then used a computed property advised by @Our_Benefactors like this:

computed: {

filteredPrizesByQuery: function() {

*return* this.prizes.filter(prize => {

*return* (

prize.prizeType1Name.match(this.query) ||

prize.prizeType2Name.match(this.query)

);

});

}

}

I wrapped my code that I want to loop through using v-for and referenced the computed property:

<div

class="col-sm-12 col-md-6 col-lg-3 w-100 prize-dropdown-wrapper pb-5"

v-for="(prize, index) in filteredPrizesByQuery"

:key="index"

>

I set query as an empty string as default in data

data() {

*return* {

query: "",

prizes: [],

}

};

I added a click event on the tabs to change the query value, similar to @christiancarrillo answer

<b-tab title="IW" @click="query='IW'">

So now when I click on a tab the value changes to CTW, IW or OG, and the computed property checks through the values and returns the results that match the query.

ImranR
  • 516
  • 2
  • 13
  • 30