I use a filtering function in my webpage. When a button is clicked, it hides the associated elements and fades itself. Once it's clicked again, it brings those elements back and restores its color.
What I want to do is to add two additional buttons: "Show all" and "Hide all". I want them to show/hide all elements and also fade/restore all the button colors upon click.
First I made an onclick
event handler for these two buttons, but it didn't work properly. I think I have to combine everything in the same onl oad event, and that's where I got stuck. Could you please help me to modify my js to achieve my goal?
Snippet:
for (let button of document.querySelectorAll(".filterbutton")) {
button.addEventListener("click", filter);
}
let filters = new Set;
function toggleDisplay(selector, display) {
let elems = document.querySelectorAll(selector);
for (let elem of elems) {
elem.style.display = display;
}
}
function filter() {
let filterSelector = this.dataset.filter;
let show = filters.delete(filterSelector);
this.style.color = show ? "" : "rgb(200,200,200)";
if (!show) {
filters.add(filterSelector); // toggle this filter
} else {
toggleDisplay(filterSelector, "");
}
if (filters.size) {
toggleDisplay([...filters].join(","), "none");
}
}
.filterbutton, .showallbutton, .hideallbutton {
border: 1px solid;
display: inline-block;
background: lightblue;
padding: 5px;
cursor: pointer
}
.group a {
display: block;
}
<div >
<div data-filter=".filter01">Filter 01</div>
<div data-filter=".filter02">Filter 02</div>
<div data-filter=".filter03">Filter 03</div>
</div>
<div >
<a >This element has filter01, filter02 and filter03</a>
<a >This element has filter01 and filter02</a>
<a >This element has filter01 and filter03</a>
<a >This element has filter02 and filter03</a>
<a >This element has filter01 only</a>
<a >This element has filter02 only</a>
<a >This element has filter03 only</a>
</div>
<div >
<div >Show all</div>
<div >Hide all</div>
</div>
CodePudding user response:
I always delegate
Wrap in a div and check the buttons - I gave the divs a common class
const filtered = document.querySelectorAll(".filtered");
const buttons = document.querySelectorAll(".filterbutton");
const toggleContent = () => {
let filterSelector = [...buttons]
.filter(btn => btn.matches(".inactive"))
.map(btn => btn.dataset.filter);
filtered.forEach(div => div.hidden = filterSelector.length > 0 &&
filterSelector.some(filter => [...div.classList].includes(filter)));
}
const filter = e => {
const tgt = e.target;
if (tgt.matches(".filterbutton")) {
tgt.classList.toggle("inactive");
toggleContent();
} else if (tgt.closest(".show-hide")) {
const show = tgt.matches(".showallbutton");
buttons.forEach(btn => btn.classList[show ? "remove" : "add"]("inactive"));
toggleContent();
}
};
document.getElementById("filterDiv").addEventListener("click", filter);
.filterbutton,
.showallbutton,
.hideallbutton {
border: 1px solid;
display: inline-block;
background: lightblue;
padding: 5px;
cursor: pointer
}
.inactive {
color: rgb(200, 200, 200);
}
<div id="filterDiv">
<div data-filter="filter01">Filter 01</div>
<div data-filter="filter02">Filter 02</div>
<div data-filter="filter03">Filter 03</div>
<div >This element has filter01, filter02 and filter03</div>
<div >This element has filter01 and filter02</div>
<div >This element has filter01 and filter03</div>
<div >This element has filter02 and filter03</div>
<div >This element has filter01 only</div>
<div >This element has filter02 only</div>
<div >This element has filter03 only</div>
<div >
<div >Show all</div>
<div >Hide all</div>
</div>
</div>
CodePudding user response:
What I've done is wrapped the adding of event listeners into a function, and made sure that function is only called after the document is loaded. This ensures that the listeners make it to the button regardless of when the script is loaded.
I've also added a function, toggleAll
that accepts a state
boolean`. If true, all filter-ables will be displayed, and if false, all hidden. Along with this, I changed toggleDisplay's second argument to be a bool instead of a string. I did this because the word "toggle" suggests an on and off state as opposed to any number of possible strings.
Lastly, I added a .toggled
class to be applied to the buttons. This just removes the color value from being a magic string.
.filterbutton, .showallbutton, .hideallbutton {
border: 1px solid;
display: inline-block;
background: lightblue;
padding: 5px;
cursor: pointer
}
.group a {
display: block;
}
.filterbutton.toggled {
color: rgb(200,200,200);
}
let filterButtons = null;
const allPossibleFilters = [];
// Cannot be const since we need to set it again in toggleAll
let filters = new Set();
/**
* Initialize the page
*
* Adds the event listeners to the buttons
*/
function init(){
// Store for later use
filterButtons = document.querySelectorAll(".filterbutton");
filterButtons.forEach((btn) => {
// Add event listeners
btn.addEventListener("click", filter);
// Scrape possible filters
allPossibleFilters.push(btn.dataset.filter);
});
document.querySelector('.showallbutton').addEventListener("click", toggleAll.bind(null, true));
document.querySelector('.hideallbutton').addEventListener("click", toggleAll.bind(null, false));
}
function toggleDisplay(selector, displayState) {
const elems = document.querySelectorAll(selector);
const display = displayState ? "" : "none"
for (const elem of elems) {
elem.style.display = display;
}
}
function filter() {
const filterSelector = this.dataset.filter;
const show = filters.delete(filterSelector);
if (show) {
toggleDisplay(filterSelector, true);
this.classList.remove("toggled");
} else {
filters.add(filterSelector);
this.classList.add("toggled");
}
if (filters.size) {
toggleDisplay([...filters].join(","), false);
}
}
/**
* Toggle the view state of all filter-ables
*
* @param {bool} state Whether or not all filter-ables should be shown
*/
function toggleAll(state) {
if (state) {
filters.clear();
filterButtons.forEach((btn) => { btn.classList.remove("toggled"); });
toggleDisplay(allPossibleFilters.join(','), state);
} else {
filters = new Set(allPossibleFilters);
filterButtons.forEach((btn) => { btn.classList.add("toggled"); });
toggleDisplay([...filters].join(','), state);
}
}
/**
* Call the init function once the page has finished loading
*/
if (document.readyState != "loading") {
init();
}
else {
document.addEventListener("DOMContentLoaded", init);
}
<div >
<div data-filter=".filter01">Filter 01</div>
<div data-filter=".filter02">Filter 02</div>
<div data-filter=".filter03">Filter 03</div>
</div>
<div >
<a >This element has filter01, filter02 and filter03</a>
<a >This element has filter01 and filter02</a>
<a >This element has filter01 and filter03</a>
<a >This element has filter02 and filter03</a>
<a >This element has filter01 only</a>
<a >This element has filter02 only</a>
<a >This element has filter03 only</a>
</div>
<div >
<div >Show all</div>
<div >Hide all</div>
</div>