Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bootstrap + Masonry add blank divs when grid have empty spaces at the bottom

I'm using masonry + bootstrap and notice that when I have a 3-column grid then I have 10 items, it would display a 3x4 grid having 2 blank spaces at the bottom. How could I automatically add 2 empty divs at the bottom just to fill it up and not having those blank spaces? So the total div would become 12 wherein the 2 divs are just blank?

This isn't supposed to be fixed to a 3-column but should dynamically add empty divs whenever it detected that there are N number of empty divs that could be filled up. Should be applicable on load and on resize.

There will be no problem with the .item size since they will all have the same width and height (box/square type)

I made a jsFiddle that could now add fillers on the empty spaces on the last row. This is working on the on resize as well by using the layoutComplete event. But the problem is, whenever I resize, it keeps on appending new fillers.

Try re-sizing to different sizes and you'll notice it keeps on adding fillers.

In case, here's the code as well.

HTML

<input type="hidden" name="hfTotalGridItems" id="hfTotalGridItems" value="10" />
<div class="grid">
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
    <div class="item">
        <div>lorem</div>
    </div>
</div>
<div class="result"></div>

CSS

.grid {
    margin: 0 auto;
}
.item {
    margin-bottom: 20px;
    border: 1px solid red;
    height: 80px;
    width: 80px;        
}
.filler {
    background-color: #999;
    border: 1px solid blue;
}

JQuery

$(function () {
    function calculateRows() {
        var lisInRow = 0;
        var $item = $('.grid .item');
        var $grid = $('.grid');
        var itemWidth = $('.grid .item').width();
        var itemHeight = $('.grid .item').height();

        $item.each(function () {
            if ($(this).prev().length > 0) {
                if ($(this).position().top != $(this).prev().position().top) return false;
                lisInRow++;
            } else {
                lisInRow++;
            }
        });

        var lisInLastRow = $item.length % lisInRow;
        if (lisInLastRow == 0) lisInLastRow = lisInRow;

        $('.result').html('No: of lis in a row = ' + lisInRow + '<br>' + 'No: of lis in last row = ' + lisInLastRow);

        if (lisInLastRow < lisInRow) {
            var $clonedItem = $('.grid .item:last-child').clone().empty().css({
                width: itemWidth,
                height: itemHeight
            }).addClass('filler');
            $grid.append($clonedItem).masonry('appended', $clonedItem);

        } else {
            if (newTotal > $('#hfTotalGridItems').val()) {
                $grid.masonry('remove', $('.grid .item.filler'));
                $grid.masonry();
            }
        }
    }

    var $grid = $('.grid');

    $grid.masonry({
        itemSelector: '.item',
        isFitWidth: true,
        gutter: 20
    });

    $grid.masonry('on', 'layoutComplete', function (event) {
        calculateRows(event.length);
    });

    $grid.masonry();
});
like image 722
basagabi Avatar asked Oct 15 '15 06:10

basagabi


2 Answers

There are two things you need to check

  1. If the number of original boxes is evenly divisible by the number of boxes in the first row (originalBoxs % lisInRow === 0).
  2. If the number of boxes is greater then a max allowed number of boxes. You can calculate the max allowed boxes as below

    var totalAllowed = lisInRow;
    while (totalAllowed < originalBoxs) {
       totalAllowed += lisInRow;
    }
    

If this is true, then you should remove all of the excess boxes. Otherwise add in the filler boxes. Here is a updated jsFiddle

I added the updated jQuery code below

