在通过异步 API 调用加载数据后,如何呈现数据并根据此数据初始化 UI 事件处理?

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

我在名为 data.json 的文件中嵌套了一些 Json。我正在使用 fetch 读取文件,然后想根据用户是否在网站的下拉列表中选择了特定选项来进行一些过滤。

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]}}
       ]}
]

我想根据 "properties": {"id": #} 字段对这个特征数组进行一些过滤。例如。如果用户选择与该 ID 匹配的值,则将其保留在我的结果显示数据中,否则将其删除。

我正在尝试通过如下所示的 Promise 做一些事情。我是 JavaScript 的新手,但在下面的代码中使用 .filter 是我尝试让它工作的尝试。

我想达到的理想方案:

我正在 U.S 上显示数据地图。使用相对于属于我提到的 id 字段的某些位置的点进行映射。我希望用户能够通过 Javascript 中的下拉工具单击其中一个 ID,然后通过他们的选择将 JSON 数据过滤为仅属于该 ID 的要素。例如。您将在数据上使用的传统过滤器。

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));

您需要对过滤器功能进行一些更改 -

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};
});
}

要修复 Uncaught (in promise) TypeError: element.filter is not a function,您需要对 getData 函数进行以下更改 -

async function getData(url) {return await fetch(url).then((res) => res.json())};

此外,您必须在全局级别的过滤器函数之外设置事件侦听器。

这里有一个技巧可以尝试,它将流程稍微改变为:

  • 用户加载页面
  • 页面为下拉列表分配一个事件侦听器
  • 页面加载data.json到全局变量

只有当用户更改下拉列表时,它才会检查以确保 data.json 已加载,然后执行所需的过滤。

// 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)); 

也许没有必要过滤,因为您在它之前建立了索引。 根据您正在做的事情,它可能会更快。

table 或图表的基本示例。

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])
})

假设 OP 最初需要使获取的数据与下拉列表同步...dropdown-option 的 id-values 毕竟必须反映 feature-items获取数据的 features 数组 ... 主要初始化过程的建议步骤如下 ...

  1. 从获取数据开始。
  2. 从已解析数据的 features 数组创建 map/index of id-specific feature-items。因此,后来甚至由于仅根据所选 id 查找 id-specific feature-list 而避免了过滤任务。
  3. 根据 ID 列表渲染 dropdown-option,这些 ID 是刚创建的地图的键。
  4. 实施'change'处理。 (以下示例代码支持处理程序的显式 data-binding 方法)。

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