Prejsť na obsah

Šablónový systém

DotApp vykresľuje HTML fasádou Renderer a malou sadou direktív {{ … }}. Šablóny sú PHP súbory, ktoré vlastní modul. Kontroléry odovzdávajú dáta cez setViewVar(). Neexistuje samostatný runtime šablónového jazyka: direktívy sa skompilujú do PHP a výsledok vyhodnotí sandbox.

Živé stránky na tomto webe sa riadia tými istými pravidlami. Hello World: /helloworld. Bezpečné formuláre: /documentation/examples/run/forms2. Podrobný návod je Sprievodca krok za krokom.

1.1 Čo je šablónový systém?

Každý modul drží prezentáciu v app/modules/{Module}/views/. View je celá stránka alebo obal. Layout je znovupoužiteľný fragment (hlavička, riadok zoznamu, nadpis). Kontrolér vyberie súbory, priradí premenné a vráti HTML reťazec z renderView() alebo renderLayout().

Hodnotu vypíšte cez {{ var: $title }}. Je to jediná podporovaná syntax výpisu. {{ $title }} nie je direktíva a premennú nevypíše.

1.2 Súbory a priečinky

Typ Cesta Vyberá sa cez
View app/modules/{Module}/views/{name}.view.php setView('name')
Layout app/modules/{Module}/views/layouts/{path}.layout.php setLayout('path') alebo {{ layout:path }}
Iný modul Rovnaká štruktúra v danom module setView('Shop:home'), {{ layout: Shop:partials/header }}
Základný layout app/parts/views/layouts/ iba {{ baselayout:name }}
Assets app/modules/{Module}/assets/... /assets/modules/{Module}/...

{{ layout:partials/header }} načíta views/layouts/partials/header.layout.php. Adresár layouts je už koreňom — nepíšte layout:layouts/header. Vnorené include sa zastavia pri hĺbke 20.

1.3 Fasáda Renderer

Renderer vytvoríte cez Renderer::new(), nasmerujete ho na modul a nastavíte view. Pomenované inštancie (Renderer::new('docs')) sa znovu používajú ako singletony. Pre stránku začnite čerstvý reťazec cez Renderer::new().


use Dotsystems\App\Parts\Logger;
use Dotsystems\App\Parts\Renderer;
use Dotsystems\App\Parts\Response;

$html = Renderer::new()
    ->module('HelloWorld')
    ->setView('hello')
    ->setViewVar('title', 'Hello World')
    ->setViewVar('message', 'DotApp 2.0 is running.')
    ->renderView();

if ($html === '') {
    Logger::use()->error('HelloWorld view produced empty output');
    return new Response(500, 'Template error');
}
return $html;
        

Neexistuje množné setViewVars() ani verejné getView() / getLayout(). Jednu hodnotu prečítate cez getViewVar('title') (chýbajúci kľúč vráti "") alebo celý balík cez getViewVars().

1.4 Chýbajúce súbory zlyhajú potichu

Chýbajúci view alebo layout nevyhodí výnimku. Renderer zaloguje varovanie a vráti prázdny reťazec. Prázdna stránka zvyčajne znamená zlé meno súboru, nesprávny modul, alebo že sa setView() nikdy nespustilo. Vždy odovzdajte záložné meno a otestujte návratovú hodnotu:


$html = Renderer::new()
    ->module('Shop')
    ->setView('home', 'fallback/empty')
    ->setLayout('catalog/list', 'catalog/empty')
    ->setViewVar('title', $title)
    ->renderView();
        

Druhý argument setView() a setLayout() je záložný súbor, nie obalový layout. loadViewStatic() nekontroluje, či súbor existuje — uprednostnite setView() / loadView().

2.1 Prvé vykreslenie

Živý modul Hello World je minimálny vzor: jeden view, dve premenné, žiadny vnorený layout.

app/modules/HelloWorld/views/hello.view.php:


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>{{ var: $title }}</title>
  <link rel="stylesheet" href="/assets/modules/HelloWorld/css/hello.css" />
