Part 4: Advanced Liquid Tags
Alright, get your SCUBA gear on, we’re about to dive deep into this Liquid stuff!
Total time for completion: 30 mins
Now that you've got the hang of calling in data and manipulating it with filters, it's time to supercharge your Liquid code with Advanced Liquid Tags. These tags give you true programmatic control over how your data is displayed and how your HTML renders. They don't produce any visible output themselves; instead, they control the logic and flow.
This is where you'll really start to build dynamic websites that conform to your data.
Logic Tags
Time for completion: 8 mins
Control flow tags are all about making decisions in your Liquid code. They let you run specific blocks of code only if certain conditions are met.
The most common conditional block is the if
statement. It evaluates a condition, and if that condition is true, the code inside the if
block is executed.
Here’s an example checking if the user is currently logged in:
{% if request.is_logged == true %}
You are logged in!
{% endif %}
We can extend on this logic by adding an alternative outcome (if the original statement is false), via the else
block. And we can extend this even further with sequential decision-making steps with elsif
blocks.
{% if request.is_logged == true %}
You are logged in!
{% elsif request.request_url.params.backdoor == "let-me-in" %}
Ok, you're in!
{% else %}
Sorry, we can't let you in.
{% endif %}
These extend your if statements beyond that initial if
condition. elsif
allows you to check alternative conditions, and if none of the if
or elsif
conditions are met, the else
block provides a fallback to execute.
In Liquid, elsif
is a single word (dropping the `e`), unlike some other programming languages where else if
is used.
If your output ONLY requires the alternative result, often you can use the unless
block, which is essentially the opposite of the if
block. However, it does not support the elsif
and else
blocks and is intended for simpler scenarios.
{% unless request.is_logged == true %}
Sorry, we can't let you in.
{% endunless %}
Sorry, we can't let you in.
Sometimes you’ll have several output options that are determined from a single data source. Like showing different badges for a member’s current status, from which there could be a wide range of possible statuses.
For this, we can use the case
block, which acts like a switch statement, allowing you to execute different code blocks based on the specific value of a single variable. Inside this block, you use when
statements to define the differences, and optionally, an else
block to provide a fallback.
{% case request.currentmember.status %}
{% when "expert" %}
Stop! You're gonna break somethin'
{% when "intermediate" %}
Nice work!
{% when "beginner" or "noob" %}
Keep trying, you'll get there.
{% else %}
Oh, you don't have any recognisable badges yet!
{% endcase %}
The case
block is ideal in these situations because it’s not relying on a sequential decision process like the if
block.
Check out full examples of these logic blocks in the Liquid Logic article.
Operators
When we define conditions for our if
, elsif
, unless
, and when
statements, we use operators to describe how we want the logic to compare the data sources against each other.
Like saying; "does this variable EQUAL another variable", or "is the member currently logged in AND have a status of 'active'", or "is the member’s date of birth GREATER than a specific date".
To express these comparisons, we use special symbols like ==
, !=
, <
, >
, and
, or
, and more…
<!-- is the member currently logged in AND have a status of 'active' -->
{% if request.is_logged == true and request.currentmember.status == "active" %}
You are logged in AND active!
{% endif %}
We also used one in our if
block example above, where we compared the member’s logged-in state to EQUAL the value of true
by using the ==
symbol.
Check out the full list of operators and what they mean in the Liquid Logic article.
Truthy/Falsy
As you begin to work with Liquid conditional statements, it’s helpful to understand that Liquid ultimately needs to determine if each piece of data presented in the condition is true
or false
. Therefore, individual data types need to have a default state of being true or being false, and so we call this concept of being; truthy or falsy.
We learnt about Data Types in Part 2 of this Learning Liquid course, and so we know we have boolean
data types, which by their very nature are either true
or false
. So that’s easy for our conditional logic to process.
However, the other data types are not so straightforward. Liquid needs to know how to evaluate strings, objects and numbers individually, in their default state. Therefore, all data types are considered truthy (true
) by default, even if they are empty. Unless the data is specifically a false
boolean, or where the special keyword of null
is used.
As a quick example, here we have a variable that is declared (so, it exists), whether it’s been given a value or not is irrelevant. Yet, placed in a conditional statement without any other comparisons, it must be determined as true
(because it exists) and will therefore render the code block.
{% assign name = "Alex" %}
{% if name %}
My name is {{ name }}.
{% endif %}
My name is .
This behaviour may at first be unexpected without prior understanding of this concept.
Variable Tags
Time for completion: 6 mins
We’ve started to mention variables, in a basic context, in Parts 3 and 4 of this course, but we haven’t yet fully introduced you to their important and powerful role in Liquid.
In their simplest sense, variables are flexible, waterproof containers for storing all manner of Liquid goodness - whether it’s a single value, a complete object, or a snippet of code.
They provide an easy, efficient and maintainable way to store, transport and manipulate data throughout the flow of your Liquid scope.
A common use case may simply be to create shorthand code for a common piece of data that may need to be used in multiple locations. For example, you may want to render the member’s name in various spots throughout your site. Accessing the request
object repeatably to call this data, complete with a filter, like this; {{request.currentmember.firstname | titlecase}}
may get a bit tiring and make a simple task seem more complex and overwhelming.
So, instead, at the start of our document/code we can assign this data to a variable like so:
{% assign firstName = request.currentmember.firstname | titlecase %}
Then, simply use the assigned variable keyword where you want to render the member's name (complete with filter applied):
{{ firstName }}
Alexander
Notice how I described this process of assigning data to the variable keyword? Well, that’s exactly why the main variable in Liquid is called an assign
tag!
You can use any combination of letters to name your variable, we've used firstName
here, but you could just as easily use memberFNAME
, or whatever makes sense in your code.
You cannot, however, use numbers or other characters (except underscores between letters), or names alreay used as keywords in Liquid. You'll also want to be carefull not to create naming conflicts by using the same variable name in different places for different things. So, a descriptive naming convention is always good to use (eg: membersFirstNameFiltered
) instead of just firstname
.
The use of uppercase and lowercase is purely for readability. In most case, Liquid is not case sensitive. So, membersFirstNameFiltered
will render just the same as membersFIRSTNAMEfiltered
.
But this is not the end of the variable story. The whole point of a
Hello. Please welcome {{firstName}}.
{% if this.nickname != '' %}
{% assign firstName = this.nickname | titlecase %}
{% endif %}
{{firstName}} is a leader in digital technology and online user experiences.
Hello. Please welcome Alexander.
Alex is a leader in digital technology and online user experiences.
In that example, if we have a nickname stored somewhere in the CMS for the member, we can switch out their full, more formal name, for a more casual name when the time is appropriate. Yet, all the while continuing to use the very same variable keyword - which will work whether they have a nickname or not.
But wait, there’s more!
Utilising variables like this can also protect our data from corruption through mixed Liquid scopes.
Let’s say we want to use the Page name throughout our document, which is easy enough using the direct Liquid source data of {{this.Name}}
.
However, this particular Liquid tag is very common throughout almost all module types, so it’s possible that the use of this common Liquid tag could be inadvertently placed within the scope of another module, and therefore render the name of that module’s item instead of the intended Page name.
Assigning the Page name to a custom variable keyword in this case will protect against this, often unexpected, situation.
Now, assign
tags are a great way to create variables. But they are not the only type of variable.
The one problem with the assign
tag is that it’s a single-line tag, meaning the data assigned to it (at least as a raw string) can’t be added with linebreaks and it can be challenging to include more complex code syntax and other mixed content types.
Objects and data already existing within a Liquid collection are an exception to this.
To solve this problem, we have a second Liquid tag for capturing a multi-line, or more complex, data source into a variable, and you guessed it, it’s called a capture
tag.
Here’s how it works:
{% capture myParagraphs %}
<h3>Welcome</h3>
<p class="lead">Thank you for joining us today!</p>
<p>Please enjoy the canapes.</p>
{% endcapture %}
{{ myParagraphs }}
<h3>Welcome</h3>
<p class="lead">Thank you for joining us today!</p>
<p>Please enjoy the canapes.</p>
There are a few other types of variables in Liquid as well, but I’ll let you explore those in the Liquid Variables documentation now that you have a better understanding of how these liquid containers work and what they do.
Iteration Tags
Time for completion: 10 mins
If we remember from Part 2 of this Learning Liquid course, where we looked at different types of data, specifically Arrays, this object type contains repeating sets of data - think of a list of blog posts, or a collection of products, or even just a comma-separated list of names.
All these arrays can be iterated over, in order to render the datasets to a templated layout or to otherwise process the information via Liquid. This iteration method forms an important part of how we create dynamic, data-driven websites.
To do this in Liquid, we have the for
tag, which allows us to “loop” through each item in the array sequentially, and execute a code block each time, repeating the same kinds of functions programmatically on each item in the array.
When we use WebinOne component tags to call data from the CMS (eg: listing recent Blog Posts), the CMS runs these kinds of loops for us, spitting out the Layout we’ve created for each item. But we can build these loops ourselves as well. Perhaps we want to bypass the standard CMS way of doing things for more control and customisation, or we are working with array data from another source.
But let’s start with the basic setup of a for
loop, using a very simple array of names which we have in our code as John,Sam,Alex,Matthew
.
In its current state, this is just a string of characters, so Liquid doesn’t recognise it as a true array. Let’s convert it into an array using a variable and our Liquid filters, and this will parse the string into the Liquid scope so we can then access it via Liquid:
{% assign myArray = "John,Sam,Alex,Matthew" | split: "," %}
Great, now we’ve created a true array that can be accessed using the myArray
keyword.
Now let’s set up the for
loop where we want to display the names in a bullet list:
<ul>
{% for name in myArray %}
<li>{{name}}</li>
{% endfor %}
</ul>
• John
• Sam
• Alex
• Matthew
The name
keyword is a variable we can set to access the specific item as each loop is processed. This variable will be overridden on each iteration. We can name this like we would any other variable in Liquid.
You’ll also notice that the opening and closing list elements (ul
) are outside of our for
block, because this part of the HTML we don’t want to repeat - it’s hardcoded as a wrapper around the list items (li
) which are our repeating elements.
We can build very complex templates/layouts for our data this way, without having to manually hardcode repeating markup. And, of course, the data we call from the CMS for things like Blog Posts, Products, etc., are going to contain a lot more content and sometimes even more arrays - so we can run loops within loops as well.
The data called from the CMS typically won’t be a simple string like in the example above. Instead, the output will come in the form of a Liquid object, or “collection”, and this will be presented in JSON format, already parsed and ready to access via Liquid.
So, let’s look at an example using a list of blog posts.
We’ll use the ‘Blog Post’ component to call our items, but instead of using the standard Layout method, we’ll push the data to a collection (which basically means the data will become accessible anywhere in our document below the component tag, rather than just within the standard Layout scope).
{% source: "Blog Post", layout: "", collectionVariable: "myBlogPosts", component type: "module" %}
{
"Pagination": {
"CurrentPage": 1,
"ItemsPerPage": 10,
"NumberOfPages": 1,
"TotalItemsCount": 3
},
"Items": [
{
"Id": 1234,
"Name": "Sample Post 1",
"Url": "/blog/sample-post-1",
"ItemTags": [
"Tag 1",
"Tag 2"
],
...
},
{
"Id": 1234,
"Name": "Sample Post 2",
"Url": "/blog/sample-post-2",
"ItemTags": [
"Tag 1"
],
...
},
{
"Id": 1234,
"Name": "Sample Post 3",
"Url": "/blog/sample-post-3",
"ItemTags": [
"Tag 3",
"Tag 4"
],
...
}
]
}
This won't render anything to the page since we’ve omitted the Layout, but it will parse the JSON output into the Liquid scope, ready for us to use.
Remembering our data types from Part 2 of this course, we can see there is an array listing all our posts (defined by the square brackets [ … ]
), which is addressed by the keyword “items”.
So now we have an addressable path to our items of myBlogPosts.items
. We can use this in our for
block to instruct Liquid what we want to iterate:
<ul>
{% for post in myBlogPosts.items %}
<li>
<a href="{{post['url']}}">{{post['name']}}</a>
</li>
{% endfor %}
</ul>
• Sample Post 1
• Sample Post 2
• Sample Post 3
Many items, such as Blog Posts, contain a large number of properties (points of data), and as we can see in the sample JSON above, can even contain other arrays. Well, we can loop through those within the parent loop as well:
<ul>
{% for post in myBlogPosts.items %}
<li>
<a href="{{post['url']}}">{{post['name']}}</a><br>
{% for tag in post.ItemTags %}
{{tag}}{% unless forloop.last %}, {% endunless %}
{% endfor %}
</li>
{% endfor %}
</ul>
• Sample Post 1
Tag 1, Tag 2
• Sample Post 2
Tag 1
• Sample Post 3
Tag 3, Tag 4
A lot of other options are available to us when using the for
block, which can be further explored in the Liquid Iteration documentation.
There is also a special iteration tag called cycle
, which can render text in sequence based on the number of times the tag has been previously parsed in the Liquid scope (its “iteration index”).
This can be handy for sequentially numbering or labelling elements throughout otherwise static markup.
A fun example:
Fool me {% cycle "once", "twice", "three times" %}, shame on you.<br>
Fool me {% cycle "once", "twice", "three times" %}, shame on me.<br>
Fool me {% cycle "once", "twice", "three times" %}, shame on both of us.
Fool me once, shame on you.
Fool me twice, shame on me.
Fool me three times, shame on both of us.
Another interesting and useful way to use cycle
is within your for
loops to create alternating layout grids:
{% for post in myBlogPosts.items %}
<div class="row">
<div class="col-{% cycle "4", "8", "8", "4" %}">
...
</div>
<div class="col-{% cycle "4", "8", "8", "4" %}">
...
</div>
</div>
{% endfor %}
<div class="row">
<div class="col-4">
...
</div>
<div class="col-8">
...
</div>
</div>
<div class="row">
<div class="col-8">
...
</div>
<div class="col-4">
...
</div>
</div>
<div class="row">
<div class="col-4">
...
</div>
<div class="col-8">
...
</div>
</div>
Iterating over data is a fundamental concept for dynamic content output, and its uses go far beyond those noted in this article. So, have fun doing loop-de-loops while pushing your content in new and interesting ways.
Comment Tag
Time for completion: 2 mins
The comment
tag is used to omit content and Liquid parsing from your final rendered document. Anything wrapped within this Liquid tag will be ignored by Liquid.
{% comment %}
My voice is not heard :(
{% endcomment %}
I will not be ignored!
I will not be ignored!
This is great for leaving notes in your code or temporarily disabling sections without deleting them.
It has the benefit of being a server-side exclusion, meaning the commented content isn't exposed in the public source code of your webpage, unlike regular HTML, JS and CSS commenting.
Raw Tag
Time for completion: 2 mins
The raw
tag prevents any Liquid syntax wrapped within it from parsing. This is useful when you want to display Liquid syntax literally on your page (like in documentation or tutorials), or when you're using other front-end templating languages (like Handlebars or Mustache) that use the same character syntax, which might cause programming conflicts.
{% raw %}
My name is {{myName}}. I hope you are well today.
{% endraw %}
My name is {{myName}}. I hope you are well today.
Here, we don’t want Liquid trying to render the name from a variable. Instead, we want the double curly brackets to render in our HTML so that a JavaScript library can parse it on the client-side.
Congratulation!
You’ve made it to the end of the Learning Liquid course.
I hope you now have a good understanding of how you can start using Liquid in WebinOne and create amazing, dynamic websites.
Armed with this knowledge, approaching the full documentation should be a lot less daunting and make a lot more sense - helping you achieve even greater projects for you and your clients.
Where to now?
The best way to learn is by doing, so sign up (if you haven't already), or spin up a trial site and see what you can create. But for pushing your Liquid knowledge even further, try the below links:
Liquid Objects: Special WebinOne outputs
Liquid Components: Output WebinOne content
...and the rest of the documentation. Enjoy!