Liquid Templates in Power Apps Portals – Part 4

Name change! I started this series of posts quite a few months ago and realized that I really should be calling things Power Apps Portals. So… welcome to Liquid Templates in Power Apps Portals!

In the last post, Liquid Templates in Dynamics Portals – Part 3, we covered working with lists of data in Liquid Templates such as Arrays and Dictionaries. Now let’s take at code reuse in Liquid Templates – I have a cool bit of code or a well designed layout that I want to apply to a few different places in my Power Apps Portal. What tools does Liquid Templates offer to accomplish this?

The include tag

First up is the include tag. It’s somewhat self explanatory – include some other bit of Liquid code in my current context. The definition from the documentation describes it well:

Includes the contents of one template in another, by name.

include, docs.microsoft.com

This tag should be familiar as it showed up in our last post:

{% extends 'Layout 1 Column' %} 
{% block main %}
     {% include 'Page Copy' %}
{% endblock %}

In this example, the include tag inserts the Page Copy Web Template to the current template. More specifically, the Liquid processor will insert and execute the contents of the Page Copy template as if it is part of the current Web Template (This is an important note… more on this in a minute). So our example above looks something like this before processing:

{% extends 'Layout 1 Column' %}
{% block main %}
    <div class="page-copy">
       {% editable page 'adx_copy' type: 'html', liquid: true %}
     </div>
{% endblock %}

We see the Page Copy template does more than just grab page adx_copy CDS field value: it adds a div with a specific CSS class and the editable Liquid tag, allowing admins to edit the section of the page.

This is not a complex example but it should spark some ideas for reuse. We can wrap up both design and data elements into reusable blocks of Liquid code that can be referenced anywhere the Portal allows Liquid. This saves us from managing multiple copies of the same code so that if we update the contents of an ‘included’ Web Template, the change applies across the entire Portal.

But wait… there’s more!

The include tag also allows passing of named parameters. So instead of hard wiring everything into a Web Template, we can pass values similar to passing values to a function in C# or JavaScript.

For example, let’s say that we want to render a Bootstrap panel in several different places on the Portal. From the Bootstrap documentation for Panels, here is the HTML we want to render:

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">Panel title
  </div>
  <div class="panel-body">
    Panel content
  </div>
  <div class="panel-footer">Panel footer</div>
</div>

If we are ok with the layout and styles and we just want to replace the text bits for Panel Header, Panel Body, and Panel Footer, we can create a new Web Template starting with the above HTML, replacing the Panel Header, Body, and Footer text with variables – panel_header, panel_body, panel_footer. We could then capture the named parameters values into each of the variables. The new Web Template that we would name Panel Template would look something like this:

{% assign panel_header=my_header %}
{% assign panel_body=my_body %}
{% assign panel_footer=my_footer %}

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">{{-panel_header-}}</h3>
  </div>
  <div class="panel-body">
    {{-panel_body-}}
  </div>
  <div class="panel-footer">{{-panel_footer-}}</div>
</div>

In the new Web Template, say we name it ‘Panel Template‘, we see the three assign tags at the very beginning, grabbing data from 3 values: my_header, my_body, and my_footer.

Where do these come from? These is how we ‘define’ the named parameters expected when calling the named template. Now that we have these assign tags defined, include tag to invoke the template would look something like this:

{% include 'Panel Template' my_header:'Futurez Consulting' 
my_body:'Welcome to my Blog!' my_footer:'Come back soon!' %}

And the rendered output markup would look like so:

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">Futurez Consulting</h3>
  </div>
  <div class="panel-body">
    Welcome to my Blog!
  </div>
  <div class="panel-footer">Come back soon!</div>
</div>

So one line of Liquid to render the entire Panel with dynamic content! You could also add checks on the parameters for conditional layout. For example, only render the panel footer if the value of panel_footer is not null (panel_footer != nil). You could add another parameter to change the panel classes, such as changing the class from panel-default to panel-success. A simple example with lots of options!

Scope of assign

Earlier, I noted that it’s important to note that include treats the contents of one Web Template as if it’s part of the parent Web Template. The reason is because of the scope of variables defined using assign tags. When you define a variable using assign, the variable is accessible from both outside and inside the included Web Template.

This makes sense given how we have seen the include statement work: the Liquid engine is effectively inserting the Web Template and evaluating the combined Liquid code as if it’s all a single template.

Looking again at the ‘Panel Template‘ example, this means that if I assign a variable before the include statement, the ‘Panel Template‘ Liquid could both use and update the variable assignment. For example, I can add the assign statement before the previous include statement:

{% assign my_new_footer="This is a new Footer label!" %} 
{% include 'Panel Template' my_header:'Futurez Consulting'
 my_body:'Welcome to my Blog!' my_footer:'Come back soon!' %}

And if I were to update the ‘Panel Template‘ code to the following, changing it to reference the new variable my_new_footer and ignore the panel_footer:

