I have some nested Json in a file called data.json. I am using fetch
to read the file in and then would like to do some filtering based on if a user has a specific option selected in a dropdown on the website.
var jsonData = [{"type": "FeatureCollection",
"features": [
{"type": 'Feature', "properties": {"id": 1}, "geometry": {"type": "Point", "coordinates": [-80.71, 28.34]}},
{"type": 'Feature', "properties": {"id": 2}, "geometry": {"type": "Point", "coordinates": [-79.89, 28.45]}},
{"type": 'Feature', "properties": {"id": 2}, "geometry": {"type": "Point", "coordinates": [-60.79, 28.32]}}
]}
]
I would like to perform some filtering on this array of features based on the "properties": {"id": #}
field. E.g. if a user selects a value that matches that id, keep that in my result display data, else remove it.
I am trying to do something via a Promise like below. I'm new to JavaScript but the usage of .filter
in the below code is my attempt to get this to work.
Ideal solution I want to achieve:
I am displaying a map of data on the U.S. Map with points relative to some locations that belong to the id
field I mentioned. I would like the user to be able to click one of the IDs via a drop-down facility in Javascript, then via their selection, filter the JSON data to only features that belong to that Id. E.g. a traditional filter you'd use on data.
function filterIds(data) {
let idFilter= document.getElementById("id-filter");
let selection = idFilter.addEventListener('change', function(event) {
return this.value;
});
data.features.map((element) => {
// spread out our array of data and filter on specific property (namely, the id key)
return {...element, properties: element.filter((property) => property.id=== selection)};
});
async function getData(url) {
let response = await fetch(url);
let data = await response.json();
// console.log(data);
return data;
};
getData("../data/processed/data.json") // fetch raw data
.then(data => filterIds(data));
CodePudding user response:
Assuming that the OP initially needs to keep the fetched data in sync with the dropdown ... the dropdown-option's id-values after all have to reflect the feature-items of the fetched data's features
array ... the proposed steps for a main initializing process are as follows ...
- Start with fetching the data.
- Create a map/index of id-specific feature-items from the resolved data's
features
array. Thus one later even avoids the filter task due to just looking up the id-specific feature-list based on the selected id. - Render the dropdown-options based on a list of ids's which are the keys of the just created map.
- Implement the
'change'
handling. (The following example code favors an explicit data-binding approach for the handler).
// ... mocked API call ...
async function fetchData() {
return new Promise(resolve =>
setTimeout(() => resolve({
data: [{
"type": "FeatureCollection",
"features": [{
"type": 'Feature',
"properties": {
"id": 1
},
"geometry": {
"type": "Point",
"coordinates": [-80.71, 28.34]
}
}, {
"type": 'Feature',
"properties": {
"id": 2
},
"geometry": {
"type": "Point",
"coordinates": [-79.89, 28.45]
}
}, {
"type": 'Feature',
"properties": {
"id": 2
},
"geometry": {
"type": "Point",
"coordinates": [-60.79, 28.32]
}
}]
}]
}), 2000)
);
}
// one time data transformation in order to avoid filtering later.
function getFeaturesMapGroupedByPropertiesId(featureList) {
return featureList.reduce((map, featureItem) => {
const { properties: { id } } = featureItem;
(map[id] ??= []).push(featureItem);
return map;
}, {});
}
function renderDropdownOptions(node, idList) {
const { options } = node;
// delete/reset the `options` collection.
options.length = 0;
// put/add initial default selected option item.
options.add(new Option('select an id', '', true, true));
idList.forEach(id =>
options.add(new Option(`feature ${ id }`, id))
);
}
function handleFeatureChangeWithBoundFeaturesMap({ currentTarget }) {
const idBasedFeaturesMap = this;
const featureId = currentTarget.value;
console.log(
`id specific feature list for id "${ featureId }" ...`,
idBasedFeaturesMap[featureId],
);
}
async function main() {
console.log('... trigger fetching data ...');
const { data } = await fetchData();
console.log('... fetching data ... done ...', { data });
const idBasedFeaturesMap =
getFeaturesMapGroupedByPropertiesId(data[0].features);
// console.log({ idBasedFeaturesMap });
// //console.log(Object.keys(idBasedFeaturesMap));
const dropdownNode = document.querySelector('select#feature');
if (dropdownNode) {
console.log('... synchronize dropdown data ...');
renderDropdownOptions(
dropdownNode,
Object.keys(idBasedFeaturesMap),
);
dropdownNode
.addEventListener(
'change',
handleFeatureChangeWithBoundFeaturesMap.bind(idBasedFeaturesMap)
);
console.log('... synchronize dropdown data ... done!');
}
}
main();
.as-console-wrapper {
min-height: 100%;
width: 80%;
left: auto!important;
}
body { margin: 0; }
<select id="feature">
<option value="">... initializing ...</option>
<!--
<option value="1">feature 1</option>
<option value="2">feature 2</option>
//-->
</select>
CodePudding user response:
Here's a technique to try, it changes the flow slightly to:
- user loads page
- page assigns an event listener to the dropdown
- page loads data.json into a global variable
Only when the user changes the dropdown does it check to make sure data.json got loaded and then perform the needed filtering.
// global var to store "data" from getData
let processedData;
// assign the listener once, on page load
document.getElementById("id-filter").addEventListener('change', function(event) {
if (!processedData) return; // still no processedData available
let selection = this.value;
let newData = processedData.features.map((element) => {
// spread out our array of data and filter on specific property (namely, the id key)
return {...element, properties: element.filter((property) => property.id === selection)};
});
// do something with newData
});
const setData = (data) => {
// this just stores data in the global processedData variable
processedData = data;
};
async function getData(url) {
let response = await fetch(url);
let data = await response.json();
return data;
};
// call getData once, on page load
getData("../data/processed/data.json") // fetch raw data
.then(data => setData(data));
CodePudding user response:
You need to make some changes in your filter function -
function filterIds(data) {
let idFilter= document.getElementById("id-filter");
let value = null;
let selection = idFilter.addEventListener('change', function(event) {
value = this.value;
});
data.features.map((element) => {
const properties = element.filter((property) =>
property.id=== value);
return {...element, properties};
});
}
To fix Uncaught (in promise) TypeError: element.filter is not a function
you need to make following changes to your getData function -
async function getData(url) {return await fetch(url).then((res) => res.json())};
Also, you must set event listener outside your filter function, at a global level.
CodePudding user response:
Maybe it's not necessary to filter, since you are indexing prior to it. Depending of what you are doing, it can be way faster.
Base example for a table, or a chart.
var jsonData = [{"type": "FeatureCollection",
"features": [
{"type": 'Feature', "properties": {"id": 1}, "geometry": {"type": "Point", "coordinates": [-80.71, 28.34]}},
{"type": 'Feature', "properties": {"id": 2}, "geometry": {"type": "Point", "coordinates": [-79.89, 28.45]}},
{"type": 'Feature', "properties": {"id": 2}, "geometry": {"type": "Point", "coordinates": [-60.79, 28.32]}}
]}
]
// Create a select element
document.body.append(Object.assign(document.createElement("select"), {id: "select" }))
// Count features and create options
for (let i = 0; i < jsonData[0].features.length; i ){
document.getElementById("select").append(
Object.assign(document.createElement("option"), {textContent: i })
)
}
// Read by index at selection change
select.addEventListener("change", (e) => {
let selected = e.target.selectedOptions[0].textContent // Cast to integer
console.log(jsonData[0].features[selected])
})