Přeskočit obsah
V
Pro vývojáře
Architektura, konvence, jádro systému a bezpečnost
UI vrstva / Komponenty

Komponenty#

Znovupoužitelný kus stránky s vlastní logikou, šablonou a případně vlastními signály. Základ: app/Core/Base/BaseComponent.php.

Kdy komponentu vůbec dělat#

Situace Řešení
Kus markupu se opakuje, ale nemá logiku partial v šabloně (_neco.latte)
Opakuje se i s logikou (načítá data, reaguje na kliknutí) komponenta
Potřebuji překreslit jen část stránky přes AJAX komponenta se {snippet}
Jde o jednu stránku a nikde jinde nechte to v presenteru

Komponenta není složka navíc — je to hranice

Co je uvnitř, může mít vlastní stav a signály a dá se to překreslit samo o sobě. Když to nepotřebujete, partial stačí a je levnější na pochopení.

Struktura složky#

Components/AdvertListComponent/
  AdvertList.php            komponenta
  AdvertListFactory.php     rozhraní továrny
  Traits/                   rozklad logiky (načítání, filtry, akce)
  Templates/
    Default/
      Style_1.latte         výchozí podoba
      Style_2.latte         jiná podoba téhož
      Elements/
        Actions.latte       kus sdílený mezi styly

Části uvnitř Elements/, ne trojí kopie

Co se opakuje ve všech stylech (cena, tlačítka, odznak uživatele), patří do Elements/. Trojí kopie se pozná tak, že se změna udělá na dvou místech ze tří — a vypadá to správně, dokud někdo nepřepne styl.

Jak vzniká#

Komponenta se nevytváří přímo. Napíše se rozhraní továrny, kontejner implementaci vygeneruje a presenter si o ni řekne:

// AdvertListFactory.php
interface AdvertListFactory
{
    public function create(): AdvertList;
}

// v presenteru
public function __construct(private readonly AdvertListFactory $advertListFactory) {}

protected function createComponentAdvertList(): AdvertList
{
    return $this->advertListFactory->create();
}

V šabloně pak {control advertList}, případně {control advertList, $argument}.

Dva traity se stejně pojmenovanou injektovanou vlastností se NESLOUČÍ

Projeví se to chybou při sestavení kontejneru, ne při psaní kódu — takže to vypadá jako chyba konfigurace, i když je to kolize jmen v traitech.

Jak si komponenta hledá šablonu#

Ve třech krocích, první existující vyhrává:

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

Podoba se volí argumentem při vykreslení; renderStyle2() se automaticky přeloží na Style_2.latte, takže se kvůli tomu nemusí psát metoda.

Nová šablona nemusí kopírovat komponenty

Přebere Templates/Default/ a přepíše jen to, co chce jinak.

V šabloně komponenty NENÍ překladač s doménou

Na rozdíl od presenteru. Texty se překládají v PHP a do šablony jdou hotové.

Cesty v {include} jsou relativní k souboru

Přesun šablony rozbije všechny její vnořené includy.

Sdílené modální okno#

Modál se v Latte nekreslí. V layoutu je jednou namountovaný sdílený shell a komponenty do něj jen vloží obsah a otevřou ho.

$this->getModalWindow()          // z ModalAwareTrait
    ->setTitle('Odpověď na inzerát')
    ->setSize('modal-lg')
    ->setContent($html)          // hotové HTML, typicky vyrenderovaný partial
    ->open();
Vlastnost Jak to je
Kde je shell jednou v admin @layout.latte jako {control modalWindow}
Kdo ho plní kterýkoli presenter nebo komponenta pod ním
Stav žádný — titulek ani obsah se neukládají do session, žijí jen v tomhle requestu
Otevření přes AJAX překreslí se jen {snippet modalBody} a do odpovědi se zapíše modalShow s id; zobrazení dělá modals.js
Víc oken naráz nepodporuje se — je to jeden sdílený shell

Proto se do modálu dá vložit i formulář z jiné komponenty

Obsah je obyčejný řetězec HTML. Komponenta si vyrenderuje svůj partial do řetězce a pošle ho do modálu; modál o ní nic neví a nemusí.

Komponenta musí běžet pod presenterem, který modál poskytuje

getModalWindow() to kontroluje a jinak vyhodí výjimku se jménem komponenty. Není to obtěžování — bez shellu v layoutu by se okno nemělo kde zobrazit.

Povinné pole ve skrytém modálu zablokuje odeslání celého formuláře

Prohlížeč odmítne odeslat formulář kvůli poli, které uživatel nevidí, a neřekne proč — stránka jen přestane reagovat na odeslání. Povinnost se proto musí přepínat podle toho, jestli je modál otevřený.

Signály#

Metoda handle*() obsluhuje akci komponenty (smazat, přepnout, načíst další). Volá se běžnou adresou, kterou si komponenta vygeneruje.

Signál je veřejná adresa — musí si ověřit původ požadavku

Bez kontroly ho jde vyvolat z cizí stránky jménem přihlášeného uživatele. Kontrola stejného původu je u handle*() povinná, ne volitelná.

AJAX překreslí jen VNITŘEK snippetu

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

Nová komponenta krok za krokem#

  1. Založit složku podle vzoru výš — komponenta, rozhraní továrny, Templates/Default/.
  2. Logiku rozdělit do traitů, když přeroste jeden soubor (načítání zvlášť od akcí).
  3. Zaregistrovat továrnu v konfiguraci a vytvořit createComponentXxx() v presenteru.
  4. Šablonu psát do Templates/Default/ — ať ji převezme každá šablona.
  5. Texty překládat v PHP, do šablony posílat hotové.
  6. Prokliknout — včetně AJAX cesty, pokud komponenta překresluje.

Oprava sdíleného základu je změna ve všech modulech, které ho používají

Komponenta se společným předkem pro tři moduly znamená, že oprava kvůli jednomu zasáhne všechny tři. Projděte je všechny — ne jen ten, kvůli kterému opravujete.

Hotové komponenty k okoukání#

Komponenta Kde Čím je zajímavá
DataGrid app/UI/Admin/System/Components/ tabulka s filtry, řazením a hromadnými akcemi
Picker tamtéž výběr entity místo <select>
Modální okno tamtéž sdílený shell popsaný výš
Výpis inzerátů app/UI/Front/Bazaar/Components/AdvertRendererComponent/ tři podoby + Elements/

Vizuální podobu komponent frontu ukazuje katalog komponent.