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í $form->render()
. 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()
. Pokud nenastavíme vlastní renderer, bude použit výchozí
vykreslovač Nette\Forms\Rendering\DefaultFormRenderer.
Stačí tedy napsat:
$form->render();
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';
$form->render();
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, Bootstrap 3 a Bootstrap 4
Latte
Šablonovací sytém Latte zásadně usnadňuje vykreslení formulářů a jejich prvků:
{form myForm}
<table>
<tr n:foreach="$form->controls as $name => $field">
<th>{label $name /}<th>
<td>{input $name}</td>
<td n:ifcontent>{inputError $name}</td>
</tr>
</table>
{/form}
Značka {form}
nebo <form n:name>
zahájí vykreslování formuláře. Zároveň vytvoří
lokální proměnnou $form
s objektem kresleného formuláře. Uzavírací značka {/form}
nebo
</form>
před uzavřením formuláře ještě vykreslí všechny nevykreslené hidden prvky.
Značka {input}
vykreslí jakýkoliv prvek, včetně select boxů nebo textarea. Tag {label}
může
být volitelně párové, např. {label}Věk{/label}
, a ohraničený text bude u vícejazyčných formulářů také
přeložen. Pokud formulářový prvek obsahuje chybu, značka {inputError}
ji vypíše.
Pokud potřebuje formuláře naroubovat na existující HTML kód, můžeme využít atribut n:name
, které
zaktivní existující prvky v šabloně:
<form n:name=myForm>
<p>Jméno: <input size="50" n:name="name"></p>
<p>Heslo: <input size="30" n:name="password"></p>
</form>
Pokud potřebujete vykreslit jen vnitřní část formuláře bez HTML značek
<form>
& </form>
, například při AJAXovém požadavku, můžete formulář otevří a
uzavřít do {formContext formName} … {/formContext}
. Funguje podobně jako {form}
v logickém
smyslu, tady umožní používat ostatní značky pro kreslení prvků formuláře, ale přitom nic nevykreslí.
Návrh kódu {formPrint}
Latte kód pro formulář si také můžete nechat vygenerovat pomocí značky
{formPrint}
. Pokud ji umístíte do šablony, místo běžného vykreslení se zobrazí návrh kódu. Ten pak stačí
označit a zkopírovat do projektu.
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é
značky {form myForm}
a {/form}
. Jednotlivé prvky můžeme vypisovat pomocí značek
{input myInput}
, které vypíší formulářový prvek, a {label myLabel /}
, které vypíší 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é značky {input gender:$key}
a {label gender:$key}
, trik je v tom názvu
s dvojtečkou. Pro jednoduchý checkbox použijte {input myCheckbox:}
.
S vykreslením prvků uvnitř formulářového kontejneru pomůže značku formContainer
.
{form signForm}
<table>
<th>Přeji si e-mailem 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ým prvkům můžeme nastavovat libovolné HTML atributy pomocí
setHtmlAttribute(string $name, $value = true)
:
$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 <form>
$form->getElementPrototype()->id = 'myForm';
$form->getElementPrototype()->target = '_top';
Nastavení typu:
$form->addText('tel', 'Váš telefon:')
->setHtmlType('tel')
->setHtmlAttribute('placeholder', 'napište telefon');
Jednotlivým položkám v radio nebo checkbox listech můžeme nastavit HTML atribut s rozdílnými hodnotami pro každou
z nich. Povšimněte si dvojtečky za style:
, která zajistí volbu hodnoty podle klíče:
$colors = ['r' => 'červená', 'g' => 'zelená', 'b' => 'modrá'];
$styles = ['r' => 'background:red', 'g' => 'background:green'];
$form->addCheckboxList('colors', 'Barvy:', $colors)
->setHtmlAttribute('style:', $styles);
Vypíše:
<label><input type="checkbox" name="colors[]" style="background:red" value="r">červená</label>
<label><input type="checkbox" name="colors[]" style="background:green" value="g">zelená</label>
<label><input type="checkbox" name="colors[]" value="b">modrá</label>
Pokud jde o logický HTML atribut (který nemá hodnotu, např. readonly
), můžeme použít zápis
s otazníkem:
$colors = ['r' => 'červená', 'g' => 'zelená', 'b' => 'modrá'];
$form->addCheckboxList('colors', 'Barvy:', $colors)
->setHtmlAttribute('readonly?', 'r'); // pro více klíču použijte pole, např. ['r', 'g']
Vypíše:
<label><input type="checkbox" name="colors[]" readonly value="r">červená</label>
<label><input type="checkbox" name="colors[]" value="g">zelená</label>
<label><input type="checkbox" name="colors[]" value="b">modrá</label>
V případě selectboxů metoda setHtmlAttribute()
nastavuje atributy elementu <select>
. Pokud
chceme nastavit atributy jednotlivým <option>
, použijeme metodu addOptionAttributes()
. Fungují
i zápisy s dvojtečkou a otazníkem uvedené výše:
$form->addSelect('colors', 'Barvy:', $colors)
->addOptionAttributes(['style:' => $styles]);
Vypíše:
<select name="colors">
<option value="r" style="background:red">červená</option>
<option value="g" style="background:green">zelená</option>
<option value="b">modrá</option>
</select>
Další nastavení
Nastavení popisku, který se vypisuje za polem:
$form->addText('phone', 'Číslo:')
->setOption('description', 'Toto číslo zůstane skryté');
Pokud do něj 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)
.
Bez Latte
Každý prvek disponuje metodami getLabel()
a getControl()
, které vracejí HTML kód popisky a
samotného prvku. Nette Framework dovoluje ke getterům přistupovat podobně, jako by to byly proměnné, takže
stačí psát jen label
a control
.
<?php $form->render('begin') ?>
<?php $form->render('errors') ?>
<table>
<tr class="required">
<th><?php echo $form['name']->label // Zavolá getLabel() ?></th>
<td><?php echo $form['name']->control // Zavolá getControl() ?></td>
</tr>
<tr class="required">
<th><?php echo $form['age']->label ?></th>
<td><?php echo $form['age']->control ?></td>
</tr>
// ...
</table>
<?php $form->render('end') ?>
Událost onRender
Těsně před tím, než se formulář vykreslí, můžeme nechat zavolat náš kód. Ten může například doplnit
formulářovým prvkům HTML třídy pro správné zobrazení. Kód přidáme do pole onRender
:
$form->onRender[] = function ($form) {
BootstrapCSS::initialize($form);
};