Complex Jinja patterns

Working with complex arrays

Sorting using lookup data

In this example we'll find the three most expensive items in an abandoned cart. We'll do that by looping through the items in the abandoned cart (found in event_raw.cartItems). As we do that, we'll retrieve the information from the product catalog (lookups.product_catalog[item.productId]) for each item, and store that in an array products. Finally we'll sort that array and retrieve the top three items.

{%- set products = products | default([]) -%}

{%- for item in event_raw.cartItems if lookups.product_catalog[item.productId] -%}
    {%- set product = lookups.product_catalog[item.productId] -%}
    {{- products.append(product) -}}
{%- endfor-%} 

{%- set products = (products | sort(reverse=true, attribute='price'))[:3] -%}

Sorting using other data

In some situations your data source is not a lookup or you want to mix different data. In that case, you can just add individual properties instead. In this example, we are sorting based on the quantity in the abandoned cart (contained in event_raw.cartItems), but also checking with a lookup if the item is in stock.

{%- set products = products | default([]) -%}

{%- for item in event_raw.cartItems if lookups.product_catalog[item.productId] -%}
    {{- products.append({'id': item.productId, 
                         'quantity': item.quantity, 
                         'is_in_stock': lookups.product_catalog[item.productId].is_in_stock }) -}}
{%- endfor-%} 

{%- set products = (products | sort(reverse=true, attribute='quantity'))[:3] -%}

In this case, we directly create an object that we then add to the list of products. This is also gives us the flexibility to change datatypes or do additional filtering or transformations.

{ 'id': item.productId, 
  'quantity': item.quantity, 
  'is_in_stock': lookups.product_catalog[item.productId].is_in_stock }

📘

Debugging complex arrays

Please note that, in some cases, if you try {{ products }} or {{ products[0] }} in the above examples it will not show anything. You'd need to do {{ products[0].name }} etc. to inspect the contents of the array.

Combining two sorted arrays

You might have two arrays and want to retrieve a certain number of items, given precedence to one array. In the below example, we want to retrieve the three highest-priced items, but give precedence to tier 1 products.

{%- set products = tier_1 | sort(reverse=true, attribute='price') + tier_2 | sort(reverse=true, attribute='price') -%}

{{- products[:3] -}}

Generating a list of dates from a start and end date

If you have a start and end date, you may want to get all the dates between those dates. In the below example we'll store them in a list, but you could just as easily print them. Make sure that you are using a duration that makes sense for the dates that you are working with. E.g. if the end date here were a hotel checkout date and you want to show the nights, you would not add + 1 to the duration like we do below.

{%- set dates = dates | default([]) -%}
{%- set start = start | default(contact.start_date) -%}
{%- set end = end | default(contact.end_date) -%}

{%- if start and end -%}
    {%- set duration = (end - start).days + 1 -%}
    {%- for day in range(0, duration) -%}
        {{- dates.append(start | add_days(day)) -}}
    {%- endfor -%}
{%- endif -%}
{{- dates -}}

Comparing dates

In this example, we want to check if the data in a contact property is before a specific date.

{%- set cutoff_date = cutoff_date | default('Sept 1,2020' | parse_datetime('%b %d,%Y')) -%}
{%- if contact.start_date -%}
    {%- if contact.start_date < cutoff_date -%}
        Congratulations! 
    {%- endif -%}
{%- endif -%}

Sharing variables between blocks

When working with variables that are shared between blocks or between blocks and templates, keep the following in mind:

  1. A child block can always read variables that are set in a parent template or block.
  2. A parent block or template can only read a variable that is defined or updated in a child block under specific circumstances.

With "child block", we mean a block that is included inside another block or template. With "parent template" (or block), we mean the template (or block) that includes another block.

Reading a variable in a child block

This scenario is straightforward: a child block can always read the variables that are defined in a parent template or block. These variables are also available to any additional blocks that are referenced in a child block.

Say I define the following variable in a template:

{%- set promo_code = 'NEWFRIEND' -%}

Then a child block could use that variable whenever necessary. You still want to check that the variable has been defined, so that you will have a default if you for instance forget to set the variable in the parent.

{%- if promo_code is defined -%}
	{{- promo_code -}}
{%- else -%}
	PROMO_DEFAULT
{%- endif -%}

Reading a variable from a child block

It is a bit more complex to edit a variable in a child block and then read it in a parent template or block. This will work under certain conditions:

  1. The variable is a more complex object, such as a dict or an array.
  2. The variable is initialized within the parent template or block.

As long as these conditions are met, you can use variables to pass data between child blocks and their parent.

Let's look at an example where we want to use a block to find some details about what is in an abandoned cart.

This could be the parent template (or block), where we initialize the variable cart, call the block, and then read the data from the cart, which was updated in the block:

{%- set cart = cart | default({}) -%}
{%- include 'get_cart_info' -%}
{{- cart -}}

The block, in turn would read data from the abandoned cart (contained in event_raw.cartItems, and load it into the variable cart. We initialize cart at the beginning of the block to prevent errors when editing the child block.

{%- set cart = cart | default({}) -%}
{%- set price = price | default([]) -%}
{%- set quantity = quantity | default([]) -%}

{%- for item in event_raw.cartItems -%}
    {{- price.append((item.price or 0)) -}}
    {{- quantity.append((item.quantity or 0)) -}}
{%- endfor -%}

{{- cart.update({"sum_price": price | sum, 
                 "sum_quantity": quantity | sum }) -}}

🚧

Templates vs. content blocks

Using &amp syntax works directly in a template but if you're trying to use it in a content block referenced in a template, then you should use & instead