Reputation: 49
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!
Upvotes: 1
Views: 952
Reputation: 14259
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);
}
}
Upvotes: 0
Reputation: 46602
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
},
Upvotes: 1
Reputation: 11
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.
Additional Info: Array.prototype.forEach()
is a kind of functional way of for-loop without break
(cf. Array.prototype.some()
).
Upvotes: 0