How to filter multiple elements/items

Viewed 3811

I'm trying to filter multiple items at once. For example fruits and animals or even 3+. So far it only does it by selecting one item at a time.How can I select more than one? I have also tried https://wch.io/static/tagsort/demo-stacks/index.html but it was bugged keept showing the text but this how it should be but in javascript?

filterSelection("all")
function filterSelection(c) {
  var x, i;
  x = document.getElementsByClassName("filterDiv");
  if (c == "all") c = "";
  for (i = 0; i < x.length; i++) {
    w3RemoveClass(x[i], "show");
    if (x[i].className.indexOf(c) > -1) w3AddClass(x[i], "show");
  }
}

function w3AddClass(element, name) {
  var i, arr1, arr2;
  arr1 = element.className.split(" ");
  arr2 = name.split(" ");
  for (i = 0; i < arr2.length; i++) {
    if (arr1.indexOf(arr2[i]) == -1) {element.className += " " + arr2[i];}
  }
}

function w3RemoveClass(element, name) {
  var i, arr1, arr2;
  arr1 = element.className.split(" ");
  arr2 = name.split(" ");
  for (i = 0; i < arr2.length; i++) {
    while (arr1.indexOf(arr2[i]) > -1) {
      arr1.splice(arr1.indexOf(arr2[i]), 1);     
    }
  }
  element.className = arr1.join(" ");
}
.filterDiv {
  float: left;
  background-color: #2196F3;
  color: #ffffff;
  width: 100px;
  line-height: 100px;
  text-align: center;
  margin: 2px;
  display: none;
}

.show {
  display: block;
}

.container {
  margin-top: 20px;
  overflow: hidden;
}
<h2>Filter DIV Elements</h2>

<input type="radio" onclick="filterSelection('all')" name="category" checked> Show all<br>
<input type="radio" onclick="filterSelection('cars')" name="category"> Cars<br>
<input type="radio" onclick="filterSelection('animals')" name="category"> Animals<br>
<input type="radio" onclick="filterSelection('fruits')" name="category"> Fruits<br>
<input type="radio" onclick="filterSelection('colors')" name="category"> Colors<br>

<div class="container">
  <div class="filterDiv cars">BMW</div>
  <div class="filterDiv colors fruits">Orange</div>
  <div class="filterDiv cars">Volvo</div>
  <div class="filterDiv colors">Red</div>
  <div class="filterDiv cars animals">Mustang</div>
  <div class="filterDiv colors">Blue</div>
  <div class="filterDiv animals">Cat</div>
  <div class="filterDiv animals">Dog</div>
  <div class="filterDiv fruits">Melon</div>
  <div class="filterDiv fruits animals">Kiwi</div>
  <div class="filterDiv fruits">Banana</div>
  <div class="filterDiv fruits">Lemon</div>
  <div class="filterDiv animals">Cow</div>
</div>

3 Answers

// If we save our current state somewhere, we can easily filter the divs.
var checkedCategories = ["cars", "animals", "fruits", "colors"];
// We need a function that detects the click on a checkbox and adds/removes that category.
var changeCategory = function changeCategory(event) {
  // The event object will tell us exactly what was clicked.
  var checkbox = event.target;
  // The category we want toa dd or remove is the attribute
  var category = checkbox.getAttribute("data-category");
  // To check if we already have the category in the array, we just check the index.
  var savedCategoryIndex = checkedCategories.indexOf(category);
  // If the checkbox is checked, that category has to already exist in the array or get added.
  if (checkbox.checked && savedCategoryIndex === -1) {
    checkedCategories.push(category);
  }
  // if it is not checked and is present in the array, it needs to be removed.
  else if (!checkbox.checked && savedCategoryIndex !== -1) {
    checkedCategories.splice(savedCategoryIndex, 1);
  }
  renderCategories();
};
// We want a reusable function that will show/hide any categories we want to see.
var renderCategories = function renderCategories() {
  // We need a list of all the divs. So we just select all the divs that have the data-category attribute and slice them into an array.
  // Could be replaced by Array.from() if your browser supports it.
  var categoryDivs = Array.prototype.slice.call(document.querySelectorAll("div[data-category]"));
  // Now we need to loop over all the divs and check if they need to get hidden or not.
  categoryDivs.forEach(function(div) {
    // Get all the tags the div has
    var tags = div.getAttribute("data-category").split(" ");
    // If at least one tag of the div is inside our categories array, we know it'll need to get shown.
    var divShouldBeShown = tags.some(function(tag) {
      return checkedCategories.indexOf(tag) !== -1;
    });
    // The decide if we need to hide the div or not.
    // Can be replaced by a classList.toggle() if your browser supports it.
    if (divShouldBeShown && div.className.indexOf("hidden") !== -1) {
      div.className = div.className.replace("hidden", "");
    } else if (!divShouldBeShown && div.className.indexOf("hidden") === -1) {
      div.className = div.className + " hidden";
    }
  });
};
// Finally we have to add an event to the checkboxes.
document.querySelector("#categoryBoxes").addEventListener('click', changeCategory);
.hidden {
  display: none;
}
<!-- I've made some changed to the structure of your program to shorten the code alot -->
<h2>Filter DIV Elements</h2>
<!--
      We need checkboxes instead of radio buttons if we want to be able to select multiples.
      By wrapping them inside a div, we can use one event handler instead of one onclick event for each element.
      This makes adding more checkboxes later easier.
     -->