$(function () {

    // remember the original box lenth. 
    var $item = $('.grid .item');
    var originalBoxs = $item.length;

    function calculateRows() {

        var lisInRow = 0;
        var $item = $('.grid .item');
        var $grid = $('.grid');
        var itemWidth = $('.grid .item').width();
        var itemHeight = $('.grid .item').height();

        // calculate how many boxes are in the first row. 
        $item.each(function () {
            if ($(this).prev().length > 0) {
                if ($(this).position().top != $(this).prev().position().top) return false;
                lisInRow++;
            } else {
                lisInRow++;
            }
        });

        // calculate how many boxes are in the last row. 
        var lisInLastRow = $item.length % lisInRow;

        $('.result').html('No: of lis in a row = ' + lisInRow + '<br>' + 'No: of lis in last row = ' + lisInLastRow);

        // the total allowed boxes on the page. 
        var totalAllowed = lisInRow;
        while (totalAllowed < originalBoxs) {
            totalAllowed += lisInRow;
        }

        if (($item.length > originalBoxs && originalBoxs % lisInRow === 0) || ($item.length > totalAllowed)) {
            // if the number of original boxes evenly divides into the number of boxes in a row.
            // or the number of boxes on the page is past the max allowed. 
            // remove any filler boxes. 
            var boxesToDelete = $('.grid .item.filler');
            var deleteBoxes = $item.length - totalAllowed;
            for (var i = 0; i < deleteBoxes; i++) {
                // delete unnesecary boxes. 
                $grid.masonry('remove', boxesToDelete[boxesToDelete.length - i - 1]);
            }
        } else if (lisInLastRow !== 0) {
            // if the last row does not equal 0 and the number of boxes is less then the original + the first row
            // then fill it in with new boxes. 
            var $clonedItem = $('.grid .item:last-child').clone().empty().css({
                width: itemWidth,
                height: itemHeight
            }).addClass('filler');
            $grid.append($clonedItem).masonry('appended', $clonedItem);    
        }

    }

    var $grid = $('.grid');

    $grid.masonry({
        itemSelector: '.item',
        isFitWidth: true,
        gutter: 20
    });

    $grid.masonry('on', 'layoutComplete', function (event) {
        calculateRows(event.length);
    });

    $grid.masonry();
});
like image 77
jjbskir Avatar answered Nov 19 '22 06:11

jjbskir


Here is a bit different version. Please check this traditional JSFiddle for tests.

In short, I have taken the way of non-intrusive modification for Masonry to achieve the expected behavior in the most optimal way.

$(function() {

  var fillerHtml = '<div></div>',
    fillerClassName = 'filler',
    initialItemCount = null;

  function toggleFillers() {
    var masonry = $grid.data('masonry'),
      currentItemCount = masonry.items.length,
      items = masonry.items,
      cols = masonry.cols,
      expectedItemCount = 0,
      lastRowItemCount = 0,
      lastRowFillerCount = 0,
      fillersToRemove = [],
      fillersToAddCount = 0,
      fillersToAdd = [];

    if (initialItemCount === null) {
      initialItemCount = currentItemCount;
    }

    lastRowItemCount = initialItemCount % cols;
    lastRowFillerCount = (lastRowItemCount !== 0) ? cols - lastRowItemCount : 0;
    expectedItemCount = initialItemCount + lastRowFillerCount;
    
    $('.result').html('No: of lis in a row = ' + cols + '<br>' + 'No: of lis in last row = ' + lastRowItemCount);

    if (expectedItemCount !== currentItemCount) {
      if (currentItemCount > expectedItemCount) {
        var itemsToRemove = items.slice(initialItemCount + lastRowFillerCount);

        $.each(itemsToRemove, function(index, item) {
          fillersToRemove.push(item.element);
        });

        masonry.remove(fillersToRemove);
        masonry.layout();
      } else {
        fillersToAddCount = expectedItemCount - currentItemCount;
        fillerClassName = masonry.options.itemSelector.replace('.', '') + ' ' + fillerClassName;

        while (fillersToAddCount) {
          $el = $(fillerHtml).addClass(fillerClassName);
          $grid.append($el);
          fillersToAddCount = fillersToAddCount - 1;
        }

        masonry.reloadItems();
        masonry.layout();
      }
    }
  }

  var $grid = $('.grid');

  $grid.masonry({
    itemSelector: '.item',
    isFitWidth: true,
    gutter: 20
  });
  $grid.masonry('on', 'layoutComplete', toggleFillers);
  $grid.masonry('layout');
});
.grid {
  margin: 0 auto;
}
.item,
.filler {
  margin-bottom: 20px;
  border: 1px solid red;
  height: 80px;
  width: 80px;
}
.filler {
  background-color: #999;
  border: 1px solid blue;
}
<script type="text/javascript" src="//code.jquery.com/jquery-2.1.3.js"></script>
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/masonry/3.3.2/masonry.pkgd.min.js"></script>
<input type="hidden" name="hfTotalGridItems" id="hfTotalGridItems" value="10" />

<div class="grid">
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
  <div class="item">
    <div>lorem</div>
  </div>
</div>
<div class="result"></div>

I've been playing around with Masonry for a while, so I have decided to package this as a plugin, if you will need toy use it elsewhere.

You can install it with bower or simply grab the source from GitHub

bower install jquery-masonry-autofill

Here is a JSFiddle for a demo with plugin.

like image 3
halfzebra Avatar answered Nov 19 '22 04:11

halfzebra