How to Maintain a Counter Outside a Loop in Frappe Jinja Templates?
Count within Loop print outside loopOvercome Jinja's loop scoping in Frappe Print Formats. Learn to use dictionaries and namespaces to maintain and access variable values, like counters, outside a for loop.
In Frappe Print Formats, a common requirement is to iterate over a child table (e.g., items in a Sales Order) and calculate an aggregate value, such as a count or a sum. A significant challenge with Jinja is that variables defined with `{% set %}` inside a loop are scoped locally, meaning their values are lost once the loop completes.
This guide presents two standard patterns using mutable objects—dictionaries and namespaces—to correctly maintain state across loop iterations. These methods allow you to define a counter or flag before the loop, update it inside, and reliably access its final value afterwards.
1
2 {# Method 1: Using a Dictionary #} 3
4 {# 1. Define a dictionary before the loop #} 5 {% set vars = {'item_count': 0} %} 6
7 {% for item in doc.items %} 8 {# 2. Update the dictionary's value inside the loop #} 9 {# The 'if' construct ensures the update function executes without printing output #} 10 {% if vars.update({'item_count': vars.item_count + 1}) %}{% endif %} 11 {% endfor %} 12
13 {# 3. Use the value outside the loop #} 14 <p>Total Items: {{ vars.item_count }}</p> 15
16
17 {# Method 2: Using a Namespace (Cleaner Approach) #} 18
19 {# 1. Define a namespace object #} 20 {% set counter = namespace(value=0) %} 21
22 {% for row in doc.some_child_table %} 23 {% if row.some_condition %} 24 {# 2. Modify the namespace attribute directly #} 25 {% set counter.value = counter.value + 1 %} 26 {% endif %} 27 {% endfor %} 28
29 {# 3. Access the attribute outside the loop #} 30 <p>Conditional Count: {{ counter.value }}</p> 31
Understanding This Code
What It Does
Provides two reliable methods (dictionary and namespace) to modify a variable's state within a Jinja `for` loop and access the updated value after the loop has completed in Frappe Print Formats.
When To Use
Use in Frappe Print Formats when you need to perform aggregations like counting rows that meet a certain condition, summing values from a child table, or setting a conditional flag based on data within a loop.
Prerequisites
- •Basic understanding of Jinja templating syntax.
- •Familiarity with the DocType and child table structure in Frappe.
- •Access to create or edit Print Formats in an ERPNext or Frappe instance.
Key Concepts
Important ideas to understand in this code
Jinja Variable Scoping
By default, variables assigned inside a Jinja `for` loop have a local scope. Any modifications are discarded at the end of each iteration. To persist changes, you must operate on a mutable object that was defined outside the loop.
Learn moreMutable Objects: Dictionaries & Namespaces
Dictionaries and namespaces are mutable, meaning they can be changed in-place. When you modify a property of these objects from within a loop (e.g., `vars.update({...})` or `counter.value = ...`), you are altering the original object, so the changes persist outside the loop's scope.
Learn morePrint Formats in Frappe
Print Formats in Frappe are templates, typically written with HTML and Jinja, that define the layout of documents like Invoices or Delivery Notes. They dynamically render data from DocTypes, making loop-based calculations a common necessity.
Learn moreStep-by-Step Tutorial
Follow along to understand how this code works
Method 1: Using a Dictionary
This classic approach involves initializing a dictionary before the loop. Inside the loop, you use the `.update()` method to modify the value associated with a key. The `if` statement is a common Jinja trick to execute the update expression without rendering any output to the page.
{# Step 1.1: Initialize the dictionary before the loop #}
{% set vars = {'uom_count': 0} %}
{% for item in doc.items %}
{# Step 1.2: Check a condition and update the dictionary #}
{% if item.uom == 'Nos' %}
{% if vars.update({'uom_count': vars.uom_count + 1}) %}{% endif %}
{% endif %}
{% endfor %}
{# Step 1.3: Access the final count #}
<p>Number of items with UOM 'Nos': {{ vars.uom_count }}</p>
Method 2: Using a Namespace
The `namespace` object is the modern, recommended way to handle this scenario as it was designed specifically for this purpose. It creates a simple, mutable object whose attributes can be freely assigned and reassigned from any scope.
{# Step 2.1: Initialize the namespace object #}
{% set counter = namespace(high_qty_items=0) %}
{% for item in doc.items %}
{# Step 2.2: Check a condition and directly modify the attribute #}
{% if item.qty > 100 %}
{% set counter.high_qty_items = counter.high_qty_items + 1 %}
{% endif %}
{% endfor %}
{# Step 2.3: Access the final count via the attribute #}
<p>Items with quantity over 100: {{ counter.high_qty_items }}</p>
Common Issues & Solutions
Troubleshoot problems you might encounter