</head>
<body>
  <main>
    <h1>{{ var: $title }}</h1>
    <p>{{ var: $message }}</p>
  </main>
</body>
</html>
        

Zavolajte setView('hello') pred akýmkoľvek setViewVar(). Neskoršia zmena view zahodí predchádzajúci balík premenných.

2.2 Premenné view verzus premenné layoutu

renderView() vyhodnotí skompilovanú šablónu s balíkom premenných view. Hodnoty nastavené iba cez setLayoutVar() sa v tom výstupe neobjavia. Keď stránku vykresľujete cez renderView(), každú hodnotu, ktorú view aj jeho vložené layouty potrebujú, odovzdajte cez setViewVar().

setLayoutVar() s renderLayout() použite, keď vykresľujete súbor layoutu samostatne (táto dokumentačná stránka to robí pre časti článkov).

2.3 Obalový view plus obsahový layout

Stránke dajte obalový view, ktorý obsahuje {{ content }}, a vnútorné HTML umiestnite do layoutu vybraného cez setLayout(). Include zvnútra view naďalej používajú {{ layout:… }}.


return Renderer::new()
    ->module('Shop')
    ->setView('home')
    ->setLayout('content/welcome')
    ->setViewVar('title', 'Shop')
    ->setViewVar('items', $items)
    ->renderView();
        

View views/home.view.php:


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>{{ var: $title }}</title>
  <link rel="stylesheet" href="/assets/modules/Shop/css/page.css" />
</head>
<body>
  
  <main>{{ content }}</main>
  <script src="/assets/dotapp/dotapp.js"></script>
  <script src="/assets/modules/Shop/js/page.js"></script>
</body>
</html>
        

Layout views/layouts/content/welcome.layout.php:


<h1>{{_ "Welcome" }}</h1>
{{ foreach $items as $item }}
  <p>{{ var: $item['title'] }}</p>
{{ /foreach }}
        

View môže byť aj kompletný HTML dokument bez setLayout() a bez {{ content }}. Hello World, demá Examples aj demo Users to tak robia.

2.4 Súbory naprieč modulmi

Cestu predponujte názvom druhého modulu a dvojbodkou:


Renderer::new()->module('Checkout')->setView('Shop:home')->renderView();
        


        

Súbor naďalej žije pod views/ (alebo views/layouts/ pri layoutoch) daného modulu.

2.5 Ďalšie metódy vykreslenia

Metóda Použitie
renderView() Bežná stránka. Vyhodnocuje sa s premennými view.
renderLayout() Jeden súbor layoutu. Vyhodnocuje sa s premennými layoutu.
renderCode($code, $vars) Skompiluje a vyhodnotí HTML reťazec, ktorý už máte.
loadView($name) Prečíta súbor view ako text. Chýbajúci súbor: "".

3.1 Výpis hodnôt


{{ var: $title }}
{{ var: $user['name'] }}
{{ var:$title }}
        

Kompilátor to premení na echo. V direktíve nie je automatické escapovanie. Dáta požiadavky z $request->data() sú už chránené proti XSS. Ak z PHP posielate surové HTML, escapujte ho v kontroléri cez htmlspecialchars() pred setViewVar(), alebo ho nechajte chránené a volajte DotApp::DotApp()->unprotect($html) iba vtedy, keď zámerne vykresľujete značkovanie.

{{ var: }} neprijíma výrazy, ??, -> ani volania funkcií. Hodnotu pripravte v kontroléri.

3.2 Preklad


{{_ "Login" }}
{{_ var: $message }}
        

Okolo zdrojového reťazca použite dvojité úvodzovky. Chýbajúci kľúč vypíše pôvodný text. JSON súbory načítajte z modulu a locale nastavte v PHP:


use Dotsystems\App\Parts\Translator;

Translator::loadLocaleFile('Shop:sk_sk.json', 'sk_sk');
Translator::setLocale('sk_sk');
echo Translator::trans('Hello, {{ arg0 }}', $name);
        

