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
{
	$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 tag {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> - od nette/forms 3.0.3
$form->setHtmlAttribute('id', 'myForm');

// před verzí 3.0.3
$form->getElementPrototype()->id = 'myForm';

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 setOptionAttribute() (od verze 3.0.4). Fungují i zápisy s dvojtečkou a otazníkem uvedené výše:

$form->addSelect('colors', 'Barvy:', $colors)
	->setOptionAttribute('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') ?>