Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stay filtered if already filtered in Jquery

If anyone can think of a better title for this, please don't hesitate to change it.

I am quite new to jQuery and have ran into an issue with filtering elements.

I am filtering DIVS with the data- attribute via a checkbox using the change function.

My issue is that if I filter on one data attribute (colour) and I then filter on another attribute (brand) the elements are all hidden. But if I untick one of the filters, the elements pop back up even though they are still filtered by another attribute.

Is there anyway I can stop this from happening?

Here is the code .

<div data-brand="Nike" data-price="31" data-colour="Blue">Blue</div>
<div data-brand="Nike" data-price="31" data-colour="Red"> Red </div>
Colours:
Blue<input type="checkbox" id="BlueCB" checked>
Red<input type="checkbox" id="RedCB" checked>

Brand: Nike <input type="checkbox" id="NikeCB" checked>

<script>
    var BlueSelector = $('#BlueCB');

    BlueSelector.on("change", function(){
        if($(this).is(":checked"))
        {
            var divs = $("div[data-colour=Blue]");  
            divs.css("display",  "block");
        } else 
        {
            var divs = $("div[data-colour=Blue]");  
            divs.css("display",  "none");
        }
    });

    var RedSelector = $('#RedCB');

    RedSelector.on("change", function(){
        if($(this).is(":checked"))
        {
            var divs = $("div[data-colour=Red]");  
            divs.css("display",  "block");
        } else 
        {
            var divs = $("div[data-colour=Red]");  
            divs.css("display",  "none");
        }
    });

    var NikeSelector = $('#NikeCB');

    NikeSelector.on("change", function(){
        if($(this).is(":checked"))
        {
            var divs = $("div[data-colour=Nike]");  
            divs.css("display",  "block");
        } else 
        {
            var divs = $("div[data-brand=Nike]");  
            divs.css("display",  "none");
        }
    });
</script>

To replicate problem, untick the nike checkbox, then untick the blue checkbox and tick again. The element will pop back out, which is the issue.

Here is the JSFIDDLE

This probably doesn't sound like the best explanation, but I have as best as I could.. you will understand after you play with the fiddle.

like image 783
SK2017 Avatar asked Dec 15 '15 21:12

SK2017


2 Answers

The below code will work for multi brands as well as multi colours, which is done by having a separated filters array for brands and colours, if the filter is checked we add to the corresponding filters array, then finally we check the each div and check whether it has both the brand and colour keywords, if both met we show( * ) it, otherwise we hide( * ) it:

JS Fiddle

var Divs = $('.divs'),
  CheckBoxes = $('.chbx'),
  BlueSelector = $('#BlueCB'),
  RedSelector = $('#RedCB'),
  NikeSelector = $('#NikeCB'),
  Index, Colour, Brand;


