Nájsť recept na správnu, rozširovateľnú a relatívne pohodlnú implementáciu formulárového modulu dá fakt zabrať. Pre Nette ujo Grudl síce vykročil, ale ja som to skúsil inak. Z fleku exámpl:
$document = new Container;
$document['title'] = new String;
$document['body'] = new Text;
Formulár z tohto pohľadu je definícia hodnôt a ich validácii. Ako si
pozorní určite všimli, formulár nedefinuje názvy políčok, ani atribúty
ako class, id, rows či
cols. To všetko nechávam na šablonku. Čo nás zaujíma sú typy
hodnôt ktoré očakávame: polia, reťazce, dátumy, súbory, atp. Nad týmito
primitívnymi typmi následne fungujú validácie a filtre.
Hodnoty
Hodnoty sa čítajú z poľa hodnôt a je im jedno či je to POST,
databáza, súbor alebo obyčajné pole. Na každom políčku je k dispozícii
dvojička metód getValue a setValue, ktoré pracujú
s obyčajnými poľami.
Integrácia s Nette
Klasickú formulárovú funkčnosť a integráciu do Nette zabezpečuje
doplnková trieda Form. Form rieši komunikáciu
s prezentérom, získavanie a distribúciu HTTP dát do políčok, nastavovanie
action, či method atribútov. Form
v sebe zapúzdruje inštanciu triedy Container, čiže zvonku sa
javí prirodzene:
$form = new Form;
$form['document'] = new Container;
$form['document']['title'] = new String;
$form['document']['body'] = new Text;
if( $form->isSubmitted() )
{
if( !$form->hasErrors() )
{
// Save data
}
}
else
{
$form['document']->setValue( array
(
'title' => 'Default title',
'body' => 'Default body'
));
}
Šablony
Na zjednodušenie práce mám FormHelper, ktorý drobnosti so
šablonou rieši za mňa. V žiadnom prípade sa však nesnaží definovať
niečo navyše, výstupom sú holé políčka s nastavenými tými
najnutnejšími atribútmi:
{widget:label $form['document']['title']}Titulok{/widget:label}
{widget:message:error $form['document']['title']}
{widget:text $form['document']['title'], 'width' => '100%'}
{widget:label $form['document']['body']}Body{/widget:label}
{widget:message:error $form['document']['body']}
{widget:text:area $form['document']['body'], 'rows' => 5, 'cols' => 40}
Až v šablone sa priraďuje políčku jeho vzhľad, spôsob rozloženia
prvkov. Je to síce pracnejšie ako s ConventionalRenderer z Nette
Forms, ale zato máte plnú voľnosť. FormHelper tiež zverejňuje
funkcie na generovanie `name, id, style,
ktoré sa dajú ľahko využiť pre písanie vlastných widgetov.
Políčka
Okrem obligátnych skalárnych, modul má aj komplikovanejšie políčka.
Jedným z nich je ElasticContainer a pomôže ak UI vyžaduje
pridávať vopred neurčený počet príloh, e-mailov, alebo tagov (každý
v samostatnom políčku).
$form['emails'] = new ElasticContainer( function() {
$prototype = new String;
$prototype->validates( 'email' );
return $prototype;
});
Základná sada políčok by mala stačiť na všetko, ak však chcete, nič
vám nebráni si nadefinovať vlastnú triedu ktorá dedí od
Field.
Validácie
Validácie to vie samozrejme tiež:
$form->validates( 'children', array
(
'document/title' => array
(
'presence' => true,
'length' => array( 'minimum' => 3 )
),
'document/body' => array
(
'presence' => true
)
));
Validácie je možné viazať na rodičovské, ale aj na detské políčka. Horný príklad je možné zapísať hneď niekoľkými spôsobmi:
// Naviazanim na 'document'; rodica 'title' a 'body' policok
$form['document']->validates( 'children', array
(
'title' => array
(
'presence' => true,
'length' => array( 'minimum' => 3 )
),
'body' => array
(
'presence' => true
)
));
// --- alebo naviazanim na samotne policko s pouzitim pola
$form['document']['title']->validates( array
(
'presence' => true,
'length' => array( 'minimum' => 3 )
));
$form['document']['body']->validates( array
(
'presence' => true
));
// --- alebo postupnym volanim metody validates
$form['document']['title']
->validates( 'presence' )
->validates( 'length', array( 'minimum' => 3 ) );
$form['document']['body']
->validates( 'presence' );
Modul so sebou nesie niekoľko základných validátorov (presence, length,
format, email, url, custom, children,…), a je samozrejme možné si
dodefinovať vlastné. Mapa string → validátor je definovaná v triede
Validator\Registry. Ak si chcete pridať vlastný, stačí
vytvoriť funkciu a zaregistrovať ju:
$form['password'] = new Container;
$form['password']['first'] = new String;
$form['password']['second'] = new String;
function validatesPasswordConfirmation( $field, $options )
{
if( $field['first']->getValue() != $field['second']->getValue() )
{
$field->addError( 'Passwords do not match.' );
return false;
}
return true;
}
Validator\Registry::register( 'password_confirmation', 'validatesPasswordConfirmation' );
$form['password']->validates( 'password_confirmation' );
Registrovať sa dá aj lambda funkcia:
Validator\Registry::register( 'password_confirmation', function( $field, $options )
{
if( $field['first']->getValue() != $field['second']->getValue() )
{
$field->addError( 'Passwords do not match.' );
return false;
}
return true;
});
Ak potrebujete špeciálnu validáciu, možete použiť lambda funkciu priamo:
$form['password'] = new Container;
$form['password']['first'] = new String;
$form['password']['second'] = new String;
$form['password']->validates( function( $field )
{
if( $field['first']->getValue() != $field['second']->getValue() )
{
$field->addError( 'Passwords do not match.' );
return false;
}
return true;
});
Celý vtip je v tom, že si validačné pravidlá naviažete tam kde potrebujete. V tomto prípade zapúzdrujem obe heslá do jedného kontajneru a tým pádom si môžem zjednodušiť validáciu. Nerobí mi ale problém to urobiť aj bez kontajnéru.
Čo s tým?
Modul nie je zatiaľ verejný, keďže ho nemám plne nasadený a ustálený. Rád by som zistil, či sa aspoň niekomu zdá byť úžitočný. Mne to už teraz ušetrilo kopu nervov.
Chápu že ConventionalRenderer leze leckomu na nervy – mně taky :) Na jakýkoliv jen lehce složitější formulář je to nepoužitelné.
Přijde mi ale škoda zahazovat formuláře z Nette, které mají za sebou už poměrně dlohou cestu a je v nich velmi elegantně vyřešená spousta problémů.
Jestli to dobře chápu hlavní cíl těchto formulářů je snadnější vykreslování.
Přijde mi ale, že tohle řešení má ale až moc ukecanou syntaxi.
Pro formuláře v Nette navíc existuje věcička, která se jmenuje FormMacros – není to nijak oficiální – zdrojáky jsou někde na gist.github.com. Umí to zhruba tohle:
{form foo begin}
{label bar} {control bar}
{form foo end}
Prozatím tomu chybí možnost jednoduše nastavit id, class apod. ale neměl by to být velký problém udělat.
Sečteno a podtrženo:
Validátory oddelené sú. Celý trik je v tom, že moje riešenie je vlastne sada tried na popis schémy dát. Trieda
Formiba obaľuje túto schému funkčnosťou Nette formuláru (Component, ISignalReceiver; viď čast Integrácia s Nette). Nič mi však nebráni nepoužiťForm, definovať iba schému a použiť to na validáciu JSON z nejakého API – viď hneď prvý príklad v článku:Pozn. meno
Containerje tu v konflikte s FormContainer z Nette. Chce to lepšie pomenovanie, ale znamená to vlastne „pole“.Predpokladám, že ukecané sa ti zdajú práve Latte makrá v šablóne (ak nie, tak pardon a prosím o upresenie). Schéma nemá za účel definovať HTML elementy, preto je potrebné vykecať aký element sa má použiť. Pochopiteľne sa dá urobiť jednoduchý mapper ktorý zo
Stringurobí<input type="text"...ale to sme späť u ConventionalRendereru a lezení na nervy.Ujo Cohen, je to teraz lepšie? http://github.com/…endering.php ;)
Nicméně ten směr, kdy by se definice formuláře zcela odprostila od rozhodování, zda seznam má být prezentován jako SELECT nebo RADIOLIST, je dobrý a pokusím se tak formuláře upravit.
<!
texy--><!texy-->Opravdu se mi moc líbí, jak lze z jednoduché datové struktury udělat formulář :) přemýšlím, že bych i vyzkoušel, ale kdyby to bylo místo nette formulářů, to by bylo něco ;-)No pan ujo mistr David uz nieco kuti. Mozno to tam i bude. Kazdopadne toto je plne samostatne (a paradne spetne nekompatibilne s Nette) takze ci to Nette mat bude, alebo nie je uplne jedno. :)
Nastal u těch alternativních formulářů nějaký vývoj za poslední dobu?
Momentalne finisujeme web v ktorom ich pouzivame. Akonahle bude web funkcny a vonku, vypustim to von.