Exact Match

The following code is something i got together and its not really doing what i need it to do.

I’m trying to get exact match results … If i clicked Internet and Telephone it should only show exactly what i click on. if i clicked on 1 it should show one and if i clicked on all 3 it should only show all three…


<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"> </script>
    <script type="text/javascript">
		 $(document).ready(function(){
		
			$('div.tags').delegate('input:checkbox', 'change', function() {
				var $lis = $('.results > li').fadeOut();
				//For each one checked
					$('input:checked').each(function() {
						$lis.filter('.' + $(this).attr('rel')).fadeIn();
					});
			});
		});
    </script>


  <div class="tags">
    <label><input type="checkbox" rel="internet" /> Internet </label>
    <label><input type="checkbox" rel="telephone" /> Telephone</label>
    <label><input type="checkbox" rel="television" /> Television</label>

</div>

<ul class="results">
    <li class="internet">
        Internet
    </li>
    <li class="telephone">
        Telephone
    </li>
    <li class="television">
        Television
    </li>
    <li class="internet telephone television">
        Internet / Telephone / Television
    </li>
    <li class="television internet">
        Television / Internet
    </li>
    <li class="television telephone">
        Television / Telephone
    </li>
</ul>


nevermind - see paul’s reply, much simpeler :slight_smile:

Here you are needing to filter the list of items so that each shown item has the same number of correct matches as the ones that are checked.


$('div.tags').on('change', 'input:checkbox', function() {
    var $lis = $('.results > li').fadeOut();
    var checkedTypes = $('input:checked');
    $lis.filter(function () {
        item = $(this);
        return checkedTypes.filter(function () {
            return item.is('.' + $(this).attr('rel'));
        }).length === checkedTypes.length;
    }).fadeIn();
});

I just removed my post because your (paul) solution seemed better written, but it’s not exactly what he wants. If a user checks ‘telephone’, then only the ‘telephone’-li should be shown. In your case it shows all li’s with the class telephone, even if it has other classes…

These were my 2 attempts:

  • This one relies on the order of your classes on your li-elements! The class “internet” should always come before “telephone” and “telephone” should always come before “television”. If you want to add another class (for lay-out purposes or so), you can add them at the end of the class-attribute and change the selector to “^=” instead of “=”.
$('div.tags').on('change', 'input:checkbox', function() {
	var $lis = $('.results > li').fadeOut();
	//For each one checked
	var c = "";
	$( "input:checked" ).each(function(){
		c += $( this ).attr( "rel" ) + " ";
	});
	c = $.trim( c );
	$lis.filter("[class='"+c+"']").fadeIn();
});

  • This one is a bit complex and I’m sure a pro JS’er will be able to rewrite it. Or it’ll give you a clue on a way to do so:
    In short: i create an array of the selected checkboxes/classes. I filter $lis and see if the number of classes is the same as the amount of checked checkboxes. If so, then i’m looping over the classes and see if they all appear in the array.
$('div.tags').on('change', 'input:checkbox', function() {
	var $lis = $('.results > li').fadeOut();
	//For each one checked
	var arr = $( "input:checked" ).map(function(){
		return $( this ).attr( "rel" );
	});
	
	$lis.filter(function(){
		var li = $( this );
		var classes = li.attr( "class" ).split( /\\s+/ );
		if( classes.length === arr.length ){
			var ok = true;
			for( var i = 0; i < classes.length; i++ ){
				if( $.inArray( classes[ i ], arr ) < 0 ){
					ok = false;
				}
			}
			
			return ok;
		}	
		return false;
	}).fadeIn();
});

Ahh, I should read things with greater care. Feel free to put your solution back up, and I’ll see what I can come up with that matches what’s wanted too

i edited my previous post, feel free to update it. i’m looking forward to seeing your solution :slight_smile:

Thanks. The simple solution involves checking if each item has no conflict with each of the three checkboxes.
How to check if there’s no conflict? Filtering the checkboxes against each item should result in the total number of checkboxes, when the item fully matches.


$('div.tags').on('change', 'input:checkbox', function() {
    var lis = $('.results > li').fadeOut(),
        inputs = $('input');
    
    lis.filter(function () {
        return item.filter(function () {
            // remove the item if it doesn't match with all of the checkboxes
            return item.is(function () {
                var item = $(this);
                // all checkboxes must agree with whether the item contains that class name or not
                return inputs.filter(function () {
                    var className = '.' + $(this).attr('rel');
                    return item.is(className) === $(this).is(':checked');
                }).length === inputs.length;
            });
        }).length > 0;
    }).fadeIn();
});

