javascriptasynchronousdata-structuresevent-handlingrendering

How to render data and initialize an UI event handling upon this data after the data has been loaded by an asynchronous API call?


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);
    return await response.json();
};
getData("../data/processed/data.json") // fetch raw data
    .then(data => filterIds(data));

Solution

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