Reputation: 45
I have a series of elements on a page that I want users to be able to filter.
The elements have tags stored in a data attribute. I have the it set up so that when the user clicks one of the tag names in the filter area (#filters), the tag is added to an array. It's removed from the array when the tag is clicked again.
When a tag is clicked, I want jQuery to scan through the elements to be filtered (#elementstofilter) and hide ones that do not have all of the tags that are currently held in the filter array.
The filter is meant to be an AND filter, so if Tag1 and Tag2 were selected, it would only show elements that had both tags (#d1 and #d3 in the example below).
I feel like the solution is probably pretty simple, but I'm having a hard time finding an answer. Maybe there's a more effective way of doing this entirely.
<ul id="filters">
<li data-filter="tag1">Tag1</li>
<li data-filter="tag2">Tag2</li>
<li data-filter="tag3">Tag3</li>
<li data-filter="tag4">Tag4</li>
<li data-filter="tag5">Tag5</li>
</ul>
<ul id="elementstofilter">
<li class="project" id="d1" data-tags="tag1 tag2 tag3 tag4 tag5"></li>
<li class="project" id="d2" data-tags="tag1"></li>
<li class="project" id="d3" data-tags="tag1 tag2 tag5"></li>
<li class="project" id="d4" data-tags="tag3 tag4"></li>
</ul>
Here's the jQ that I've got so far. There's not a lot to it yet.
var filter=[]; // Array to hold filter values
$('#filters li').click(function() {
var sel=$(this).attr('data-filter');
if($(this).hasClass('sel')) { // Add or remove values from the filter
filter.push(sel);
} else {
filter=$.grep(filter, function(value) {
return value!=sel;
});
}
if(filter.length==0) { // Show all projects if no filters are selected
$('.project:hidden').show();
} else { // At least one filter is on
/*
if all of the values in the filter are represented in the data-tags of a project
if that project is hidden
show that project
else
hide that project
*/
}
});
Upvotes: 1
Views: 2439
Reputation: 1106
Here is one solution sticking to the array. It's a bit long and I'm sure it can be compressed but the logic is there. http://jsfiddle.net/csrow/PczUm/4/
var filter=[]; // Array to hold filter values 21270555
var matchCount=[]; //Array to hold matched count
$('.project').each(function(i){
matchCount[i]=0;
});
var totalFilter=0;
$('#filters li').click(function() {
var sel=$(this).attr('data-filter');
if ($(this).css("color") == "rgb(255, 0, 0)") {
$(this).css("color","black");
} else {
$(this).css("color","red");
};
if($.inArray(sel,filter)===-1) {
filter.push(sel);
totalFilter+=1;
$('.project').each(function(i) {
var target=$(this).attr('data-tags');
if (target.indexOf(sel)>=0) {
matchCount[i]+=1;
if (matchCount[i] == totalFilter) {
$(this).css("display","none");
} else {
$(this).css("display","list-item");
};
} else {
$(this).css("display","list-item");
};
});
} else {
filter=$.grep(filter, function(value) {
return value!=sel;
});
totalFilter -= 1;
$('.project').each(function(i) {
var target=$(this).attr('data-tags');
if (target.indexOf(sel)>=0) {
matchCount[i]-=1;
if (matchCount[i] !== totalFilter || totalFilter == 0) {
$(this).css("display","list-item");
} else {
$(this).css("display","none");
};
} else {
if (matchCount[i] == totalFilter && totalFilter != 0) {
$(this).css("display","none");
} else {
$(this).css("display","list-item");
};
};
});
};
});
Upvotes: 0
Reputation: 21249
Change your code to use your tags as classes so you can use standard selectors.
The filtering becomes very easy:
$('.project').hide().filter('.tag1,.tag2').show();
Upvotes: 2
Reputation: 199
If you will, please redirect to the page what I do for your logic, as below:
var filters = [];
$("#filters").on("click", "li", function() {
var $scope = $(this);
var filterTag = $scope.data("filter");
if ($scope.hasClass("sel-filter")) {
filters = $.grep(filters, function(obj) {
return filterTag != obj;
});
$scope.removeClass("sel-filter");
} else {
filters.push(filterTag);
$scope.addClass("sel-filter");
}
if (filters.length === 0) {
$(".project:hidden").show();
} else {
$("#elementstofilter").trigger("projectToFilter");
}
});
$("#elementstofilter").bind("projectToFilter", function() {
$.each($(".project", $(this)), function(i, obj) {
var $obj = $(obj);
var storeTags = $obj.data("tags").split(" ");
var found = false;
for (var j in filters) {
if ($.inArray(filters[j], storeTags) == -1) {
$obj.hide();
found = true;
break;
}
}
if (!found) {
$obj.show();
}
});
});
Upvotes: 1
Reputation: 5985
This might be a little dirty... Check out this fiddle
I've gotten rid of the array idea (although it's not a bad idea, so don't get me wrong) and just used simple classes to show and hide. I'm sure someone will show you a "faster" or "better" way of doing this, and may even think arrays will be faster in certain situations, but this is how I would handle it. I've also added a .show
class to all of your project lis
Assuming I am understanding what you've said, I've made the jquery get tag of the clicked tag
$('#filters li').click(function(){
var tag = $(this).attr('data-filter');
This will store the variable of the clicked tag (just like you have)
Then I loop through all of the project tags
$('.project').each(function(){
And write some code to show and hide them if they have that tag
var tags = $(this).attr('data-tags').split(" "); //Get all of the tag names and put them in an array called tags
for(var i=0; i < tags.length; i++){ //Loop through all of the tags
if(tags[i] == tag){ //if one of the tags == the tag
if($(this).hasClass('show')){ //and if it is showing
console.log("hide");
$(this).removeClass("show").addClass("hide"); //hide it
} else {
console.log("show");
$(this).removeClass("hide").addClass("show");
}
}
}
Upvotes: 1