Jinja Blocks

Many of the Jinja functionalities reside as code blocks. These blocks are usually declared as:
{% blocktype %} ... {% endblocktype %}. Code blocks not only enable you to organize your code better visually, but they also allow you to semantically divide your code into coherent blocks that provide one certain function. There are eight block types supported by Jinja. However, template importing is not supported in our implementation of Jinja. Therefore, the basic block type block does not have any practical use and will not be covered.

Scope of a variable

By introducing blocks to your code, you will need to keep in mind one important new thing called variable scope. To make efficient use of memory resources, variables should only exist as long as they are needed. Some variables might be important for the whole of your template, such as the name of your customer, but often you will find yourself working with a variable only inside the block where it was defined. Hence, Jinja includes a property of variables called variable scope. Some variables have their scope defined as Global, which denotes that they are defined and accessible in all of your template code. However, generally, the variables defined in your templates will have a defined scope only inside a certain block, such as inside a for loop, macro, or a custom-defined block. They will have the so-called Local variable scope. It is considered to be good practice to stay away from Global variables unless they are truly necessary. However, with local variables, you need to keep track of the scope they are defined for.

For a list of Global Jinja variables in Bloomreach Engagement see Personalization using Jinja.

Block types

Set

The set block is used to assign values to variables. See using your variables in our Jinja Basics article.

🚧

Set blocks and filters

Unlike in Jinja documentation, applying filter directly to {% set %} block, as illustrated below, is NOT supported,

Control flow

Your templates are generally being evaluated line by line from the first line to the last line. But often, you might want to repeat certain statements in your template or you might want to perform certain statements only if a certain condition was met. Jinja provides this functionality through the control flow blocks.

If blocks