<div id="categoryBoxes">
  <input type="checkbox" data-category="cars" name="category" checked>Cars<br>
  <input type="checkbox" data-category="animals" name="category" checked>Animals<br>
  <input type="checkbox" data-category="fruits" name="category" checked>Fruits<br>
  <input type="checkbox" data-category="colors" name="category" checked>Colors<br>
</div>
<div class="container">
  <!--
       By using data-attributes instead of a classname, we make it easier to change the classname, no need to split/rejoin etc 
       This seperates the javascript from the css, so you can keep the css for styling only and the data-attribute for JS
      -->
  <div data-category="cars" class="filterDiv">BMW</div>
  <div data-category="colors fruits" class="filterDiv">Orange</div>
  <div data-category="cars" class="filterDiv">Volvo</div>
  <div data-category="colors" class="filterDiv">Red</div>
  <div data-category="cars animal" class="filterDiv">Mustang</div>
  <div data-category="colors" class="filterDiv">Blue</div>
  <div data-category="animals" class="filterDiv">Cat</div>
  <div data-category="animals" class="filterDiv">Dog</div>
  <div data-category="fruits" class="filterDiv">Melon</div>
  <div data-category="fruits animals" class="filterDiv">Kiwi</div>
  <div data-category="fruits" class="filterDiv">Banana</div>
  <div data-category="fruits" class="filterDiv">Lemon</div>
  <div data-category="animals" class="filterDiv">Cow</div>
</div>

var inputs = document.getElementsByTagName("input");
var cbs = []; //will contain all checkboxes

for (var i = 0; i < inputs.length; i++) {
  if (inputs[i].type == "checkbox") {
   cbs.push(inputs[i]);
  }
}

filterSelection();

function filterSelection() {
 var checkedVal = [],showAll = false;
 for (var i = 0; i < cbs.length; i++) {
  if (cbs[i].checked && cbs[i].value == "all") {
   var showAll = true;
   break;
  } else if (cbs[i].checked) {
   checkedVal.push(cbs[i].value);
  }
 }

 var x, i;
 x = document.getElementsByClassName("filterDiv");
 //if (c == "all") c = "";
 for (i = 0; i < x.length; i++) {
  w3RemoveClass(x[i], "show");
  if (!showAll) {
   for (var j = 0; j < checkedVal.length; j++) {
    if (x[i].className.indexOf(checkedVal[j]) > -1) {
     w3AddClass(x[i], "show");
    }
   }
  }
    else {
     w3AddClass(x[i], "show");
    }

 }
}



function w3AddClass(element, name) {
 var i, arr1, arr2;
 arr1 = element.className.split(" ");
 arr2 = name.split(" ");
 for (i = 0; i < arr2.length; i++) {
  if (arr1.indexOf(arr2[i]) == -1) {
   element.className += " " + arr2[i];
  }
 }
}

