Filtering with checkboxes using jQuery

I want to filter some content using checkboxes.

I've managed to do that thanks to an earlier post which I've simplified a bit here DEMO.

My problem is that each content item can have more than one category attached.When I select category A and category B and then deselect category B, the content item that have both categories attached are removed.

The project I'm working on is going to contain more than two categories. A content item can have many categories attached

HTML:

<ul id="filters">
    <li>
        <input type="checkbox" value="categorya" id="filter-categorya" />
        <label for="filter-categorya">Category A</label>
    </li>
    <li>
        <input type="checkbox" value="categoryb" id="filter-categoryb" />
        <label for="filter-categoryb">Category B</label>
    </li>
</ul>

<div class="categorya categoryb">A, B</div>
<div class="categorya">A</div>
<div class="categorya">A</div>
<div class="categorya">A</div>
<div class="categoryb">B</div>
<div class="categoryb">B</div>
<div class="categoryb">B</div>

Javascript:

$('input').click(function() {
    var category = $(this).val();

    if (!$(this).attr('checked')) $('.' + category).hide();
    else $('.' + category).show();

});

Solution 1:

I think the two most straightforward approaches would be on click of any of the filter checkboxes either:

  1. Hide all <div> elements, then loop through the checkboxes and for each checked one .show() the <div> elements with the associated category.

  2. Loop through all checkboxes to make a list of the classes to be shown, then loop through the <div> elements, checking each one to see if it has one of those classes and .hide() or .show() as appropriate.

Is there some general selector you can use to get all the <div> elements? Do they have a particular container element, like how all the checkboxes are descendents of "#filters"?

// Solution 1

$("#filters :checkbox").click(function() {
   $("div").hide();
   $("#filters :checkbox:checked").each(function() {
       $("." + $(this).val()).show();
   });
});

Demo: http://jsfiddle.net/6wYzw/41/

// Solution 2

$("#filters :checkbox").click(function() {

   var re = new RegExp($("#filters :checkbox:checked").map(function() {
                          return this.value;
                       }).get().join("|") );
   $("div").each(function() {
      var $this = $(this);
      $this[re.source!="" && re.test($this.attr("class")) ? "show" : "hide"]();
   });
});

Demo: http://jsfiddle.net/6wYzw/42/

I guess the first way is shorter, and easier to maintain...

Solution 2:

I wanted to add jquery animation to showing and hiding. The above solutions were not allowing this (first hiding everything, then showing) or were cumbersome with regular expressions. My solution, based on the above, is as follows:

var showselector = $('input:checkbox').map(function(){ 
        return this.checked ? '.' + this.id : null; 
    }).get().join(','); 
var hideselector = (showselector) ? ':not(' + showselector + ')' : '*';

$("div").filter(showselector).slideDown(1500);
$("div").filter(hideselector).slideUp(1500);

Solution 3:

http://jsfiddle.net/FfZsC/

$('input').click(function() {
    var category = $(this).val();

    $('.' + category).each(function () {
        var anyChecked = false;
        var classArray = this.className.split(/\s+/);

        for(idx in classArray)
        {
            if ($('#filter-' + classArray[idx]).is(":checked"))
            {
                anyChecked = true;
                break;
            }
        }

        if (! anyChecked) $(this).hide();
        else $(this).show();

    });

});

Basically, I check each category checkbox associated with those items related to the currently-clicked filter. If any of them are still checked, then the item is displayed; otherwise, it is hidden.