Zobrazovaní formulářů

Jak jsme si již ukázali v první kapitole, tak definovaný formulář se ve view zobrazí takto: use Jet\MVC_View;
use 
Jet\Form;
use 
Jet\UI;

/**
 * @var MVC_View $this
 * @var Form $form
 */

$form $this->getRaw('registration_form');
?>

<?=$form->start()?>
    <?=$form->message()?>

    <?=$form->field('name')?>
    <?=$form->field('birthdate')?>

    <?=UI::button_save()?>

<?=$form->end()?>

Jak je patrné pro zobrazení formulářového prvku (začátku formuláře, zprávy formuláře, jednotlivých polí a konce formuláře) se zobrazí jednoduše tak, že se převedou na řetězec. Tedy v konečném důsledku je to vlastně jednoduché, ale samozřejmě tato ukázka použití je pouze naprostý základ. Především je důležité si ukázat, jak celý systém zobrazení formulářů funguje a v neposlední řadě co vše umožňuje.

Než se do toho pustíme, tak si ujasněme několik důležitých věcí:

  • Jet (myšleno knihovna Jet) vlastně sám o sobě nic nezobrazuje.
    Pouze nabízí rozhraní (z hlediska paradigmatu MVC je to vlastně Model) na jehož základě se vše zobrazí již ve view skriptech.
  • Všechny view skripty jsou součástí aplikačního prostoru.
    To znamená, že zobrazení je plně pod vaší kontrolou. Součástí ukázkové aplikace jsou samozřejmě připravené view skripty, které můžete, ale nemusíte použít. Tedy i to že ukázková aplikace využívá framework Bootstrap není nic definitivního a pevně daného.
  • Systém zobrazení formulářů umí mnohem víc věcí než je v ukázce.
    To znamená, že vše můžete plně ovlivňovat, měnit, předávat HTML tagům atributy, mít zcela unikátní view skripty pro určité formuláře a tak dále. Vše si ukážeme.
  • I přes neomezenou flexibilitu zobrazení zůstává unifikované rozhraní.
    Jednoduše řečeno to znamená, že view můžete úplně "překopat", ale formuláře se definují stále stejně. Tedy máte separovanou logiku od zobrazení (i systém formulářů je zkrátka navržen dle MVC paradigmatu).
  • Smyslem tohoto systému je unifikovat podobu formulářů v aplikaci a zároveň si usnadnit a sjednotit práci.

Renderer - co to je?

Pojem renderer se bude v táto kapitolo vyskytovat často. Aby ne, je to vlastně podstata celého systému zobrazení formulářů. Ale abychom si ujasnili co to je, tak se ještě jednou vraťme k příkladu zobrazení formulář.

Zobrazení formulářového pole je provedeno takto: <?=$form->field('name')?>

Ovšem to je ve skutečnosti pouze zkrácený zápis. Skutečné volání (a v praxi nepožívaný plný zápis) má tuto podobu: <?=$form->field('name')->renderer()->render()?>

Renderer je instance jednoho z potomků třídy Jet\Form_Renderer, kterou si u sebe drží každý prvek definující formulář - ať je to formulář samotný (pro zobrazení začátku a konce formuláře), nebo jednotlivá vstupní pole.

Každý prvek formuláře má svůj renderer určité specifické konkrétní třídy. Tedy ne přímo třídy Jet\Form_Rendererer, ale jejích potomků přímo určených pro konkrétní účel. To znamená, že například obecné vstupní pole Input má jako renderer samotného vstupního pole instanci třídy Jet\Form_Renderer_Field_Input_Common, políčko Select používá jako renderer Jet\Form_Renderer_Field_Input_Select a tak dále.

Jak si později ukážeme detailněji, tak například renderer formulářového pole se ve skutečnosti skládá z dalších dílčích rendererů (zvlášť je renderer pro řádek pole, zvlášť pro popis pole, zvlášť pro vstupní prvek a tak dále).

Pro teď je důležité, že render je právě onen zmíněný model podle kterého následně view daný prvek zobrazí. Tedy pomocí rendereru se nejen že daný element zobrazujeme, ale můžeme jej dále nastavovat.

Ukažme si to rovnou prakticky. Opět se vraťme k našemu příkladu. Dejme tomu, že chceme, aby se vstupní pro zadání jména po té co do něj uživatel "klikne" (nebo se dostane pomocí klávesnice) vymazalo i když v něm nějaká hodnota byla. Provede se to takto: <?php 
$form
->field('name')
    ->
input()
        ->
