Reputation: 117
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 onload 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 class="filter">
<div class="filterbutton" data-filter=".filter01">Filter 01</div>
<div class="filterbutton" data-filter=".filter02">Filter 02</div>
<div class="filterbutton" data-filter=".filter03">Filter 03</div>
</div>
<div class="group">
<a class="filter01 filter02 filter03">This element has filter01, filter02 and filter03</a>
<a class="filter01 filter02">This element has filter01 and filter02</a>
<a class="filter01 filter03">This element has filter01 and filter03</a>
<a class="filter02 filter03">This element has filter02 and filter03</a>
<a class="filter01">This element has filter01 only</a>
<a class="filter02">This element has filter02 only</a>
<a class="filter03">This element has filter03 only</a>
</div>
<div class="show-hide">
<div class="showallbutton">Show all</div>
<div class="hideallbutton">Hide all</div>
</div>
Upvotes: 1
Views: 2815
Reputation: 178375
I always delegate
Here I delegate from document; if you can find a closer container, use that instead
I also removed the dot from the data-filter=".
<<<
Lastly I made the color change a class.
the code to detect the show/hide buttons can be simpler if you give both a common class
Here is the solution based on a PEN you showed me
window.addEventListener("DOMContentLoaded", function() {
const filtered = document.querySelectorAll(".group a");
const buttons = document.querySelectorAll(".filter-button");
const toggleContent = () => {
let filterSelector = [...buttons] // the buttons
.filter(btn => btn.matches(".inactive")) // that are grey
.map(btn => btn.dataset.filter); // get their data-filter
console.log(filterSelector)
filtered.forEach(anc => { // Hide the links whose classes are are in the filterSelector
const hide = filterSelector.length > 0 &&
filterSelector.some(filter => [...anc.classList].includes(filter))
anc.style.display = hide ? "none" : "inline-block";
});
}
const filter = e => {
const tgt = e.target; // what was clicked?
if (tgt.matches(".filter-button")) { // a button?
tgt.classList.toggle("inactive"); // toggle it
toggleContent(); // toggle the links
} else if (tgt.closest(".toggleall")) { // or if (!tgt.matches("toggleButton")) return if you give the buttons a class
const show = tgt.matches(".toggleall-show"); // show all?
const hide = tgt.matches(".toggleall-hide"); // hide all?
if (!show && !hide) return; // something else was clicked
buttons.forEach(btn => btn.classList[show ? "remove" : "add"]("inactive")); // if show, remove all inactive if not, addd all inacctive
toggleContent(); // toggle the links
}
};
document.addEventListener("click", filter);
});
@charset "utf-8";
.container {
width: calc(100% - 56px);
padding: 18px;
padding-bottom: 2px;
margin: 10px;
float: left;
overflow: hidden;
border-radius: 10px;
box-shadow: 0 0 1px rgb(0, 0, 0);
}
.toggleall {
margin-bottom: 16px;
display: block;
font-family: Arial, Helvetica, sans-serif;
color: rgb(70, 70, 70);
}
.toggleall-show,
.toggleall-hide {
width: 70px;
padding: 5px;
margin: 2px;
font-family: Arial, Helvetica, sans-serif;
color: rgb(70, 70, 70);
font-weight: bold;
font-size: 12px;
text-align: center;
cursor: pointer;
display: inline-block;
border-radius: 5px;
box-shadow: inset 0 0 0 1px rgb(200, 200, 200);
transition: all 0.5s ease;
}
.toggleall-show:hover,
.toggleall-hide:hover {
text-decoration: underline;
transition: all 0.1s ease;
}
.filter {
margin-bottom: 16px;
display: block;
font-family: Arial, Helvetica, sans-serif;
color: rgb(70, 70, 70);
}
.filter-title,
.filter-button {
padding: 5px;
margin: 2px;
font-family: Arial, Helvetica, sans-serif;
color: rgb(70, 70, 70);
font-weight: bold;
font-size: 12px;
text-align: center;
display: inline-block;
border-radius: 5px;
}
.filter-button {
cursor: pointer;
box-shadow: inset 0 0 0 1px rgb(200, 200, 200);
}
.filter-button:hover {
text-decoration: underline;
}
.inactive {
color: rgb(200, 200, 200);
}
.group {
width: calc(100% - 60px);
padding: 20px;
padding-bottom: 40px;
margin: 10px;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-auto-rows: 200px;
grid-gap: 20px;
grid-auto-flow: dense;
counter-reset: div;
float: left;
overflow: hidden;
border-radius: 10px;
box-shadow: 0 0 1px rgb(0, 0, 0);
}
.group a {
height: 200px;
display: inline-flex;
position: relative;
overflow: hidden;
background: rgb(255, 255, 255);
transition: all 0.5s ease, height 0s;
}
<div class="platform-controller">
<div class="filter">
<div class="filter-title">Filter List 1:</div>
<div class="filter-button" data-filter="filter0a">Item A</div>
<div class="filter-button" data-filter="filter0b">Item B</div>
<div class="filter-button" data-filter="filter0c">Item C</div>
</div>
<div class="filter">
<div class="filter-title">Filter List 2:</div>
<div class="filter-button" data-filter="filter01">Item 1</div>
<div class="filter-button" data-filter="filter02">Item 2</div>
<div class="filter-button" data-filter="filter03">Item 3</div>
</div>
<div class="toggleall">
<div class="toggleall-show">Show all</div>
<div class="toggleall-hide">Hide all</div>
</div>
</div>
<div class="group">
<a class="filter0a filter01" href="www.google.com">Image 1</a>
<a class="filter0b filter02" href="www.google.com">Image 2</a>
<a class="filter0c filter03" href="www.google.com">Image 3</a>
</div>
Upvotes: 1
Reputation: 283
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.
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);
}
.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);
}
<div class="filter">
<div class="filterbutton" data-filter=".filter01">Filter 01</div>
<div class="filterbutton" data-filter=".filter02">Filter 02</div>
<div class="filterbutton" data-filter=".filter03">Filter 03</div>
</div>
<div class="group">
<a class="filter01 filter02 filter03">This element has filter01, filter02 and filter03</a>
<a class="filter01 filter02">This element has filter01 and filter02</a>
<a class="filter01 filter03">This element has filter01 and filter03</a>
<a class="filter02 filter03">This element has filter02 and filter03</a>
<a class="filter01">This element has filter01 only</a>
<a class="filter02">This element has filter02 only</a>
<a class="filter03">This element has filter03 only</a>
</div>
<div class="show-hide">
<div class="showallbutton">Show all</div>
<div class="hideallbutton">Hide all</div>
</div>
Upvotes: 1