Exclude options in a Filter based on other selected filters

Hi,

Is there a way to exclude options in another filter option based on a selection in the previous option?

For example, the filter contains branches, features, and clients. So when a specific branch is selected only a selected amount of features should be visible because they are connected to that specific branch.

I’ve included the read-only link.
https://preview.webflow.com/preview/zuid?utm_medium=preview_link&utm_source=designer&utm_content=zuid&preview=5599629e1f66a5e9f483c50d2d3ad4f1&pageId=65dd9829ceea0216ccf36e28&locale=nl&workflow=preview

Thanks in advance!

Very best,

Bart

Hey @Bart! I have shared this snippet with several users now, Can you test it and let me know how it goes?

<script>
  window.fsAttributes = window.fsAttributes || [];
  window.fsAttributes.push([
    'cmsfilter',
    (filterInstances) => {
      const searchInput = document.querySelector('#global-field-textsearch');
      searchInput.dispatchEvent(new Event('input', { bubbles: true }));
      setTimeout(function () {
        filterInstances.forEach((filterInstance) => {
          function hideEmptyFilters() {
            const filtersData = filterInstance.filtersData;
            let resultsArray = [];

            filtersData.forEach(function (element) {
              const elements = element.elements;
              elements.forEach(function (element) {
                let filterValue = element.value;
                let resultsNumber = element.resultsCount;
                resultsArray.push({ filterName: filterValue, filterResults: resultsNumber });
              });
            });

            resultsArray.forEach(function (filter) {
              let elements = Array.from(document.querySelectorAll('[fs-cmsfilter-field]')).filter(
                function (element) {
                  return element.textContent.trim() === filter.filterName;
                }
              );

              elements.forEach(function (element) {
                let parentElement = element.parentElement;

                if (parentElement.tagName.toLowerCase() !== 'div') {
                  if (filter.filterResults === 0) {
                    parentElement.style.display = 'none';
                  } else {
                    parentElement.style.display = 'block';
                  }
                }
              });
            });
          }

          hideEmptyFilters();
          filterInstance.listInstance.on('renderitems', (renderedItems) => {
            hideEmptyFilters();
          });
        });
      }, 500);
    },
  ]);
</script>

Hello @Support-Luis

Should this still work? I just can’t get it to work.

Here is the read-only link just in case: Webflow - Elkuch Test

Thank you in advance for any help you can offer :pray:

Cheers,
Markus

Hello, I got this working in my project, so I’m sharing the code below in case it helps anyone else.

Important:

  1. Adjust the Class Names – The script looks for specific classes in the DOM, for example:
  • filter_block (the container for each filter group)
  • checkbox_field, checkbox_label, filter_options, etc.
    Make sure your Webflow or HTML structure has the same class names or update the script to match yours.
  1. Change “Türenkatalog” / “Zargenkatalog” – The script has special logic for these two labels to keep them at the top. If you don’t need special checkboxes or you have different names, replace them (or remove that entire section).
  2. Search Field & Other IDs – The script references #global-field-textsearch as the text input for searching. Replace it with the ID or selector you actually use in your own project.
  3. Delay (setTimeout) – The code uses a setTimeout of 1000ms to ensure everything has loaded before running. You might want to increase or decrease this timeout depending on your site performance.
  4. Pointer Events & Opacity – We disable or enable checkboxes by setting pointerEvents and opacity. If you prefer a different styling or method (e.g., disabling the <input> itself), feel free to adjust that portion.
  5. Event Listeners – The initInteractionListeners() function listens for the user’s first interaction (e.g., a checkbox change or text input). If you prefer that the script runs immediately on page load, you can remove or modify these listeners.
