Latte 2.9 – new cool features!

David Grudl
Nette Core | 8229
+
+35
-

{foreach} + {else}

The {foreach} tag can take an optional {else} clause whose text is displayed if the given array is empty:

<ul>
    {foreach $people as $person}
        <li>{$person->name}</li>
    {else}
        <li><em>Sorry, no users in this list</em></li>
    {/foreach}
</ul>

{skipIf}

It is very similar to {continueIf}, but does not increment the counter. So there are no holes in the numbering when you print $iterator->counter and skip some items. And most importantly, the {else} clause will be rendered when you skip all items. Cool!

<ul>
    {foreach $people as $person}
		{skipIf $person->age < 18}
        <li>{$iterator->counter}. {$person->name}</li>
    {else}
        <li><em>Sorry, no adult users in this list</em></li>
    {/foreach}
</ul>

$iterator: counter0 & parent

A two new $iterator properties has been added. Property $iterator->counter0, an 0-indexed counter sibling of $iterator->counter.

And property $iterator->parent which returns the iterator surrounding the current one.

{try} & {else} & {rollback}

Awesome new feature that makes it extremely easy to build robust templates. If an exception occurs while rendering the {try} block, the entire block is skipped and rendering continues after it:

{try}
	<ul>
		{foreach $twitter->loadTweets() as $tweet}
  			<li>{$tweet->text}</li>
		{/foreach}
	</ul>
{/try}

The optional {else} clause is rendered on exception:

{try}
	<ul>
		{foreach $twitter->loadTweets() as $tweet}
  			<li>{$tweet->text}</li>
		{/foreach}
	</ul>
	{else}
	<p>Sorry, unable to load tweets.</p>
{/try}

The {try} block can also be stopped and skipped manually using {rollback}. You do not have to check all the input data in advance, and you can decide during rendering whether it makes sense to render the object.

{try}
<ul>
    {foreach $people as $person}
 		{skipIf $person->age < 18}
       <li>{$person->name}</li>
    {else}
		{rollback}
    {/foreach}
</ul>
{/try}

It is also possible to define own exception handler for i.e logging:

$latte = new Latte\Engine;
$latte->setExceptionHandler(function (\Throwable $e) {
	...
});

{ifchanged}

Check if a value has changed from the last iteration within a loop (for, foreach, while).

If given one or more variables, check whether any variable has changed. For example, the following example prints a heading with the first letter each time it changes while listing names:

{foreach ($names|sort) as $name}
    {ifchanged $name[0]} <h2>{$name[0]}</h2> {/ifchanged}

	<p>{$name}</p>
{/foreach}

If no argument is specified, it checks its own rendered contents against its previous state and only displays the content if it has changed. So in the previous example, we can omit the argument. We can also use n:attribute:

{foreach ($names|sort) as $name}
    <h2 n:ifchanged>{$name[0]}</h2>

	<p>{$name}</p>
{/foreach}

The {ifchanged} tag can also take an optional {else} clause that will be displayed if the value has not changed.

{embed}

Game-changer in the block inheritance. It allows you to include another template’s contents but also allows you to override any block defined inside the included template, like when extending a template.

{embed 'object.latte'}
    {* these blocks defined in object.latte we override right here: *}
    {block title}Hello{/block}

	{block description}
        Some content for the description
    {/block}
{/embed}

object.latte:

<div class="media">
	<h1 class="media-title">{block title}default content{/block}</h1>

	<div class="media-inner">
		{include description}
	</div>
</div>

You can also pass variables, ie {embed template.latte, id: 10, name: $name}, like {include} does.

Local blocks

Blocks are shared between multiple templates if they are part of an inheritance hierarchy ({layout}) or if we import them ({import}). But sometimes in such templates we need to create a block (via {block} or {define}) that should not be available in other templates, nor should it rewrite other blocks if their names match.

The solution is to mark such a block as local using word local before name:

{block local tree}
	This is local block
{/block}

{case 1, 2, 3}

Clause {case} can now contain multiple values:

{switch $status}
{case $status::NEW}<b>new item</b>
{case $status::SOLD, $status::UNKNOWN}<i>not available</i>
{/switch}

Because the {switch} in Latte uses strict comparisons and does not need break, it is now the exact equivalent for PHP 8 match statement.

{include block/file} & {ifset block}

The {include} tag can be used to insert blocks or files. Similarly, the {ifset} can be used to test for the existence of a variable or block. Latte recognizes that it is about block if the argument consists of only alphanumeric characters.

It is now possible to distinguish that it is a block (resp. file) by explicitly specifying the keyword block (resp. file), which is useful, for example, if the name is stored in a variable.

{include block $blockName}
{include file $file}
{ifset block $blockName} ... {/ifset}

{include block from file}

You can now use the tag to insert only one block from a file. It also works for local blocks.

{include block main from template.latte}

Filter and function clamp