addJsAction('onfocus'"this.value='';")
?>

<?=$form->field('name')?>

Pozor! Opět se jedná o zkrácený zápis, který se v praxi používá. Ale pro úplnost a lepší pochopitelnost si ukažme plný zápis. <?php 
$form
->field('name')
    ->
renderer()
        ->
input()
            ->
addJsAction('onfocus'"this.value='';")
?>

<?=$form->field('name')?>

Tedy co jsme provedli:

  • Z definice formulářového prvku jsme si vzali instanci rendereru.
  • Z rendereru jsme si vzali dílčí renderer, který se stará o samotný vstupný element (HTML tag input).
  • Renderu jsme předali obsluhu události onfocus.
  • Ve finále vše zobrazili. Zobrazení proběhne tak, že renderer předá všechny informace view skriptům a tak se z modelu stane konkrétní HTML.

Rovnou si ukážeme další praktické použití. Nejen že potřebujeme obsluhu události onfocus. Z nějakého důvodu potřebujeme danému input tagu předat ještě vlastní CSS třídu a aby to nebylo málo, tak i data-* atribut (konkrétně data-my-attribute="Value"): <?php 
$form
->field('name')
    ->
input()
        ->
addJsAction('onfocus'"this.value='';")
        ->
addCustomCssClass('my-css-class')
        ->
setDataAttribute('my-attribute''Value')
?>

<?=$form->field('name')?>

Co vše renderer umí se dozvíte v referencích ke třídě Jet\Form_Renderer a v neposlední řadě ve všem ostatním třídám Jet\Form_Renderer_* se specifickým určením (speciální renderery jako například ten pro WYSIWYG editor mají další specifické metody).

V souvislosti s renderery je důležité si ještě říct, že sice dědí od společné základní třídy Jet\Form_Renderer, ale existují dva základní druhy:

  • Nepárový Jet\Form_Renderer_Single
    Určený pro nepárové elementy jako je popis pole, chybové hlášení, vstupní element a tak dále (viz dále).
  • Párový Jet\Form_Renderer_Pair
    Určený například pro tag formuláře samotného (formulář má začátek a konec), řádek formulářového pole a kontejner vstupního elementu (viz dále).

Renderer a view

Řekli jsme si, že renderer je model, podle kterého view zobrazí daný element. Teď si pojďme ukázat jak přesně se to dělá.

Předpokládám, že již víte jak view v Jet funguje. Tedy úplné detaily zde vynechám.

Jak víte, tak aby view fungovalo, tak potřebuje tyto základní věci:

  • Kořenový adresář kde má hledat view skripty.
  • Název view skriptu který se má zobrazovat.
  • A samozřejmě nějaká data podle kterých view zobrazí co zobrazit má.
Tak si to teď pojďme rozebrat :-)

Kořenový adresář view skriptů

Určitě jste si všimli, že v příkladech se zatím nikde žádný view adresář nenastavoval. Systém formulářů ve výchozím případě počítá s tím, že tento adresář je nastaven systémovou konfigurací, konkrétně například takto: //... ... ...
public static function initMVC_Router $router ): void
{
    
//... ... ...
    
SysConf_Jet_Form::setDefaultViewsDir$router->getBase()->getViewsPath() . 'form/' );
    
//... ... ...
}
//... ... ...

Záměrně jsem jako ukázku použil kousek inicializátoru MVC báze, kde právě je vhodné místo pro provedení tohoto nastavení.

Ale pochopitelně ne vždy je nutné použít celý MVC systém a kořenový adresář lze prostě a jednoduše nastavit takto: SysConf_Jet_Form::setDefaultViewsDir$some_dir.'/views/form/' );

Podstatné je, že pokud nechcete pro každý formulář nastavovat kořenovou cestu view extra, tak je nutné toto nastavení před použitím formulářů provést.

View adresáře v ukázkové aplikaci

Pravděpodobně jste si již všimli, že v ukázkové aplikaci jsou hned čtyři adresáře s view skripty formulářů:

  • ~/_installer/views/form/
    Formuláře mikroaplikace instalátor
  • ~/_tools/studio/application/views/form/
    Formuláře nástroje Jet Studio
  • ~/application/bases/admin/views/form/
    Formuláře administrace
  • ~/application/bases/web/views/form
    Formuláře ukázkového webu

