Vykreslování formulářů

Vzhled formulářů může být velmi různorodý. V praxi můžeme narazit na dva extrémy. Na jedné straně stojí potřeba v aplikaci vykreslovat řadu formulářů, které jsou si vizuálně podobné jako vejce vejci, a oceníme snadné vykreslení pomocí echo $form. Jde obvykle o případ administračních rozhraní.

Na druhé straně tu jsou rozmanité formuláře, kde platí: co kus, to originál. Jejich podobu nejlépe popíšeme jazykem HTML. A samozřejmě kromě obou zmíněných extrémů narazíme na spoustu formulářů, které se pohybují někde mezi.

Výchozí vykreslování

Automatické vykreslení formuláře obstarává tzv. renderer. Ten lze nastavit metodou setRenderer. Předá se mu řízení při zavolání metody $form->render() nebo echo $form. Pokud nenastavíme vlastní renderer, bude použit výchozí vykreslovač Nette\Forms\Rendering\DefaultFormRenderer. Stačí tedy napsat:

echo $form;

nebo v Latte:

{control form}

a formulář je na světě. Prvky formuláře se vykreslí do HTML tabulky. Výstup vypadá takto:

<table>
<tr class="required">
    <th><label class="required" for="frm-name">Jméno:</label></th>

    <td><input type="text" class="text" name="name" id="frm-name" value="" /></td>
</tr>

<tr class="required">
    <th><label class="required" for="frm-age">Věk:</label></th>

    <td><input type="text" class="text" name="age" id="frm-age" value="" /></td>
</tr>

<tr>
    <th><label>Pohlaví:</label></th>
    ...

Hezky naformátované, viďte? :-)

Zda použít nebo nepoužít pro kostru formuláře tabulku je sporné a řada webdesignerů preferuje jiný markup. Například definiční seznam. Překonfigurujeme DefaultFormRenderer tak, aby formulář v podobě seznamu vykreslil. Konfigurace se provádí editací pole $wrappers. První index vždy představuje oblast a druhý její atribut. Jednotlivé oblasti znázorňuje obrázek:

Standardně je skupina prvků controls obalena tabulkou <table>, každý pair představuje řádek tabulky <tr> a dvojice label a control jsou buňky <th> a <td>. Nyní obalující elementy změníme. Oblast controls vložíme do kontejneru <dl>, oblast pair necháme bez kontejneru, label vložíme do <dt> a nakonec control obalíme značkami <dd>:

$renderer = $form->getRenderer();
$renderer->wrappers['controls']['container'] = 'dl';
$renderer->wrappers['pair']['container'] = null;
$renderer->wrappers['label']['container'] = 'dt';
$renderer->wrappers['control']['container'] = 'dd';

echo $form;

Výsledkem je tento HTML kód:

<dl>
    <dt><label class="required" for="frm-name">Jméno:</label></dt>

    <dd><input type="text" class="text" name="name" id="frm-name" value="" /></dd>


    <dt><label class="required" for="frm-age">Věk:</label></dt>

    <dd><input type="text" class="text" name="age" id="frm-age" value="" /></dd>


    <dt><label>Pohlaví:</label></dt>
    ...
</dl>

V poli wrappers lze ovlivnit celou řadu dalších atributů:

  • přidávat CSS třídy jednotlivým typům formulářových prvků
  • rozlišovat CSS třídou liché a sudé řádky
  • vizuálně odlišit povinné a volitelné položky
  • určovat, zda se chybové zprávy zobrazí přímo u prvků nebo nad formulářem

Podpora pro Bootstrap

V příkladech najdete ukázky, jak nakonfigurovat vykreslování formulářů pro Twitter Bootstrap 2 a Bootstrap 3

Manuální vykreslování

Formuláře lze vykreslovat i ručně a tím získat větší kontrolu nad kódem. Celý formulář umístíme mezi párové makra {form myForm} a {/form}. Jednotlivé prvky můžeme vypisovat pomocí maker {input myInput}, které vypíše formulářový prvek, a {label myLabel /}, které vypíše jeho popisek.

{form signForm}

<!-- Jednoduché vykreslení chyb -->
<ul class="errors" n:if="$form->hasErrors()">
    <li n:foreach="$form->errors as $error">{$error}</li>
</ul>

<table>
<tr class="required">
    <th>{label name /}</th>
    <td>{input name}</td>
