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

David Grudl
Nette Core | 8290
+
+16
-

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:

  1. Větší pohodlí a flexibilita: Aby bylo možné psát <div foo="{$val}"> a Latte si s tím inteligentně poradilo
  2. Kontrola nad existencí atributu: Aby hodnota rozhodla nejen o obsahu, ale i o tom, zda se atribut vůbec vykreslí
  3. Typová kontrola: Aby vás Latte upozornilo, když se snažíte vypsat nesmysl (např. pole do title)
  4. 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:

  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.
  2. Boolean atributy (checked…): Očekávají cokoliv.
  3. 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 $var je null. (Dříve class="", 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
David Grudl
Nette Core | 8290
+
+1
-
kukulich
Člen | 63
+
0
-

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
+
0
-

@kukulich hmm… Je to vlastní Latte tag {_} nebo nativní?

kukulich
Člen | 63
+
0
-

David Grudl napsal(a):

@kukulich hmm… Je to vlastní Latte tag {_} nebo nativní?

Vlastní. Ok, jdu se podívat, jak je implementováno nativní :)

David Grudl
Nette Core | 8290
+
0
-

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
+
0
-

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
+
0
-

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é :)