23

I'm working on a Jekyll site and am trying to output three column divs nested in a row div. Liquid makes this pretty easy with their cycle filter:

{% for p in site.categories.post %}
    {% cycle 'add rows': '<div class="row">', nil, nil %}
        <div class="column">
            <a href="{{ p.url }}">{{ p.title }}</a>
        </div>
    {% cycle 'close rows': nil, nil, '</div>' %}
{% endfor %}

However, this only really works when there are 3, 6, 9, etc. posts. When the total number of posts is not a multiple of three, the <div class="row"> never gets closed--the for loop ends before the closing tag can be output as part of the close rows cycle.

In Ruby, PHP, or any other language I could easily fix this with a modulus operator, so in addition to close rows cycle I would output </div> when if site.categories.size % 3 == 0. However, Liquid, because it's a safe templating language, doesn't support the modulus.

What else can I do to properly close <div class="row"> when the total number of posts is not a multiple of three?

halfer
  • 19,824
  • 17
  • 99
  • 186
Andrew
  • 36,541
  • 13
  • 67
  • 93

7 Answers7

15

I've found this way to work great!

{% assign mod = forloop.index0 | modulo:4 %}
{% if mod == 0 %}
   <!-- Do stuff -->
{% endif %}
Dehli
  • 5,950
  • 5
  • 29
  • 44
13

For your specific example, you could use {% cycle 'close rows': nil, '</div>', '</div>' %} after the {% endfor %}.

Grant Husbands
  • 138
  • 1
  • 6
8

The only way for now is to write a liquid filter to accomplish this. Register the filter somewhere in your code where it's appropriate (it's in different places if using with rails and without them).

Liquid::Template.register_filter(LiquidFilters)

In you projects /lib directory add liquid_filters.rb:

module LiquidFilters  
  # makes modulus operation available to templates
  def mod(data, param)
    data % param
  end  
end

After that you can use it like following in your templates: {{ variable | mod:5 }}

And if you need to use it for some logic you can capture the value.

{% capture modulus %}{{ variable | mod:5 }}{% endcapture %}

Just I've noticed that captured value is a string so in order to compare it you use

{% if modulus == "0" %}
 ..
{% endif %}
Rytis Lukoševičius
  • 1,288
  • 10
  • 10
3

I realize the question has been solved for the asker, but I recently came upon this kind of situation in Liquid and thought I'd provide my solution in case it helps someone with similar markup requirements.

In my case, I've already passed an if statement verifying that there is at least one post, so I created the first "row" div outside the loop. I also close it after the for loop. This protects against a case where there are less than three posts.

<div class="row">

    {% for p in posts %}
        <div class="column">
            <!-- Post code here -->
        </div>
        {% unless forloop.last %}
            {% cycle '', '', '</div><div class="row">' %}
        {% endunless %}
    {% endfor %}

</div>

After every three posts, cycle will close the current row and open up a new one unless the post was the last one in the forloop, in which case we don't want to open a new row, and let the wrapping </div> close it up.

Naomi
  • 61
  • 1
3

I used another trick in a for loop : useless in your case, useful if you just want a modulo in order to find out if your line has ended and you need a new row, like I did.

In this example I'll go with a line of 4 items :

{% assign currentRow = 1 %}
# enter the for loop ... then, with 4 as the divisor:
{% if forloop.index == 4 * currentRow %}
  # do whatever you want
  {% assign currentRow = currentRow + 1 %}
{% endif %}
# exit the for loop

Not very nice, but easy.

G.M.L
  • 39
  • 1
2

I learned a lot from this post, and these are three patterns I used throughout my project. It worked great with Bootstrap too. Just change the column class in the following code. Instead of columns, the same patterns can be applied to other scenarios where modulo is useful, such as odd-even rows. Hope it helps someone -

Four columns:

<div class="container">
    {% for post in site.posts %}
        {% cycle 'add row' : '<div class="row">', nil, nil, nil %}
            <div class="column">
                <!-- liquid tags here -->
            </div>
        {% cycle 'end row' : nil, nil, nil, '</div>' %}
    {% endfor %}
    {% cycle 'end row' : nil, '</div>', '</div>', '</div>' %}
</div>

Three columns:

<div class="container">
    {% for post in site.posts %}
        {% cycle 'add row' : '<div class="row">', nil, nil %}
            <div class="column">
                <!-- liquid tags here -->
            </div>
        {% cycle 'end row' : nil, nil, '</div>' %}
    {% endfor %}
    {% cycle 'end row' : nil, '</div>', '</div>' %}
</div>

Two columns:

<div class="container">
    {% for post in site.posts %}
        {% cycle 'add row' : '<div class="row">', nil %}
            <div class="column">
                <!-- liquid tags here -->
            </div>
        {% cycle 'end row' : nil, '</div>' %}
    {% endfor %}
    {% cycle 'end row' : nil, '</div>' %}
</div>
ThisClark
  • 14,352
  • 10
  • 69
  • 100
0

IIRC Liquid doesn't block the modulo operation, only the % character. You can perform a modulus without using the % operator. For example, 14.modulo(3) => 2 instead of 14 % 3.

bta
  • 43,959
  • 6
  • 69
  • 99
  • That does make sense, since all the other arithmetic functions are abstracted out like that, but unfortunately neither `modulo` or `modulus` work… – Andrew Feb 12 '10 at 11:14
  • Yeah. Just checked the source code. Sadly, no `modulo`. I'll have to maybe fork it and add it in or something. My current solution is a mess: `unless total == 3 or total == 6 or total == 9 or total == 12 or total == 15…` – Andrew Feb 12 '10 at 12:09
  • 1
    `x.modulo(y)` is just an alias for `x.divmod(y)[1]`. If `divmod` is allowed, you could use that form. Or, you could always roll your own modulo function: `x - (x / y)` (using whatever abstracted versions of the arithmetic operators you need). – bta Feb 12 '10 at 17:05