CheckBoxes.on("change", function() {
  // Reset filters Arrays
  var ColoursFilters = [],
    BrandsFilters = [];

  // If Blue checkbox is checked, add it to colours array -
  // Otherwise it's unchecked, remove it from the colours array
  if (BlueSelector.is(":checked")) {
    ColoursFilters.push('Blue');
  } else if (BlueSelector.is(":checked") == false) {
    Index = ColoursFilters.indexOf('Blue');
    ColoursFilters.splice(Index, 'Blue');
  }

  // If Red checkbox is checked, add it to colours array -
  // Otherwise it's unchecked, remove it from the colours array
  if (RedSelector.is(":checked")) {
    ColoursFilters.push('Red');
  } else if (RedSelector.is(":checked") == false) {
    Index = ColoursFilters.indexOf('Red');
    ColoursFilters.splice(Index, 'Red');
  }

  // If Nike checkbox is checked, add it to brands array -
  // Otherwise it's unchecked, remove it from the brands array
  if (NikeSelector.is(":checked")) {
    BrandsFilters.push('Nike');
  } else if (NikeSelector.is(":checked") == false) {
    Index = BrandsFilters.indexOf('Red');
    BrandsFilters.splice(Index, 'Blue');
  }

  // Now we check for every colour and every brand -
  // If both conditions met, show the div, else hide it.
  Divs.each(function() {
    Colour = $(this).attr('data-colour');
    Brand = $(this).attr('data-brand');
    if ($.inArray(Colour, ColoursFilters) > -1) {
      if ($.inArray(Brand, BrandsFilters) > -1) {
        $(this).show();
      } else {
        $(this).hide();
      }
    } else {
      $(this).hide();
    }
  })
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div class="divs" data-brand="Nike" data-price="31" data-colour="Blue">
  Blue</div>
<div class="divs" data-brand="Nike" data-price="31" data-colour="Red">
  Red</div>
Colours: Blue
<input class="chbx" type="checkbox" id="BlueCB" checked>Red
<input class="chbx" type="checkbox" id="RedCB" checked>
<br>Brand: Nike
<input class="chbx" type="checkbox" id="NikeCB" checked>

EDIT 1:

In case you want to set all checkboxes default state to be unchecked, we create a function checkFilters() having all the logic in and we call it as the page first loads as well as every time one of the checkboxes state is changed, as shown in this JS Fiddle 2.


EDIT 2:

The javascript code still having redundant code and this is only for 3 checkboxes, supposing there are 10 brands and you have 5 colours for each brand then the code would look messy and repeated with more chances for bugs, to make the code more robust, flexible and also much shorter, we add a data-type and data-keyword attributes to every checkbox, and depending on the value of the data-type attribute we return the respective array and push the value of the data-keyword attribute into the corresponding array so it will work no matter how many colours or brands checkboxes there are, just like in this JS Fiddle 3

Input example:

<input class="chbx" type="checkbox" id="BlueCB" data-type="Colours" data-keyword="Blue" checked> Red

JS:

function checkFilters() {
    // Reset filters Arrays
    var ColoursFilters = [],
    BrandsFilters = [];

   CheckBoxes.each(function() {
       var $th = $(this), ArrayType, keyword;

       // Using ternary operator, we return the array that corresponds to the data-type
       ArrayType = $th.attr('data-type') == 'Colours' ? ColoursFilters : BrandsFilters;
       keyword = $th.attr('data-keyword');
       if ($th.is(':checked')) {
           ArrayType.push(keyword);
       } else if ($th.is(":checked") == false) {
           Index = ArrayType.indexOf(keyword);
           ArrayType.splice(Index, keyword);
       }
   });
   // ...  
}

* Note that jQuery .show() "is roughly equivalent to calling .css( "display", "block")" while .hide() "is roughly equivalent to calling .css( "display", "none")"

like image 145
Mi-Creativity Avatar answered Oct 12 '22 20:10

Mi-Creativity


A very late addition, but had a momentarily forgotten fiddle open in the background. After a letter of the Forgotten Fiddle action group, I decided to post it after all.

Since the goal seems to be to hide data related to unchecked boxes, rather than show any data for which any data-attribute is checked, you could create a blacklist for which data elements should be hidden. As well as mentioned in other answers my preference would be to put all meta data in (data)-tags, such as <input type="checkbox" data-type="colour" data-value="Blue" checked>, but rather than keep separate filters, create a combined filter of the elements that should be hidden. Since jquery can easily select on element attributes, the jquery functionality can be used to do the filtering for you.

The idea is that by determining the filters like this, any new data attribute can be added (such as size, which I took the liberty of adding in the fiddle too).

var $boxes = $('input[data-type]'), //all input boxes with data-type attribute
    $dataObjects =$(); //will be filled with all bound data elements
$boxes.each(function(ind, inp){     //create filter information
    var type = inp.dataset.type, value = inp.dataset.value; //for older browsers, use  $(inp).data('type')  
  var filter =  'div[data-' + type +'="' + value +'"]';     
  inp.dataset.filter = filter;
    $.merge($dataObjects,$(filter));
}); 

$boxes.change(function(){
    var blacklist = $boxes.filter(function(i,b){return !b.checked})
    .map(function(i,b){return b.dataset.filter}).toArray().join();
  $dataObjects.hide().not(blacklist).show();
});

Fiddle

like image 25
Me.Name Avatar answered Oct 12 '22 20:10

Me.Name