Liquid Loop prints duplicates - loops

I'm editing a "freshdesk knowledgebase" theme which uses (parts of?) liquid. I don't have a lot of experience.
The knowledgebase uses a structure of category: -> folder -> article
Here's the loop i'm having trouble with. While it outputs the list of available categories, it also prints a duplicate depending on how many articles are inside the folder.
<div class="category-list__items">
{% for category in portal.solution_categories%}
{% for folder in category.folders %}
{% if folder.articles_count > 0 %}
<div class="category-list-item">
<a href="{{category.url}}" class="category-list-item__link">
<div class="category-list-item__content">
<h3 class="category-list-item__title">{{category.name}}({{ folder.articles_count }})</h3>
</div>
</a>
</div>
{% endif %}
{% endfor %}
{% endfor %}
</div>
What I am wanting to output is a just a list of categories that have at least 1 article inside.
I'm getting:
Fruits (2)
Fruits (2)
Vegetables (1)
When I just want:
Fruits (2)
Vegetables (1)

You can use {% break %} to break the for loop conditionally.
So if you want to render a category only once when it finds a first folder with articles_count > 0 then break the loop and continue for next category like below.
<div class="category-list__items">
{% for category in portal.solution_categories%}
{% for folder in category.folders %}
{% if folder.articles_count > 0 %}
<div class="category-list-item">
<a href="{{category.url}}" class="category-list-item__link">
<div class="category-list-item__content">
<h3 class="category-list-item__title">{{category.name}}({{ folder.articles_count }})</h3>
</div>
</a>
</div>
{% break %}
{% endif %}
{% endfor %}
{% endfor %}
</div>

Related

How to use parent loop counter in the nested for-loop to access to a specific row in a json like data in django templates (.html file)

at the following code the interior for loop needs to access to a specific row of entries.
In the other words the nested loop should be {% for entry in entries.(topic.id) %}
the entries is a JSON like array as the following:
entries = [
{'a', 'b', 'c'},
{'d'},
{'e', 'f', 'g', 'h', 'i'},
{'j','k'}
.
.
.
]
{% for topic in topics %}
<li>
<h5>
{{topic.id}} - {{topic}}
<ul>
<small>
{% with i=topic.id %}
{{i}}
{% for entry in entries.i %}
<li>{{forloop.counter}} . {{entry}}</li>
{% empty %}
<li>No entries available!</li>
{% endfor %}
{% endwith %}
</small>
</ul>
</h5>
</li>
{% empty %}
<li>
<h4 style="color: tomato;">There is no available topic(s)</h4>
</li>
{% endfor %}
A custom template tag fixed my issue.
Refer to:
https://docs.djangoproject.com/en/3.2/howto/custom-template-tags/
from django import template
register = template.Library()
#register.filter
def topicID(List, i):
return List[int(i)]
And by apply this filter to the nested for-loop my issue has been solved, my code is now as following:
{% for topic in topics %}
<li>
<h5>
{{forloop.counter}} - {{topic}}
<ul>
<small>
{% for entry in entries|topicID:forloop.counter0%}
<li>{{forloop.counter}} . {{entry}}</li>
{% empty %}
<li>No entries available!</li>
{% endfor %}
</small>
</ul>
</h5>
</li>
{% empty %}
<li>
<h4 style="color: tomato;">There is no available topic(s)</h4>
</li>
{% endfor %}

How do I make my list code run vertically instead of horizontal in Shopify?

I have created a vendor page for my Shopify store, and it seems to be running fine, however, now I want it to run the list alphabetically across 4 columns in a vertical setup. Currently, the list runs horizontally across the columns. Could someone please look at my code and see if this is possible?
{% assign counter = 0 %}
{% for vendor in shop.vendors %}
{% assign counter = counter | plus: 1 %}
{% endfor %}
{% assign counter_divided_by_3 = counter | divided_by: 3 | floor %}
<section class="section inner-page-section designer-section" id="section-{{ section.id }}" data-section-id="{{ section.id }}" data-section-type="vendor-list">
<div class="container">
{% if section.settings.enable_headline %}
<div class="row">
<div class="col-sm-12">
<h1 class="page-title text-center">{{ section.settings.headline | upcase }}</h1>
</div>
</div>
{% endif %}
<div class="desktop">
<div class="row vendor-row">
{%- for product_vendor in shop.vendors -%}
<ul class="col-sm-3 col-xs-12 designers-list">
{% assign its_a_match = false %}
{% capture my_collection_handle %} {{ product_vendor | handleize | strip | escape }} {% endcapture %}
{% assign my_collection_handle_stripped = my_collection_handle | strip | escape %}
{% for collection in collections %}
{% if my_collection_handle_stripped == collection.handle %}
{% assign its_a_match = true %}
{% endif %}
{% endfor %}
{% if its_a_match %}
<li class="">{{ product_vendor | upcase }}</li>
{% else %}
<li class="">{{ product_vendor | link_to_vendor | upcase }}</li>
{% endif %}
</ul>
{%- endfor -%}
</div>
</div>
</div>
</section>

