Latte 3.1: Revoluce v atributech, která vám usnadní život

- David Grudl
- Nette Core | 8290
V nové verzi Latte 3.1 přichází sada vylepšení, která se zaměřuje na jednu z nejčastějších činností v šablonách – vypisování HTML atributů. Možná to zní jako drobnost, ale ve skutečnosti jde o zásadní posun v pohodlí (DX) a bezpečnosti vašeho kódu.
Hlavní motivací pro tyto změny byly čtyři klíčové body:
- Větší pohodlí a flexibilita: Aby bylo možné psát
<div foo="{$val}">a Latte si s tím inteligentně poradilo - Kontrola nad existencí atributu: Aby hodnota rozhodla nejen o obsahu, ale i o tom, zda se atribut vůbec vykreslí
- Typová kontrola: Aby vás Latte upozornilo, když se
snažíte vypsat nesmysl (např. pole do
title) - Zpětná kompatibilita: Aby změny byly evoluční, nikoliv destruktivní
Než se pustíme do detailů, pojďme vyřešit tu nejdůležitější otázku, která vás teď asi napadá.
Rozbije mi to šablony?
Ne, nerozbije.
Ačkoliv Latte 3.1 mění logiku vykreslování, bylo navrženo
s maximálním ohledem na zpětnou kompatibilitu. Většina běžných zápisů
se chová stále stejně. Změny se týkají primárně situací, které byly
dříve „šedou zónou“ (například vypsání null nebo
booleanu přímo do atributu), nebo situací, které dříve vygenerovaly
v HTML nesmysl.
Pojďme se podívat, co všechno teď Latte umí.
Boolean atributy: Konec složitých podmínek
HTML má specifickou kategorii atributů, jako jsou checked,
disabled, hidden, readonly nebo
required. U nich nezáleží na hodnotě, ale na jejich
přítomnosti.
Dříve jste museli používat n:attr nebo podmínky
{if}...{/if}. V Latte 3.1 stačí poslat true nebo
false přímo do atributu:
{* Latte 3.1 *}
<input hidden={=true} readonly={=false}>
{* Výsledek: <input hidden> *}
Pokud je hodnota truthy, atribut se vykreslí. Pokud falsey, atribut zmizí. Je to intuitivní a kód je mnohem čitelnější.
Pokud používáte nějakou JS knihovnu, která vyžaduje
přítomnost/nepřítomnost atributu a chcete tohoto chování dosáhnout
i u ostatních atributů (včetně například data-atributů), použijte
filtr |toggle:
<div uk-grid="{$isGrid|toggle}">
{* Je-li truthy: <div uk-grid> *}
{* Je-li falsey: <div> *}
Hodnota null maže atributy
Toto je jedna z nejpříjemnějších změn. Doposud platilo, že pokud byla
proměnná null, vypsala se jako prázdný řetězec
"". To často vedlo k prázdným atributům v HTML
(class="", title=""), které tam jen
zabírají místo.
V Latte 3.1 platí nové univerzální pravidlo: Hodnota
null znamená, že atribut neexistuje.
<div title="{$title}">
{* Pokud je $title === null, vykreslí se: <div> *}
{* Dříve by se vykreslilo: <div title=""> *}
Díky tomu nemusíte obalovat atributy do podmínek. Prostě tam pošlete data a pokud data nejsou, atribut se nevykreslí.
Pole v class
Atribut n:class je milován pro svou schopnost skládat třídy
z polí a podmínek. Nyní tuto superschopnost získává i samotný atribut
class a další atributy očekávající
space-separated obsah.
Do atributu class="..." můžete předat pole (i asociativní
s podmínkami), přesně tak, jak jste zvyklí:
<div class={[
'btn',
'btn-primary',
'active' => $isActive,
'disabled' => $isDisabled
]}>
Pokud $isActive je true a $isDisabled false,
vykreslí se <div class="btn btn-primary active">.
Atribut style
Podobnou flexibilitu získává atribut style. Místo lepení
řetězců můžete předat pole s hodnotami.
<div style={[
background: 'lightblue',
font-size: '16px',
display: $isVisible ? 'block' : 'none'
]}>
{* Vykreslí: <div style="background-color: lightblue; font-size: 16px; display: block"> *}
Data atributy (JSON serializace)
Často třeba potřebujeme do HTML předat konfiguraci pro JavaScript.
Dříve se to řešilo přes json_encode. Nyní stačí předat
pole nebo objekt:
<div data-config={[ theme: 'dark', version: 2 ]}>
{* Vykreslí: <div data-config='{"theme":"dark","version":2}'> *}
Zároveň platí, že true a false se zde
nevypisují jako "1"/"", ale jako řetězce
"true" a "false" (tedy opět jako JSON).
Aria atributy (přístupnost)
Specifikace WAI-ARIA vyžaduje u boolean hodnot textové "true"
a "false". Latte to ví a zařídí to za vás:
<button aria-expanded="{=true}" aria-checked="{=false}">
{* Vykreslí: <button aria-expanded="true" aria-checked="false"> *}
Vylepšený n:attr
Pokud používáte n:attr, všechny tyto novinky budou fungovat
i zde.
Typová kontrola
Jak jsme zmínili na začátku, cílem je bezpečnost a čistota kódu. Proto Latte 3.1 přidává typovou kontrolu.
V předchozích verzích Latte občas „přimhouřilo oko“. Pokud jste
omylem poslali pole do atributu title, vypsalo se
title="Array". Pokud jste poslali true, vypsalo se
"1". To prakticky nikdy není to, co chcete.
V Latte 3.1:
- Běžné atributy (href, src, id…): Očekávají stringable nebo null. Pokud dostanou pole, objekt nebo boolean, vygenerují varování a atribut se nevypíše.
- Boolean atributy (checked…): Očekávají cokoliv.
- Speciální (class, style, data-, aria-): Mají svá vlastní pravidla popsaná výše.
Migrace a kompatibilita
Protože se mění chování null a v případě
data- atributů i true/false, Latte nabízí způsob,
který vám pomůže odhalit místa, kde se výstup změní. Můžete si zapnout
migration warnings:
$latte->setMigrationWarnings();
Když je toto zapnuto, Latte během vykreslování porovnává, jak by atribut vypadal ve verzi 3.0 a jak vypadá ve verzi 3.1. Pokud se výstup liší, vyhodí warning.
Typicky uvidíte warningy v těchto případech:
- Máte
<div class="{$var}">a$varjenull. (Dříveclass="", nyní atribut zmizí). - Máte
<div data-foo="{$bool}">. (Dříve"1"/"", nyní"true"/"false").
Jak vyřešit nalezené rozdíly?
A) Chci původní chování (vynucení stringu)
Pokud chcete, aby se null vypsalo jako prázdný atribut,
stačí použít: (u některých atributů je mezi absencí a prázdným
atributem rozdíl)
<div foo={$foo ?? ''}>
{* Vykreslí null jako foo="" *}
Nebo pokud opravdu potřebujete, aby se boolean vypsal jako
"1"/"", stačí hodnotu přetypovat na string. Tím
Latte řeknete „Vím co dělám, chci text“:
<div data-foo={(string) $foo}>
{* Vykreslí se přesně tak, jako ve starém Latte *}
B) Nové chování je správné (akceptování změny)
Často zjistíte, že nové chování je vlastně lepší (např. že
prázdný class atribut zmizí). Abyste se zbavili migration
warningu, použijte filtr |accept. Tím říkáte: „Zkontroloval
jsem to, změna je v pořádku.“
<div class="{$var|accept}">
{* Použije se nové chování (atribut zmizí při null) a warning se nevypíše *}
Po vypnutí migration warnings všechny |accept se
svých šablon jednoduše smažte.
Latte 3.1 tak přináší moderní standardy do psaní šablon, čistí vygenerované HTML od balastu a hlídá, abyste omylem nevypisovali nesmysly. Přechod je díky migračním nástrojům bezpečný a postupný.
Latte 3.1 je těsně před vydáním stable verze, pojďte jej prosím otestovat!
composer require latte/latte:^3.1.x-dev

