Hi there !
For those interested, here is a script I tweaked and rewrote to fit my ongoing CMS project
Using the latest MixItUp 3 library, the script performs the following on Webflow dynamic CMS items:
- filter “featuring” elements on load (via Webflow CMS switch)
- filter by category (via CMS “categ” text field)
- sort (via native MixItUp 3)
- search by text input (via CMS “title” text field)
- manage button checked state on click & hash URL load
Have a look at the live codepen, reproducing the Webflow HTML structure.
The script is inspired by @sabanna and patrickkunka and is for one dimension deep only.
Hope that helps !
JavaScript
/**
* one dimensional filtering module
* MixItUp 3 & wf CMS
*/
// 🌮 on DOM loaded
document.addEventListener("DOMContentLoaded", event => {
// 🥑 manage button state
filterChecked();
// 🍅 convert wf switch "featuring" into CSS comboclass
featToClass();
// 🌽 convert wf text field "category" into CSS comboclass
categToClass();
// 🍆 convert wf text field "title" into CSS comboclass
titleToClass();
// 🍋 initialize MixItUp 3 (incl. search module + hash URL filtering)
mixItUp();
});
// 🥑 manage button state
function filterChecked() {
const controls = document.getElementById("controlsMixItUp");
const buttons = document.getElementsByClassName("filter");
for (let i = 0; i < buttons.length; i++) {
let button = buttons[i];
button.addEventListener("click", event => {
// use "currentTarget" to select parent trigger not its children...
let target = event.currentTarget;
if (target.classList != "filter checked") {
// button not yet checked, remove "old" class + add new one
controls.querySelector(".filter.checked").classList.remove("checked");
target.classList.add("checked");
} else {
// button already checked
return;
} // end if
}); // end eventlistener
} // end for loop
} // end checked()
// 🍅 convert wf switch "featuring" into CSS comboclass
function featToClass() {
const mixes = document.getElementsByClassName("mix");
for (let i = 0; i < mixes.length; i++) {
let mix = mixes[i];
let stringFeat = mix.querySelector(".feat").innerHTML;
let classNameFeat = stringFeat.split(" ").join("");
// if wf switch is on, then add classNameFeat as combo class
let feat = mix.querySelector(".feat");
if (mix.querySelector(".feat").classList != "feat w-condition-invisible") {
mix.classList.add(classNameFeat.toLowerCase().trim());
}
}
}
// 🌽 convert wf text field "category" into CSS comboclass
function categToClass() {
const mixes = document.getElementsByClassName("mix");
for (let i = 0; i < mixes.length; i++) {
let mix = mixes[i];
let stringCateg = mix.querySelector(".categ").innerHTML;
let classNameCateg = stringCateg.split(" ").join("");
mix.classList.add(classNameCateg.toLowerCase().trim());
}
}
// 🍆 convert wf text field "title" into CSS comboclass
function titleToClass() {
const mixes = document.getElementsByClassName("mix");
for (let i = 0; i < mixes.length; i++) {
let mix = mixes[i];
let stringTitle = mix.querySelector(".title").innerHTML;
let classNameTitle = stringTitle.split(" ").join("");
mix.classList.add(classNameTitle.toLowerCase().trim());
}
}
// 🍋 initialize MixItUp 3 (with search module)
function mixItUp() {
const container = document.getElementById("containerMixItUp");
const inputSearch = document.getElementById("inputSearch");
let keyupTimeout, searchValue;
// mixer options
let mixer = mixitup(container, {
load: {
filter: ".feat"
},
animation: {
duration: 450,
nudge: true,
reverseOut: true,
effects: "fade scale(0.77) translateZ(-68px) stagger(6ms)"
},
callbacks: {
onMixClick: function() {
// reset the search if a filter is clicked
if (this.matches("[data-filter]")) {
inputSearch.value = "";
}
}
}
});
// 🍧 handle hash URL filtering
(function setHashFromFilter() {
let filterValue,
filterValueCleaned,
filterFromHash;
let filters = document.getElementsByClassName("filter");
for (let i = 0; i < filters.length; i++) {
let filter = filters[i];
filter.addEventListener("click", event => {
// get the "data-filter" respectively "data-sort" attribute
if (event.currentTarget.hasAttribute("data-filter")) {
filterValue = event.currentTarget.getAttribute("data-filter");
}
// handle the "all" data-filter edge case
if (!filterValue.includes(".")) {
filterValue = `.${filterValue}`;
}
filterValueCleaned = filterValue.split(".")[1];
location.hash = "filter=" + encodeURIComponent(filterValueCleaned);
}); // end listener
// 🍪 if hash exists
if (location.hash) {
// handling of the data-filter="all" misssing the "."
if (location.hash == "#filter=all") {
filterFromHash = "all";
} else {
filterFromHash = location.hash.replace("#filter=", ".");
} // end if
// update mixer on hash exists
mixer.filter(filterFromHash);
// handle button state on hash exists
let oldFilter = document
.querySelector(".filter.checked")
.classList.remove("checked");
let newFilter = document
.querySelector(`[data-filter="${filterFromHash}"]`)
.classList.add("checked");
}
} // end for loop
})(); // end setHashFromFilter()
// 🥤 set up a handler to listen for "keyup" events
inputSearch.addEventListener("keyup", event => {
if (inputSearch.value.length < 1) {
searchValue = "";
} else {
searchValue = inputSearch.value.toLowerCase().trim();
}
// basic throttling to prevent mixer thrashing
clearTimeout(keyupTimeout);
keyupTimeout = setTimeout(function() {
filterByString(searchValue);
}, 350);
});
// 🍸 update mixitup mixer.filter method
function filterByString(searchValue) {
if (searchValue) {
// use an attribute wildcard selector to check for matches
mixer.filter(`[class*="${searchValue}"]`);
} else {
// update current category button state
document.querySelector(".filter.checked").classList.remove("checked");
document.querySelector("[data-filter='.feat']").classList.add("checked");
// update mixer's filter
mixer.filter(".feat");
}
}
}
HTML reproducing Webflow structure
<!-- mixitup controls -->
<div id="controlsMixItUp">
<button type="button" class="filter" data-filter="all">all</button>
<button type="button" class="filter checked" data-filter=".feat">featuring</button>
<button type="button" class="filter" data-filter=".green">green</button>
<button type="button" class="filter" data-filter=".blue">blue</button>
<button type="button" class="filter" data-filter=".pink">pink</button>
<button type="button" class="filter" data-sort="default:asc">Asc</button>
<button type="button" class="filter" data-sort="default:desc">Desc</button>
<button type="button" class="filter" data-sort="random">random 🤪</button>
<input type="text" id="inputSearch" placeholder="🔮 Search category or title" />
</div>
<!-- mixitup elements -->
<div id="containerMixItUp">
<div class="mix w-dyn-item">
<div class="wrap_helpers">
<div class="feat w-condition-invisible">feat</div>
<div class="categ">green</div>
<div class="title">Leeloo Dallas Multipass</div>
</div>
</div>
<div class="mix w-dyn-item">
<div class="wrap_helpers">
<div class="feat">feat</div>
<div class="categ">green</div>
<div class="title">Korben Dallas</div>
</div>
</div>
<div class="mix w-dyn-item">
<div class="wrap_helpers">
<div class="feat w-condition-invisible">feat</div>
<div class="categ">blue</div>
<div class="title">Jessica Alba</div>
</div>
</div>
<div class="mix w-dyn-item">
<div class="wrap_helpers">
<div class="feat">feat</div>
<div class="categ">pink</div>
<div class="title">Léon The Professional</div>
</div>
</div>
<div class="mix w-dyn-item">
<div class="wrap_helpers">
<div class="feat w-condition-invisible">feat</div>
<div class="categ">blue</div>
<div class="title">Jim Carrey</div>
</div>
</div>
</div>