javascripthtmlonclick

Multiple radio buttons as filters


Problem

I am trying to create a double filter, where you can combine filters from multiple categories. For example, first category of filters are years, and the second one types of media. I want to make so that you can filter only through the years, media type or both year x type of media (Music form the 1960s). Also, I'm trying to keep selected filters highlighted, somehow so you can keep track of which ones are active (I tried to make them bold, and it works for first set of filters, but fails for the second. How do I solve this problem?

Codepen

https://codepen.io/erutuf/pen/ZPwdBq

Attempt

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(" ");
}

// Add active class to the current button (highlight it)
var btnContainer = document.getElementById("myBtnContainer");
var btns = btnContainer.getElementsByClassName("btn");
for (var i = 0; i < btns.length; i++) {
  btns[i].addEventListener("click", function() {
    var current = document.getElementsByClassName("active");
    current[0].className = current[0].className.replace(" active", "");
    this.className += " active";
  });
}
.filterDiv {
  float: left;
  background-color: white;
  color: black;
  width: 37vw;
  line-height: 100px;
  text-align: center;
  margin: 5px;
  display: none;
  margin-right: 15px;
}

.show {
  display: block;
}

.container {
  margin-top: 20px;
  overflow: hidden;
}

/* Style the buttons */
.btn {
  border: none;
  outline: none;
  padding: 0;
  padding-right: 40px;
  background-color: white;
  cursor: pointer;
  font-size: 16px;
  font-weight: normal;
}

.btn:hover {}

.btn.active {
  font-weight: bold;
}

.content {
  font-size: 16px;
  line-height: 20px;
  text-align: left;
}
<div id="myBtnContainer" align="center" style="line-height: 20pt">
  <!-- <button class="btn active" onclick="filterSelection('all')"> Show all</button>   Show ALL -->
  <button class="btn active" onclick="filterSelection('ShowAll')">Show all</button>
  <button class="btn" onclick="filterSelection('1950')">1950</button>
  <button class="btn" onclick="filterSelection('1960')">1960</button>
  <button class="btn" onclick="filterSelection('1970')">1970</button>
</div>
<br>
<div id="myBtnContainer" align="center" style="line-height: 20pt">
  <!-- <button class="btn active" onclick="filterSelection('all')"> Show all</button>   Show ALL -->
  <button class="btn" onclick="filterSelection('AllMedia')">All Media</button>
  <button class="btn" onclick="filterSelection('Movies')">Movies</button>
  <button class="btn" onclick="filterSelection('Music')">Music</button>
  <button class="btn" onclick="filterSelection('Newspapers')">Newspapers</button>
</div>
<br><br>
<div class="filterDiv AllShapes ShowAll 1950 Newspapers">
  <div class="content">
    1950 Newspapers Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
  </div>
</div>
<div class="filterDiv AllShapes ShowAll 1960 Music">
  <div class="content">
    1960 Music Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
  </div>
</div>
<div class="filterDiv AllShapes ShowAll 1960 Newspapers">
  <div class="content">
    1960 Newspapers Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
  </div>
</div>


Solution

  • The simplest way would be to use <label> and type="radio" inputs.
    You can style the inner <i> element, adjacent (+) to the :checked input.

    Store the filter references (props) into a data-filterable="value1 value2 valueZ" space delimited.

    The trick is to:

    The below example will work for any number of filter-sets:

    const elsInputs = document.querySelectorAll(`[name="year"], [name="type"]`);
    const elsFilterable = document.querySelectorAll('[data-filterable]');
    
    const filterItems = () => {
    
      // Filter checked inputs
      const checked = [...elsInputs].filter(el => el.checked && el.value);
      
      // Collect checked inputs values to array
      const filters = [...checked].map(el => el.value);
      
      // Get elements to filter
      const elsFiltered = [...elsFilterable].filter(el => {
        const props = el.getAttribute("data-filterable").trim().split(/\s+/);
        return filters.every(prop => props.includes(prop));
      });
    
      // Hide all
      elsFilterable.forEach(el => el.classList.add("is-hidden"));
    
      // Show filtered
      elsFiltered.forEach(el => el.classList.remove("is-hidden"));
    };
    
    // Events
    elsInputs.forEach(el => el.addEventListener("input", filterItems));
    // Init
    filterItems();
    /* FILTER INPUTS */
    
    .filterInputs {
      padding-bottom: 5px;
    }
    
    .filterInputs input {
      display: none;
    }
    
    .filterInputs label {
      display: inline-block;
      position: relative;
      padding: 10px 20px;
      cursor: pointer;
      user-select: none; /* prevent highlight */
    }
    
    .filterInputs i {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      box-shadow: inset 0 0 0 2px #0bf;
      z-index: -1;
    }
    
    .filterInputs input:checked + i {
      background: #0bf;
    }
    
    
    /* HELPER CLASSES */
    
    .is-hidden {
      display: none;
    }
    <div class="filterInputs">
      <label><input type="radio" name="year" value="" checked><i></i>All years</label>
      <label><input type="radio" name="year" value="1950"><i></i>1950</label>
      <label><input type="radio" name="year" value="1960"><i></i>1960</label>
      <label><input type="radio" name="year" value="1970"><i></i>1970</label>
    </div>
    
    <div class="filterInputs">
      <label><input type="radio" name="type" value=""><i></i>All types</label>
      <label><input type="radio" name="type" value="movies"><i></i>Movies</label>
      <label><input type="radio" name="type" value="music" checked><i></i>Music</label>
      <label><input type="radio" name="type" value="newspapers"><i></i>Newspapers</label>
    </div>
    
    <div data-filterable="1950 newspapers">1950 Newspapers</div>
    <div data-filterable="1960 music">1960 Music</div>
    <div data-filterable="1960 newspapers">1960 Newspapers</div>