</tr>

<!-- V případě že potřebujeme vykreslit ručně jednotlivé prvky radiolistu -->
<p>{input radioList:itemKey} | {input radioList:itemKeyTwo}</p>

...

</table>
{/form}

Velmi snadno také můžete propojit formulář s existující šablonou. Stačí jen doplnit atributy n:name:

protected function createComponentSignInForm()
{
    $form = new Form;
    $form->addText('user')->setRequired();
    $form->addPassword('password')->setRequired();
    $form->addSubmit('send');
    return $form;
}
<form n:name=signInForm class=form>
    <p><label n:name=user>Username: <input n:name=user size=20></label>
    <p><label n:name=password>Password: <input n:name=password></label>
    <p><input n:name=send class="btn btn-default">
</form>

Atribut n:name lze používat i s elementy <select>, <button> nebo <textarea> a vnitřní obsah se automaticky doplní.

Dále můžete vykreslovat prvky jako je RadioList, Checkbox nebo CheckboxList pěkně po jednotlivých HTML elementech. Říká se tomu partial rendering:

{foreach $form[gender]->items as $key => $label}
    <label n:name="gender:$key"><input n:name="gender:$key"> {$label}</label>
{/foreach}

Nebo lze použít klasická makra {input gender:$key} a {label gender:$key}, trik je tom názvu s dvojtečkou. Pro jednoduchý checkbox použijte {input myCheckbox:}.

S vykreslením prvků uvnitř formulářového kontejneru pomůže makro formContainer.

{form signForm}
<table>
    <th>Přeji si emailem odebírat tyto novinky:</th>
    <td>
        {formContainer emailNews}
        <ul>
            <li>{input sport} {label sport /}</li>
            <li>{input science} {label science /}</li>
        </ul>
        {/formContainer}
    </td>
    ...
</table>
{/form}

Jak nastavit HTML elementům další atributy? Metody getControl() a getLabel() vrací element v podobě Nette\Utils\Html objektu, se kterým se dá snadno pracovat. Takto například v Latte:

{form signForm class => 'big'}
<table>
<tr class="required">
    <th>{label name /}</th>
    <td>{input name cols => 40, autofocus => true}</td>
</tr>

Seskupování prvků

Prvky lze seskupovat do vizuálních skupin (fieldsetů) vytvořením skupiny:

$form->addGroup('Personal data');

Po vytvoření nové skupiny se tato stává aktivní a každý nově přidaný prvek je zároveň přidán i do ní. Takže formulář lze stavět tímto způsobem:

$form = new Form;
$form->addGroup('Personal data');
$form->addText('name', 'Your name:');
$form->addInteger('age', 'Your age:');
$form->addEmail('email', 'Email:');

$form->addGroup('Shipping address');
$form->addCheckbox('send', 'Ship to address');
$form->addText('street', 'Street:');
$form->addText('city', 'City:');
$form->addSelect('country', 'Country:', $countries);

HTML atributy

Formulářové prvky můžeme kromě podmínek a pravidel rozšířit o popisek, upravit class apod.

Pozor na pořadí podmínek, pravidel a užití těchto rozšíření.

Nastavení třídy nebo JavaScriptu:

$form->addInteger('number', 'Číslo:')
    ->setHtmlAttribute('class', 'bigNumbers');

$form->addSelect('rank', 'Řazení dle:', ['ceny', 'názvu'])
    ->setHtmlAttribute('onchange', 'submit()'); // při změně odeslat


// chceme-li to samé udělat pro celý $form
$form->getElementPrototype()->id = 'myForm';
$form->getElementPrototype()->target = '_top';

Nastavení typu (např. pro HTML5):

$form->addText('email', 'Váš email:')
    ->setHtmlType('email')
    ->setHtmlAttribute('placeholder', 'napište email');

Nastavení popisku (defaultně se vypisuje za polem):

$form->addText('phone', 'Číslo:')
    ->setOption('description', 'Toto číslo zůstane skryté');

Pokud chceme umístit HTML obsah, využijeme třídy Html

use Nette\Utils\Html;

$form->addText('phone', 'Číslo:')
    ->setOption('description', Html::el('p')
        ->setHtml('Toto číslo zůstane skryté. <a href="...">Podmínky uchovávání Vašeho čísla</a>')
    );

Html prvek lze využít i místo labelu: $form->addCheckbox('conditions', $label).