Twig for loop - output only parent if any children exists

These lines prints all categories and the products within. But if the category has no products the category title is still printed.
{% for category in categories %}
<h1>{{ category.title }}</h1>
{% for product in products if products.category|first == category.title|lower %}
<h2>{{ product.title }}</h2>
{% endfor %}
{% endfor %}
How can I optimize this so category titles are printed only when the category contains products?
There are (imo) 2 solution for this. The first one is pure twig-based, but is the worst of the two methods.
In order to skip categories without children, this would require a second loop and flag variable to determine wether to skip the category or not. As twig can't break out of loops this means u would need to foreach the products twice completely
{% for category in categories %}
{% set flag = false %}
{% for product in products if products.category|first == category.title|lower %}
{% set flag = true %}
{% endfor %}
{% if flag %}
<h1>{{ category.title }}</h1>
{% for product in products if products.category|first == category.title|lower %}
<h2>{{ product.title }}</h2>
{% endfor %}
{% endif %}
{% endfor %}
The second and better solution is just to add an extra method to your category-model and do something like
{% for category in categories if category.hasProducts() %}
<h1>{{ category.title }}</h1>
{% for product in products if products.category|first == category.title|lower %}
<h2>{{ product.title }}</h2>
{% endfor %}
{% endfor %}
An easy solution would be to add a is defined condition to your twig loop.
Example:
{% for category in categories if categories is defined %}
<h1>{{ category.title }}</h1>
{% for product in products if products.category|first == category.title|lower %}
<h2>{{ product.title }}</h2>
{% endfor %}
{% endfor %}

How to assign values to an array using HubSpot's HubL

