Latte 2.9 – new cool features!
- David Grudl
- Nette Core | 8218
{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.
- Rick Strafy
- Nette Blogger | 81
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 | 8218
To add new parameter is not possible even now. Or how would you suggest to do it?
- Rick Strafy
- Nette Blogger | 81
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 | 8218
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 | 8218
The syntax has always been link(string $destination, ...$args)
because of func_get_args()
- dsar
- Backer | 53
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
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 | 8218
@medhi It should work in the dev version of Latte & Application on PHP 8