Twig: "if not loop.last" not working for second loop (but is for first)

In my blog.htm, I load all categories:
(called “allCategories”)

[blogPosts blog]
postsPerPage = "5"
==
<?php
function onStart()
{
    $this['allCategories'] = RainLab\Blog\Models\Category::all();
}
==

{% component 'blog::list' %}

In my list.htm (where the above template is rendered): I have the following:

<ul class="post-list">
    {% for post in posts %}

    {# <!-- reset for each post --> #}
    {% set otherCats = allCategories %}
    <li class="mb_2 pb_1">
        <h2 class="post-title"><a href="{{ post.url }}">{{ post.title }}</a></h2>

        <p class="info greyed">
            Posted
            {% if post.categories|length %} in {% endif %}
            {% for category in post.categories %}
                <a href="{{ category.url }}">{{ category.name }}</a>
               <!-- working fine -->
               {% if not loop.last %}, {% endif %}
                {% set otherCats = otherCats | filter((v,k) => v.id != category.id ) %}
            {% endfor %}
            on {{ post.published_at|date('M d, Y') }}.

            <span class="small-text">
                (more posts in
                {% for otherCat in otherCats %}
                <a href="{{ 'blog/category' | page({'slug': otherCat.slug}) }}">{{ otherCat.name }}</a>
<!-- not working! -->
{% if not loop.last %}, {% endif %}
                {% endfor %})
            </span>
        </p>

The second for loop (“for otherCat in otherCats”), is correctly rendering the remaining other categories (not belonging to the current post in the loop), however, the ", " is still being added even though it should not, as shown here:

image

Very strange behaviour, perhaps I have missed something with the array? I looked but it doesn’t seem like there’s an array clone feature for twig (?)

Hm, maybe this issue (will be solved in Twig 4) is relevant: loop.last missing when applying filter · Issue #3297 · twigphp/Twig · GitHub

Maybe try

(more posts in {% for otherCat in otherCats %} {{ otherCat.name }} {% if loop.index < otherCats|length %}, {% endif %} {% endfor %} )

1 Like

Main problem of this loop is, that you “replaces” array, then first iterator of loop contains items from allCategories. just simply rename that second array as {% set filteredCats = ... %} and make loop on this.

that will create correct length for iterator.

Thanks @roulendz tried this and bizarrely it works for the first post but not the following ones in the list!

/blog:

1st (correct):
image

2nd (missing ‘Crypto’ category completely!):

Tried this:

<ul class="post-list">
    {% for post in posts %}

    {# <!-- reset for each post --> #}
    {% set otherCats = allCategories %}
    <li class="mb_2 pb_1">
        <h2 class="post-title"><a href="{{ post.url }}">{{ post.title }}</a></h2>

        <p class="info greyed">
            Posted
            {% if post.categories|length %} in {% endif %}
            {% for category in post.categories %}
                <a href="{{ category.url }}">{{ category.name }}</a>{% if not loop.last %}, {% endif %}
                {% set filteredCats = otherCats | filter((v,k) => v.id != category.id ) %}
            {% endfor %}
            on {{ post.published_at|date('M d, Y') }}.

            <span class="small-text">
                (more posts in
                {% for otherCat in filteredCats %}
                <a href="{{ 'blog/category' | page({'slug': otherCat.slug}) }}">{{ otherCat.name }}</a>{% if not loop.last %}, {% endif %}
                {% endfor %})
            </span>
        </p>

But now just getting blank (doesn’t seem to create iteratable filter properly):

image

OK I got it working the long way (lots of for loops lol - see below), but I think @Eoler is correct with the issue solved in twig 4 as logically I can’t see anything wrong with the filtering code (I think issue is it returns a filterIterable thing instead of an actual array)

{% set posts = __SELF__.posts %}

{#<!-- overrides both default.htm (blog home) and category.htm, to render list of posts -->#}

<ul class="post-list">
    {% for post in posts %}

    {# <!-- reset for each post --> #}
    {#{% set otherCats = allCategories %}#}
    <li class="mb_2 pb_1">
        <h2 class="post-title"><a href="{{ post.url }}">{{ post.title }}</a></h2>

        <p class="info greyed">
            Posted
            {% if post.categories|length %} in {% endif %}
            {% for category in post.categories %}
                <a href="{{ category.url }}">{{ category.name }}</a>{% if not loop.last %}, {% endif %}
{#                Doesn't work, see#}
{#                https://talk.octobercms.com/t/twig-if-not-loop-last-not-working-for-second-loop-but-is-for-first/3031/3#}
                {#{% set otherCats = otherCats | filter((v,k) => v.id != category.id ) %}#}
            {% endfor %}
            on {{ post.published_at|date('M d, Y') }}.

            {# Find the other categories, the long way :) #}
            {% set otherCats = [] %}
            {% for cat in allCategories %}
                {% set found = false %}

                {# Note: if (cat not in post.categories) doesn't work #}
                {% for postCat in post.categories %}
                    {% if cat.id == postCat.id %}
                        {% set found = true %}
                    {% endif %}
                {% endfor %}

                {% if found == false %}
                    {% set otherCats = otherCats|merge([cat]) %}
                {% endif %}
            {% endfor %}

            <span class="small-text">
                (more posts in
                {% for otherCat in otherCats %}
                    <a href="{{ 'blog/category' | page({'slug': otherCat.slug}) }}">{{ otherCat.name }}</a>{% if not loop.last %}, {% endif %}{% endfor %})
            </span>
        </p>

        <p class="excerpt">{{ post.summary|raw }}</p>

        {% if post.featured_images|length %}
        <div class="featured-images text-center">
            {% for image in post.featured_images %}
            <p>
                <a href="{{ post.url }}">
                    <img
                        data-src="{{ image.filename }}"
                        src="{{ image.path }}"
                        alt="{{ image.description }}"
                        style="max-width: 30%" />
                </a>
            </p>
            {% endfor %}
        </div>
        {% endif %}


        <a class="" href="{{ post.url }}">Read more ...</a>

    </li>
    {% else %}
    <li class="no-data">{{ __SELF__.noPostsMessage }}</li>
    {% endfor %}
</ul>

{% if posts.lastPage > 1 %}
<ul class="pagination">
    {% if posts.currentPage > 1 %}
    <li><a href="{{ this.page.baseFileName|page({ (__SELF__.pageParam): (posts.currentPage-1) }) }}">&larr; Prev</a></li>
    {% endif %}

    {% for page in 1..posts.lastPage %}
    <li class="{{ posts.currentPage == page ? 'active' : null }}">
        <a href="{{ this.page.baseFileName|page({ (__SELF__.pageParam): page }) }}">{{ page }}</a>
    </li>
    {% endfor %}

    {% if posts.lastPage > posts.currentPage %}
    <li><a href="{{ this.page.baseFileName|page({ (__SELF__.pageParam): (posts.currentPage+1) }) }}">Next &rarr;</a></li>
    {% endif %}
</ul>
{% endif %}