- kukulich
- Člen | 63
Mělo by fungovat toto?
<strong>{_'Aby vám šel web jako na drátkách, %1$szapněte si JavaScript%2$s. Jen tak se vyhnete potížím v košíku.'|printf:'<a href="https://www.enable-javascript.com/cz/" target="_blank">','</a>'|noescape}</strong>
Dostávám chybu:
Filter 'noescape' is not defined, did you mean 'escape'?

- David Grudl
- Nette Core | 8290
Už vím, kde je problém. Mělo by stačit změnit v kodu
$node->modifier->escape = true; za
$node->modifier->escape = !$node->modifier->removeFilter('noescape');. https://github.com/…8012efee4d75#…
Dal jsem removeFilter() i do Latte 3.0, takže to bude zpětně kompatibilní.
Jde o to, že dosud mohl uživatel filtr |noescape napsat kdekoliv, i tam, kde se ignoroval, a prošlo to. Teď to chce jakési potvrzení, že tento filtr fakt hraje roli.

- kukulich
- Člen | 63
David Grudl napsal(a):
Už vím, kde je problém. Mělo by stačit změnit v kodu
$node->modifier->escape = true;za$node->modifier->escape = !$node->modifier->removeFilter('noescape');. https://github.com/…8012efee4d75#…Dal jsem removeFilter() i do Latte 3.0, takže to bude zpětně kompatibilní.
Jde o to, že dosud mohl uživatel filtr |noescape napsat kdekoliv, i tam, kde se ignoroval, a prošlo to. Teď to chce jakési potvrzení, že tento filtr fakt hraje roli.
Potvrzuju! Zrovna jsem postupně doiteroval k tomu samému :D Jdu pokračovat v testování.

- kukulich
- Člen | 63
Dal jsem to na produkci a kromě těch warningů aktuálně nepozoruju žádný problém :)
Při nasazování mne teda ještě trochu zlobila Latte\Cache.
Dříve jsme si generovali sami filePath (přepsání metody
getCacheFile(), aby mohlo obsahovat název projektu, jazyk apod.
Vyřešil jsem nakonec přes generateConfigurationSignature(), ale
je to takové méně hezké :)
