---
title: Category rows
layout: default
category: custom-layout-modes
---

<section id="copy">
  <p>This demo uses a <a href="../docs/extending-isotope.html">custom layout mode</a>, <code>categoryRows</code> that arranges elements into rows based on their category. The layout mode logic relies on sorting to define rows.</p>
</section>

<section id="options" class="clearfix">

  {% include filter-buttons.html %}

  <h3>Etc</h3>

  <ul id="etc" class="clearfix">
    <li id="toggle-sizes"><a href="#toggle-sizes">Toggle variable sizes</a></li>
    <li id="insert"><a href="#insert">Insert new elements</a></li>
  </ul>
</section> <!-- #options -->

<div id="container" class="clearfix">
  {% for elem_number in site.random_order | limit:60 %}
    {% assign element = site.elements[elem_number] %}
    {% include element-partial.html %}
  {% endfor %}
</div>

<script src="../{{ site.jquery_js }}"></script>
<script src="../{{ site.isotope_js }}"></script>
<script src="../js/fake-element.js"></script>
<script>

  // categoryRows custom layout mode
  $.extend( $.Isotope.prototype, {
  
    _categoryRowsReset : function() {
      this.categoryRows = {
        x : 0,
        y : 0,
        height : 0,
        currentCategory : null
      };
    },
  
    _categoryRowsLayout : function( $elems ) {
      var instance = this,
          containerWidth = this.element.width(),
          sortBy = this.options.sortBy,
          props = this.categoryRows;
      
      $elems.each( function() {
        var $this = $(this),
            atomW = $this.outerWidth(true),
            atomH = $this.outerHeight(true),
            category = $.data( this, 'isotope-sort-data' )[ sortBy ],
            x, y;
      
        if ( category !== props.currentCategory ) {
          // new category, new row
          props.x = 0;
          props.height += props.currentCategory ? instance.options.categoryRows.gutter : 0;
          props.y = props.height;
          props.currentCategory = category;
        } else if ( props.x !== 0 && atomW + props.x > containerWidth ) {
          // if this element cannot fit in the current row
          props.x = 0;
          props.y = props.height;
        } 
      
        // position the atom
        instance._pushPosition( $this, props.x, props.y );
  
        props.height = Math.max( props.y + atomH, props.height );
        props.x += atomW;
  
      });
    },
  
    _categoryRowsGetContainerSize : function () {
      return { height : this.categoryRows.height };
    },
  
    _categoryRowsResizeChanged : function() {
      return true;
    }
  
  });
  
  $(function(){
    
    var $container = $('#container');
    
    {% include random-sizes.js %}
    
    $container.isotope({
      itemSelector : '.element',
      layoutMode : 'categoryRows',
      categoryRows : {
        gutter : 20
      },
      getSortData : {
        category : function( $elem ) {
          return $elem.attr('data-category');
        }
      },
      sortBy: 'category'
    });

    {% include option-set-buttons.js %}

    {% include add-buttons.js %}

    // toggle variable sizes of all elements
    $('#toggle-sizes').find('a').click(function(){
      $container
        .toggleClass('variable-sizes')
        .isotope('reLayout');
      return false;
    });

  });
</script>
  
  