Súbory žijú v app/modules/{Module}/translations/{locale}.json. Placeholdery sú {{ arg0 }}, {{ arg1 }}. Neexistuje pluralizácia ani reťazec záložných locale.

3.3 Podmienky


{{ if isset($user) }}
  <p>Signed in</p>
{{ elseif $guest === true }}
  <p>Guest</p>
{{ else }}
  <p>Unknown</p>
{{ /if }}
        

Za {{ dajte medzeru pred if, elseif, else a /if. Uzatváracia značka je {{ /if }}, nie {{ endif }}.

3.4 Cykly


{{ foreach $items as $item }}
  <li>{{ var: $item['title'] }}</li>
{{ /foreach }}

{{ while $i < 5 }}
  <p>{{ var: $i }}</p>
{{ /while }}
        

Uzatváracie značky sú {{ /foreach }} a {{ /while }}. Počítadlá inkrementujte v kontroléri alebo malým PHP blokom v šablóne. Biznis logiku nechajte v kontroléri.

3.5 Include a slot obsahu





{{ content }}
        

Značky layout sú include. Nemajú uzatváraciu značku. {{ content }} sa vyplní iba vtedy, keď ste zavolali setLayout() a potom renderView().

3.6 Formuláre a šifrovanie


<fo-rm method="POST" id="saveForm">
  <input type="text" name="title" />
  {{ formName(saveItem) }}
  <button type="submit">Save</button>
</fo-rm>
<script src="/assets/dotapp/dotapp.js"></script>
        
  • {{ formName(saveItem) }} musí byť medzi <fo-rm> (alebo <form>) a zodpovedajúcou uzatváracou značkou. Značka potrebuje atribút method. Mimo tohto páru renderer token nezmení.
  • Uprednostnite <fo-rm>. dotapp.js ho prevedie na skutočný formulár a odošle s CRC. PHP stále spúšťa $request->crcCheck() a potom $request->form(…).
  • Keď sa formulár odosiela na aktuálnu stránku, vynechajte action a ako posledný argument form() odovzdajte $request->getPath().
  • {{ CSRF }} vypíše obyčajný token. Pre aplikačné formuláre použite formName.

Hodnoty v šablóne šifrujte s vyhradeným extra kľúčom na každé pole:


<option value="{{ enc(Shop.user.id): $u['id'] }}">{{ var: $u['name'] }}</option>
{{ enc: $secret }}
{{ enc(mykey): "literal" }}
        

{{ enc: "literal" }} šifruje počas kompilácie šablóny. {{ enc(key): $var }} šifruje pri behu stránky. Dešifrujte tým istým extra kľúčom: Crypto::decrypt($cipher, 'Shop.user.id'). Zlyhanie je === false. Úplný návod k formulárom: Bezpečné formuláre.

3.7 Bloky a superbloky

Jadro má dva rôzne nástroje „blok“. Ľahko sa pomýlia. Návod s kompletným zoznamom kariet Shop: How to use blocks and superblocks.

Nástroj Tag Kde žije
Pomenovaný blok {{ block:name(args) }}…{{ /block:name }} Handler zaregistrujete cez Renderer::new()->addBlock()
Superblok {{ privateblock:name }}…{{ /privateblock }} Ten istý súbor: kompilátor vytvorí $block['name']
V jadre nie je {{ sblock:… }}, {{ article }} Len starý CMS modul. Renderer ich nechá ako text.

Priečinok views/layouts/sblocks/ nie je špeciálny formát. Sú to obyčajné layouty. Načítate ich cez setLayout('sblocks/…')->setLayoutVar('fndata', $data)->renderLayout().

3.7.1 Pomenované bloky — addBlock + {{ block: }}

Handler zaregistrujte raz v initialize($dotApp). Názov v addBlock musí sedieť s tagom. Meno môže obsahovať písmená, číslice, _, . a - (teda shop.cards je v poriadku). Signatúra handlera je function (string $inner, array $args, array $vars): string. Argumenty v tagu sa delia čiarkou bez trim — píšte shop.cards(partials/item-cards), nie medzery za čiarkami, alebo v handleri volajte trim(). Funguje aj reťazec na controller: addBlock('youtube', 'Shop:Embed@youtube!').


use Dotsystems\App\Parts\Renderer;

public function initialize($dotApp)
{
    Renderer::new()->addBlock('alert', function (string $inner, array $args): string {
        $kind = htmlspecialchars(trim((string) ($args[0] ?? 'info')), ENT_QUOTES, 'UTF-8');
        return '<div class="alert-' . $kind . '">' . $inner . '</div>';
    });
}
        

{{ blockerror:alert }} Undefined callable function ! {{ /blockerror:alert }}
        

Tretí argument je balík premenných aktuálneho renderu: view vars pri renderView(), layout vars pri renderLayout(). Registrácia ide na customRenderer jadra, takže jeden addBlock v Shop stačí pre každý ďalší render v tom requeste.

3.7.2 Superbloky — privateblock + $block['name']

Superblok je HTML fragment uložený ako PHP objekt. Nie je to PHP funkcia s tým menom. Meno je kľúč:

  • v tagu: {{ privateblock:card }}
  • v PHP: $block['card']

Kompilátor vyreže fragment zo súboru pred kompiláciou {{ var: }} / {{ if }} a vloží $block['card'] = new PrivateBlock(…). Klony plníte cez ->set($key, $value). Kľúče musia sedieť s {{ var: $key }} vo fragmente. set() odmietne callable a mená funkcií zakázaných sandboxom. set() reťazte; potom klon skompilujte.

->html() vráti PHP priradenia plus fragment s prefixovanými menami premenných (aby sa dva klony nezrazili). V tom reťazci ostávajú {{ var: }}. Hlavný layout už tieto direktívy skompiloval, preto klon vypíšte cez renderCode(), inak v HTML uvidíte surové {{ var: }}:


<?php $block['card'] = new \Dotsystems\App\Parts\PrivateBlock(base64_decode("CiAgJmx0O2FydGljbGUgY2xhc3M9InNob3AtY2FyZCImZ3Q7CiAgICAmbHQ7aDImZ3Q7e3sgdmFyOiAkdGl0bGUgfX0mbHQ7L2gyJmd0OwogICAgJmx0O3AmZ3Q7e3sgdmFyOiAkcGVyZXggfX0mbHQ7L3AmZ3Q7CiAgJmx0Oy9hcnRpY2xlJmd0Owo=")); ?>
        

use Dotsystems\App\Parts\Renderer;

$paint = function ($fndata) use ($block) {
    foreach ($fndata as $item) {
        echo Renderer::new()->renderCode(
            $block['card']
                ->set('title', $item['title'])
                ->set('perex', $item['perex'])
                ->html()
        );
    }
};
$paint($fndata);
        

Zoznam odovzdajte ako layout premennú. renderLayout() vidí len layout vars:


$html = Renderer::new()
    ->module('Shop')
    ->setLayout('partials/item-cards')
    ->setLayoutVar('fndata', $items)
    ->renderLayout();
        

Súbor: app/modules/Shop/views/layouts/partials/item-cards.layout.php. Pomenované funkcie v layoute (function create_menu($menu)) sú tiež v poriadku — každý render ide do náhodného namespace, takže druhý request neskončí na „Cannot redeclare“. Keď potrebujete $block, uprednostnite uzáver $paint = function (…) use ($block).

Jednoduchý zoznam môže ostať na {{ foreach $items as $item }} bez superbloku. Superblok použite, keď je riadok znovupoužiteľná karta s rôznymi poľami (starý CMS vzor „najnovšie články“).

3.7.3 Zloženie: addBlock vykreslí layout so superblokom

Toto je náhrada starého CMS dispatchera v jadre. View len pomenuje blok. PHP v initialize() načíta dáta a vykreslí layout, v ktorom je superblok.


use Dotsystems\App\Parts\Renderer;

Renderer::new()->addBlock('shop.cards', function (string $inner, array $args, array $vars): string {
    $layout = trim((string) ($args[0] ?? 'partials/item-cards'));
    $items = $vars['items'] ?? [];
    return Renderer::new()
        ->module('Shop')
        ->setLayout($layout)
        ->setLayoutVar('fndata', $items)
        ->useCache(false)
        ->renderLayout();
});
        

{{ blockerror:shop.cards }} Undefined callable function ! {{ /blockerror:shop.cards }}
        

View s týmto tagom musí dostať items cez setViewVar('items', $rows) (lebo renderView() je cesta, ktorá tu kompiluje {{ block: }}). Vnútorné HTML tagu handler ignoruje, kým nepoužije $inner.

3.7.4 Čo jadro nerobí

{{ sblock:dotcms.menu(mainmenu,/sblocks/sblock.menu) }} a {{ article }}2{{ /article }} boli CMS parser, nie Renderer. V frameworku nie je tabuľka erp_dotcms_smart_blocks ani block_concater_add. Tie tagy prepíšte na {{ block:… }} plus addBlock. Staré súbory sblocks/*.layout.php môžete nechať ako layouty, ak už čakajú na $fndata.

Natívne PHP v šablóne je povolené, ale sandbox ticho odstráni eval, exec, system, file_*, curl_*, mail, header, extract, call_user_func* a podobné. Ak volanie nič neurobí, je to preto. Dopyty a auth dávajte do kontroléra.

3.8 Nepodporovaná syntax

Nepíšte Píšte
{{ $title }} {{ var: $title }}
{{ endif }} / {{ endforeach }} {{ /if }} / {{ /foreach }}
{{ include 'x' }} v PHP view {{ layout:x }}
extends / section / yield renderView() + {{ content }} alebo {{ layout: }}
{{ $x ?? 'd' }} Hodnotu pripravte v kontroléri

{{ include path }} existuje iba v voliteľnom JavaScript šablónovom engine (časť 8), nikdy vo PHP views.

Značky input-group ako {{ InputKeys('register_form') }} a {{ input:text … }} pochádzajú z Input.php, nie z jadra tabuľky direktív. Pre bežné HTML formuláre uprednostnite formName.

Atribúty Bridge ({{ dotbridge:on(click)="…" }}) sú zdokumentované na DotBridge.

4. Assets

CSS, JS a obrázky ukladajte pod app/modules/{Module}/assets/. Framework ich servíruje ako:


/assets/modules/{Module}/{path}
        

<link rel="stylesheet" href="/assets/modules/Shop/css/page.css" />
<script src="/assets/dotapp/dotapp.js"></script>
<script src="/assets/modules/Shop/js/page.js"></script>
        

Stránky, ktoré odosielajú <fo-rm>, volajú $dotapp().load() alebo používajú Bridge, musia najprv načítať /assets/dotapp/dotapp.js. Táto URL je trasa frameworku. Vkladá kľúče relácie. Na verejnej stránke nelinkujte surový súbor z app/parts/js/.

Voliteľné CSS pomocníky: prepareCss() zreťazí a minifikuje do cache súboru a vypíše značku <link>. removeUnusedCss(true) odstráni selektory, ktoré sa v HTML nevyskytujú ako class="…" — odstráni aj triedy pridané neskôr JavaScriptom. Nechajte to vypnuté, kým neoveríte výstup. Vestavaný helper na cache-busting nie je; ak ho potrebujete, pridajte ?v= sami. Nepovoľujte cache HTML stránky cez useCache(true).

5. Vlastné renderery

Vlastný renderer je callable, ktorý dostane skompilované HTML (a ak je prítomný, balík premenných) a vráti HTML. Zaregistrujte ho raz v initialize($dotApp). Potom beží pri každom vykreslení.


use Dotsystems\App\Parts\Renderer;

Renderer::new()->addRenderer('shop.money', function (string $code, array $vars = []): string {
    $amount = number_format((float) ($vars['price'] ?? 0), 2);
    return str_replace('{{ money }}', $amount, $code);
});
        

Renderer::add($name, $callable) je tá istá registrácia na fasáde. getRenderer($name) vráti callable alebo false. renderWith($name, $code) spustí jeden renderer na reťazci.

Vstavané renderery registrované frameworkom zahŕňajú dotapp.block, reactive a input_form_*. Tento dokumentačný modul registruje Docs.code.replace, aby sa vzorky vo vnútri <pre><code> escapovali.

6. Pipeline, sandbox, ladenie

Typický beh renderView():

  1. Vyrieši vnorené {{ layout: }} / {{ baselayout: }} (hĺbka ≤ 20).
  2. Extrahuje privateblock a spustí vlastné renderery.
  3. Vloží HTML z setLayout() do {{ content }}.
  4. Skompiluje var / if / foreach / while / enc / preklad.
  5. Nahradí {{ CSRF }} a {{ formName() }}.
  6. Spracuje značky Bridge.
  7. Vyhodnotí v RenderingIsolator.

Zlyhanie kompilácie alebo eval vypíše ERROR WHILE EVAL: … do odpovede. Pre skutočné čísla riadkov:


define('__RENDER_TO_FILE__', true);
        

Skompilované PHP sa zapíše pod app/runtime/generator/rendering_*.php, načíta a potom zmaže.

7. Translator API

Metóda Výsledok
trans($text, ...$args) / t() Preložený reťazec, alebo pôvodný text, ak kľúč chýba
setLocale($locale) / getLocale() Aktuálne locale (predvolené en_us)
loadLocaleFile('Module:file.json', $locale) Chýbajúci súbor sa preskočí bez výnimky
has($key, $locale = null) bool — použite na zistenie chýbajúceho kľúča
all($locale = null) Všetky kľúče pre dané locale

Produktové texty, ktoré človek vidí (tlačidlá, prázdne stavy, názvy oprávnení), musia znieť ako hotové UI, nie ako odpoveď na prompt. Kľúče sú zdrojový anglický (alebo zdrojový) reťazec, pri vyhľadávaní sa prevádzajú na malé písmená.

8. Klientske šablóny

Voliteľný skript /assets/dotapp/dotapp.template.js pridáva v prehliadači $dotapp('#box').template('path/to/view', { items: […] }). Rozumie {{ var: }}, {{ if }}, {{ foreach }}, {{ block: }} a {{ include partials/header }}. Predvolená základná cesta je /app/views/. Načítajte ho po dotapp.js. Ak sa doplnok ešte načítava, počkajte na udalosť dotapp-template-ready.

PHP views nikdy nezískajú include. Serverové HTML ostáva na Renderer + {{ layout: }}. JS engine použite, keď aktualizujete zoznam cez $dotapp().load() bez plného vykreslenia stránky. Jadrová reaktivita (variable, databind, computed) je v príklade reaktivity. Vlastné widgety $dotapp().fn sú v príklade JS knižnice. Živé demo zoznamu je /documentation/examples/run/lists.

9. Kontrolný zoznam

  • Súbor view: {name}.view.php. Súbor layoutu: views/layouts/{path}.layout.php.
  • Renderer::new()->module('Name')->setView('name') pred setViewVar().
  • renderView() === '' berte ako chybu.
  • Vypisujte cez {{ var: $x }}. Vetvy uzatvárajte cez {{ /if }} / {{ /foreach }}.
  • Pomenované bloky: Renderer::new()->addBlock() a potom {{ block:name(args) }}. Superbloky: {{ privateblock:name }} plus $block['name']->set()->html() skompilované cez renderCode().
  • V jadre nie je {{ sblock: }}. Zoznam pri renderLayout() posielajte cez setLayoutVar('fndata', $rows).
  • Pri volaní renderView() odovzdajte každú hodnotu cez setViewVar().
  • {{ formName(handler) }} vložte do <fo-rm method="…"> a načítajte /assets/dotapp/dotapp.js.
  • Dopyty, autentifikáciu a zápisy nechajte v kontroléri. Sandbox šablóny odstráni nebezpečné PHP.