There’s a lot of nesting in there though.

This version of the code has some functions expanded out - but is this easier to understand?


$('div.tags').on('change', 'input:checkbox', function() {
    function itemMatchingAllCheckboxes() {
        var item = $(this);
        // all checkboxes must agree with whether the item contains that class name or not
        return inputs.filter(function () {
            var className = '.' + $(this).attr('rel');
            return item.is(className) === $(this).is(':checked');
        }).length === inputs.length;
    }
    
    function compareItemsWithCheckboxes() {
        return $(this).filter(function () {
            return $(this).is(itemMatchingAllCheckboxes);
        }).length > 0;
    }

    var lis = $('.results > li').fadeOut(),
        inputs = $('input');
    
    lis.filter(compareItemsWithCheckboxes).fadeIn();
});

wow… i really appreciate the help. My script was way… off … again thank you so much. honestly would’ve never figured that out in a million years. lol

Well my code started off as being messier than what I provided up there. It began with a series of variables, and then creating more variables for itemIsChecked and noItemIsChecked then checking that both itemIsChecked and !noItemIsChecked apply. That way we’re checking if items matching the checked checkboxes are consistent with the items that don’t match the unchecked checkboxes. Because we only want an item that is checked, but is also not one of the ones that is not checked.

So from a start with “I want the item when it’s in checked group, and when it’s not in the not checked group”, I then make further improvements to the code and simplify things until the original mess no longer exists, and cleaner code is what tends to remain.

I’m trying to figure out how to reset the filters. Everytime I put if length == 0 inside the function it’ll show all the results disappear again.

if non of the checkboxes are checked it should show all the results.

We could update the following piece instead:


var lis = $('.results > li').fadeOut(),
    inputs = $('input');
 
lis.filter(compareItemsWithCheckboxes).fadeIn();

What you can do is to save the checked items and the filtered list of items to variables:


var lis = $('.results > li').fadeOut(),
    inputs = $('input'),
    checkedItems = inputs.filter(':checked'),
    itemsToShow = lis.filter(compareItemsWithCheckboxes);
 
itemsToShow.fadeIn();

Which then allows you to see if nothing is selected, so that you can update the items that you want shown instead:


...
if (checkedItems.length === 0) {
    itemsToShow = lis;
}
itemsToShow.fadeIn();

And because items are animated individually, the fadeIn will be out of sync when different items are being faded out then in, so we can delay the fadeIn until the initial fadeOut has finished:


...
setTimeout(function () {
    itemsToShow.fadeIn();
}, 400);

And now, we can tidy up by bringing the fadeOut and fadeIn closer together in the code, which results in the following:


var lis = $('.results > li'),
...
lis.fadeOut();
setTimeout(function () {
    itemsToShow.fadeIn();
}, 400);

That helps us to see a further improvement that could be made. Check out the following:


var inputs = $('input'),
    checkedItems = inputs.filter(':checked'),
    lis = $('.results > li'),
    itemsToShow = lis;
 
if (checkedItems.length > 0) {
    itemsToShow = itemsToShow.filter(compareItemsWithCheckboxes);
}
...

Instead of performing the compareItemsWithCheckboxes every time, we could instead have the itemsToShow start off as the full list of items, then see if any checkboxes are checked. If there are, we can then go ahead with filtering them.


var inputs = $('input'),
    checkedItems = inputs.filter(':checked'),
    lis = $('.results > li'),
    itemsToShow = lis;

if (checkedItems.length > 0) {
    itemsToShow = itemsToShow.filter(compareItemsWithCheckboxes);
}
...

That leaves us with the following final set of code:


var inputs = $('input'),
    checkedItems = inputs.filter(':checked'),
    lis = $('.results > li'),
    itemsToShow = lis;

if (checkedItems.length > 0) {
    itemsToShow = itemsToShow.filter(compareItemsWithCheckboxes);
}
lis.fadeOut();
setTimeout(function () {
    itemsToShow.fadeIn();
}, 400);

You can find the above test code at http://jsfiddle.net/pmw57/J4fbj/4/