{% assign panel_header=my_header %}
{% assign panel_body=my_body %}
{% assign panel_footer=my_footer %}

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">{{-panel_header-}}</h3>
  </div>
  <div class="panel-body">
    {{-panel_body-}}
  </div>
  <div class="panel-footer">{{-my_new_footer-}}</div>
</div>

The resulting panel HTML block would now look like this:

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">Futurez Consulting</h3>
  </div>
  <div class="panel-body">
    Welcome to my Blog!
  </div>
  <div class="panel-footer">This is a new Footer label!!</div>
</div>

So it looks like this would be a huge bug in our code! If I remember to assign the variable my_new_footer, then we are fine. But that sounds like it throws out the benefit of using named parameters. I will also need to remember to set this assign each time I call include the ‘Panel Template‘ Web Template.

More importantly, it shows a potential issue if you did not mean to set the value. What if you inadvertently named the assigned variable the same as that within the ‘Panel Template‘ Web Template by mistake? You would then see some unexpected output. This can happen in JavaScript when a variable or even a function is named the same as one that already exists. NOT FUN to debug, which is one reason you see namespaces in JavaScript.

So a best practice that I would suggest is to try and be as unique as you can when naming the variables that capture the incoming named parameters. In the above example, maybe we can change the name to panel_tmpl_header_val or something similar. With more complex Portals that incorporate a lot of Liquid code, this can be a challenge so it’s best to go in with this best practice in mind. This is a best practice that I personally need to improve upon myself!

The upside? include as a function

We know that we can access values of variables assigned outside of the include statement from within the included Web Templates. This can cause us some pain if we are not careful! But we can actually take advantage of this capability.

We can also assign variables within the included Web Template and access the results after the include statement. This means that if we know of a variable assigned within an included Web Template, we can treat Web Templates as functions – include a Web Template, it assigns a known variable, and we grab the result after the include statement. Here is an example that has come in handy when working with dates.

I came across this cool example on comparing dates in Liquid in a post by Mahender Pal, Comparing Dates In Dynamics 365 Portals Using Liquid. Mahender explains how to convert a date into a number so that you can do a simple comparison. I took his example and created a new Web Template called ‘Date as Integer‘:

{%- assign thisDate = dateToConvert -%}
{%- assign month = thisDate | date: "%M" | integer -%}
{%- assign day = thisDate | date: "%d" | integer -%}
{%- if day < 10 and month < 10 -%}
   {%- assign dateAsInt = thisDate | date: "%y%0M%0d" | integer -%}
{%- elsif day < 10 and month >= 10 -%}
   {%- assign dateAsInt = thisDate | date: "%y%M%0d" | integer -%}
{%- elsif day >= 10 and month < 10 -%}
   {%- assign dateAsInt = thisDate | date: "%y%0M%d" | integer -%}
{%- else -%}
   {%- assign dateAsInt = thisDate | date: "%y%M%d" | integer -%}
{%- endif -%}

This Web Template has a named parameter dateToConvert and uses date filter to create an integer representation using the year, month and day values. The result is assigned to the variable dateAsInt and converted to an integer. Since I know the variable name 'Date as Integer' will assign, I can access it after the include statement. For example, I might want to compare a registration date to the current date and apply some logic:

{%- include 'Date As Integer' dateToConvert: 'now' -%}
{%- assign now_date = dateAsInt -%}

{%- include 'Date As Integer' dateToConvert:e.new_regdate | date: '%M-%d-%y' -%}
{%- assign reg_date = dateAsInt -%}

{%- if now_date > reg_date -%}
    ... do some work here ...
{%- endif -%}

In this example, I "called" the 'Date as Integer' Web Template and passed the current date (now) and then the registration field date. After each call, I retrieved the value that was assigned within the 'Date as Integer' as if it was the return value or out parameter of a JavaScript or C# method call. Ok, not EXACTLY like the return value. A closer comparison is a C# class method calling another method that sets a global or class level variable.

I think this method works fairly well and I am using this code in the 365 Power Up events Web Templates and it's been useful with no issues so far. That said, I also have full awareness of how it works. It may not be that easy to understand if you are not familiar with the code, so this approach makes good documentation even more important for your Web Templates. The extra comments are worth the effort because it gives us a really nice bit of reusable code within our Portal.

Up next - The extends and block tags

Now that we have seen more detail on the include tag, we can take a look at another tool for Liquid code reuse with the extends and block tags. These combined tags allow us to derive from existing Web Templates, reusing the parts of the base template while overriding others. More on this in the next post!

A side note...

I am working on gathering examples from past posts like this and code libraries into a Portal where we can see the code in action. I think this will be a much better way of learning and testing than just reading snippets in a post. Keep an eye out for this Portal over the next few weeks!

As always, comments, questions, suggestions are all welcome!


Leave a Reply

Your email address will not be published. Required fields are marked *