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.