I'm currently trying to figure out how to allow a content manager to change the order in which elements appear within recent post link lists. Essentially, the concept involves them picking the index in a sorting array that will hold that element's type as a string, which will later be iterated over with output logic determining where each piece gets placed. Unfortunately, one of the drawbacks of using HubL is the lack of debugging or error reporting so I can't see where my syntax is failing. I was wondering if anyone in InternetLand has had any similar experience with this.
Here's the HubL:
<div class="theme-recent-posts">
{% if widget.title_name %}
<div class="theme-recent-posts-title">
<{{ widget.title_tag }}><span>{{ widget.title_name }}</span></{{ widget.title_tag }}>
</div>
{% endif %}
{% set posts = blog_recent_posts(widget.select_blog, widget.max_links) %}
{% for post in posts %}
{% set item_objects = [] %}
{% if widget.show_images == "true" %}
{% set item_objects[widget.sort_images] = "image" %}
{% endif %}
{% if widget.show_titles == "true" %}
{% set item_objects[widget.sort_titles] = "title" %}
{% endif %}
{% if widget.show_dates == "true" %}
{% set item_objects[widget.sort_dates] = "date" %}
{% endif %}
{% if widget.show_authors == "true" %}
{% set item_objects[widget.sort_authors] = "author" %}
{% endif %}
<div class="theme-recent-posts-item">
<a href="{{ post.absolute_url }}">
{% for object in item_objects %}
{% if object == "image" %}
<span class="theme-recent-posts-item-image"><img src="{{ post.featured_image }}" alt="{{ post.name }}" /></span>
{% endif %}
{% if object == "title" %}
<span class="theme-recent-posts-item-title">{{ post.name }}</span>
{% endif %}
{% if object == "date" %}
<span class="theme-recent-posts-item-date">{{ post.created }}</span>
{% endif %}
{% if object == "author" %}
<span class="theme-recent-posts-item-author">{{ post.author_name }}</span>
{% endif %}
{% endfor %}
</a>
</div>
{% endfor %}
</div>
Output currently results in:
<div class="theme-recent-posts">
<div class="theme-recent-posts-title">
<h2><span>Recent Posts</span></h2>
</div>
<div class="theme-recent-posts-item">
</div>
<div class="theme-recent-posts-item">
</div>
<div class="theme-recent-posts-item">
</div>
</div>
Assume the default values wired up in the widget panel:
widget.show_images = true
widget.show_images = true
widget.show_images = true
widget.show_images = true
widget.sort_images = 0
widget.sort_titles = 1
widget.sort_dates = 2
widget.sort_authors = 3
The output should essentially be this:
<div class="theme-recent-posts">
<div class="theme-recent-posts-title">
<h2><span>Recent Posts</span></h2>
</div>
<div class="theme-recent-posts-item">
<a href="//website/path/post-name-3">
<span class="theme-recent-posts-item-image"><img src="path/to/image-3.ext" alt="Post Name 3" /></span>
<span class="theme-recent-posts-item-title">Post Name 3</span>
<span class="theme-recent-posts-item-date">1234567890</span>
<span class="theme-recent-posts-item-author">FirstName LastName</span>
</a>
</div>
<div class="theme-recent-posts-item">
<a href="//website/path/post-name-2">
<span class="theme-recent-posts-item-image"><img src="path/to/image-2.ext" alt="Post Name 2" /></span>
<span class="theme-recent-posts-item-title">Post Name 2</span>
<span class="theme-recent-posts-item-date">1234567890</span>
<span class="theme-recent-posts-item-author">FirstName LastName</span>
</a>
</div>
<div class="theme-recent-posts-item">
<a href="//website/path/post-name-1">
<span class="theme-recent-posts-item-image"><img src="path/to/image-1.ext" alt="Post Name 1" /></span>
<span class="theme-recent-posts-item-title">Post Name 1</span>
<span class="theme-recent-posts-item-date">1234567890</span>
<span class="theme-recent-posts-item-author">FirstName LastName</span>
</a>
</div>
</div>
I'm going to tag Jinja in this post, because HubL is based on Jinja, though it's not a direct translation.
If someone with enough reputation is reading this, I would greatly appreciate the addition of the HubL language tag, as it currently does not exist to select (even though HubSpot was).
Update
Twig is also a close cousin to HubL, and so I looked up how to update elements in an array there, and came up with this answer:
Setting element of array from Twig
{% set item_objects = item_objects|merge({widget.sort_images:"image"}) %}
Implementation did not work as expected. Output remains unchanged.
While this is not an answer to the question I asked, it is an alternate solution, so I provide it as a work around until a real solution is determined.
<div class="theme-recent-posts">
{% if widget.title_name %}
<div class="theme-recent-posts-title">
<{{ widget.title_tag }}><span>{{ widget.title_name }}</span></{{ widget.title_tag }}>
</div>
{% endif %}
{% set object_order = [0, 1, 2, 3] %}
{% set posts = blog_recent_posts(widget.select_blog, widget.max_links) %}
{% for post in posts %}
<div class="theme-recent-posts-item">
<a href="{{ post.absolute_url }}">
{% for order in object_order %}
{% if widget.show_images == "true" and widget.sort_images == order %}
<span class="theme-recent-posts-item-image"><img src="{{ post.featured_image }}" alt="{{ post.name }}" /></span>
{% endif %}
{% if widget.show_titles == "true" and widget.sort_titles == order %}
<span class="theme-recent-posts-item-title">{{ post.name }}</span>
{% endif %}
{% if widget.show_dates == "true" and widget.sort_dates == order %}
<span class="theme-recent-posts-item-date">{{ post.created }}</span>
{% endif %}
{% if widget.show_authors == "true" and widget.sort_authors == order %}
<span class="theme-recent-posts-item-author">{{ post.author_name }}</span>
{% endif %}
{% endfor %}
</a>
</div>
{% endfor %}
</div>
This gives me the flexibility I was looking for and the code is actually lightened quite a bit from the original proposed method. The idea is to use the object_order list as a mask for the for order in object_order loop. This gets the job done, and while I know it's hacky, it has a touch of elegance to it.