The simplest control flow block to understand is the if block. If blocks are used to include conditional statements to your templates. The syntax of block statements is quite simple. Firstly, you have a line with a boolean expression that represents your condition {% if myCondition %}, then you have a bunch of lines that will get rendered only if your condition was indeed true. Optionally, you can include the elif statement {% elif mySecondCondition %}. Elif is an abbreviation of else if and as the name suggests, the elif statement gets evaluated if the first condition was false. Once again, elif statement is followed by a bunch of lines that will get rendered if the second condition is true ( hence the lines after elif will get evaluated if and only if the first condition was false and the second true. You can include the else statement {% else %} and again a bunch of lines that will get evaluated if neither of the conditions was met. Finally, the if block has to end with a {%endif%} line.

To sum up, in a single if block, the first if/elif whose statement evaluates to true is rendered, the other one is skipped. If all if/elif statements evaluate to false the else block is rendered.

The simple examples below illustrate the use of if blocks.

{# If block number one #}
{% if 1 < 1 %} 
One is less than one. 
{% elif 1< 2 %}
One is less than two 
{% else %} one is greater or equal to two
{% endif %} 

Output: one is less than two 
Because the first condition fails, the second one gets evaluated. It evaluates to true, hence the statements following it gets rendered.

Inline expressions

You can also use if statements outside of if blocks by using inline expressions. They are very similar to python conditional expressions, see the reference in Conditional expressions - python docs. You use the inline expressions to quickly assign conditional values to variables inside the set blocks. The syntax is valueIfTrue if myCondition else valueIfFalse. See the code below for an example

{% set x = 'apples' if 13 is even else 'oranges' %}
{{ x }}

Output: oranges

Jinja Tests

To help you create common conditions that include more than just simple boolean evaluation, Jinja includes functionality called testing. You can test your variables using the is or in keyword to evaluate the common properties of your variables. The tests with the is operator are used to test whether the variable being tested has a certain property, while the in operator is being used to test whether a value can be found in the variable being tested. The result is a boolean True or False in both cases and as any boolean value, it can be printed, included in a conditional statement, or even saved in a variable for later use.

Test categories are:

  • Tests that check type
  • Tests that check object characteristics
  • Tests that check Strings
  • Tests that check Numbers
  • Tests that check Equality and Identity
  • Tests that check ownership
List of all tests
Tests that check types
TestFunctionality
defined(value)Returns true if the variable is defined.
none(value)Returns true if the variable is of type none.
number(value)Returns true if the variable is a number.
string(value)Returns true if the object is a string.
undefined(value)Returns true if the variable is undefined.
Opposite of defined().
Tests check object characteristics
TestFunctionality
callable(object)Returns true if the object is callable (i.e., some kind of function, e.g. macro, filter or test). Note that classes are callable, as are instances with a call() method.
iterable(value)Returns true if the object is iterable (List, Tuple, String, Dictionary). Same as sequence().
mapping(value)Returns true if the object is a mapping (dictionary).
sequence(value)Returns true if the variable is a sequence. Sequences are variables that are iterable (List, Tuple, String, Dictionary). Same as iterable().
Tests that check Strings
TestFunctionality
escaped(value)Returns true if the value is escaped (does NOT contain any of the following: '&', '>', '<', single-quote ( ' )).
lower(value)Returns true if the variable is lowercased (All alphabetical characters are lowercase, and there is at least 1 alphabetical character).
upper(value)Returns true if the variable is uppercased (All alphabetical characters are uppercase, and there is at least 1 alphabetical character).
Tests that check Numbers
TestFunctionality
odd(value)Returns true if the value in the variable is odd.
even(value)Returns true if the value in the variable is even.
divisibleby(value, num)Returns true if the variable is divisible by a number.
Tests that check ownership
TestFunctionality
in(value, seq)Returns true if the iterable sequence contains value as one of its items.

🚧

The in test syntax

Note the slightly different syntax for in operator (as well as the different position of the not keyword).
'{% set x = {'a': 1, 'b': 2} %}'
'{{ 'a' in x }}' // prints true, 'a' is one of keys
'{{ 2 not in x }}' // prints true, 2 is not one of keys

For blocks

For blocks are used to go through the elements of iterables while performing some actions on each element.

Firstly, you will need to initiate the for block using {% for myItem in myIterable%}, where myIterable stands for the given iterable. Then, the following lines will be the actions to be run on the item that is currently being accessed from the iterable. You can also include the {% else %} block, which will be run if no items were passed through in the for loop, i.e. the supplied iterable was empty. Finally, you need to end the for block by using {% endfor %}.

📘

For block on a dictionary

If a dictionary is provided, the for loop will iterate over its keys.

{%- for x in [] %}
{{ 'cat' }}
{%- else %}
{{ 'dog' }}
{%- endfor %}

Output:
dog

{%- for x in [1,2] %}
{{ x }}
{%- else %}
{{ 'dog' }}
{%- endfor %}

Output: 
1
2

Recursive for loop

Jinja supports recursive loop with 'recursive' flag at the end of for loop statement. Such for loops can use loop(List) function, which repeats the for loop with the recursive flag for each item in List. This allows you to effectively work with nested data structures.

{%- set x = [11, [21, 22], [ [23] ] ] %}
{%- for item in x recursive %}
{%- if item is iterable %}
{{- loop(item) }}
{%- else %}
{{- item ~ ' says hello from depth: ' ~ loop.depth }}
{%- endif %}
{%- endfor %}

Output: 
11 says hello from depth: 1
21 says hello from depth: 2
22 says hello from depth: 2
23 says hello from depth: 3

For loop filtering

If iterable contains items that you would like to exclude from the for loop, these can be filtered out at the end of the for loop statement.

If used on for loop with the recursive flag, the recursive flag is at the end of the statement.

{%- for item in ['hello', 42, 'world'] if item is string %}
{{ item }}
{%- endfor %}

Output: 
hello
world
{%- for item in ['hello', [42], 13, 'world'] if (item is not string) recursive %}
{{ loop(item) if item is iterable else item ~ ', depth: ' ~ loop.depth }}
{%- endfor %}

Output:
42, depth: 2     // 'hello' and 'world' were filtered out
13, depth: 1     // 42 is in extra List, therefore deeper

For blocks loop variables

When inside for loop, a special variable 'loop' is available with the properties listed in the following table.

Property nameTypeReturns
loop.firstIntegerTrue if it is the first iteration, else false
loop.lastIntegerTrue if it is the last iteration, else false
loop.lengthIntegerNumber of total iterations
loop.depthIntegerCurrent depth in a loop with 'recursive' tag. Starts at level 1. See Recursive for loop.
loop.depth0IntegerCurrent depth in a loop with 'recursive' tag. Starts at level 0. See Recursive for loop.
loop.indexIntegerCurrent index starting from 1
loop.index0IntegerCurrent index starting from 0
loop.revindexIntegerCurrent index from end starting from 1. (On first iteration: loop.revindex == loop.length, on last iteration: loop.revindex == 1).
loop.revindex0IntegerCurrent index from end starting from 0. (On first iteration: loop.revindex0 == loop.length - 1, on iteration loop: loop.revindex0 == 0)
loop.cycle(arg1, arg2, ...)FunctionFor each iteration n in current loop, returns n-th item in the sequence of arguments. See loop.cycle().
loop.cycle()

The loop.cycle() is a helper function that for each iteration n in current loop, returns n-th item in the provided sequence of arguments. Loops over the given sequence if reached the end.

{%- for item in range(3) %}
{{ loop.cycle('hello', 'there') }} // multiple calls within same loop iteration
{{ loop.cycle('hello', 'there') }} // return item on same n-th position
{%- endfor %}

output: 
hello // 1st iteration
hello
there // 2nd iteration
there
hello // 3rd iteration
hello

📘

loop.cycle() and recursive for loop

loop.cycle() also resets for recursive loops.

Macros

Macros are Jinja equivalent of functions, a callable block to which arguments can be passed. They allow you to write clearer code or save up space by replacing the same commands repeated in multiple places with one macro.

The syntax of macro blocks is simple. As with the other block types, you begin by {% macro macroName(args) %} and end the block with {% endmacro $}. To enable macros to really behave like functions, they too have parameters, that are referred to as arguments.

{%- macro printMood(day, mood='happy') %}
{{ (day | title) ~ ', I feel ' ~ mood ~ '!' }}
{%- endmacro %}
...
{%- set currMood = 'amazing' %}
{%- set currDay = 'today' %}
{{- printMood(currDay, currMood) }}

Output: 
Today, I feel amazing!

📘

Nested macros

Macros can call other macros as well as themselves recursively inside the macro block.

Macros' arguments can have default values, defined by arg=value in the parenthesis in the macro statement

🚧

Default values in macros

If defining default values for arguments, arguments without default value must be declared first.

Call

The last Jinja block is directly connected to macros. Instead of calling a macro only by its name, you can use the call block to call a specified macro with the block's content appended as its last argument. When the call block is passed to the macro, its content is wrapped in a macro and assigned to the 'caller' argument. Since the passed call block is passed as a macro, it must be called caller().

{#- This one throws error because argument caller was not declared #}
{%- macro errorMacro(arg1) %}
{{ errorMacro.name ~ "'s arg1 is " ~ arg1 }}
{%- endmacro %}
...
{%- call errorMacro() %}
This is the content of the call block.
{%- callend %}
 
{#- This one works because argument caller was declared #}
{%- macro workingMacro(arg1='', caller='') %}
{{ workingMacro.name ~ "'s args are " ~ arg1 ~ ' and "' ~ caller() ~ '"' }}
{%- endmacro %}
...
{%- call workingMacro() %}
This is the content of the call block.
{%- endcall %}

Output: 
workingMacro's args are and "
This is the content of the call block."

Raw

The raw block's content is evaluated as a string. Any Jinja syntax within raw block is left as is and is not evaluated. The raw blocks are useful when you want to print out Jinja syntax. However, you will only rarely need them while using Bloomreach Engagement.

{% raw  %} 
{% set x = 5 %}
{% endraw %} 
Output: 
"{% set x = 5 %}"