Přeskočit obsah

Dělení šablony na Latte soubory#

Kde která šablona bydlí, jak si systém vybere tu správnou, co do ní přiteče z presenteru a čím se dá skládat.

Tři místa, kde šablony žijí#

Kde Co tam patří Příklad
témawww/themes/frontend/<šablona>/desktop/layouts/ layouty a jejich části: hlavička, patička, obal obsahu @bazaar_layout_one.latte
modulapp/UI/Front/<Modul>/Templates/<Presenter>/ obsah konkrétní stránky Advert/list.latte
komponentaapp/UI/Front/<Modul>/Components/<Komponenta>/Templates/ opakovaně použitelný kus výpisu Default/Style_1.latte

Dělení podle toho, co se mění s vzhledem

V tématu je to, co se s novou šablonou přepisuje (rozvržení stránky). V modulu to, co je dané funkcí (co stránka vypisuje). Proto se výpis inzerátů dá přebarvit bez sáhnutí do PHP a nová šablona nemusí kopírovat obsah stránek.

Jak si systém vybere šablonu stránky#

Hledá dvě cesty v tomhle pořadí a použije první existující:

1. téma:  layouts/modules/<modul>/presenters/<presenter>/<akce>.latte
2. modul: app/UI/Front/<Modul>/Templates/<Presenter>/<akce>.latte

Přesněji: název presenteru se rozseká podle dvojteček, před poslední část se vloží presenters a všechno se převede na malé první písmeno. Z presenteru Front:Bazaar:Advert a akce list tak vznikne layouts/modules/front/bazaar/presenters/advert/list.latte.

Téma může přebít kteroukoli stránku

Stačí položit soubor na cestu 1 a modul se nepoužije. Hodí se, když jedna šablona potřebuje jinak vypsat jeden konkrétní výpis — a nikde se kvůli tomu nevětví PHP.

Když neexistuje ani jedna, uvidíte obě v chybě

Hláška 404 vypíše obě kandidátní cesty v pořadí hledání. Není to seznam „co je špatně“, ale „kam ten soubor můžete dát“.

Uvnitř tématu se navíc zkouší nejdřív varianta zařízení (desktop), pak desktop jako záloha — takže mobilní varianta může mít vlastní soubor, ale nemusí.

Jak si systém vybere layout#

Layout NENÍ v kódu — je uložený u stránky v databázi

Každá stránka má v administraci pole layout a druhé pole pro přihlášeného uživatele. Vybere se takhle:

přihlášený → loggedLayout ?? layout ?? '@layout.latte'
nepřihlášený → layout ?? '@layout.latte'

Hledá se v layouts/ tématu. Když tedy stránka vypadá jinak, než čekáte, nehledejte nejdřív v kódu — podívejte se, jaký layout má nastavený.

@layout.latte je holá kostra, ne výchozí vzhled

Je to nouzový layout Nette. Když se stránka vykreslí bez hlavičky a patičky, skoro jistě má prázdné pole layoutu.

Layouty jsou pojmenované podle modulu a rozvržení — @bazaar_layout_one.latte, @blog_layout_two.latte, @eshop_layout.latte, @homepage.latte. Varianty _one/_two/_three se liší počtem a stranou postranních panelů.

Anatomie layoutu#

