- Accessing config settings in templates
- Sequential CSS animations with for loop
- Replace string with regexp - using captured groups
- Twig perversion
- Local macro
- Execution order and extends
- Parsing and modifying HTML with retcon plugin
- Multi-page articles
- Using "set" with opening and closing tag
- Adding condition to "for" loop
- Using "else" in "for" loop
- Generating input elements using "tag" function
- Stopping image transforms from upscaling images
- Generating BR HTML elements from newlines
- Using build-in Craft static message translations
- Using Twig code in Craft CMS fields content
- Switch tag - using a single block of code for multiple conditions
- Displaying template structure in rendered HTML code
- Running code specific number of times
This article focuses mostly on usage of Twig as Craft CMS templating system - although non Craft users also might find here some useful bits of knowledge.
Accessing config settings in templates #
Settings in config/general.php
file are available in Twig templates under {{craft.app.config.general}}
. Pretty useful if you want to set some variables with global scope, available in all template files. I recommend however to keep such custom variables within a specific array, like this:
'theme' => [
'google_analitics_id' => '123'
],
Thanks to that, you will not confuse them with built-in Craft config settings. Setting above would be available in twig under {{craft.app.config.general.theme.google_analitics_id}}
.
Sequential CSS animations with for loop #
Let's say you want to apply sequential CSS animations on set of HTML elements - the animation of each element should be a bit delayed, compared to previous element animation. Here's a demonstration of how such animation can look like (source: "Creating smooth sequential animations with Sass" by Glenn McComb):
Delaying animation can be achieved with CSS animation-delay
property. Using Twig loop.index
variable available in for
loop, we can provide value to this property which will be higher with each for
loop iteration:
{% for entry in craft.entries.all() %}
{% set seconds = loop.index/2 %}
<div style="animation-delay: {{seconds}}s;">{{entry.title}}</div>
{% endfor %}
In the example above, each element of animation will be delayed by 0.5 seconds compared to the previous one.
Replace string with regexp - using captured groups #
replace
filter can be used with regular expressions - which is mentioned in Craft documentation.
How about something undocumented? We can used captured groups (also called backreferences) in second argument of replace
- in value that will replaces our regexp string. Let's use example from article about replacing email addresses in text with links:
{% set text = text|replace('/([a-zA-Z0-9_.+-]+)+@([a-zA-Z0-9-]+.[a-zA-Z]+)/', '<a ' ~ 'href="mailto:'~ '\\1@\\2' ~'">'~ '\\1@\\2' ~'</a>') %}
{{text|raw}}
In this example, there are two capture groups - fragment of email address before "@" character and after "@" character. In second argument of replace
which determines what will replace the initial value, we invoke these two capture groups with \\1
and \\2
(@
between two of them is just HTML entity of "@").
Twig perversion #
Twig is intentionally limited with its syntax. It was done to discourage people from using it as something more than templating language. Business logic should live in Craft modules and plugins.
But what if you really want to use this while
loop or ===
- type comparison operator? Twig perversion plugin got you covered. Still, you should consider using a module or plugin for more complicated things.
Local macro #
You can easily use Twig macro in the same file it was defined, without importing it - like this:
{% macro someMacro() %}
{# something... %}
{% endmacro %}
{{ _self.someMacro() }}
It's useful when you want to define local "function" which will not be used outside a specific file.
Execution order and extends #
If you set Twig variable, it will be available below the place it was defined. This can lead to not so obvious results when using {% extends %}
tag.
Lets's think of common setup - entry.twig
file that extends base.twig
file.
{# entry.twig #}
{% extends 'base.twig' %}
{% set class="some-class" %}
{% block body %}
some value...
{% endblock %}
{# base.twig #}
<html>
<head>
<title>Page</title>
</head >
<body class="{{class ?? null}}">
{% block body %}{% endblock %}
</body >
</html>
Craft first accessed entry.twig
file. Then the variable was set and finally, the content was injected into blocks of base.twig
that entry.twig
was extending. Thanks to that, variable set in entry.twig
was available in base.twig
file.
You can read more about the execution order in The Twig processing order in Craft templates article on straight up Craft.
Parsing and modifying HTML with retcon plugin #
In an ideal world, we would have full control over HTML markup. Data queried from the database would contain just variables and string of text which we could wrap with HTML tags ourselves. This is often not the case. Sometimes data that Twig receives already contains HTML - for example when Redactor or other WYSWIG fields are used.
To manipulate such content, we can use Retcon plugin - it provides us with Twig filters for advanced HTML manipulation. Retcon plugin can add HTML attributes to specific tags, wrap them with other tags, extract specific tags content from chunks of HTML and do many other things.
Retcon syntax is symilar to jQuery. For example, this code will make all images with class "some-class" in HTML lazy loaded, by adding loading
attribute with value lazy
to them:
{{ some_html|retconAttr('img.some-class', { loading: 'lazy'}) }}
By comparision, here is jQuery code that accomplishes similar thing:
$('img.some-class').attr({'loading': 'lazy'})
Multi-page articles #
Nobody likes multi-page articles - except maybe advertisers who gain more ad views because visitors are forced to refresh page a few times more. Still, if you must, you can create a multi-page article in Craft CMS by paginating matrix blocks.
Let's think of entry containing matrix field with matrix_field
handle:
{% set query = entry.matrix_field.limit(2) %}
{% paginate query as pageInfo, pageEntries %}
As you can see, paginating matrix blocks works exactly the same as paginating entries. More about pagination can be found in Craft documentation or in my article about ellipsis pagination component.
Using "set" with opening and closing tag #
This is set
tag we all know and love:
{% set something = 'value' %}
What about setting variable to chunk of HTML? Do we need to set it like this?
{% set something = '<ul>
<li><a href="#" id="current">Fruit</a>
<ul>
<li><a href="#">Apples</a></li>
<li><a href="#">Oranges</a></li>
</ul>
</li>
</ul>' %}
Not really - we can use set
with opening and closing tag instead:
{% set something %}
<ul>
<li><a href="#" id="current">Fruit</a>
<ul>
<li><a href="#">Apples</a></li>
<li><a href="#">Oranges</a></li>
</ul>
</li>
</ul>
{% endset %}
Adding condition to "for" loop #
You can attach a condition to for
loop in order to execute code inside for
only when specific conditions are met.
Think of a situation when you want output entries data into template - but only entries with images:
{% for entry in craft.entries.all() %}
{% if entry.pictureField.exists() %}
{{entry.title}}
{% endif %}
{% endfor %}
Same effect can be achived with less verbose code if you use filter filter within for
:
{% for entry in craft.entries.all()|filter(single => single.pictureField.exists()) %}
{{entry.title}}
{% endfor %}
You can also use if
keyword, but this approach is deprecated in favor of using filter
filter.
{% for entry in craft.entries.all() if entry.pictureField.exists() %}
{{entry.title}}
{% endfor %}
Using "else" in "for" loop #
When you loop through entries, you might want to display some message in case there are no entries to loop through - if the object provided to for
is empty.
This can be done by using else
tag inside for
loop:
{% for single in entries.all() %}
<a href="{{single.url}}">{{single.title}}</a>
{% else %}
There are no entries!
{% endfor %}
Generating input elements using "tag" function #
You can generate HTML elements using tag() Twig function. This is especially useful in case of inputs, which often have lots of HTML attributes. Consider such input element:
<input type="text" class="input some-class" id="description" name="inputName" value="{{craft.app.request.getQueryParam['inputName']}}" required>
This doesn't look especially readable. Now, same input rendered using tag()
function:
{{tag('input', {
type: 'text',
class: ['input', 'some-class'],
id: 'description',
name: 'inputName',
value: craft.app.request.getQueryParam['inputName'],
required: true
}) }}
Much better now.
Stopping image transforms from upscaling images #
Let's say that you use such image transform:
{% set transform = {
width: 500,
height: 500,
mode: 'crop',
} %}
<img src="{{imageObject.getUrl(transform)}}">
It's all good unless transformed image has smaller dimensions then width
and height
set in transform setting. In such a situation, image may become pixelated because it will be up-scaled.
To avoid that, you can use min() Twig function:
{% set transform = {
width: min(imageObject.width, 500),
height: min(imageObject.height, 500),
mode: 'crop',
} %}
<img src="{{imageObject.getUrl(transform)}}">
While using such transform settings, image will be scaled down if its dimensions are larger then transform settings, but will retain its original size if it's smaller.
You can also enable such functionality globally, without using min()
function - by setting upscaleImages setting to false
in config/general.php
file.
Generating BR HTML elements from newlines #
If you put some text directly in Twig template, you need to use <br>
HTML elements to put text into new line. Or do you?
nl2br Twig filter can be used to transform newlines (when you press enter during typing) into <br>
elements. Now combine that with {% apply tag %} which can be used to apply Twig filters to large amount of markup.
{% apply nl2br %}
some text
some other text, two lines below
{% apply nl2br %}
Text visible in web browser that will be generated by this code, will have exactly the same newline layout as text in your Twig code.
Using build-in Craft static message translations #
Craft CMS has some built-in static translation files that are mainly used to translate the control panel. These translations can be used in your Twig code - thanks to that, your site will be automatically translated if its language is available as control panel translation.
To use Craft translations, add app
as parameter of translation filter.
<button>{{'Search'|t('app')}}</button>
app
is Craft's static message category. Every Craft plugin can also have its own category, so we might as well use their static message translations in Twig templates as well.
Using Twig code in Craft CMS fields content #
template_from_string() function allows you to parse string of text as Twig code. This means thay you can put Twig syntaxt like variables or functions in content stored in Craft CMS fields. Then you just need to parse this content using template_from_string()
function.
{{template_from_string(entry.someField)}}
You can check out a nice example of this functionality in this tweet by nystudio107.
Switch tag - using a single block of code for multiple conditions #
Switch tag is extremely useful addition to Twig syntax introduced by Craft CMS. If you want to check for multiple values from a single {% case %}
tag, separate the values with or
operator.
{% switch variable %}
{% case "abc" %}
{# do something for variable with value "abc" #}
{% case "xyz" or "123" %}
{# do something for variable with value "xyz" or "123" #}
{% endswitch %}
Displaying template structure in rendered HTML code #
If you can't wrap your head around your complicated template structure when you look at rendered HTML code, Template Comments plugin might be a good solution for you. It adds HTML comments in places where Twig components are included and block tags are extended.
As plugins author said - "This can be especially handy when dealing with OPC (Other People's Code)".
Running code specific number of times #
If you want to run code specific number of times, you can use range function to create an array of specific length and use it in for
loop:
{% for i in 1..5 %}
{# run this 5 times #}
{% endfor %}
Note the 1..5
syntax - it's just shorthand for range
function.