Looking to extrapolate on the already widely existing JSON search/filter/match functionality available with computed functions in Vue. Heres my situation.
I have a local JSON file (shortened example):
Updated JSON to reflect most recent progress
{
"examplejson": [
{
"section_title": "title 1",
"section_category": "category1",
"items": [
{
"category": "category1",
"title": "item title 1",
"url": "url 1",
"description": "Etiam turpis ipsum, gravida a odio ac, sollicitudin egestas tortor.",
"footnote": "footnote 1"
},
{
"category": "category1",
"title": "item title 2",
"url": "url 2",
"description": "Suspendisse consectetur lacus sed maximus consectetur. Etiam nunc magna, fringilla.",
"footnote": "footnote 2"
}
]
},
{
"section_title": "title 2",
"section_category": "category2",
"items": [
{
"category": "category2",
"title": "title 3",
"url": "url 3",
"description": "Donec venenatis justo at ligula dictum tempus. In euismod nulla.",
"footnote": "footnote 3"
},
{
"category": "category2",
"title": "title 4",
"url": "url 4",
"description": "Cras dui felis, pulvinar vel elit quis, imperdiet sollicitudin massa.",
"footnote": "footnote 4"
}
]
}
]
}
Heres my Vue component:
import Vue from 'vue';
import axios from 'axios';
Vue.component('searchableList', {
template: `
<!-- Searchable List Component -->
<div v-cloak>
<label class="search-bar">
<input type="text" class="search__input" v-model="searchString" placeholder="Search...">
</label>
<div class="searchable-content">
<ul class="list">
<li :class="section.section_category + '-section'" v-for="section in filteredList" :key="section.section_title">
<h3 class="section-title">{{ section.section_title }}</h3>
<ul :class="section.section_category + '-section-list'">
<li v-for="item in section.items">
<a :href="item.url">{{ item.title }}</a>
<p>{{ item.description }}</p>
<p class="note">{{ item.footnote }}</p>
</li>
</ul>
</li>
</ul>
</div>
</div>
`
//other items listed below are contained within this component but I separated into individual segments for clear explanation
});
Component data ('componentLoaded' flag to eliminate race condition between computed property & axios.get method):
data() {
return {
componentLoaded: false,
list: [],
searchString: ''
}
},
// mounted lifecycle hook:
mounted() {
this.getListData();
}
// Axios JSON localized JSON get method:
methods: {
getListData() {
var self = this;
const url = '/path/to/localized/list.json';
axios.get(url)
.then(response => {
self.list = response.data.examplejson;
})
.then(function() {
self.componentLoaded = true;
console.log("content loaded");
})
.catch(error => {
console.log("The file:" + ` "${url}" ` + "does not exist or path/filename is incorrect.");
});
}
},
// Computed filtered list property (this is where im having trouble):
computed: {
filteredList: function() {
let self = this;
if (!self.componentLoaded) {
return null;
}
else {
console.log("list loaded successfully");
return self.list;
}
}
}
Component injection point in DOM (conditional to determine if class exists in page, need individual page DOM control, chose not a have a global injection point):
if (document.getElementsByClassName("my-list")[0]){
new Vue({
el: ".my-list"
});
}
HTML:
<div class="container">
<div class="my-list">
<searchable-list></searchable-list>
</div>
</div>
My general issue is that my filter function (filteredList) and rendering of content (getListData()) is getting overly complicated and thus my search filtering is not working, or i am just not building my search filter correctly because i'm not understanding my JSON array completely. This is what i need help with.
Simplest explanation of expected behaviors:
Create empty array for list. Create empty string for search query. Create flag for race condition between axios request and computed property injection and set it to false.
Component renders a list of nested objects ('items') based on a local JSON file data by making a request to the JSON (axios.get()), and assigning the response from that axios.get() request to the empty list array in my data (). Then, after axios request and assignment into empty array has been made, set the flag to true.
Then, based on whether or not flag is true or false, the list array with the newly formed JSON data returned is injected into the vue component through the computed property of filteredList and its assignment into the highest level v-for loop (and subsequent nested v-for loops for rest of nested content).
Help area
Where i'm getting hung up is I have a search input that i need to filter (.filter() function) the content based on the query string ('searchString'), and then (i think what I need to do is) re-render the JSON object based on its match (.match() function) to the query string. I only need to filter the 'items' arrays within each section (and maybe return the applicable section_title).
It seems that a basic filter function on the computed property and a returning all matches to the searchString data query doesn't work correctly. I've been trying some things like the following:
computed: {
filteredList: function() {
let self = this;
if (!self.componentLoaded) {
return null;
}
else {
let listData = self.list;
//-- new search code --//
let searchItem = listData.section.items;
return calcData.filter((searchItem) => {
return searchItem.title.toLowerCase().match(this.searchString.toLowerCase());
});
//-----//
}
}
}
Or something a little more robust like this:
computed: {
filteredList: function() {
let self = this;
if (!self.componentLoaded) {
return null;
}
else {
let listData = self.list;
//-- new search code --//
let searchItem = listData.section.items;
let searchTerm = (this.searchString || "").toLowerCase();
return listData.filter(function(searchItem) {
let title = (searchItem.title || "").toLowerCase();
let description = (searchItem.description || "").toLowerCase();
let footnote = (searchItem.footnote || "").toLowerCase();
return title.indexOf(searchTerm) > -1 || description.indexOf(searchTerm) > -1 || footnote.indexOf(searchTerm) > -1;
});
//-----//
}
}
}
Both functions return the same error in the console:
TypeError: Cannot read property 'items' of undefined
I can console.log any/all items within each key with something like:
console.log(JSON.stringify(self.list.KEY_1.items));
Which is great, but sort of irrelevant. But all it does really, is confirm that my nesting is set up correctly(?).
Im thinking maybe the fact that i'm not iterating through all the initial objects (with keys) correctly. Or/Also due to the fact that I have a general JSON object ("examplejson") with multiple child objects (2 sibling objects with custom keys ("KEY_1", "KEY_2")), with further nested objects ("section_title", "section_category"), and another nested sibling object ("items"), with an array of objects within them, might be causing my "simple" call to actually need a more complicated sort of identification/order of operations, thus needing a more complicated/robust sort of filtering mechanism?
Or maybe it's still because of the race condition? (Which I doubt because console logging shows axios.get() request is made first, then computed function conditional is run after flag is set to true).
Or maybe it's something completely different i'm not even noticing.
Any help or clarification on direction or what i'm doing right/wrong is greatly appreciated. I'm pretty new to Vue and still trying to figure things out. Thanks in advance.
*****UPDATE*****
I am now successfully able to filter the results based on the "section_title" by removing the keys ("KEY_1", "KEY_2") and converting the "examplejson" object to an array (has been updated in example). I can target the section_title and return the entire object the contains "section_title", which also contains the "items" array.
*****UPDATED computed function that works ONLY with "section_title" targeting**
computed: {
filteredList: function(){
let self = this;
if(! self.componentLoaded) {
return null;
} else {
let listData = self.list;
//-- new working search code for section objects ONLY --//
let searchItem = listData.section;
return listData.filter((searchItem) => {
return searchItem.section_title.match(this.searchString);
});
//-----//
}
}
}
The problem now is I need to go one level deeper and target the content within the items
array, in addition to targeting the "section_title" strings. Just appending a .items
to the searchItem
variable, so it reads let searchItem = listData.section.item;
or let searchItem = listData.section.items;
does not work and returns Cannot read property 'item' of undefined
, so i'm not sure how to properly target the objects in the items
array in addition to the section_title
.
Any help appreciated
TypeError: Cannot read property 'items' of undefined
occurs when it is trying to access.items
of anundefined
value. In this case, this means thatlistData.section
isundefined
inlistData.section.items
. – Wei Seng TanlistData.section
to target the key likelistData.KEY_1
inlistData.KEY_1.items
i do not get undefined, but listData filtering does not work – jazzninja"KEY_1"
array"items"
object with"title" : "item title 2"
. Ideally i need to do any string, partial or otherwise that match any object within the "items" array for any/all keys within the larger JSON object"examplejson"
– jazzninjaitems
array within each section in addition to thissection_title
– jazzninja