26

Basically, I am using Jekyll (which uses the Liquid templating language) and I am trying to write a for loop which wraps every two items in a div.

This is my current loop:

<div>
  {% for post in site.posts %}
    <a href="{{ post.url }}">{{ post.title }}</a>
  {% endfor %}
</div>

Which would output four posts like so:

<div>
  <a href="#">Title</a>
  <a href="#">Title</a>
  <a href="#">Title</a>
  <a href="#">Title</a>
</div>

My desired output for four posts is:

<div>
  <a href="#">Title</a>
  <a href="#">Title</a>
</div>

<div>
  <a href="#">Title</a>
  <a href="#">Title</a>
</div>

How can I accomplish this?

Nathan Arthur
  • 8,287
  • 7
  • 55
  • 80
Tom
  • 825
  • 2
  • 11
  • 22

9 Answers9

35

If the number of <div>s and posts is fixed (which seems to be the case based on which answer you selected), there's a shorter way to get the same output - using limit and offset:
(Liquid's approach to paging)

<div>
  {% for post in site.posts limit: 2 %}
    <a href="{{ post.url }}">{{ post.title }}</a>
  {% endfor %}
</div>
<div>
  {% for post in site.posts limit: 2 offset: 2 %}
    <a href="{{ post.url }}">{{ post.title }}</a>
  {% endfor %}
</div>

Even better solution:

If the number of posts is not fixed (so when you have 100 posts, you want 50 <div>s with two posts each), then you can use forloop.index (which was already mentioned in most of the other answers), and use modulo to find out if the current index is even or odd:

{% for post in site.posts %}
  {% assign loopindex = forloop.index | modulo: 2 %}
  {% if loopindex == 1 %}
    <div>
      <a href="{{ post.url }}">{{ post.title }}</a>
  {% else %}
      <a href="{{ post.url }}">{{ post.title }}</a>
    </div>
  {% endif %}
{% endfor %}

This returns your desired output as well, but works for any number of posts.

Christian Specht
  • 35,843
  • 15
  • 128
  • 182
  • 1
    Thanks a lot for this, although I didn't use your exact answer, researching into the "modulo" function pointed me here (https://gist.github.com/leemachin/2366832) which is really good method for what I'm trying to do, I'll post another answer of my updated code. – Tom Jan 09 '14 at 15:26
  • 5
    Isn't this missing a for an uneven number of site posts? – P3trus Jun 06 '17 at 07:29
22

I know I'm late to the game but I found what I feel is a fairly elegant solution that doesn't feel hacky.

With for loop's limit and offset params, we can iterate one row at a time, N posts per row.

First, we count the number of rows we'll need to enumerate over:

{% assign rows = site.posts.size | divided_by: 2.0 | ceil %}

The Ruby equivalent would be rows = (posts.size / 2.0).ceil (odd numbers get their own row).

Next, we'll iterate over the rows:

{% for i in (1..rows) %}
  <div>

Now we need to calculate the collection offset with (i - 1) * 2 (using forloop.index0):

  {% assign offset = forloop.index0 | times: 2 %}

Then we can iterate over the slice of posts starting at the row's offset (equivalent to posts[offset, 2] in Ruby):

    {% for post in site.posts limit:2 offset:offset %}
      <a href="{{ post.url }}">{{ post.title }}</a>
    {% endfor %}

Close the row div element and for loop:

  </div>
{% endfor %}

That's it!

In Ruby, this would be:

rows = (posts.size / 2.0).ceil # the number of rows
(1..rows).each do |i|
  offset = (i - 1) * 2
  # <div>
  posts[offset, 2].each do |post|
    # <a href="#{post.url}>#{post.title}</a>
  end
  # </div>
end

All together now, in Liquid:

{% assign rows = site.posts.size | divided_by: 2.0 | ceil %}
{% for i in (1..rows) %}
  {% assign offset = forloop.index0 | times: 2 %}
  <div>
    {% for post in site.posts limit:2 offset:offset %}
      <a href="{{ post.url }}">{{ post.title }}</a>
    {% endfor %}
  </div>
{% endfor %}

Hope this helps someone!

Matt Todd
  • 817
  • 8
  • 12
6

Try this one:

<div>
    {% for post in paginator.posts %}
        <div>
            {% if forloop.index == 1 %}
                <a href="">{{ post.title }}</a>
            {% endif %}
            {% if forloop.index == 2 %}
                <a href="">{{ post.title }}</a>
            {% endif %}
        </div>
        <div>
            {% if forloop.index == 3 %}
                <a href="">{{ post.title }}</a>
            {% endif %}
            {% if forloop.index == 4 %}
                <a href="">{{ post.title }}</a>
            {% endif %}
        </div>
    {% endfor %}
</div>
Community
  • 1
  • 1
Nigel Greenway
  • 373
  • 2
  • 10
  • I had to replace some instances `{% endfor %}` with `{% endif %}` to stop getting errors with this, but it's still not working properly I'm afraid, it produces the following output http://pastebin.com/Kt8gA3nw I do really appreciate you helping me though :) – Tom Jan 04 '14 at 19:44
6

I've just come across this (https://gist.github.com/leemachin/2366832) which is a much better solution then ones posted in other answers, bare in mind you'll need this plugin (https://gist.github.com/leemachin/2366832#file-modulo-filter-rb) for it to work:

{% for post in paginator.posts %}

  {% capture modulo %}{{ forloop.index0 | mod:2 }}{% endcapture %}

  {% if modulo == '0' or forloop.first %}
    <div>
  {% endif %}

    <a href="{{ post.url }}">{{ post.title }}</a>

  {% if modulo == '1' or forloop.last %}
    </div>
  {% endif %}

{% endfor %}
Tom
  • 825
  • 2
  • 11
  • 22
2

I really should concentrate on what I am doing, but hard with a one year old giving me all her toys... :D

The code should now work:

<div>
    <div>
        {% for post in paginator.posts %}
            {% if forloop.index == 1 %}
                <a href="">{{ post.title }}</a>
            {% endif %}
            {% if forloop.index == 2 %}
                <a href="">{{ post.title }}</a>
            {% endif %}
        {% endfor %}
    </div>
    <div>
        {% for post in paginator.posts %}
            {% if forloop.index == 3 %}
                <a href="">{{ post.title }}</a>
            {% endif %}
            {% if forloop.index == 4 %}
                <a href="">{{ post.title }}</a>
            {% endif %}
        {% endfor %}
    </div>
</div>

Should give the html of:

<div>
    <div>
        <a href="">Title 1</a>
        <a href="">Title 2</a>
    </div>
    <div>
        <a href="">Title 3</a>
        <a href="">Title 4</a>
    </div>
</div>
Nigel Greenway
  • 373
  • 2
  • 10
  • SUCCESS! You are the man, just going to update this post with the final code, thanks again! – Tom Jan 04 '14 at 20:23
1

After looking at Christian's solution I updated my (pug based) code to:

.blog
    .container
        .row
            .col-xs-0
            .col-xs-12
                h1 Blog
                p Summit blog with latest news, thinking and participant's posts.
            .col-xs-0
        | {% for page in site.posts                         %}
        | {% assign loopindex = forloop.index | modulo: 2   %}
        | {% if loopindex == 1                              %}
        .row
        | {% endif %}
        span
            .col-xs-6.col-sm-6
                .card
                    .card-top
                        + add-title
                        + add-author
                    .card-block
                        + add-snippet
        | {% endfor                                        %}
Dinis Cruz
  • 4,161
  • 2
  • 31
  • 49
0

Ok, I have done a quick try with no proper styling but this should work:

<div>
{% for post in paginator.posts %}
    {% case forloop.index %}
    <div>
    {% when 1 %}
        <a href="">{{ post.title }}</a>
    {% when 2 %}
        <a href="">{{ post.title }}</a>
    </div>
    <div>
    {% when 3 %}
        <a href="">{{ post.title }}</a>
    {% when 4 %}
        <a href="">{{ post.title }}</a>
    </div>
{% endcase %}
{% endfor %}
</div>
Nigel Greenway
  • 373
  • 2
  • 10
  • Although not the best of code, being new to jekyll myself it is hard to try stuff like this as (for what I am aware of) specific manipulation of this sort is not very easy to sort, but as you are going for four posts per page, this should do the trick with pagination... Although I have not tested that theory which I should have done... – Nigel Greenway Jan 04 '14 at 18:33
  • Thanks a lot for this but I think I'd run into problems using this. It assumes that there will always be exactly four posts on a page which isn't the case. For example when I only have less than 4 published posts, or I have something like 7 published posts; the first page will have 4 posts but the second page will have 3, which will cause invalid HTML because a `` is missed out. You obviously have a lot more experience with Jekyll loops than I do as I come from a WordPress background where I'm used to doing things like http://bit.ly/1f3vIqw is there anyway to mimic this? – Tom Jan 04 '14 at 18:48
0

With the help from @smilinmonki666 I have got this working how I want it too, here's the final code I went with:

{% assign current_page_posts = paginator.posts | size %}

{% if current_page_posts > 0 %}
  <div>

    <div>
      {% for post in paginator.posts %}
        {% if forloop.index == 1 %}
          <a href="{{ post.url }}">{{ post.title }}</a>
        {% endif %}

        {% if forloop.index == 2 %}
          <a href="{{ post.url }}">{{ post.title }}</a>
        {% endif %}
      {% endfor %}
    </div>

    {% if current_page_posts > 2 %}
      <div>
        {% for post in paginator.posts %}
          {% if forloop.index == 3 %}
            <a href="{{ post.url }}">{{ post.title }}</a>
          {% endif %}

          {% if forloop.index == 4 %}
            <a href="{{ post.url }}">{{ post.title }}</a>
          {% endif %}
        {% endfor %}
      </div>
    {% endif %}

  </div>
{% endif %}
Tom
  • 825
  • 2
  • 11
  • 22
0

You can do this with the cycle tag as described in https://shopify.github.io/liquid/tags/iteration/

{% for post in site.posts %}
  {% cycle '<div>', '' %}
    <a href="{{ post.url }}">{{ post.title }}</a>
  {% cycle '', '</div>' %}
{% endfor %}

outputs

<div>
  <a href="#">Title</a>
  <a href="#">Title</a>
</div>

<div>
  <a href="#">Title</a>
  <a href="#">Title</a>
</div>