Filtering with dynamic radius data

What we’re trying to do

We want to filter CMS items based on a dynamic radius calculation:

  • User selects a ZIP code via Google Maps Autocomplete

  • We calculate the distance from that ZIP to each CMS item (lat/lng stored as CMS fields → added as data-lat, data-lng, data-radius on each item)

  • Every item that is within the radius has a textfield that is filled via js with “Ja” (calculated dynamically)

  • A checkbox should act as a toggle to show only items within their individual radius.

    <input type="checkbox" id="radiusYesCheckbox">
    
    
    
  • The checkbox is set as true as soon as a zip code is entered - Hoping that the finsweet filter is kicking in and filtering the list (disclaimer: it doesnt)

    cb.checked = true;
    cb.dispatchEvent(new Event("change", { bubbles: true }));
    
    

But List Filter v2 does not respond to the checkbox being checked:

  • No filtering is triggered

  • The list does not update

Is there a correct way to:

  1. Use a checkbox as a custom trigger for filtering (not tied to a CMS field)?

  2. Tell List Filter v2 to re-run filtering when this checkbox changes?

  3. Integrate a custom filtering condition (radius logic) with the List Filter system?

Any guidance on how to properly register a checkbox as a filter trigger — or how to manually make List Filter re-filter — would be super appreciated.

URL: https://talnt-2025.webflow.io/

image

Thanks for you support :slight_smile:
Sarah

Required Script in head:

<script async type="module"
src="https://cdn.jsdelivr.net/npm/@finsweet/attributes@2/attributes.js"
fs-list
></script>

Checkbox

<input type="checkbox" id="radiusCheckbox" fs-list-field="radius-toggle">

CMS List wrapper

<div fs-list="list">
  <!-- your CMS items -->
</div>

Filters wrapper

<form fs-list="filters">
  <!-- radiusCheckbox lives here -->
</form>

Each CMS Item (store lat/lng + radius)

<div class="item"
     data-lat="48.12345"
     data-lng="11.56789"
     data-radius="30">
  <!-- Your job card content -->
</div>

Complete radius filtering script

<script>
window.FsAttributes = window.FsAttributes || [];
window.FsAttributes.push([
  'list',
  (listInstances) => {
    const list = listInstances[0];

    // The checkbox toggle element
    const radiusCheckbox = document.getElementById("radiusCheckbox");

    // Global user location variables (fill these when ZIP is selected)
    let userLat = null;
    let userLng = null;

    // Haversine distance (km)
    function calculateDistance(lat1, lon1, lat2, lon2) {
      const R = 6371;
      const dLat = (lat2 - lat1) * Math.PI / 180;
      const dLon = (lon2 - lon1) * Math.PI / 180;

      const a =
        Math.sin(dLat/2) * Math.sin(dLat/2) +
        Math.cos(lat1 * Math.PI/180) *
        Math.cos(lat2 * Math.PI/180) *
        Math.sin(dLon/2) * Math.sin(dLon/2);

      return R * (2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)));
    }

    // ---- 🔥 Dynamic filter logic (THIS is the magic) ----
    list.dynamicFilters.push((item) => {
      // Checkbox off → show all items
      if (!radiusCheckbox.checked) return true;

      // User has not selected a ZIP → show all items
      if (userLat === null || userLng === null) return true;

      const el = item.element;

      const itemLat = parseFloat(el.dataset.lat);
      const itemLng = parseFloat(el.dataset.lng);
      const itemRadius = parseFloat(el.dataset.radius);

      const distance = calculateDistance(userLat, userLng, itemLat, itemLng);

      return distance <= itemRadius;
    });

    // ---- When checkbox changes → reapply all filters ----
    radiusCheckbox.addEventListener("change", () => {
      list.runFilters();
    });

    // ---- ZIP selection handler (Google Autocomplete) ----
    window.setUserLocationFromZip = function(lat, lng) {
      userLat = lat;
      userLng = lng;

      // Auto-enable radius checkbox (optional)
      radiusCheckbox.checked = true;

      // Re-filter list based on new user coordinates
      list.runFilters();
    };

  }
]);
</script>

How to use

After user picks a ZIP code from Google Autocomplete, run:

setUserLocationFromZip(lat, lng);

This will:

  • update userLat / userLng
  • automatically enable the checkbox (optional)
  • re-run all List Filter logic
  • filter items according to your radius

Thanks for your answer, however this seems to not be working unfortunately. I receive the following error:

Uncaught TypeError: list.runFilters is not a function

Are you sure fs-list=“list” & fs-list=“filters” is correct? The Finsweet V2 Doc uses fs-list-element=”list” and fs-list-element=”list”

Thanks again!

Hi @talnt.company

You are absolutely correct, I sort of mixed it up

Sorry & let’s have another go at this

The list wrapper

<div fs-list-element="list">
  <!-- CMS Items -->
</div>

The filters wrapper

<form fs-list-element="filters">
  <!-- checkbox lives here -->
</form>

The cms item

<div class="item"
     data-lat="48.12345"
     data-lng="11.12345"
     data-radius="50">
</div>

For the filtering

<script>
window.FsAttributes = window.FsAttributes || [];
window.FsAttributes.push([
  'list',
  (listInstances) => {
    const listInstance = listInstances[0];

    const checkbox = document.getElementById("radiusCheckbox");
    let userLat = null;
    let userLng = null;

    // Haversine
    function calculateDistance(lat1, lon1, lat2, lon2) {
      const R = 6371;
      const dLat = (lat2 - lat1) * Math.PI / 180;
      const dLon = (lon2 - lon1) * Math.PI / 180;
      const a =
        Math.sin(dLat/2) ** 2 +
        Math.cos(lat1 * Math.PI / 180) *
        Math.cos(lat2 * Math.PI / 180) *
        Math.sin(dLon/2) ** 2;
      return R * (2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)));
    }

    // 🔥 Dynamic filter logic using List Filter V2's internal filtering
    const radiusFilter = (items) => {
      // If toggle off or no ZIP → show all
      if (!checkbox.checked || !userLat || !userLng) return items;

      return items.filter((item) => {
        const el = item.element;
        const lat = parseFloat(el.dataset.lat);
        const lng = parseFloat(el.dataset.lng);
        const radius = parseFloat(el.dataset.radius);

        const distance = calculateDistance(userLat, userLng, lat, lng);

        return distance <= radius;
      });
    };

    // Register custom filter
    listInstance.addFilter(radiusFilter);

    // Checkbox → re-filter
    checkbox.addEventListener("change", () => listInstance.filter());

    // Called when ZIP is selected
    window.setUserLocationFromZip = (lat, lng) => {
      userLat = lat;
      userLng = lng;
      checkbox.checked = true;
      listInstance.filter();
    };
  }
]);
</script>

Of course the checkbox remains as is

<input type="checkbox" id="radiusCheckbox" fs-list-field="radius-toggle">

Please let me know how it goes

Thanks