Dynamically Filtering Layouts with Isotope & Bootstrap


By

I’m sure pretty much anyone who makes websites has stumbled upon jQuery Masonry at some point or other. Some people haven’t though, so here’s what Masonry is – it’s a fantastic plugin by David DeSandro that allows for extremely creative layouts.

You specify which HTML element for Masonry to do its magic on, and the children of that element are all positioned the way a mason positions stones.

Now, while Masonry is absolutely fantastic, it is lacking a few features. One of the biggest is the fact that it is not possible to dynamically filter elements by class name. You can’t say, “Only show elements with class ‘show'”.

Now, imagine a plugin that has that, and some more mixed in. That plugin is called Isotope. Isotope is essentially Masonry, but with some nifty features thrown in, such as filtering and custom layout modes.

Dynamically Filtering Layouts with Isotope and Bootstrap portfolio filter custom layout

In this post, I will show you how to use Isotope to create a magical layout of your own, and how to dynamically filter which elements are displayed.

You’ll learn to:

  • Apply isotope to an element correctly
  • Make it responsive
  • Be able to dynamically filter an element with more than one filter
  • Use hash history to save the current state

Before you begin, download Bootstrap, because we’ll use that to make a navigation bar that will control the stuff on the page.

Step 1: The HTML

Before we can use Isotope, we need to create acontainer div that will hold some child elements.

<section id="main" class="container">

</section>

Now, just add a bunch of divs with different classes and text.

