Home > Blockchain >  How to render data and initialize an UI event handling upon this data after the data has been loaded
How to render data and initialize an UI event handling upon this data after the data has been loaded

Time:03-12

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 ...

  1. Start with fetching the data.
  2. 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.
  3. Render the dropdown-options based on a list of ids's which are the keys of the just created map.
  4. 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])
})

  • Related