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.
Hello, I got this working in my project, so I’m sharing the code below in case it helps anyone else.
Important:
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.
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).
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.
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.
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.
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>