<!doctype html>
<html lang="{$language->getAlias()}">
    {import "parts/shared/head.latte"}
    {include head}
    <body id="bazaar-page" n:class="$htmlClass ?? '', 'bazaar-page'">
    {snippet page}
        {var $containerClass='container-lg'}
        <div id="toastContainer" class="toast-container toast-container--top-right"></div>
        {include 'parts/bazaar/header/default.latte' containerClass => $containerClass}
        <main class="site-main">
            <section id="content">
                {snippet flashMessagesWrapperSnippet}
                    {include 'parts/shared/main/flash-messages.latte'}
                {/snippet}
                {include beforeContent}
                {include #content}          {* ← sem se vloží šablona stránky *}
            </section>
        </main>
        {include 'parts/shared/footer.latte' containerClass => $containerClass}
    {/snippet}
        {include 'parts/shared/bottom.latte'}
        {block scripts}{include 'parts/shared/scripts.latte'}{/block}
        {block extraScripts}{/block}
    </body>
</html>
Prvek Význam
{include #content} místo, kam se vloží blok content ze šablony stránky
{snippet page} obal pro překreslování přes AJAX
{block scripts} / {block extraScripts} stránka si sem může přidat vlastní skripty
parts/shared/ části sdílené všemi moduly (hlava, patička, skripty, hlášky)
parts/<modul>/ části jednoho modulu (hlavička e-shopu se liší od inzerce)

{import} versus {include}

{import} jen zpřístupní bloky z jiného souboru (nic nevypíše), {include} je vloží. V hlavičce proto stojí obojí: nejdřív {import "parts/shared/head.latte"}, pak {include head}.

Anatomie stránkové šablony#

{block canonical}
<link rel="canonical" href="{$canonicalUrl}">
{/block}

{define metaTitle}{$categoryTranslation?->getMetaTitle($lang)}{/define}
{define metaDescription}{$categoryTranslation?->getMetaDescription($lang)}{/define}

{block content}
    {var $categoryTranslation = $category?->getTranslation($lang)}
    <h1>{$categoryTranslation?->getName()}</h1>
    {control advertList, $items}
{/block}
Konstrukce K čemu
{block content} povinné — obsah stránky, layout ho vkládá
{define metaTitle} a spol. meta údaje; layout si je vyzvedne, když existují
{block canonical} vlastní kanonická adresa
{control …} vykreslení komponenty

{define} se samo nevypíše

Na rozdíl od {block}. Proto se hodí přesně na meta údaje — layout si je vyžádá jen tam a tehdy, kdy je potřebuje.

Co do šablony přiteče z presenteru#

Tyhle proměnné máte k dispozici, aniž byste je kamkoli předávali:

Proměnná Co je zač
$page entita stránky z databáze
$lang číselné ID jazyka
$language entita jazyka ($language->getAlias() je cs)
$locale locale pro překlady
$title, $showTitle nadpis stránky a příznak, jestli se má vypsat
$content textový obsah stránky z administrace (HTML)
$metaTitle, $metaDescription, $metaKeywords meta údaje ze stránky
$metaData, $ogData doplněná metadata a Open Graph
$cssList seznam CSS souborů navázaných na stránku
$theme entita aktivní šablony
$defaultUrlParams parametry pro sestavování odkazů
$flashes hlášky (viz past níž)

$lang je ID, ne alias

Do getTranslation() a podobných metod patří $lang. Kdo tam pošle 'cs', dostane chybu typu — a kdo si alias vezme z presenteru, sáhne vedle úplně. Alias je $language->getAlias().

Vlastní data si přidá presenter

Cokoli dalšího ($items, $category, $searchHighlights…) přiřazuje presenter do $this->template. Když proměnná v šabloně chybí, chybí přiřazení v presenteru — ne v šabloně.

Komponenty#

Komponenta se vykreslí {control jmeno} nebo {control jmeno, argument}. Šablonu si hledá sama, a to ve třech krocích — první existující vyhrává:

1. Templates/<Šablona>/<Varianta>/Style_1.latte     (přesně pro tuhle šablonu a zařízení)
2. Templates/<Šablona>/default/Style_1.latte        (pro tuhle šablonu, všechna zařízení)
3. Templates/Default/Style_1.latte                  (společná výchozí podoba)

Nová šablona nemusí kopírovat všechny komponenty

Přebere Templates/Default/ a přepíše jen to, co chce jinak. Proto většina komponent v repozitáři má jen výchozí variantu.

Styly a rozpad na části#

Komponenta obvykle nabízí několik podob — Style_1, Style_2, Style_3 (řádek, dlaždice, kompaktní výpis). Volí se argumentem při vykreslení; renderStyle2() se automaticky přeloží na Style_2.latte, takže se kvůli tomu nemusí psát metoda.

Opakující se kusy uvnitř stylů patří do podsložky Elements/:

Templates/Default/
├── Style_1.latte      ← řádkový výpis
├── Style_2.latte      ← dlaždice
├── Style_3.latte      ← kompaktní
└── Elements/
    ├── Actions.latte  ← tlačítka, sdílená všemi styly
    ├── Price.latte
    └── UserBadge.latte

Proč zvlášť a ne trojí kopie

Cena se vypisuje ve všech třech stylech stejně. Kdyby byla vepsaná třikrát, změna formátu by se udělala na dvou místech ze tří — a tenhle druh chyby se hledá dlouho, protože stránka vypadá správně, dokud nepřepnete styl.

Styl se dá zvolit jménem#

Kde má komponenta stylů víc, jde vedle čísla použít i jméno — v šabloně se pak píše {control jmeno:box} místo {control jmeno, 4}. Číslo nikomu neřekne, jak výpis vypadá; jméno ano.

Sekce stránek (frontSystemPageSection)#

Editovatelný blok šablony (System › Stránky › Sekce) se adresuje klíčem, ne ID ani slugem — klíč zapíše kodér natvrdo do šablony, redaktor pod ním sekci najde v administraci:

{control frontSystemPageSection 'homepage-how'}          {* Default: <section class="page-section"> s nadpisem (h2) a obsahem *}
{control frontSystemPageSection:plain 'cta-providers'}    {* jen obsah, bez nadpisu a bez obalu — obal si drží šablona sama *}

Obsah je HTML z administrace a vypisuje se 1:1 — žádné skládání, žádné ořezávání. Uložit ho smí jen administrace (RBAC), nikdy vstup od návštěvníka.

Neexistující nebo neaktivní klíč nevykreslí nic

Žádná chyba, žádný zástupný text — komponenta vrátí prázdno a šablona sekci schová stejně jako jinde :not(:has(...)). Chybějící seed pozná jen vývojář v ladicím logu (kanál page-section), na produkci je ticho.

Dvě sekce na jedné stránce chtějí multicontrol

Dvě volání se stejným jménem komponenty by si mezi sebou přepisovala $uniqueId. Druhé (a další) volání proto jde přes multiplikátor s klíčem sekce jako rozlišovačem:

{multicontrol frontSystemPageSection-homepage-how, 'homepage-how'}
{multicontrol frontSystemPageSection-cta-providers:plain, 'cta-providers'}

Část za pomlčkou (homepage-how) je jen unikátní klíč instance multiplikátoru — klíč sekce se proto do volání píše ještě jednou, za čárkou, jako skutečný argument.

Aktuality na hlavní straně (frontBlogIntroList)#

Vypisuje aktivní, už zveřejněná intra (nejvýš pět). Všechny podoby kreslí markup výpisů .list--* ze základu, takže je šablona stylovala stejnými pravidly jako články.

Styl Volání Kreslí Kdy ho použít
Editorial {control frontBlogIntroList:editorial} .list.list--editorial — mřížka karet s miniaturou vlevo, nad titulkem datum, pod ním řádek textu Klidný pás pod hlavičkou, kde aktuality nemají přebít zbytek strany. Snese i drobné fotky.
Box {control frontBlogIntroList:box} .list.list--box — mřížka karet, obrázek nahoře, pod ním datum, titulek a text Nejvíc „e-shopová“ podoba. Chce u každé aktuality fotku; bez ní zůstane zástupný obrázek.
Karusel {control frontBlogIntroList:carousel}, s počtem sloupců {control frontBlogIntroList:carousel, 4} .list.list--carousel — vodorovný karusel se šipkami a tečkami (JS carousel.js šablony) Když je aktualit hodně a mají se střídat na jednom pruhu. Počet viditelných karet dodá parametr volání (výchozí 3) do --carousel-cols; přepsat ho jde i v CSS sekce.
Hero {control frontBlogIntroList:hero} .list.list--carousel.list--carousel-hero — jedna karta přes celou šířku, fotka přes celou plochu a text v přechodu přes ni, přepíná se vodorovně Vodorovné dvojče svislého karuselu. Šipky stojí svisle na střed u obou okrajů, tečky dole na střed. Chce velkou fotku na šířku.
Svislý karusel {control frontBlogIntroList} .list.list--carousel-vertical — jedna karta přes celou šířku, přepíná se svisle Výchozí podoba. Jedna velká fotka s popiskem místo výpisu.
Bootstrap karusel {control frontBlogIntroList:style2} Bootstrap .carousel s popiskem přes fotku Starší podoba, ponechaná kvůli šablonám, které ji už používají.

Sekce se schová, když v ní není .list-item

Šablony si sekci s aktualitami běžně schovávají pravidlem &:not(:has(.list-item)) { display: none; }, aby prázdný výpis nenechal na straně díru. Styl Editorial ale kreslí .list-item--small (tak to má _editorial.scss), ne .list-item — v takové sekci by zmizel i s obsahem. Podmínku je pak potřeba rozšířit o .list-item--small.

Pasti#

|noescape nikdy do atributu

Hodnota připravená jako bezpečné HTML platí pro tělo elementu. V title= nebo alt= je stejné escapování k ničemu. Do atributů patří holý text.

{snippet} má vlastní rozsah proměnných

Proměnná nastavená {var} mimo snippet uvnitř něj po překreslení nemusí existovat — při AJAXu se vykreslí jen ten kus. Co snippet potřebuje, ať si nastaví uvnitř, nebo ať to přijde z presenteru.

Jméno snippetu musí být literál

{snippet "row-$id"} se nepřeloží. Dynamické části řeší {snippetArea} a n:snippet na prvku cyklu.

AJAX přepíše jen vnitřek snippetu

Atributy elementu, na kterém snippet visí (třídy, data-), zůstanou původní. Když se má měnit i obal, musí být uvnitř.

n:if na poli formuláře zahodí jeho hodnotu

Pole se nevykreslí, takže se ani neodešle — a při uložení se zapíše prázdno. Skrývat se má obal, ne samotné pole.

Cesty v {include} jsou relativní k souboru, ne ke kořenu

Přesun šablony do jiné složky rozbije všechny její vnořené includy.

Hlášky se na frontu mění na plovoucí oznámení

Vypsaná hláška se skriptem přesune do #toastContainer a z původního místa zmizí. Kdo ji hledá v DOM tam, kde ji vypsal, nenajde ji.

V šabloně komponenty není překladač s doménou

Překládá se v presenteru nebo v komponentě v PHP a do šablony jde hotový text.

Kde co hledat#

Chci změnit Soubor
hlavičku, patičku, rozvržení stránky www/themes/<šablona>/desktop/layouts/
co konkrétní stránka vypisuje app/UI/Front/<Modul>/Templates/<Presenter>/<akce>.latte
podobu opakovaného prvku výpisu …/Components/<Komponenta>/Templates/Default/Style_N.latte
barvy, rozestupy, typografii assets/scss/custom/styly šablony
jak má prvek vypadat správně katalog komponent