<script>
  window.fsAttributes = window.fsAttributes || [];
  window.fsAttributes.push([
    'cmsfilter',
    (filterInstances) => {
      // Flag to track if anything has changed
      let hasInteracted = false;

      // Function to disable filter options (checkbox items) with 0 results and reorder them within their groups
      function disableEmptyFiltersAndReorder() {
        // Select all checkbox groups
        const filterGroups = document.querySelectorAll('.filter_block');

        filterGroups.forEach(function (group) {
          const checkboxes = group.querySelectorAll('.checkbox_field');
          const enabledCheckboxes = [];
          const disabledCheckboxes = [];
          const specialCheckboxes = []; // For storing the special checkboxes (Türenkatalog, Zargenkatalog)

          checkboxes.forEach(function (checkbox) {
            const labelElem = checkbox.querySelector('.checkbox_label');
            if (!labelElem) return; // Just a safeguard if there's any unexpected markup

            const labelText = labelElem.textContent.trim();
            const optionResults = checkbox.querySelector('.option-results span[fs-cmsfilter-element="filter-results-count"]');

            // Ensure we have an option-results span to extract the result count
            if (optionResults) {
              const resultCount = parseInt(optionResults.textContent.replace(/[^\d]/g, '')) || 0;

              // Check if this checkbox is either "Türenkatalog" or "Zargenkatalog"
              if (labelText === 'Türenkatalog' || labelText === 'Zargenkatalog') {
                // We do NOT sort them, but we DO disable them if 0
                if (resultCount === 0) {
                  const checkboxInput = checkbox.querySelector('.checkbox_input');
                  checkboxInput.style.pointerEvents = 'none'; // Make the checkbox non-clickable
                  checkbox.style.pointerEvents = 'none'; // Disable the entire label
                  checkbox.style.opacity = '0.5'; // Slightly fade out the checkbox
                } else {
                  // Enable if non-zero results
                  const checkboxInput = checkbox.querySelector('.checkbox_input');
                  checkboxInput.style.pointerEvents = 'auto';
                  checkbox.style.pointerEvents = 'auto';
                  checkbox.style.opacity = '1';
                }
                // Always push into special checkboxes (at the top), ignoring alphabetical sorting
                specialCheckboxes.push(checkbox);
              } else {
                // Normal checkboxes
                if (resultCount === 0) {
                  // Disable
                  const checkboxInput = checkbox.querySelector('.checkbox_input');
                  checkboxInput.style.pointerEvents = 'none'; 
                  checkbox.style.pointerEvents = 'none';
                  checkbox.style.opacity = '0.5';
                  disabledCheckboxes.push(checkbox);
                } else {
                  // Enable
                  const checkboxInput = checkbox.querySelector('.checkbox_input');
                  checkboxInput.style.pointerEvents = 'auto'; 
                  checkbox.style.pointerEvents = 'auto';
                  checkbox.style.opacity = '1';
                  enabledCheckboxes.push(checkbox);
                }
              }
            }
          });

          // Sort both enabled and disabled checkboxes alphabetically
          enabledCheckboxes.sort((a, b) => {
            const labelA = a.querySelector('.checkbox_label').textContent.trim().toLowerCase();
            const labelB = b.querySelector('.checkbox_label').textContent.trim().toLowerCase();
            return labelA.localeCompare(labelB);
          });

          disabledCheckboxes.sort((a, b) => {
            const labelA = a.querySelector('.checkbox_label').textContent.trim().toLowerCase();
            const labelB = b.querySelector('.checkbox_label').textContent.trim().toLowerCase();
            return labelA.localeCompare(labelB);
          });

          // Now reorder the checkboxes inside the group:
          // 1. specialCheckboxes (in the order they appear), 
          // 2. enabled (alphabetically sorted),
          // 3. disabled (alphabetically sorted).
          const checkboxContainer = group.querySelector('.filter_options');
          if (!checkboxContainer) return;

          // First append special checkboxes (always remain at the top)
          specialCheckboxes.forEach(function (checkbox) {
            checkboxContainer.appendChild(checkbox);
          });

          // Then the enabled checkboxes
          enabledCheckboxes.forEach(function (checkbox) {
            checkboxContainer.appendChild(checkbox);
          });

          // Finally, the disabled checkboxes
          disabledCheckboxes.forEach(function (checkbox) {
            checkboxContainer.appendChild(checkbox);
          });
        });
      }

      // Listen for changes (checkbox selection or text input) to apply the filtering logic
      function initInteractionListeners() {
        if (hasInteracted) return; // If already interacted, no need to attach listeners again

        // Listen for checkbox change
        const checkboxes = document.querySelectorAll('.checkbox_field input[type="checkbox"]');
        checkboxes.forEach(function (checkbox) {
          checkbox.addEventListener('change', function () {
            if (!hasInteracted) {
              hasInteracted = true; 
              disableEmptyFiltersAndReorder(); // Apply the reordering logic after first interaction
            }
          });
        });

        // Listen for text input (search field)
        const searchInput = document.querySelector('#global-field-textsearch');
        if (searchInput) {
          searchInput.addEventListener('input', function () {
            if (!hasInteracted) {
              hasInteracted = true; 
              disableEmptyFiltersAndReorder(); 
            }
          });
        }
      }

      // Initial setup when the page loads
      setTimeout(function () {
        filterInstances.forEach((filterInstance) => {
          // Only apply after the first interaction
          initInteractionListeners();

          // Re-run when items are rendered (after a filter selection)
          filterInstance.listInstance.on('renderitems', (renderedItems) => {
            if (hasInteracted) {
              disableEmptyFiltersAndReorder();  // Re-apply logic on subsequent interactions
            }
          });
        });
      }, 1000); // Delay to make sure everything is loaded
    },
  ]);
</script>
1 Like

Thank you for sharing @markus3!