How to render a tree in Twig

I would like to render a tree with an undetermined depth (children of children of children, etc.). I need to loop through the array recursively; how can I do this in Twig?
I played around with domi27's idea and came up with this. I made a nested array as my tree, ['link']['sublinks'] is null or another array of more of the same.
Templates
The sub-template file to recurse with:
<!--includes/menu-links.html-->
{% for link in links %}
<li>
{{ link.name }}
{% if link.sublinks %}
<ul>
{% include "includes/menu-links.html" with {'links': link.sublinks} %}
</ul>
{% endif %}
</li>
{% endfor %}
Then in the main template, call this (kind of redundant 'with' stuff there):
<ul class="main-menu">
{% include "includes/menu-links.html" with {'links':links} only %}
</ul>
Macros
A similar effect can be achieved with macros:
<!--macros/menu-macros.html-->
{% macro menu_links(links) %}
{% for link in links %}
<li>
{{ link.name }}
{% if link.sublinks %}
<ul>
{{ _self.menu_links(link.sublinks) }}
</ul>
{% endif %}
</li>
{% endfor %}
{% endmacro %}
In the main template, do this:
{% import "macros/menu-macros.html" as macros %}
<ul class="main-menu">
{{ macros.menu_links(links) }}
</ul>
Twig 2.0 - 2.11
If you want to use a macro in the same template, you should use something like this to stay compatible with Twig 2.x:
{% macro menu_links(links) %}
{% import _self as macros %}
{% for link in links %}
<li>
{{ link.name }}
{% if link.sublinks %}
<ul>
{{ macros.menu_links(link.sublinks) }}
</ul>
{% endif %}
</li>
{% endfor %}
{% endmacro %}
{% import _self as macros %}
<ul class="main-menu">
{{ macros.menu_links(links) }}
</ul>
This extends random-coder's answer and incorporates dr.scre's hint to the Twig documentation about macros to now use _self, but import locally.
Twig >= 2.11
As of Twig 2.11, you can omit the {% import _self as macros %}, as inlined macros are imported automatically under the _self namespace (see Twig announcement: Automatic macro import):
{# {% import _self as macros %} - Can be removed #}
<ul class="main-menu">
{{ _self.menu_links(links) }} {# Use _self for inlined macros #}
</ul>
If you're running PHP 5.4 or higher, there is a wonderful new solution (as of May 2016) to this problem by Alain Tiemblo: https://github.com/ninsuo/jordan-tree.
It's a "tree" tag that serves this exact purpose. Markup would look like this:
{% tree link in links %}
{% if treeloop.first %}<ul>{% endif %}
<li>
{{ link.name }}
{% subtree link.sublinks %}
</li>
{% if treeloop.last %}</ul>{% endif %}
{% endtree %}
First I thought this may be solved in a straightforward way, but it isn't that easy.
You need to create logic, maybe with a PHP class method, when to include a Twig subtemplate and when not.
<!-- tpl.html.twig -->
<ul>
{% for key, item in menu %}
{# Pseudo Twig code #}
{% if item|hassubitem %}
{% include "subitem.html.tpl" %}
{% else %}
<li>{{ item }}</li>
{% endif %}
{% endfor %}
</ul>
So you could use the special Twig loop variable, which is available inside a Twig for loop. But I'm not sure about the scope of this loop variable.
This and other information are available on Twigs "for" Docu!
Took flu's answer and modified it a little:
{# Macro #}
{% macro tree(items) %}
{% import _self as m %}
{% if items %}
<ul>
{% for i in items %}
<li>
{{ i.title }}
{{ m.tree(i.items) }}
</li>
{% endfor %}
</ul>
{% endif %}
{% endmacro %}
{# Usage #}
{% import 'macros.twig' as m %}
{{ m.tree(items) }}
The answers here lead my to my solution.
I have a category entity with a self-referencing many-to-one association (parent to children).
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="children")
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent")
*/
private $children;
In my Twig template I am rendering the tree view like this:
<ul>
{% for category in categories %}
{% if category.parent == null %}
<li>
{{ category.name }}
{% if category.children|length > 0 %}
<ul>
{% for category in category.children %}
<li>
{{ category.name }}
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endif %}
{% endfor %}
</ul>

Resources