Filtrer depuis votre page

Les filtres déclarés sont les entrées que l'auteur de la carte a choisi de proposer à une page hôte. Ils constituent le seul endroit où une valeur provenant de votre page atteint les données de la carte : la règle est donc stricte, et un nom que l'auteur n'a pas déclaré est refusé avant que quoi que ce soit ne soit appliqué. Appuyez sur Try an undeclared filter pour le constater.

Ouvrir cet exemple seul

Points à remarquer

  • getFilterSchema() vous indique les noms, types et opérateurs : construisez votre interface à partir de ces informations.
  • Une valeur de liste signifie « l'une des valeurs » ; les nombres acceptent { low, high }, les dates { from, to } ou une période relative telle que { operator: "inLast", count: 30, unit: "days" }.
  • Lors de la publication d'une carte, un filtre est déclaré automatiquement pour chaque colonne que l'un de ses segments peut atteindre.
  • Cette page masque toutes les cartes de segment et liste pourtant les valeurs de chaque filtre : getSlicerValues répond aussi pour un segment masqué.
  • Les filtres restreignent ce que la carte affiche ; ils ne constituent pas une frontière de sécurité entre utilisateurs.

Le code

La page complète. Remplacez l'identifiant de publication par le vôtre, et demandez à l'auteur de la carte d'ajouter votre site à ses origines autorisées.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Filter from your page - Icon Map Embed API</title>
  <link rel="stylesheet" href="https://www.icon-map.com/embed-examples/examples.css"> <!-- demo styling only -->
</head>
<body>
  <div class="ex-main">
    <div class="ex-side">
      <div id="filters">loading...</div>
      <h2>Applied</h2>
      <pre class="ex-readout" id="applied">{}</pre>
      <div class="ex-chips" style="margin-top:10px">
        <button id="clear">Clear filters</button>
        <button id="bad">Try an undeclared filter</button>
      </div>
      <pre class="ex-readout" id="error" style="margin-top:8px;color:#ff8a8a"></pre>
    </div>
    <div id="map"></div>
  </div>

  <script src="https://www.icon-map.com/js/iconmap-embed/2/iconmap-embed.umd.min.js"></script>
  <script>
    const el = document.getElementById("map");
    const map = IconMapEmbed.embed(el, {
      publishId: "pub_...",
      // The map is a globe: let this page's own background show in the space around it.
      background: "transparent",
      chrome: { legend: false, layerControl: false, bookmarksBar: false, tourTransport: false },
      // Every card hidden: this page owns the filter UI. (getSlicerValues still answers for a hidden slicer.)
      visibility: { visuals: { "chart-continent": false, "slicer-continent": false, "slicer-size": false, "slicer-capital": false } },
      events: ["filtersChanged"],
    });
    map.ready.catch((error) => console.error(error.code, error.message));

    const values = {};   // filter name -> Set

    (async () => {
      // The filters the map's AUTHOR declared. A page can set these and nothing else.
      const schema = await map.getFilterSchema();
      const host = document.getElementById("filters");
      host.textContent = schema.length ? "" : "This map declares no filters.";

      // Value lists: from the schema when the author fixed them, else from the slicer on the same column.
      const slicers = await map.getSlicers();
      for (const filter of schema) {
        values[filter.name] = new Set();
        let options = filter.values;
        const slicer = slicers.find((s) => s.field === filter.column);
        if (!options && slicer) options = (await map.getSlicerValues({ slicerId: slicer.id })).values;

        host.append(Object.assign(document.createElement("h2"), { textContent: filter.label ?? slicer?.name ?? filter.name }));
        const chips = Object.assign(document.createElement("div"), { className: "ex-chips" });
        for (const option of options ?? []) {
          const chip = Object.assign(document.createElement("button"), { textContent: String(option).replace(/^\d\. /, "") });
          chip.onclick = () => {
            const set = values[filter.name];
            set.has(option) ? set.delete(option) : set.add(option);
            chip.classList.toggle("on", set.has(option));
            apply();
          };
          chips.append(chip);
        }
        host.append(chips);
      }
    })();

    // setFilters replaces every filter value at once; a list means "any of".
    function apply() {
      const filters = {};
      for (const [name, set] of Object.entries(values)) if (set.size) filters[name] = [...set];
      return map.setFilters(filters).catch(show);
    }

    document.getElementById("clear").onclick = () => {
      for (const set of Object.values(values)) set.clear();
      for (const chip of document.querySelectorAll(".ex-chips button.on")) chip.classList.remove("on");
      map.clearFilters();
    };

    // A name the author did not declare is refused before anything is applied.
    document.getElementById("bad").onclick = () => map.setFilters({ salary: [100000] }).catch(show);

    function show(error) {
      document.getElementById("error").textContent = `${error.code}\n${error.message}`;
    }
    map.on("filtersChanged", ({ filters, reason }) => {
      document.getElementById("error").textContent = "";
      document.getElementById("applied").textContent = `${JSON.stringify(filters, null, 1)}\n(${reason})`;
    });
  </script>
</body>
</html>

Pour aller plus loin