function w3RemoveClass(element, name) {
 var i, arr1, arr2;
 arr1 = element.className.split(" ");
 arr2 = name.split(" ");
 for (i = 0; i < arr2.length; i++) {
  while (arr1.indexOf(arr2[i]) > -1) {
   arr1.splice(arr1.indexOf(arr2[i]), 1);
  }
 }
 element.className = arr1.join(" ");
}
.filterDiv {
  float: left;
  background-color: #2196F3;
  color: #ffffff;
  width: 100px;
  line-height: 100px;
  text-align: center;
  margin: 2px;
  display: none;
}

.show {
  display: block;
}

.container {
  margin-top: 20px;
  overflow: hidden;
}
<h2>Filter DIV Elements</h2>

<div><input type="checkbox" onclick="filterSelection()" name="category" value="all" checked> Show all</div>
<input type="checkbox" onclick="filterSelection();" name="category" value="cars"> Cars<br>
<input type="checkbox" onchange="filterSelection();" name="category" value="animals"> Animals<br>
<input type="checkbox" onchange="filterSelection();" name="category" value="fruits"> Fruits<br>
<input type="checkbox" onchange="filterSelection()" name="category" value="colors"> Colors<br>

<div class="container">
  <div class="filterDiv cars show">BMW</div>
  <div class="filterDiv colors fruits">Orange</div>
  <div class="filterDiv cars">Volvo</div>
  <div class="filterDiv colors">Red</div>
  <div class="filterDiv cars animals">Mustang</div>
  <div class="filterDiv colors">Blue</div>
  <div class="filterDiv animals">Cat</div>
  <div class="filterDiv animals">Dog</div>
  <div class="filterDiv fruits">Melon</div>
  <div class="filterDiv fruits animals">Kiwi</div>
  <div class="filterDiv fruits">Banana</div>
  <div class="filterDiv fruits">Lemon</div>
  <div class="filterDiv animals">Cow</div>
</div>

You can use check boxes instead of radio buttons to do so. Have one button called Apply which will trigger the filtering logic.

Else, if you don't want to use Apply button then you can use the technique used in https://wch.io/static/tagsort/demo-stacks/index.html Here, user is toggling classes based on the click on the span and depending on the clicked divs(having active class), you can build your filtering logic.

Hope this helps.

The attribute ID is missing on your inputs.

the attribute name groups the radio and allows only one to be checked at once but it will not help on its own to treat the inputs via a submit or javascript.

You can use CSS :checked to select siblings coming next if its only about visual(nothing submit or to treat via js) but it will still require an id in order to work as expected.

example with checkbox :

#all:checked  ~ .container .filterDiv{
  display: block;
}


.filterDiv, :not(#all):checked ~ .container  .filterDiv{
  float: left;
  background-color: #2196F3;
  color: #ffffff;
  width: 100px;
  line-height: 100px;
  text-align: center;
  margin: 2px;
  display: none;
}

#cars:checked ~ .container .cars,
#animals:checked ~ .container .animals,
#fruits:checked ~ .container .fruits,
#color:checked ~ .container .colors{
  display: block;
}

.container {
  margin-top: 20px;
  overflow: hidden;
}
<h2>Filter DIV Elements</h2>

<input type="checkbox"  id="all"  checked> Show all<br>
<input type="checkbox" id="cars"> Cars<br>
<input type="checkbox"  id="animals"> Animals<br>
<input type="checkbox"  id="fruits"  > Fruits<br>
<input type="checkbox" id="color" > Colors<br>

<div class="container">
  <div class="filterDiv cars">BMW</div>
  <div class="filterDiv colors fruits">Orange</div>
  <div class="filterDiv cars">Volvo</div>
  <div class="filterDiv colors">Red</div>
  <div class="filterDiv cars animals">Mustang</div>
  <div class="filterDiv colors">Blue</div>
  <div class="filterDiv animals">Cat</div>
  <div class="filterDiv animals">Dog</div>
  <div class="filterDiv fruits">Melon</div>
  <div class="filterDiv fruits animals">Kiwi</div>
  <div class="filterDiv fruits">Banana</div>
  <div class="filterDiv fruits">Lemon</div>
  <div class="filterDiv animals">Cow</div>
</div>

Here is an old pen of mine sorting gallerie via radio input and labels inside a form , so selection can be submited and treated on server side or via javascript. You can inspire yourself from the linked codepen where form and radio are used.

Related