Search code examples
jquerymasonryjquery-isotope

Isotope Grid Layout Issues After Filter


I'm struggling trying to figure out a good solution dealing with Isotope, filters, and a grid. The problem is whenever filtering occurs on my elements, isotope uses what's left to determine the layout of the grid (based on the CSS). So when there are invisible elements wedged between visible ones, :nth-child selectors that style the grid include those, which skews the actual style of the elements. Here's a video of this problem in action: http://3cd.co/172n1C2f2k17

So for example, I have 6 items, if item #2 is removed, item #3 should #2 and the rest adjust accordingly. The only way I have figured out to make this happen is to physically move the invisible elements to the end so they do not affect the styling of the visible elements. Then I have to reset them when everything is re-sorted.

I think maybe what I have is mostly an event issue. The only event I could find to fix this arrangeComplete. The problem is Isotope has already determined it's layout by this point and it doesn't solve the problem, so I need to run $archive_grid.isotope('layout'), which would work great except for the fact that it happens too fast and the layout just goes crazy (see here: http://3cd.co/023R2e0i2d3n). So I had to add a Timeout to delay the event.

Here's the jsfiddle: https://jsfiddle.net/cfpjf5c7/

Is there a better way to handle this? I've been struggling with this and can't come up with a solution.

This is my make-shift, somewhat working solution, but not happy with:

http://3cd.co/0F3h1V1x0P0P

This is the main code for this (in document ready event):

// Set an initial index for each element to retain their order
$('.archive-contents > article').each(function(i){
    $(this).attr('data-initial-index', i);
});

// init isotope
var $archive_grid = $('.archive-contents').isotope({
    itemSelector: 'article',
    layoutMode: 'fitRows'
});

// on arrangeComplete, find all the hidden elements and move them to the end, then re-layout isotope
$archive_grid.on('arrangeComplete', function(){
    var $last = $archive_grid.find('article:visible:last');
    $archive_grid.find('article:not(:visible)').insertAfter( $last );
    setTimeout( function(){
        $archive_grid.isotope('layout');
    }, 500 );
});

var isIsotopeInit = false;

function onHashchange() {
    // re-sort all elements based on their original index values
    $archive_grid.find('article').sort(function(a, b) {
        return + $(a).attr('data-initial-index') - + $(b).attr('data-initial-index');
    }).appendTo( $archive_grid );

    var hashFilter = getHashFilter(),
        isotopeFilter = hashFilter;

    if( isotopeFilter && isotopeFilter != 'all' )
        isotopeFilter = '.wh_team_category-' + isotopeFilter;
    else if( isotopeFilter == 'all' )
        isotopeFilter = '*';

    if ( !hashFilter && isIsotopeInit ) {
        return;
    }

    isIsotopeInit = true;
    // filter isotope
    $archive_grid.isotope({
        itemSelector: 'article',
        filter: isotopeFilter
    });

    // set selected class on button
    if ( hashFilter ) {
        $archive_filters.find('.active').removeClass('active');
        $archive_filters.find('[data-filter="' + hashFilter + '"]').addClass('active');
    }
}

$(window).on( 'hashchange', onHashchange );

// trigger event handler to init Isotope
onHashchange();

Solution

  • I figured out a solution where I moved the hidden elements prior to Isotope Layout (during the filtering), so there doesn't need to be 2 calls to Layout. Just before isIsotopeInit = true, I added:

    if( isotopeFilter != '*' ){
        var $last = $archive_grid.find( isotopeFilter ).last(),
            $hidden = $archive_grid.find('article').not( isotopeFilter );
    
        $hidden.insertAfter( $last );
    }
    

    I basically moved the contents of the arrangeComplete callback into where the filtering was being done, and had to rewrite it a bit based on how the filter selectors were being filtered.