<section id="main" class="container">
    <div class="box cat1 cat2">
    Odd future 3 wolf moon twee, cliche marfa post-ironic pop-up master cleanse next level pickled gentrify. Direct trade small batch YOLO, pitchfork lomo PBR high life intelligentsia readymade letterpress flexitarian tousled. Direct trade pitchfork hoodie twee, yr pour-over Austin marfa 90's flexitarian food truck mlkshk helvetica +1 trust fund.
    </div>

    <div class="box cat3">
    Bushwick marfa craft beer, wes anderson you probably haven't heard of them locavore tousled carles pork belly synth DIY blog cray. Farm-to-table craft beer typewriter, polaroid keytar intelligentsia cliche mcsweeney's ethnic mustache cred DIY. Typewriter semiotics raw denim, organic vinyl sustainable meggings beard carles banh mi you probably haven't heard of them. Vinyl salvia brooklyn, four loko biodiesel meh scenester lomo.
    </div>

    <div class="box cat1">
    Try-hard thundercats food truck, shoreditch mlkshk polaroid truffaut ugh small batch cred vice mixtape williamsburg stumptown street art. Cliche intelligentsia cred high life, trust fund meggings leggings. Blog swag banksy freegan, ethical selfies ennui cray helvetica cliche literally umami vinyl. Banh mi post-ironic mixtape, food truck PBR pickled vinyl umami.
    </div>

    <div class="box cat2">
    Dreamcatcher actually pug, bushwick occupy semiotics banjo. Sartorial pop-up PBR, etsy truffaut post-ironic marfa carles banksy helvetica. Leggings mixtape chambray gluten-free pickled cred american apparel, shoreditch church-key synth gastropub pinterest semiotics twee messenger bag.
    </div>

    <div class="box cat2 cat3">
    Odd future sriracha fixie disrupt, helvetica ugh ennui semiotics selfies. Pitchfork blog food truck art party ethical, skateboard flannel hella. Wes anderson thundercats squid four loko art party chillwave +1, leggings scenester fashion axe trust fund photo booth meggings direct trade 90's. Squid sriracha 3 wolf moon, Austin wayfarers biodiesel put a bird on it food truck try-hard street art pop-up mustache retro.
    </div>

    <div class="box cat2">
    Mustache flannel whatever blue bottle stumptown terry richardson swag scenester, retro ugh pop-up tonx ennui kogi chillwave. Craft beer skateboard tattooed cray, high life scenester marfa salvia american apparel twee pitchfork master cleanse leggings banksy four loko. You probably haven't heard of them skateboard banksy beard.
    </div>

    <div class="box cat1 cat3">
    Butcher etsy vinyl skateboard photo booth, wolf gastropub twee. Pork belly williamsburg disrupt, art party deep v mcsweeney's flannel vinyl master cleanse cliche. PBR intelligentsia vice small batch.
    </div>

    <div class="box cat3">
    Mcsweeney's freegan readymade, fingerstache pitchfork organic food truck mixtape odd future single-origin coffee PBR. Four loko selfies PBR church-key, twee vice locavore art party. Brunch blog beard direct trade, fixie readymade cosby sweater. Marfa wayfarers fixie, squid selfies hashtag trust fund cliche.
    </div>


    <div class="box cat1 cat3">
    Odd future 3 wolf moon twee, cliche marfa post-ironic pop-up master cleanse next level pickled gentrify. Direct trade small batch YOLO, pitchfork lomo PBR high life intelligentsia readymade letterpress flexitarian tousled. Direct trade pitchfork hoodie twee, yr pour-over Austin marfa 90's flexitarian food truck mlkshk helvetica +1 trust fund.
    </div>

    <div class="box cat2">
    Bushwick marfa craft beer, wes anderson you probably haven't heard of them locavore tousled carles pork belly synth DIY blog cray. Farm-to-table craft beer typewriter, polaroid keytar intelligentsia cliche mcsweeney's ethnic mustache cred DIY. Typewriter semiotics raw denim, organic vinyl sustainable meggings beard carles banh mi you probably haven't heard of them. Vinyl salvia brooklyn, four loko biodiesel meh scenester lomo.
    </div>

    <div class="box cat3">
    Try-hard thundercats food truck, shoreditch mlkshk polaroid truffaut ugh small batch cred vice mixtape williamsburg stumptown street art. Cliche intelligentsia cred high life, trust fund meggings leggings. Blog swag banksy freegan, ethical selfies ennui cray helvetica cliche literally umami vinyl. Banh mi post-ironic mixtape, food truck PBR pickled vinyl umami.
    </div>

    <div class="box cat1">
    Dreamcatcher actually pug, bushwick occupy semiotics banjo. Sartorial pop-up PBR, etsy truffaut post-ironic marfa carles banksy helvetica. Leggings mixtape chambray gluten-free pickled cred american apparel, shoreditch church-key synth gastropub pinterest semiotics twee messenger bag.
    </div>

    <div class="box cat2 cat1">
    Odd future sriracha fixie disrupt, helvetica ugh ennui semiotics selfies. Pitchfork blog food truck art party ethical, skateboard flannel hella. Wes anderson thundercats squid four loko art party chillwave +1, leggings scenester fashion axe trust fund photo booth meggings direct trade 90's. Squid sriracha 3 wolf moon, Austin wayfarers biodiesel put a bird on it food truck try-hard street art pop-up mustache retro.
    </div>

    <div class="box cat3">
    Mustache flannel whatever blue bottle stumptown terry richardson swag scenester, retro ugh pop-up tonx ennui kogi chillwave. Craft beer skateboard tattooed cray, high life scenester marfa salvia american apparel twee pitchfork master cleanse leggings banksy four loko. You probably haven't heard of them skateboard banksy beard.
    </div>

    <div class="box cat2 cat3">
    Butcher etsy vinyl skateboard photo booth, wolf gastropub twee. Pork belly williamsburg disrupt, art party deep v mcsweeney's flannel vinyl master cleanse cliche. PBR intelligentsia vice small batch.
    </div>

    <div class="box cat1">
    Mcsweeney's freegan readymade, fingerstache pitchfork organic food truck mixtape odd future single-origin coffee PBR. Four loko selfies PBR church-key, twee vice locavore art party. Brunch blog beard direct trade, fixie readymade cosby sweater. Marfa wayfarers fixie, squid selfies hashtag trust fund cliche.
    </div>

    <div class="box cat2 cat1">
    Odd future 3 wolf moon twee, cliche marfa post-ironic pop-up master cleanse next level pickled gentrify. Direct trade small batch YOLO, pitchfork lomo PBR high life intelligentsia readymade letterpress flexitarian tousled. Direct trade pitchfork hoodie twee, yr pour-over Austin marfa 90's flexitarian food truck mlkshk helvetica +1 trust fund.
    </div>

    <div class="box cat2">
    Bushwick marfa craft beer, wes anderson you probably haven't heard of them locavore tousled carles pork belly synth DIY blog cray. Farm-to-table craft beer typewriter, polaroid keytar intelligentsia cliche mcsweeney's ethnic mustache cred DIY. Typewriter semiotics raw denim, organic vinyl sustainable meggings beard carles banh mi you probably haven't heard of them. Vinyl salvia brooklyn, four loko biodiesel meh scenester lomo.
    </div>

    <div class="box cat1">
    Try-hard thundercats food truck, shoreditch mlkshk polaroid truffaut ugh small batch cred vice mixtape williamsburg stumptown street art. Cliche intelligentsia cred high life, trust fund meggings leggings. Blog swag banksy freegan, ethical selfies ennui cray helvetica cliche literally umami vinyl. Banh mi post-ironic mixtape, food truck PBR pickled vinyl umami.
    </div>

    <div class="box cat3">
    Dreamcatcher actually pug, bushwick occupy semiotics banjo. Sartorial pop-up PBR, etsy truffaut post-ironic marfa carles banksy helvetica. Leggings mixtape chambray gluten-free pickled cred american apparel, shoreditch church-key synth gastropub pinterest semiotics twee messenger bag.
    </div>

    <div class="box cat1 cat3">
    Odd future sriracha fixie disrupt, helvetica ugh ennui semiotics selfies. Pitchfork blog food truck art party ethical, skateboard flannel hella. Wes anderson thundercats squid four loko art party chillwave +1, leggings scenester fashion axe trust fund photo booth meggings direct trade 90's. Squid sriracha 3 wolf moon, Austin wayfarers biodiesel put a bird on it food truck try-hard street art pop-up mustache retro.
    </div>

    <div class="box cat3">
    Mustache flannel whatever blue bottle stumptown terry richardson swag scenester, retro ugh pop-up tonx ennui kogi chillwave. Craft beer skateboard tattooed cray, high life scenester marfa salvia american apparel twee pitchfork master cleanse leggings banksy four loko. You probably haven't heard of them skateboard banksy beard.
    </div>

    <div class="box cat1 cat3">
    Butcher etsy vinyl skateboard photo booth, wolf gastropub twee. Pork belly williamsburg disrupt, art party deep v mcsweeney's flannel vinyl master cleanse cliche. PBR intelligentsia vice small batch.
    </div>

    <div class="box cat3">
    Mcsweeney's freegan readymade, fingerstache pitchfork organic food truck mixtape odd future single-origin coffee PBR. Four loko selfies PBR church-key, twee vice locavore art party. Brunch blog beard direct trade, fixie readymade cosby sweater. Marfa wayfarers fixie, squid selfies hashtag trust fund cliche.
    </div>

