tez
tez

Reputation: 117

Making a filter to show/hide the elements

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

Answers (2)

mplungjan
mplungjan

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

Bee H.
Bee H.

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

Related Questions