In Adxstudio (version 7.x), we can create templates using one of two methods.
- Page Templates – Custom .aspx pages deployed to the server
- Web Templates – Dynamics 365 Entity which is used to define dynamic content rendering
With the introduction of SaaS based Dynamics 365 Portals, the Page Template is no longer an option (For comparison of version 7.x and 8.x of Portals, click here). Web Templates are built using a web templating system called “Liquid”. Liquid can also be used inside Web Page’s Copy (HTML) field to render dynamic content. We are heavily reliant on Web Templates and Liquid now than ever before. This article serves as a quick reference guide which includes many resources to master Liquid.
What is Liquid?
Liquid is an open-source template language created by Shopify and written in Ruby. The code base was ported to .NET by Tim Jones to another open-source project called DotLiquid. Dynamics 365 Portals (also Adxstudio) use DotLiquid as the Liquid Template Engine of their portal. DotLiquid is a complete port of the original Shopify Liquid project. Therefore, the Shopify documentation is also valid and useful when designing Dynamics 365 Portal’s Liquid statements.
Where can I use Liquid?
- Web Templates
- Copy (HTML) field of a Web Page
- Content Snippets
Liquid Basics
We need to learn three key terms, when using Liquid.
Tags – Liquid tags are the programming logic that tells templates what to do. Tags are wrapped in: {% %} characters. Certain tags, such as for and cycle can take on parameters.
Objects – Liquid objects contain attributes to output dynamic content on the page. For example, the page object contains an attribute called title that can be used to output the title of a page. Liquid objects are also often referred to as Liquid variables. To output an object’s attribute, wrap the object’s name in {{ and }}.
Filters – Filters are simple methods that modify the output of numbers, strings, variables and objects. They are placed within an output tag {{ }} and are denoted by a pipe character |.
Tags
if
1 2 3 |
{% if product.title == 'Awesome Shoes' %} You are buying some awesome shoes! {% endif %} |
unless
1 2 3 |
{% unless product.title == 'Awesome Shoes' %} You are not buying awesome shoes. {% endunless %} |
else / elsif
1 2 3 4 5 6 7 |
{% if shipping_method.title == 'International Shipping' %} You're shipping internationally. Your order should arrive in 2–3 weeks. {% elsif shipping_method.title == 'Domestic Shipping' %} Your order should arrive in 3–4 days. {% else %} Thank you for your order! {% endif %} |
case / when
1 2 3 4 5 6 7 8 9 10 |
{% case shipping_method.title %} {% when 'International Shipping' %} You're shipping internationally. Your order should arrive in 2–3 weeks. {% when 'Domestic Shipping' %} Your order should arrive in 3–4 days. {% when 'Local Pick-Up' %} Your order will be ready for pick-up tomorrow. {% else %} Thank you for your order! {% endcase %} |
Multiple conditions (and / or)
and
1 2 3 |
{% if line_item.grams > 20000 and customer_address.city == 'Ottawa' %} You're buying a heavy item, and live in the same city as our store. Choose local pick-up as a shipping option to avoid paying high shipping costs. {% endif %} |
or
1 2 3 4 5 |
{% if customer.tags contains 'VIP' or customer.email contains 'mycompany.com' %} Welcome! We're pleased to offer you a special discount of 15% on all products. {% else %} Welcome to our store! {% endif %} |
for (maximum of 50 results per page)
1 2 3 |
{% for product in collection.products %} {{ product.title }} {% endfor %} |
else
1 2 3 4 5 |
{% for product in collection.products %} {{ product.title }} {% else %} The collection is empty. {% endfor %} |
break
1 2 3 4 5 6 7 |
{% for i in (1..5) %} {% if i == 4 %} {% break %} {% else %} {{ i }} {% endif %} {% endfor %} |
continue
1 2 3 4 5 6 7 |
{% for i in (1..5) %} {% if i == 4 %} {% continue %} {% else %} {{ i }} {% endif %} {% endfor %} |
limit
1 2 3 4 5 6 7 8 |
<!-- numbers = [1,2,3,4,5] --> {% for item in numbers limit:2 %} {{ item }} {% endfor %} <!-- output --> <!-- 1 2 --> |
offset
1 2 3 4 5 6 |
<!-- numbers = [1,2,3,4,5] --> {% for item in numbers offset:2 %} {{ item }} {% endfor %} <!-- output 3 4 5 6 --> |
range
1 2 3 4 5 6 7 8 9 10 11 |
{% for i in (3..5) %} {{ i }} {% endfor %} {% assign my_limit = 4 %} {% for i in (1..my_limit) %} {{ i }} {% endfor %} <!-- output 3 4 5 --> <!-- output 1 2 3 4 --> |
reversed
1 2 3 4 5 6 |
<!-- if array = [1,2,3,4,5,6] --> {% for item in array reversed %} {{ item }} {% endfor %} <!-- output 6 5 4 3 2 1 --> |
cycle
1 2 3 4 5 6 7 8 9 10 11 |
{% cycle 'one', 'two', 'three' %} {% cycle 'one', 'two', 'three' %} {% cycle 'one', 'two', 'three' %} {% cycle 'one', 'two', 'three' %} <!-- output --> one two three one |
tablerow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<!-- input --> <table> {% tablerow product in collection.products %} {{ product.title }} {% endtablerow %} </table> <!-- output --> <table> <tr class="row1"> <td class="col1"> Cool Shirt </td> <td class="col2"> Alien Poster </td> <td class="col3"> Batman Poster </td> <td class="col4"> Bullseye Shirt </td> <td class="col5"> Another Classic Vinyl </td> <td class="col6"> Awesome Jeans </td> </tr> </table> |
assign
1 2 3 4 5 6 7 8 9 |
<!-- input --> {% assign favorite_food = 'apples' %} My favorite food is {{ favorite_food }}. <!-- output --> My favorite food is apples. |
capture
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!-- input --> {% assign favorite_food = 'pizza' %} {% assign age = 35 %} {% capture about_me %} I am {{ age }} and my favorite food is {{ favorite_food }}. {% endcapture %} {{ about_me }} <!-- output --> I am 35 and my favorite food is pizza. |
increment
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!-- input --> <ul> <li class="item-{% increment counter %}">apples</li> <li class="item-{% increment counter %}">oranges</li> <li class="item-{% increment counter %}">peaches</li> <li class="item-{% increment counter %}">plums</li> </ul> <!-- output --> <ul> <li class="item-0">apples</li> <li class="item-1">oranges</li> <li class="item-2">peaches</li> <li class="item-3">plums</li> </ul> |
decrement
1 2 3 4 5 6 7 8 9 10 11 |
<!-- input --> {% decrement variable %} {% decrement variable %} {% decrement variable %} <!-- output --> -1 -2 -3 |
Objects
Objects are specific to Dynamics 365 Portals. Below are the commonly used objects.
Global Objects
entities – Allows you to load any Dynamics 365 entity by ID.
1 2 3 4 5 6 7 8 9 10 |
{% assign account = entities.account['936DA01F-9ABD-4d9d-80C7-02AF85C822A8'] %} {% if account %} {{ account.name }} ({{ account.statecode.label }}) {% endif %} {% assign entity_logical_name = 'contact' %} {% assign contact = entities[entity_logical_name][request.params.contactid] %} {% if contact %} {{ contact.fullname }} ({{ contact.parentcustomerid.name }}) {% endif %} |
now – A date/time object that refers to the current UTC time, at the time the template is rendered.
page – Refers to the current portal request page. The page object provides access to things like the breadcrumbs for the current page, the title or URL of the current page, and any other attributes or related entities of the underlying Dynamics 365 record.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<ul class="breadcrumb"> {% for crumb in page.breadcrumbs %} <li><a href="{{ crumb.url | escape }}">{{ crumb.title | escape }}</a></li> {% endfor %} <li class="active">{{ page.title | escape }}</li> </ul> <div class="page-header"> <h1>{{ page.title | escape }}</h1> </div> <div class="page-copy"> {{ page.adx_copy }} </div> <div class="list-group"> {% for child in page.children %} <a class="list-group-item" href="{{ child.url | escape }}"> {{ child.title | escape }} </a> {% endfor %} </div> <!-- Page {{ page.id }} was last modified on {{ page.modifiedon }}. --> |
params – A convenient shortcut for request.params.
request – Contains information about the current HTTP request.
1 2 3 |
{% assign id = request.params['id'] %} <a href="{{ request.url | add_query: 'foo', 1 }}">Link</a> |
settings – Allows you to load any Site Setting by name.
1 2 3 4 5 6 7 8 9 10 11 12 |
{{ settings["My Setting"] }} {% assign search_enabled = settings["Search/Enabled"] | boolean %} {% if search_enabled %} Search is enabled. {% endif %} {% assign pagesize = settings['page size'] | integer | default: 10 %} {% if pagesize > 10 %} Page size is greater than 10. {% endif %} |
sitemap – Allows access to the portal site map.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<h1>{{ sitemap.root.title }}</h1> <ul class="breadcrumb"> {% for crumb in sitemap.current.breadcrumbs %} <li><a href="{{ crumb.title }}">{{ crumb.title }}</a></li> {% endfor %} <li class="active">{{ sitemap.current.title }}</li> </ul> {% for child in sitemap.current.children %} <a href="{{ child.url }}">{{ child.title }}</a> {% endfor %} <!-- It's also possible to load a site map node by URL path: --> {% assign node = sitemap["/content/page1/"] %} {% if node %} {% for child in node.children %} <a href="{{ child.url }}">{{ child.title }}</a> {% endfor %} {% endif %} |
sitemarkers – Allows you to load any Site Markers by name.
1 2 3 4 5 6 7 8 |
{{ sitemarkers["Login"].url }} {% assign my_sitemarker = sitemarkers["My Site Marker"] %} {% if my_sitemarker %} <a href="{{ my_sitemarker.url }}">{{ my_sitemarker.adx_name }}</a> {% else %} Site marker "My Site Marker" does not exist. {% endif %} |
snippets – Allows you to load any Content Snippet by name.
user – Refers to the current portal user, allowing access to all attributes of the underlying Dynamics 365 contact record. If no user is signed in, this variable will be null.
weblinks – Allows you to load any Web Link Set by name or ID.
1 2 3 4 5 6 7 |
<!-- input --> {{ website.adx_name }} ({{ website.id }}) <!-- output --> Community Portal (936DA01F-9ABD-4d9d-80C7-02AF85C822A8) |
website – Refers to the portal Website record, allowing access to all attributes of the Dynamics 365 Website (adx_website) record for the portal.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<!-- Load web link set by ID --> {{ weblinks[page.adx_navigation.id].name }} <!-- Load web link set by name --> {% assign nav = weblinks["Primary Navigation"] %} {% if nav %} <h1>{{ nav.title | escape }}</h1> <ul> {% for link in nav.weblinks %} <li> <a href="{{ link.url | escape }}" title="{{ link.tooltip | escape }}"> {% if link.image %} <img src="{{ link.image.url | escape }}" alt="{{ link.image.alternate_text | escape }}" /> {% endif %} {{ link.name | escape }} </a> </li> {% endfor %} </ul> {% endif %} |
Filters
batch
concat
except
first
group_by
join
last
order_by
random
select
shuffle
size
skip
take
then_by
where
current_sort
metafilters
reverse_sort
ceil
divided_by
floor
minus
modulo
plus
round
times
append
capitalize
downcase
escape
newline_to_br
prepend
remove
remove_first
replace
replace_first
split
strip_html
strip_newlines
text_to_html
truncate
truncate_words
upcase
url_escape
xml_escape
boolean
decimal
integer
string
add_query
base
host
path
path_and_query
port
remove_query
scheme
default
file_size
has_role
liquid
Thank you for visiting Dyn365Apps.com.
Follow me on Twitter to get the latest news, tips and tricks and more …
Until next time…