</section>

Now, you have a big page full of text. Each div has a class of box so that we can style each one appropriately. Now, look at the other classes – either cat1, cat2, or cat3. These are the classes based on which we’ll be filtering.

Let’s add the navigation bar which will contain the links that filter the content.

<nav class="navbar navbar-default" role="navigation">
        <div class="container group">

            <a class="navbar-brand" href="#">Learning to use Isotope.js</a>
            
            <ul class="nav navbar-nav navbar-left" id="controls">
                <li><a href="#cat1">Category 1</a></li>
                <li><a href="#cat2">Category 2</a></li>
                <li><a href="#cat3">Category 3</a></li>
            </ul>
        </div>
</nav>

<!-- the boxes are after this -->

This is just a typical Bootstrap navbar, take a look at the bootstrap page for more info.

At this point, we have all the HTML necessary – now, we can make it look pretty.

Step 2: The CSS

The first thing you want to do, always, before you start writing CSS, is include a CSS reset. I personally like to use Eric Meyer’s fantastic CSS reset, but it’s up to you what you would like to use.

Next, we can make the typography look a bit better.

body {
font-family: arial;
font-size: 15px;
line-height: 25px;
color: #515151;
}

While the text looks fine, the boxes themselves… don’t even really look like boxes. Let’s make them look good as well.

.box {
/* if not using border-box 
width: 218px; 
*/
width: 250px;
background: #ffffff;
border: 1px solid #999;
padding: 15px;
margin: 10px;
float: left;

-webkit-border-radius: 5px;
border-radius: 5px;

-webkit-box-shadow: 0px 0px 5px 1px #aaa;
box-shadow: 0px 0px 5px 1px #aaa;
}

We want the boxes to be 250px wide, so that’s what we set. Keep in mind, however, that Bootstrap applies box-sizing: border-box by default – this allows everything to work a bit more intuitively.

I write width: 250px and that’s how wide the box is. However, in case you don’t want to use that property, use width: 218px instead. Check out CSS-Tricks’ explanation for more details.

The page seems a little cramped; let’s shift the boxes down just a bit.

#main {
margin-top: 10px;
}

The last thing we need to do with the CSS is add some styles to the isotope boxes so that they animate properly.

