jupiteror
jupiteror

Reputation: 1205

Filter popular posts by category in a collection in Jekyll using Liquid

I have a plugin which generates a list of popular posts in a collection in Jekyll based on their page views. Inside collection I have two categories and I would like to filter popular posts by the same category as the original post.

Here is my code I've written:

<ul>
  {% if page.path contains '_kb/cat1' %}
    {% assign popular_posts = site.popular_posts | where:"category","cat1" %}
    {% for page in popular_posts | limit:3 %}
      <li>
        <a href="{{ page.url }}" title="{{ page.title }}">{{ page.title }}</a>
      </li>
    {% endfor %}
  {% else %}
    {% assign popular_posts = site.popular_posts | where:"category","cat2" %}
    {% for page in popular_posts | limit:3 %}
      <li>
        <a href="{{ page.url }}" title="{{ page.title }}">{{ page.title }}</a>
      </li>
    {% endfor %}
  {% endif %}
</ul>
  1. Is there any way to make this code simpler?
  2. How can I exclude the original post from this list? For example, if I'm on popular post 1, I would like to show links to popular posts 2, 3 and 4, and not the original posts itself.

I know I can use something like if post != self in Ruby, but I don't know, how to do it in Liquid. I've read some tutorials, but didn't find an answer. That's why I'm asking here.

EDIT: I tried to access collection's attributes first, as suggested by @matrixanomaly below. But now it iterates through every document in a collection and gives the number of lists of popular posts equal to the number of documents in a collection. Here is my code. Is there any way to limit this loop only to one, the document itself (or the original post)?

{% for collection in site.collections %}
  {% capture label %}{{ collection | first }}{% endcapture %}
  {% for doc in site.collections.[label].docs %}
    {% assign category = doc.category %}
    {% if page.category == category %}
      {% assign popular_posts = site.popular_posts | where:"category",category %}
      {% for node in popular_posts | limit:3 %}
        {% unless node.url == page.url %}
          <li>
            <a href="{{ node.url }}" title="{{ node.title }}">{{ node.title }}</a>
          </li>
        {% endunless %}
      {% endfor %}
    {% endif %}
  {% endfor %}
{% endfor %}

Upvotes: 2

Views: 1604

Answers (1)

matrixanomaly
matrixanomaly

Reputation: 6947

I'm assuming you're trying to get your code to display other popular posts in the same category as the current post. For example if you're on a post about Java, you'd like to display popular posts about Java, except the current post itself.

  1. If you've already set category in each of your post, you can use the built-in variable provided in Jekyll, page.categories which returns a list of categories, e.g if you have a post under the category of work and code, you get ['work','code']. Here's a list of page variables.

Rather than {% if page.path contains '_kb/cat1' %}, you could immediately get popular posts based on the category. This should work (I do not have a Jekyll install right now to test this).

<ul>
<!-- uses categories of the current post -->
<!--I used theCategory to avoid confusion, use any variable you want -->
{% for theCategory in page.categories %} 
    {% assign popular_posts = site.popular_posts | where:"category", theCategory %} 
      {% for page in popular_posts | limit:3 %}
         <li>
         <a href="{{ page.url }}" title="{{ page.title }}">{{ page.title }}</a>
         </li>
      {% endfor %}
{% endfor %}
</ul>
  1. You can exclude the original post using 2 methods. Either check if the post you're iterating through has the same value as the current post's id, url, title, something like {% if popular_page.id != page.id %} or page.url as shown in the page variables. The second method would be to use the unless clause.

As shown in Liquid for Designers:

{% if user.name != 'tobi' %}
  Hello non-tobi
{% endif %}
# Same as above
{% unless user.name == 'tobi' %}
  Hello non-tobi
{% endunless %}

I believe your issue is that you have reused the page variable, once you do {% for page in popular_posts %} you make page a local variable and you lose access to the current posts variable value.

What you would want to swap out in the innermost loop would be:

{% for popular_post in popular_posts | limit:3 %} <!-- renamed variable -->
    {% unless popular_post.id == page.id %}
             <li>
             <a href="{{ page.url }}" title="{{ page.title }}">{{ page.title }}</a>
             </li>
    {% endunless %}   <!-- you can use if and endif also --->
{% endfor %}

EDIT: Since the above code might only show 3, the ugly workaround would be to use limit:4, but there is the edge case of showing 4 posts when the current post is not popular, you can then do:

{% assign popular_posts = site.popular_posts | where "id" != page.id %} {% for popular_post in popular_posts | limit:3 %} {% unless popular_post.id == page.id %}

  • {{ page.title }}
  • {% endunless %} {% endfor %}

    I suppose it's a matter of nesting the conditionals as I'm unsure if Liquid allows multiple conditions in their filters, but you want to filter out the current post before doing a limit:3.

    Upvotes: 1

    Related Questions