Historically the first default custom function in Latte!

Returns value clamped to the inclusive range of min and max.

{=clamp($level, 0, 255)}

{$level|clamp: 0, 255}

Filter |sort

Filter that sorts array:

{foreach ($names|sort) as $name}
	...
{/foreach}

Array sorted in reverse order:

{foreach ($names|sort|reverse) as $name}
	...
{/foreach}

PHP 8 influence: named arguments

You can use PHP8-like syntax for named arguments in tags like {include} or {link}:

before
{include 'file.latte' arg1 => 1, arg2 => 2}
now
{include 'file.latte' arg1: 1, arg2: 2}

before
{link default page => 1}
now
{link default page: 1}

Additionally, if you are using PHP 8, you can use arguments inside all function calls like {=func(a: 10, b: 20)}

Due to future support for named arguments in modifiers, the use of a colon as an argument delimiter is deprecated. Use a comma instead of a colon: {$var|filter: a: 2} ⇒ {$var|filter: a, 2}

PHP 8 influence: operators ?-> and ??->

Latte came up with a optional chaining feature a year ago. PHP 8 now comes up with something similar and it is named nullsafe operator:

$var?->prop?->elem[1]?->call()?->item

But the behavior is not exactly the same. Behavior in PHP 8 triggers a warning if a variable, property or array index does not exist (in the given example, if $var, $var->prop or elem[1] does not exist). It just tests if the value is null.

In contrast, in Latte behavior is similar to null coalescing operator ??. And a non-existent variable, property or array index is not an error.

Latte 2.9 unifies the behavior and the ?-> operator now behaves the same as in PHP to be consistent. But the previous behavior is now implemented by the new nullsafe coalescing operator ??->:

$var??->prop??->elem[1]??->call()

In new Latte there are a lot of small improvements under the hood, especially better error messages.

dsar
Backer | 53
+
0
-

Great :-)

There should be also {cycle} in this new release

Pavel Kravčík
Member | 1196
+
0
-
{else}
❤︎
Rick Strafy
Nette Blogger | 81
+
0
-

Awesome, btw regarding links and include, isn't array syntax better? Because once you add the syntax (also in $presenter->link()) it will be impossible to add a new parameter if needed, and we will not have autocomplete anyway

David Grudl
Nette Core | 8229
+
0
-

To add new parameter is not possible even now. Or how would you suggest to do it?

Rick Strafy
Nette Blogger | 81
+
0
-

Now the syntax in presenter and link generator is link(string $destination, $args = []), so it's possible to add new parameter in the future if needed, but if syntax will be link(string $destination, ...$args) it will be impossible

Last edited by Rick Strafy (2020-11-06 13:01)

David Grudl
Nette Core | 8229
+
0
-

dsar wrote:
There should be also {cycle} in this new release

It's always been enough for me {$iterator->odd ? one : two}, in which cases the cycle is suitable?

David Grudl
Nette Core | 8229
+
0
-

The syntax has always been link(string $destination, ...$args) because of func_get_args()

dsar
Backer | 53
+
0
-

David Grudl wrote:

It's always been enough for me {$iterator->odd ? one : two}, in which cases the cycle is suitable?

No no, I didn't ask for it.
I just saw {cycle} implemented in this github commit and said it should be part of the new 2.9 features.

I first saw it in Django template system in 2008 but never used it (alternated colors in tables were managed by css). However since the implementation cost is low, a way to write short code is always welcome

Last edited by dsar (2020-11-06 16:45)

medhi
Generous Backer | 255
+
0
-

How about a possibility to pass variables into {embed} element too? These could be a nice complement to the blocks (which are for large blocks of code, but variables are better for something like a html tag parameter value.

My particular use case is a modal window, to which I need pass the title, body and the html identificator to make the modal unique. OK, I can do it like this:

{embed 'widget.latte'}
    {block id}myModal{/block}
    {block title}Hello{/block}

	{block description}
        Some content for the description
    {/block}
{/embed}

but wouldn't be this better?

{embed 'modal.latte' id: 'myModal', title: 'Hello'}
	{block description}
        Some content for the description
    {/block}
{/embed}
David Grudl
Nette Core | 8229
+
+2
-

Yes, it is possible too.

dkorpar
Member | 136
+
0
-

Thanks for nice new features!

Last edited by dkorpar (2020-11-20 10:00)

medhi
Generous Backer | 255
+
0
-

Do named arguments work with {control} macro? I tried it, but it returns an error. Is it possible to add it as well? That would be awesome!

David Grudl
Nette Core | 8229
+
+1
-

@medhi It should work in the dev version of Latte & Application on PHP 8

medhi
Generous Backer | 255
+
0
-

The {foreach} tag can take an optional {else} clause whose text is displayed if the given array is empty

Would make any sense, if the {else} was triggered even if the given “array” was null?

I would use it, but not sure if it is considered as a clean code then :)