Reputation: 151
To all jQuery heros out there, I need help! (again)
I found similar questions to mine on the internet but there were no answers so far :-/
I want to filter a bunch of items with jquery isotope. I adapted the combination filter example from the plugin homepage, which works fine and looks like so:
$(function () {
var $container = $('#container'),
filters = {};
$container.isotope({
itemSelector: '.item',
filter: '',
});
// filter links
$('.filter a').click(function () {
var $this = $(this);
// don't proceed if already selected
if ($this.hasClass('selected')) {
return;
}
var $optionSet = $this.parents('.option-set');
// change selected class
$optionSet.find('.selected').removeClass('selected');
$this.addClass('selected');
// store filter value in object
// i.e. filters.color = 'red'
var group = $optionSet.attr('data-filter-group');
filters[group] = $this.attr('data-filter-value');
// convert object into array
var isoFilters = [];
for (var prop in filters) {
isoFilters.push(filters[prop])
}
var selector = isoFilters.join('');
$container.isotope({
filter: selector
});
return false;
}); });
and some html of the filter menu:
<div id="nav">
<ul class="filter option-set" data-filter-group="who">
<li><a href="#filter-who-any" data-filter-value="" class="selected">Any</a></li>
<li><a href="#filter-who-boys" data-filter-value=".boys">Boys</a></li>
<li><a href="#filter-who-girls" data-filter-value=".girls">Girls</a></li>
</ul>
</div>
I set up a fiddle demo with 3 filter categories which can be combined: jsFiddle
Now I'd like to make it possible to select multiple tags from each category:
Category 1: a OR b OR c
AND
Categoy 2: a OR b OR c
AND
Categoy 3: a OR b OR c
There's a check-box-example by desandro, but this is not quite the thing I'm searching for, because I want to keep the AND condition between the categories. Basically I want 3 filter categories with some check-boxes for each of them (or links but I guess check-boxes are more functional). I think the methods used in both examples are completely different and I don't know how to combine their functionality.
I hope you get my point and that anyone could help me find a solution.
Thanks in advance!
Upvotes: 3
Views: 40218
Reputation: 5072
Using multiple filter types with metafizzy isotope filtering requires a multi-dimensional array to hold an array of each filter type.
For a full code explanation see my answer to this question
Upvotes: 13
Reputation: 768
Don't like jQuery? I modified the code from http://codepen.io/desandro/pen/btFfG which @Luke posted as an answer to ESNext/Vanilla.
I figured I'd post this, since it is a fairly looked up question.
The only difference here is, that you have to create the HTML yourself.
class Metafizzy {
constructor(isotope) {
this.isotope = isotope; // pass Isotope
this.filters = {};
}
run(optionsContainerID, filterDisplayID) {
let options = document.getElementById(optionsContainerID);
let filterDisplay = document.getElementById(filterDisplayID);
// do stuff when checkbox change
options.addEventListener('change', (event) => {
let inputElem = event.target;
this.manageInputElement(inputElem);
let comboFilter = this.getComboFilter(this.filters);
this.isotope.arrange({ filter: comboFilter });
//console.log(comboFilter); // uncomment to see the current filter output
});
}
manageInputElement(inputElem) {
let _this = this;
// get the closest matching parent, and it's attribute value
let parent = inputElem.closest('.iso-option-set');
let group = parent.getAttribute('data-group');
// create array for filter group, if not there yet
let filterGroup = this.filters[group];
if ( ! filterGroup) {
filterGroup = this.filters[group] = [];
}
let isAll = inputElem.classList.contains('iso-all');
// reset filter group if the all box was checked
if (inputElem.type === 'checkbox') {
this.doCheckBoxStuff(inputElem, parent, group, filterGroup, isAll)
} else if (inputElem.type === 'range') {
console.log('is element type "range"')
}
}
doCheckBoxStuff(checkbox, parent, group, filterGroup, isAll) {
if (isAll) {
delete this.filters[group];
if ( ! checkbox.checked) {
checkbox.checked = true;
}
}
// index of
let hasCheckboxValue = filterGroup.includes(checkbox.value);
let index = hasCheckboxValue ? 1 : -1;
if (checkbox.checked) {
let selector = isAll ? 'input' : 'input.iso-all';
let allButton = parent.querySelector(selector);
if (allButton) {
allButton.checked = false;
}
if ( ! isAll && hasCheckboxValue === false) {
// add filter to group
this.filters[group].push(checkbox.value);
}
} else if ( ! isAll) {
// remove filter from group
// gets the index of the property by value
function findWithAttr(array, value) {
for(let i = 0; i < array.length; i += 1) {
if(array[i] === value) {
return i;
}
}
return -1;
}
let startIndex = findWithAttr(this.filters[ group ], checkbox.value)
this.filters[ group ].splice( startIndex, 1 );
// if all boxes unchecked, check the iso-all
if ( ! parent.querySelectorAll('input:not(.iso-all):checked').length) {
parent.querySelector('input.iso-all').checked = true;
}
}
}
getComboFilter(filters) {
let i = 0;
let comboFilters = [];
let message = [];
for (let prop in filters) {
message.push(filters[prop].join(' '));
let filterGroup = filters[prop];
// skip to next filter group if it doesn't have any values
if ( ! filterGroup.length) {
continue;
}
if (i === 0) {
// copy to new array
comboFilters = filterGroup.slice(0);
} else {
let filterSelectors = [];
// copy to fresh array
let groupCombo = comboFilters.slice(0); // [ A, B ]
// merge filter Groups
for (let k = 0, len3 = filterGroup.length; k < len3; k++) {
for (let j = 0, len2 = groupCombo.length; j < len2; j++) {
filterSelectors.push(groupCombo[j] + filterGroup[k]); // [ 1, 2 ]
}
}
// apply filter selectors to combo filters for next group
comboFilters = filterSelectors;
}
i++;
}
let comboFilter = comboFilters.join(', ');
return comboFilter;
}
}
This is how you instantiate it:
<script>
window.onload = function () {
let element = document.getElementById('iso-filter-display');
let iso = new Isotope(element, {
// options
itemSelector: '.iso-display-element'
});
let isoFilter = new Metafizzy(iso);
isoFilter.run('iso-options-container','iso-filter-display')
};
</script>
Upvotes: 0
Reputation: 19587
have a look here
http://codepen.io/desandro/pen/btFfG
it's the same answer by DrewT but from the Isotope team.
all you have to do in order to filter is put every group under same object
var filters = {}; // should be outside the scope of the filtering function
//filtering function
$a.click(function() {
var group = $optionSet.attr('data-filter-group');
var filterGroup = filters[group];
if (!filterGroup) {
filterGroup = filters[group] = [];
}
filters[group].push($this.attr('data-filter-value'));
})
now all you have to do is call getComboFilter function (you don't have to understand the code inside getComboFilter, think of it as a part of isotope)
var comboFilter = getComboFilter( filters );
$container.isotope({ filter: comboFilter});
function getComboFilter( filters ) {
var i = 0;
var comboFilters = [];
var message = [];
for ( var prop in filters ) {
message.push( filters[ prop ].join(' ') );
var filterGroup = filters[ prop ];
// skip to next filter group if it doesn't have any values
if ( !filterGroup.length ) {
continue;
}
if ( i === 0 ) {
// copy to new array
comboFilters = filterGroup.slice(0);
} else {
var filterSelectors = [];
// copy to fresh array
var groupCombo = comboFilters.slice(0); // [ A, B ]
// merge filter Groups
for (var k=0, len3 = filterGroup.length; k < len3; k++) {
for (var j=0, len2 = groupCombo.length; j < len2; j++) {
filterSelectors.push( groupCombo[j] + filterGroup[k] ); // [ 1, 2 ]
}
}
// apply filter selectors to combo filters for next group
comboFilters = filterSelectors;
}
i++;
}
var comboFilter = comboFilters.join(', ');
return comboFilter;
}
Upvotes: 11
Reputation: 53
Here is the JS Fiddle code you can follow for Isotope Combination Filter with multiple select dropdown
$(document).ready(function(){
// init Isotope
var $grid = $('.grid').isotope({
itemSelector: '.item'
});
// store filter for each group
var filters = {};
$('.filters').on('change', '.filters-select', function(){
var $this = $(this);
var filterGroup = $this.attr('data-filter-group');
filters[ filterGroup ] = $this.val();
var filterValue = concatValues( filters );
$grid.isotope({ filter: filterValue });
});
// flatten object by concatting values
function concatValues( obj ) {
var value = '';
for ( var prop in obj ) {
value += obj[ prop ];
}
return value;
}
});
https://jsfiddle.net/srikanthLavudia/vhrbqn6p/
Upvotes: 4
Reputation: 4689
Sorry to dredge this question up, but I recently ran into the same problem and (imho) wasted a lot of time solving this with combined selectors. (Combine every class from array A with every class from array B, etc.)
Use a filtering function. It allows arbitrary complexity and is probably the right choice for all non-trivial cases.
Most importantly, there is no performance benefit to using selectors (which you might expect if the selector were fed directly into a querySelector
or jQuery call). Isotope iterates through each item and applies the selector as a function regardless:
return function( item ) {
return matchesSelector( item.element, filter );
};
Therefore, you might as well just put your filtering logic into a function instead of trying to generate a selector string.
Upvotes: 0
Reputation: 224
I could make it work but it was not as straightforward as I thought in a firts instance. I think you need to bear on mind a couple of things:
If you want to use a restrictive AND condition, then I suggest don´t use the "data-filter-value" option. Instead, put your filter variables as classes. Then, you will need to fire the isotope function by yourself in the onclick event. This is because otherwise you will fire the "default" isotope function when the user clicks the button and you won´t be able to achieve the restrictive AND mentioned.
In the same direction, you will need to use new variable names (so new classes) for each combination of nested filter options you are going to work with. Otherwise, I don´t see other way (at the moment) to get a restrictive AND condition.
This concept about restrictive and nonrestrictive AND clauses is like the difference between an outer and an inner join. It's the concept you should use if you want to handle two filters, one subfilter of the other one.
One example would be something like a list of ebooks which could be filtered by brand, an other subfilter based on the different range prices they belong to.
I show you below a code example where I implement this idea, it can give you a hint of what i mean:
HTML:
<!--Filters section -->
<div id="filters">
<ul id="optionSet1" class="option-set" data-option-key="filter">
<li><a id="filter10" href="#filter" class="selected">Everything</a></li>
<c:forEach var="brand" items="${brands}" varStatus="status" >
<li><a id="filter1${status.count}" href="#filter" class='${brand}'>${brand}</a></li>
</c:forEach>
</ul>
<ul id="optionSet2" class="option-set" data-option-key="filter">
<li><a id="filter20" href="#filter" class="selected">Any Price</a> </li>
<c:forEach var="brandPrice" items="${brandPrices}" varStatus="status" >
<li><a id="filter2${status.count}" href="#filter" class='${brandPrice}'>${brandPrice}</a></li>
</c:forEach>
</ul>
</div>
<!--Elements to filter -->
<div id="portfolio-wrapper" class="row">
<c:forEach var="publication" items="${publications}" varStatus="status" >
<div class='span4 portfolio-item ${publication.filterOption1} ${publication.filterOption2} ${publication.filterOption3}...'>
<!-- filterOption3 would be the combination of filterOption1 and filterOption2, you can make this building a string as the addition of filterOption1 and filterOption2 strings-->
<!--Content to display-->
</c:forEach>
</div>
javascript:
$(function(){
$("a[id^='filter1']").on('click', function() {
// alert($(this).attr('class'));
// alert($('#optionSet2 .selected').attr('class'));
var myclass = $('#optionSet2 .selected').attr('class');
myclass = myclass.replace(' selected','');
myclass = myclass.replace('selected','');
var myclass2 = $(this).attr('class');
myclass2 = myclass2.replace(' selected','');
myclass2 = myclass2.replace('selected','');
if($(myclass=='' && myclass2==''){
$('#portfolio-wrapper').isotope({ filter: '*'});
}else if(myclass==''){
$('#portfolio-wrapper').isotope({ filter: '.'+ myclass2+'' });
}else if(myclass2==''){
$('#portfolio-wrapper').isotope({ filter: '.'+myclass+'' });
}else{
$('#portfolio-wrapper').isotope({ filter: '.'+myclass2+myclass+''});
}
});
$("a[id^='filter2']").on('click', function() {
// alert($(this).attr('class'));
// alert($('#optionSet1 .selected').attr('class'));
var myclass = $('#optionSet1 .selected').attr('class');
myclass = myclass.replace(' selected','');
myclass = myclass.replace('selected','');
var myclass2 = $(this).attr('class');
myclass2 = myclass2.replace(' selected','');
myclass2 = myclass2.replace('selected','');
if(myclass=='' && myclass2==''){
$('#portfolio-wrapper').isotope({ filter: '*'});
}else if(myclass==''){
$('#portfolio-wrapper').isotope({ filter: '.'+ myclass2+'' });
}else if(myclass2==''){
$('#portfolio-wrapper').isotope({ filter: '.'+myclass+'' });
}else{
$('#portfolio-wrapper').isotope({ filter: '.'+myclass+myclass2+''});
}
});
});
Upvotes: 0