.isotope-item {
  z-index: 2;
}
 
.isotope-hidden.isotope-item {
  pointer-events: none;
  z-index: 1;
}
 
.isotope,
.isotope .isotope-item {
  /* change duration value to whatever you like */
  -webkit-transition-duration: 0.8s;
     -moz-transition-duration: 0.8s;
          transition-duration: 0.8s;
}
 
.isotope {
  -webkit-transition-property: height, width;
     -moz-transition-property: height, width;
          transition-property: height, width;
}
 
.isotope .isotope-item {
  -webkit-transition-property: -webkit-transform, opacity;
     -moz-transition-property:    -moz-transform, opacity;
          transition-property:         transform, opacity;
}

Now, the page should look good.

Step 3: The JavaScript

Now, the fun begins.

$(document).ready(function(){

//substr so there isn't a '#'
var hashFilter = location.hash.substr(1);

var mainEl = $('#main');
var transitionDuration = 800;
var columnWidth = 270;

mainEl.isotope({
    filter: hashFilter,
    animationEngine: 'best-available', //CSS3 if browser supports it, jQuery otherwise
    animationOptions: {
        duration: transitionDuration
    },
    containerStyle: {
        position: 'relative',
        overflow: 'visible'
    },
    masonry: {
        columnWidth: columnWidth
    }
});     

Let me explain, step by step. hashFilter takes the hash and gets rid of the ‘#‘ character.

For example, if the hash was #.cat1, hashFilter would be .cat1. If the hash is just ‘#‘ or even just an empty string, ”, hashFilter would also be an empty string. If the filter option receives an empty string, '', as the value, then Isotope just displays everything.

mainEl is the element that we want to isotope. transitionDuration is, unsurprisingly, the duration of the animation when the boxes are dynamically filtered. columnWidth is the width of each column.

The next code block actually isotopes the element. It filters it by the hash, since we will implement hash history functionality. animationEngine is set to best-available, which means that isotope uses CSS3 to animate the boxes if the browser supports it, and jQuery if the browser does not support CSS3.

Isotope knows what the browser supports or doesn’t support by using modernizr. In animationOptions, we say that the duration is transitionDuration. The defaults in containerStyle are position: relative and overflow: hidden, but we tell overflow to be visible, because if the overflow is hidden, that makes for some ugly-looking animations where boxes pop out of nowhere.

Lastly, under “masonry”, we say that the columnWidth is columnWidth.

Next, we create a function that resizes the “isotoped” element and the navbar so that they are both centered on the page.

function setSizes(){
    var availableSpace = $(window).width();
    var potentialColumns = availableSpace/columnWidth;
    potentialColumns = Math.floor(potentialColumns);
    
    var newWidth = potentialColumns * columnWidth;
    $('.container').width(newWidth);
}

setSizes();

Here, availableSpace is the width of the window, because that’s the only thing that might constrain the size of the main element. potentialColumns is simply the amount of columns that would be able to fit.

However, we can’t have a decimal number of columns, so we’re going to have to round down. That’s what Math.floor does. Rounding up would not work since then we’d have more columns than actually fit. newWidth takes the number of columns and multiplies it by the width of each column to get the total width.

Lastly, we give $('.container') the new width. Both the navigation bar and the main container have a class of .container so we can modify them both together. Lastly, we actually call the setSizes() function so that when the page loads, everything has the correct width.

Before we continue, I would like for you to try something. Resize your browser so that only 3 columns fit. Now, quickly make your browser wider to the point where 4 columns would be able to fit.

Notice how there are still only 3 columns? This is because mainEl, the “isotoped” container uses CSS3 transitions to smoothly change its width, and when you finish moving your mouse to make the browser window wider, the animation hasn’t finished yet, so Isotope thinks that the mainEl isn’t wide enough for there to be another column.

function layoutTimer(){

        setTimeout(function(){
        mainEl.isotope('reLayout');
    }, transitionDuration);
    
}

layoutTimer();

This code waits the duration of transitionDuration, and then executes isotope’s reLayout function. By waiting the duration of the animation of the main container resizing, this guarantees that the element will have completed the animation by the time isotope “reLayouts” again.

However, the code snippet above is kind of useless the way it is now. This snippet needs to happen every time the window is resized.

$(window).resize(function(){
    
    setSizes();
        
    layoutTimer();
        
});

I also added setSizes() into there so that that works when the window is resized.

At this point, everything works except for the part that actually filters the content – clicking the links doesn’t do anything right now.

We’ll make multiple filtering work by using an array to store what mainEl is currently being filtered by. One possibility that the array might look like is “[“.cat1”, “.cat2”, “.cat3″]”.

We’ll store this array in the hash every time a link is clicked, and will withdraw the value from the hash whenever the page is loaded.

When the page loads, we need to create an array based on what the hash is.

var currentCats = hashFilter.split(".");

Remember the hashFilter variable we used above to filter the mainEl when the page loads? Here, we’re taking that and converting it into array. Basically, every time the browser sees a “.” inside the hashFilter string, it creates a new array element at the point.

For example, if hashFilter was .cat1.cat2, the split() function would create an array that looks like this: “[“”, “cat1”, “cat2″]”. There are two things that are wrong with this: there’s an empty element at the beginning, and the other elements inside the array have no dots. Fortunately, both of these issues are easy to fix.

var currentCats = hashFilter.split(".");
//splice because the first element will be just an empty '', so we get rid of it
currentCats.splice(0, 1);

for (current in currentCats){
    currentCat = currentCats[current];
    
    //Since it splices based on the '.', each '.' disappears, so we need to re-add it
    currentCats[current] = '.' + currentCat;

}

In order to get rid of the first empty element, we use the split function. The two parameters are position, length. In this case, we are starting to delete stuff at position 0, and we only delete 1 thing.

The next thing is a for loop. Basically, it goes through each element inside the currentCats array where current is the current position in the for loop. currentCat is the text at the “current” position, for example “cat1”.

Then, we take the current text, add a dot to the beginning and put it back into the array at the same position. This way, an array that looks like “[“cat1”, “cat2”, “cat3″]” is converted into “[“.cat1”, “.cat2”, “.cat3″]”.

The last thing we need to do is handle clicks on the links in the navbar.

$('#controls a').click(function(){
    //Change '#cat1' into '.cat1'
    var catClass = '.'+$(this).attr('href').substr(1);
        
    //If the current category is not in the array, add it and make the link active
    if($.inArray(catClass, currentCats)==-1){ 
        currentCats.push(catClass);
        $(this).parent().addClass('active');
    }
    //If the current category is in the array, get rid of it and remove the 'active' class
    else {
        //position of the current category in the array
        position = $.inArray(catClass, currentCats); 
        currentCats.splice(position,1);
        $(this).parent().removeClass('active');
    }
});

Whenever a link inside the “controls” is clicked, catClass takes its href attribute, for example #cat1, and turns it into .cat1 by getting rid of the first character, #, and prepending a dot.

Next, we use jQuery’s inArray function to check whether or not catClass is inside the currentCats array. If the inArray function returns a value of “-1”, that means that catClass is not inside the array.

Otherwise, inArray returns the position of the element inside the array. If currentClass is not inside the array, we add it by using the push function, and we take the parent li of the link and make it active.

If it is in the array, we remove it by using the splice function and we remove the active class from its parent.

Now, we need to actually filter mainEl.

$('#controls a').click(function(){
    //Change '#cat1' into '.cat1'
    var catClass = '.'+$(this).attr('href').substr(1);
        
    //If the current category is not in the array, add it and make the link active
    if($.inArray(catClass, currentCats)==-1){ 
        currentCats.push(catClass);
        $(this).parent().addClass('active');
    }
    //If the current category is in the array, get rid of it and remove the 'active' class
    else {
        //position of the current category in the array
        position = $.inArray(catClass, currentCats); 
        currentCats.splice(position,1);
        $(this).parent().removeClass('active');
    }
        
    var newFilter = "";
        
    //generate a 'newFilter' string that will be saved into the hash
    for (current in currentCats){
        currentCat = currentCats[current];
        newFilter = newFilter + currentCat;
    }
        
    location.hash = newFilter;
        
    mainEl.isotope({
        filter: newFilter
    });
        
    return false;
        
});

The last part creates a string, “newFilter“, and appends each element of the array onto it. It then sets “newFilter” as the hash of the page, and filters the “mainEl” based on that. Then, it returns false so that the browser doesn’t actually follow the link.

Finished!

Congratulations; you have reached the end of this tutorial! Hopefully, this helped you learn how to use isotope. If you have any questions or comments, please post them below :)


Top
This page may contain affiliate links. At no extra cost to you, we may earn a commission from any purchase via the links on our site. You can read our Disclosure Policy at any time.