Možná vás napadne: "Proč to tak je? Však by stačil jeden společný adresář, ne?" A máte pravdu. Stačil. Ovšem z dobrých důvodů je to takto. Ten první důvod je demonstrace technických možností. Druhý a důležitější důvod je tzv. dobrý zvyk. Každá z těch částí ukázkové aplikace je vlastně unikátní a svébytná. A je velice pravděpodobné, že pro web si budete potřebovat udělat zobrazení formulářů úplně jinak, třeba čistě za použití vašeho CSS a tak dále. Ale zároveň budete chtít ponechat administraci tak jak je a hádám, že si nebudete chtít rozbít Jet Studio ;-). Proto jsou adresáře rovnou oddělené a je možné si view rovnou upravovat dle vašich potřeb s tím, že vše ostatní zůstane nedotčené. Pokud by ukázková aplikace měla jen jeden adresář, tak by to bylo neintuitivní a vlastně by to neodpovídalo reálnému světu - stejně by bylo nutné view takto separovat.

Extra view adresáře pro určitý formulář, nebo prvek

A co když potřebujete pro jeden jediný hodně specifický formulář vlastní sadu view skriptů? Samozřejmě to možné je. A nejen pro celý formulář, ale pro libovolný prvek. Ukažme si to prakticky.

Pokud chcete pro celý formulář (to znamená i úplně všechny jeho prvky) nastavit extra view adresář, tak view adresář jednoduše nastavíte na renderu formuláře: $form->renderer()->setViewDir$my_view_dir );

Pokud chcete extra view adresář pro jedno konkrétní celé formulářové pole (pro všechny prvky ze kterých se pole skládá - viz dále), pak to provedete takto: $form->field('name')->renderer()->setViewDir$my_view_dir );

A pokud chcete z nějakého důvodu extra view adresář třeba pro samotné vstupní pole, tak není problém: $form->field('name')->renderer()->input()->setViewDir$my_view_dir );

To samé platí pro popisek pole, nápovědu a tak dále: $form->field('name')->renderer()->label()->setViewDir$my_view_dir );
$form->field('name')->renderer()->help()->setViewDir$my_view_dir );
Nebo zkráceně takto: $form->field('name')->label()->setViewDir$my_view_dir );
$form->field('name')->help()->setViewDir$my_view_dir );

Název view skriptu

Princip je vlastně takřka stejný jako u adresářů. S tím rozdílem, že každý element již má předdefinované názvy view skriptů a na rozdíl od adresáře tyto samozřejmě není nutné natavovat.

Ovšem i zde platí, že je použita systémová konfigurace, konkrétně SysConf_Jet_Form_DefaultViews. Jak konkrétně je použita a jaké jsou výchozí názvy view skriptů je popsáno u každé třídy rendereru. Důležité je, že máte možnost výchozí nastavení změnit úplně stejně jako určujete view adresář.

A pochopitelně máte možnost měnit nejen výchozí nastavení, ale názvy view skriptů konkrétních elementů. Dejme tomu, že pro určité použití potřebujete úplně jiný WYSIWYG editor než ten, který je integrován ve výchozím view skript. Není problém udělat to takto: $form->field('article_text')->input()->setViewScript('field/input/wysiwyg-another');

Toto můžete provést s libovolným elementem.

Instance view

Pro to aby view fungovalo, tak potřebuje instanci Jet\MVC_View. Tu si každý renderer vytváří a drží a je vám přístupná: $form->field('name')->renderer()->getView()

A pochopitelně je to běžné view jako každé jiné. Máte tedy možnost do view předávat další informace, když budete potřebovat. Lze udělat například toto: $form->field('name')
    ->
renderer()
        ->
getView()
            ->
setVar('extra_help'Tr::_('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.'));

Příklad view skriptu

Již jsme si ukázali jak nastavit renderer - tedy model. Teď si ukažme příklad jednoduchého view skriptu. Konkrétně jde o formulářové pole pro zadání datumu. (Pozor! Pouze vstupní pole samotné, ne popisku, nápověda a tak dále - viz dále), jedná se o skript view skript field/input/date: <?php
use Jet\MVC_View;
use 
Jet\Form_Renderer_Field_Input;


/**
 * @var MVC_View $this
 * @var Form_Renderer_Field_Input $r
 */
$r $this->getRaw'renderer' );
$field $r->getField();

$r->setInputType('date');

if( !
$r->getBaseCssClasses() ) {
    
$r->setBaseCssClass'form-control' );
}

if( 
$field->getLastErrorCode() ) {
    
$r->addCustomCssClass'is-invalid' );
}

?>
<input <?=$r->renderTagAttributes()?>/>

Pochopitelně zde není prostor rozebírat každé view - tedy prosím view skripty si prozkoumejte dle libosti. Ovšem na této ukázce je patrná funkce view skriptů a další účel rendereru. View skript dělá to, že renderer donastaví. Tedy nastaví základní CSS třídy a případné další atributy budoucího HTML tagu. A když je vše nastaveno, tak již zbývá daný HTML tak prostě zobrazit, ale opět s pomocí rendereru, který umí na základě nastavení vygenerovat atributy tagu (a věřte mi, že skládat atributy "ručně" je pořádná otrava). Tedy renderer je nápomocen až do poslední chvíle.

Ovšem view samozřejmě může být implementováno úplně jinak - čistě dle vašich potřeb. Každopádně renderer je vždy ve view k dispozici a je případně jen na vás jak s tím naložíte.

A protože view nejsou pouze o vstupních polích, ale třeba i o popiscích formulářových polí, tak si ukažme ještě jeden příklad a to právě view popisku pole - view skript field/label: <?php

use Jet\MVC_View;
use 
Jet\Form_Renderer_Field_Label;

/**
 * @var MVC_View $this
 * @var Form_Renderer_Field_Label $r
 */
$r $this->getRaw'renderer' );

if( !
$r->getBaseCssClasses() ) {
    
$r->setBaseCssClass'col-form-label' );
}

$r->setWidthCssClassesCreator(function( $size$width ) {
    return 
'col-' $size '-' $width;
});

$r->setCustomTagAttribute'for'$r->getField()->getId() );

?>
<label <?=$r->renderTagAttributes()?>><?php
    
    
if( $r->getField()->getIsRequired() ):
        
?><em class="form-required">*</em><?php
    
endif;
    
    echo 
$r->getField()->getLabel();
    
?></label>

Elementy ze kterých se skládá formulářový prvek

Ještě než se vrhnete do důkladného průzkumu konkrétních rendererů, tak si ukažme ještě jednu věc, kterou v předchozím textu zmiňuji. Již byl zmíněno, že renderer má své dílčí renderery že například formulářové pole se skládá z několika elementů. Pro pořádek si to ukažme názorně. Běžný formulářový prvek má tuto strukturu, respektive skládá se z těchto částí:

Začátek řádku (row - start)
Začátek kontejneru (container - start)

Nápověda - help
Chybové hlášení - error
Konec kontejneru (container - end)
Konec řádku (row - end)

(Uspořádání / umístění elementů je pouze ilustrativní - v praxi může být zcela odlišné)

Hlavní renderer formulářového prvku má pak za úkol připravit rederery dílčích částí a z nich pak poskládat celý element.

K hlavnímu rendereru se přistupuje takto: $form->field('name')->renderer()

Pro lepší představu si ukažme view skript tohoto hlavního rendereru. Tedy view skript field: <?php

use Jet\MVC_View;
use 
Jet\Form_Renderer_Field;

/**
 * @var MVC_View $this
 * @var Form_Renderer_Field $r
 */
$r $this->getRaw'renderer' );

echo
    
$r->row()->start() .
        
$r->label() .
        
$r->container()->start() .
            
$r->input() .
            
$r->help() .
            
$r->error() .
        
$r->container()->end() .
    
$r->row()->end();
Z toho je zřejmé jak hlavní renderer skládá celý výsledek z dílčích částí.

A už nám zbývá si zrekapitulovat jak přistupovat k rendererům dílčích částí. Právě to je důležité, když těmto dílčím částem chcete předávat parametry, nebo je dokonce samostatně zobrazovat (ano, i to je možné - například zobrazit pouze vstupní pole bez všeho okolo):

  • Řádek (row)
    Zkráceně: $form->field('name')->row() Plný zápis: $form->field('name')->renderer()->row()
  • Popisek pole (label)
    Zkráceně: $form->field('name')->label() Plný zápis: $form->field('name')->renderer()->label()
  • Kontejner (container)
    Zkráceně: $form->field('name')->container() Plný zápis: $form->field('name')->renderer()->container()
  • Vstupní prvek (input)
    Zkráceně: $form->field('name')->input() Plný zápis: $form->field('name')->renderer()->input()
  • Nápověda prvek (help)
    Zkráceně: $form->field('name')->help() Plný zápis: $form->field('name')->renderer()->help()
  • Chybové hlášení - error
    Zkráceně: $form->field('name')->error() Plný zápis: $form->field('name')->renderer()->error()
Předchozí kapitola
Zachytávání, validace a předání dat
Další kapitola
Jet\Form_Renderer