Mapování tříd na formuláře
Pokud jste si již prošli kapitoli o DataModel a konfiguračním systému, tak jistě neuniklo vaší pozornosti, že tyto entity umí přímo generovat formuláře a je to vlastnost v Jet hojně využívaná, která dokáže ušetřit opravdu hodně práce. Mám dobrou zprávu. Toto generování formulářů není záležitostí pouze DataModelu a konfigurace, ale na formulář lze namapovat vlastně libovolnou třídu.
V kapitole o zachytávání, validaci a předání dat byl ukázka hypotetického registračního formuláře a jeho napojení na třídu. S dovětkem, že v praxi se to dá udělat jinak. Tak si teď úplně stejný příklad, ovšem s využitím definic formuláře, ukažme ještě jednou:
use Jet\Form;
use Jet\Form_Field;
use Jet\Form_Definition;
use Jet\Form_Definition_Trait;
use Jet\Form_Definition_Interface;
class MyUser implements Form_Definition_Interface {
use Form_Definition_Trait;
#[Form_Definition(
type: Form_Field::TYPE_INPUT,
label: 'Username',
is_required: true,
error_messages: [
Form_Field::ERROR_CODE_EMPTY => 'Please enter username'
]
)]
protected string $username = '';
protected ?Form $reg_form = null;
public function getUsername(): string
{
return $this->username;
}
public function setUsername( string $username ): void
{
$this->username = $username;
}
public function getRegForm() : Form
{
if(!$this->reg_form) {
$this->reg_form = $this->createForm('reg_form');
}
return $this->reg_form;
}
}
Tím je vyřešeno vše. Definice formuláře, zachytávání hodnot, validace a také předání zachycených a validních hodnot instanci třídy. O dost méně psaní a práce, že? Rozdíl je ještě markantnější, když potřebujeme další a další vlastnosti a k nim formulářová pole. V tomto případě jméno, e-mail, heslo a tak dále. Už stačí pouze přidávat vlastnosti třídy a k nim definice formulářového pole (a samozřejmě gettery a settery, dobré moderní IDE je vygeneruje automaticky a je to zvyk, který se vyplatí).
Náležitosti třídy mapované na formulář
Aby třída mohla být automaticky namapována na formulář (tedy aby mohla formulář definovat a automaticky vytvářet a napoji se na něj), tak musí splňovat dvě jednoduché věci:
- Implementovat rozhraní Jet\Form_Definition_Interface
- Použít trait Jet\Form_Definition_Trait - který implementuje výše uvedené rozhraní.
Pokud vytváříte DataModel, nebo definici konfigurace, tak ale máte splněno. Možnost mapovat formuláře je u takových tříd automatická.
Definice formulářového pole - atributy a jejich parametry
Určitě jste nepřehlédli v Jetu běžné použití atributů, konkrétně Form_Definition. Použití atributů zde již vysvětlovat nebudu a pojďme se rovnou kouknout na seznam parametrů, které definice musí (a může) mít.
Parametr | Typ | Povinný | Význam |
---|---|---|---|
type | string | ano | Základní parametr určující typ generovaného formulářového pole. Jedná se o řetězec a je vhodné používat konstanty Form_Field::TYPE_*, nebo vaše konstanty pro vaše vlastní typy formulářových polí. Nepoužívá se zde název třídy daného typu pole, ale identifikátor typu. Pro vytváření instancí příslušných tříd je použita továrna. |
is_required | bool | ne | Indikuje zda formulářové pole bude / nebude označeno jako povinné. |
label | bool | ne | Popisek formulářového pole. |
help_text | string | ne | Text nápovědy formulářového pole. |
help_text | asociované pole | ne | Data nápovědy formulářového pole. |
error_messages | asociované pole | podmíněně ano | Texty chybových hlášení k chybovým kódům formulářového pole. Parametr je povinný pokud pole bude taková hlášení potřebovat. Tedy pokud je například pole povinné, tak určitě bude požadováno chybové hlášení pro kód Form_Field::ERROR_CODE_EMPTY. |
default_value_getter_name | string | ne | Název metody, která při vytváření formulářového pole vrátí hodnotu, která bude použita jako výchozí pro toto pole. Běžně se jako výchozí hodnota bere aktuální hodnota vlastnosti objektu ke kterému formulářové pole náleží. A běžně není nutné metodu definovat. Ovšem může se stát, že hodnota vlastnosti není vhodná jako výchozí hodnota formulářového pole. Může se například jednat o pole objektů, nebo jiná komplexní data. V takovém případě je nutné definovat metodu, která formulářovému poli předá použitelná data. |
setter_name | string | ne | Pomocí tohoto parametru lze určit metodu třídy, kterou bude volat zachytávač formulářového pole. Běžné se Jet pokusí zjistit název příslušného setteru sám (stačí dodržet princip jak je název tvořen všude v Jetu a v ukázkové aplikaci) a pokud příslušnou metodu nenajde, tak předává hodnotu přímo do vlastnosti objektu. Ovšem pokud chcete použít specifickou metodu, tak pomocí tohoto parametru je možné určit její název. |
creator | callable | ne | Někdy může nastat situace, kdy definice na vytvoření správně nastaveného formulářového pole nestačí a je nutné pole donastavit nějakou komplexnější logikou. V tom případě je možné definovat volání metody, která jako parametr dostane předgenerované pole a jako návratová hodnota je očekávána konečná podoba pole (tedy instance formulářového pole, která bude brána jako definitivní). |
Další parametry jsou závislé na konkrétním typu formulářového pole.
Pod-formulář / Pod-formuláře
Systém mapování formulářů disponuje další zajímavou vlastností a to vnořování formulářů do formulářů. Zní to bláznivě? Pojďme si to opět ukázat na něčem z praxe a opět na mém oblíbeném tématu e-shopu.
Vytváříte e-shop a v dnešní době již pravděpodobně nebude stačit jedna jazyková mutace. Potřebujete tedy, aby vše bylo lokalizovatelné. Například popisky zboží. Ukažme si základní definici zboží s lokalizovatelnými daty (pro zjednodušení bez definice DataModel)
Nejprve si uděláme hlavní třídu produktu:
namespace JetApplication;
use Jet\Form_Definition;
use Jet\Form_Definition_Interface;
use Jet\Form_Definition_Trait;
use Jet\Form_Field;
use Jet\Locale;
use JetApplication\Application_Web;
class Product implements Form_Definition_Interface {
use Form_Definition_Trait;
protected int $id;
#[Form_Definition(
type: Form_Field::TYPE_INPUT,
label: 'Internal code:'
)]
protected string $internal_code = '';
#[Form_Definition(
type: Form_Field::TYPE_INPUT,
label: 'EAN:'
)]
protected string $EAN = '';
/**
* @var Product_Localized[]
*/
#[Form_Definition(is_sub_forms:true)]
protected array $localized = [];
public function __construct()
{
foreach( Application_Web::getBase()->getLocales() as $locale ) {
$this->localized[(string)$locale] = new Product_Localized();
$this->localized[(string)$locale]->setLocale( $locale );
}
}
public function getInternalCode(): string
{
return $this->internal_code;
}
public function setInternalCode( string $internal_code ): void
{
$this->internal_code = $internal_code;
}
public function getEAN(): string
{
return $this->EAN;
}
public function setEAN( string $EAN ): void
{
$this->EAN = $EAN;
}
public function getLocalized( Locale $locale ) : Product_Localized
{
return $this->localized[(string)$locale];
}
}
Ta již obsahuje nějaké základní společné údaje a především vlastnost $localized, která je pro nás teď klíčová. Jedná se o pole objektů třídy Product_Localized a vlastnost má atribut #[Form_Definition(is_sub_forms:true)]
Dále si prosím všimněte, že konstruktor vytváří do této vlastnosti instance podle seznamu lokalizací báze.
A teď se koukneme alespoň na torzo třídy Product_Localized, která reprezentuje vše o produktu co bude vázáno na konkrétní lokalizaci:
namespace JetApplication;
use Jet\Form_Definition;
use Jet\Form_Definition_Interface;
use Jet\Form_Definition_Trait;
use Jet\Form_Field;
use Jet\Locale;
class Product_Localized implements Form_Definition_Interface {
use Form_Definition_Trait;
protected int $product_id;
protected Locale $locale;
#[Form_Definition(
type: Form_Field::TYPE_INPUT,
label: 'Name:'
)]
protected string $name = '';
#[Form_Definition(
type: Form_Field::TYPE_WYSIWYG,
label: 'Description:'
)]
protected string $description = '';
#[Form_Definition(
type: Form_Field::TYPE_FLOAT,
label: 'Price:'
)]
protected float $price = 0.0;
public function getLocale(): Locale
{
return $this->locale;
}
public function setLocale( Locale $locale ): void
{
$this->locale = $locale;
}
}
Zde již na první pohled nic zvláštního není. Poměrně běžná třída napojená na formulář. Ovšem z prvků tohoto formuláře se stanou prvky formuláře pro editaci produktu.
Zkuste si tedy vytvořit objekt produkt a vytvořit formulář (třeba pro přidání produktu):
$product = new Product();
Stále celkem běžný postup (krom toho, že z praktických důvodů je dobré držet instance formulářů jako singeltony v objektech - viz ukázková aplikace).
$add_form = $product->createForm('add_product');
Formulář je možné běžně zachytávat:
if($add_form->catch()) {
$product->save();
}
Ovšem ve view můžeme mít toto:
<?=$add_form->field('internal_code')?>
<?=$add_form->field('EAN')?>
<?php foreach( Application_Web::getBase()->getLocales() as $locale ): ?>
<?=UI::locale($locale)?>
<?=$add_form->field('/localized/'.$locale.'/name');?>
<?=$add_form->field('/localized/'.$locale.'/description');?>
<?=$add_form->field('/localized/'.$locale.'/price');?>
<?php endforeach;?>
A teď kontrolní otázka, soudruzi: Co se stane, když oné bázi přidáme další lokalizaci? No nic nezkoroduje, ale přidá se nám další lokalizace i k produktům a automaticky to ovlivní i formulář.
Jedinou podmínkou je říct, že z těch objektů co jsou v poli chceme jejich formulářové prvky začlenit pomocí atributu:
#[Form_Definition(is_sub_forms:true)].
Pokud by se nejednalo o pole objektů, ale o prostou instanci jiného objektu, tak se postupuje úplně stejně, je pouze malý rozdíl v atributu:
#[Form_Definition(is_sub_form:true)]