简体   繁体   中英

Vue.js: Search through nested arrays

I have been working on a FAQ page that has categories and once you have selected a category, it shows questions users have asked. You can then select a question to display the answer. There are 7 categories and each has an array named "questionList" that has various amounts of questions. I am creating a search bar that will filter through ALL questions in each category and display the question and answer that matches the search. Right now the filtered list only displays the first categories questions....

Here is an example of how my data is setup

categoryList : [
           {
             category: 'Category Title',
             questionList: [
                      {
                         question: 'Question',
                         answer: '<p>copy goes here</p>'
                       }, ... 
                     ]
            },
            {                 
               category: 'Next Title',
               questionList: [
                       { 
                         question: 'Question',
                         answer: '<p>copy goes here</p>'
                       },
                     ]
             }, ...

I was looking at another stackoverflow solution and I really could not figure out how to have it function with my multiple nested arrays. Here is the html:

<div class="filtered" v-for="qa in filteredList">
     <div class="filter-container">
          <h3>{{ qa.question }}</h3>
          <div v-html="qa.answer"></div>
     </div>
</div>

and my computed function:

filteredList() {
      for (i = 0; i < this.categoryList.length; i++) {

            var list =  this.categoryList[i];

            for (j = 0; j < list.questionList.length; j++ ) {

                    return list.questionList.filter(qa => {
                                 return qa.question.toLowerCase().includes(this.search.toLowerCase());
                        })
                   }
              }
          }

I'm not sure if I am even close to the correct solution or waaaay off here... please help!

You're returning abit too early, use filter then loop over the sub-items, though your HTML part is only looping over the main categories, you want two loops for that too.

const component = {
  template: `
  <div>
    <input v-model="search"/>

    <ul v-for="qa in filteredList">
      <li>
        {{qa.category}}
          <ul v-for="qa in qa.questionList">
            <li>
              {{qa.question}}:<br>
              <span v-html="qa.answer"></span>
             </li>
          </ul>
       </li>
    </ul>
  </div>
  `,
  computed: {
    filteredList() {
      return this.categoryList.filter(item => {
        for (const {question, answer} of item.questionList) {
          if (
            question.indexOf(this.search) > -1 ||
            answer.indexOf(this.search) > -1
          ) {
            return item
          }
        }
      })
    }
  },
  data() {
    return {
      search: '',
      categoryList: [
        {
          category: 'Category Title',
          questionList: [
            {
              question: 'Question',
              answer: '<p>copy goes here</p>',
            },
          ],
        },
        {
          category: 'Next Title',
          questionList: [
            {
              question: 'Question',
              answer: '<p>copy goes here</p>'
            }
          ]
        }
      ]
    }
  }
}

Edit (filter questions):

Using bog standard for loops, create a new copy of the array, loop over and only push the new item if questionList contains matches.

filteredList() {
  const ret = []
  for (const item of this.categoryList) {
    const t = {
      ...item,
      questionList: []
    }
    for (const q of item.questionList) {
      if (
        q.question.indexOf(this.search) > -1 ||
        q.answer.indexOf(this.search) > -1
      ) {
        t.questionList.push(q)
      }
    }
    if (t.questionList.length) ret.push(t)
  }
  return ret
},

This is the code:

data()
{
  return {
    searchTerm: '',
  }
},
computed:
{
  allQuestions()
  {
    // un-nest all questions as a flat array
    return this.categoryList
      .map(category => category.questionList)
      .reduce((acc, item) => acc.concat(item), []);
  }
  filteredList()
  {
    const term = this.searchTerm.toLowerCase();
    if(!term) return this.allQuestions;
    // only questions or answers which contain the term will be returned
    return this.allQuestions.filter(qa => qa.question.toLowerCase().indexOf(term) >= 0 
      || qa.answer.toLowerCase().indexOf(term) >= 0);
  }
}

Right now the filtered list only displays the first categories questions....

That's because you return the filtered array of first category in the for-loop.

Note that computed properties are functions. They are done when they return something.

What you should do is to iterate all QAs and create a new array whose elements have question text including keywords.

JSFiddle DEMO

Additional Info: Array.prototype.forEach() is a kind of functional way of for-loop without break (cf. Array.prototype.some() ) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM