Űrlapok megjelenítése

Az űrlapok megjelenése nagyon változatos lehet. A gyakorlatban két szélsőséggel találkozhatunk. Az egyik oldalon az az igény áll, hogy az alkalmazásban számos olyan űrlapot jelenítsünk meg, amelyek vizuálisan hasonlítanak egymásra, mint két tojás, és értékelnénk az egyszerű megjelenítést sablon nélkül a $form->render() segítségével. Ez általában az adminisztrációs felületek esete.

A másik oldalon pedig ott vannak a változatos űrlapok, amelyekre igaz: minden darab egyedi. Formájukat leginkább HTML nyelven írhatjuk le az űrlap sablonjában. És természetesen a két említett szélsőségen kívül számos olyan űrlappal találkozunk, amelyek valahol a kettő között helyezkednek el.

Megjelenítés Latte segítségével

Latte sablonrendszer alapvetően megkönnyíti az űrlapok és elemeik megjelenítését. Először megmutatjuk, hogyan lehet az űrlapokat manuálisan, elemenként megjeleníteni, és ezzel teljes kontrollt szerezni a kód felett. Később megmutatjuk, hogyan lehet ezt a megjelenítést automatizálni.

Az űrlap Latte sablonjának tervét legeneráltathatja a Nette\Forms\Blueprint::latte($form) metódussal, amely kiírja azt a böngésző oldalára. A kódot ezután elég egy kattintással kijelölni és bemásolni a projektbe.

{control}

Az űrlap megjelenítésének legegyszerűbb módja, ha a sablonba beírjuk:

{control signInForm}

Az így megjelenített űrlap kinézetét a Renderer és az egyes elemek konfigurálásával lehet befolyásolni.

n:name

Az űrlap definícióját a PHP kódban rendkívül egyszerűen össze lehet kapcsolni a HTML kóddal. Csak hozzá kell adni a n:name attribútumokat. Ennyire egyszerű!

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

Az eredményül kapott HTML kód formáját teljes mértékben Ön irányítja. Ha az n:name attribútumot a <select>, <button> vagy <textarea> elemeknél használja, azok belső tartalma automatikusan kiegészül. A <form n:name> tag ezenkívül létrehoz egy lokális $form változót a rajzolt űrlap objektumával, és a záró </form> megjeleníti az összes meg nem jelenített rejtett elemet (ugyanez érvényes a {form} ... {/form}-ra is).

Nem szabad azonban elfelejtenünk a lehetséges hibaüzenetek megjelenítését. Mindazokat, amelyeket az addError() metódussal adtak hozzá az egyes elemekhez (a {inputError} segítségével), mindazokat, amelyeket közvetlenül az űrlaphoz adtak hozzá (ezeket a $form->getOwnErrors() adja vissza):

<form n:name=signInForm class=form>
	<ul class="errors" n:ifcontent>
		<li n:foreach="$form->getOwnErrors() as $error">{$error}</li>
	</ul>

	<div>
		<label n:name=username>Username: <input n:name=username size=20 autofocus></label>
		<span class=error n:ifcontent>{inputError username}</span>
	</div>
	<div>
		<label n:name=password>Password: <input n:name=password></label>
		<span class=error n:ifcontent>{inputError password}</span>
	</div>
	<div>
		<input n:name=send class="btn btn-default">
	</div>
</form>

A bonyolultabb űrlap elemeket, mint a RadioList vagy a CheckboxList, így lehet elemenként megjeleníteni:

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

{label} {input}

Nem akar minden elemnél azon gondolkodni, hogy milyen HTML elemet használjon hozzá a sablonban, legyen az <input>, <textarea> stb? A megoldás az univerzális {input} tag:

<form n:name=signInForm class=form>
	<ul class="errors" n:ifcontent>
		<li n:foreach="$form->getOwnErrors() as $error">{$error}</li>
	</ul>

	<div>
		{label username}Username: {input username, size: 20, autofocus: true}{/label}
		{inputError username}
	</div>
	<div>
		{label password}Password: {input password}{/label}
		{inputError password}
	</div>
	<div>
		{input send, class: "btn btn-default"}
	</div>
</form>

Ha az űrlap fordítót használ, a {label} tageken belüli szöveg lefordításra kerül.

Ebben az esetben is a bonyolultabb űrlap elemeket, mint a RadioList vagy a CheckboxList, elemenként lehet megjeleníteni:

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

Magának az <input> elemnek a megjelenítéséhez a Checkbox elemben használja a {input myCheckbox:}-t. A HTML attribútumokat ebben az esetben mindig vesszővel válassza el {input myCheckbox:, class: required}.

{inputError}

Kiírja a hibaüzenetet az űrlap elemhez, ha van ilyen. Az üzenetet általában HTML elembe csomagoljuk a stílusozás miatt. Az üres elem megjelenítésének elkerülését, ha nincs üzenet, elegánsan meg lehet oldani az n:ifcontent segítségével:

<span class=error n:ifcontent>{inputError $input}</span>

A hiba jelenlétét a hasErrors() metódussal ellenőrizhetjük, és ennek megfelelően beállíthatjuk a szülő elem osztályát:

<div n:class="$form[username]->hasErrors() ? 'error'">
	{input username}
	{inputError username}
</div>

{form}

A {form signInForm}...{/form} tagek alternatívái a <form n:name="signInForm">...</form>-nak.

Automatikus megjelenítés

Az {input} és {label} tageknek köszönhetően könnyen létrehozhatunk egy általános sablont bármilyen űrlaphoz. Fokozatosan iterál és megjeleníti az összes elemét, kivéve a rejtett elemeket, amelyek automatikusan megjelennek az űrlap lezárásakor a </form> taggel. A megjelenítendő űrlap nevét a $form változóban fogja várni.

<form n:name=$form class=form>
	<ul class="errors" n:ifcontent>
		<li n:foreach="$form->getOwnErrors() as $error">{$error}</li>
	</ul>

	<div n:foreach="$form->getControls() as $input"
		n:if="$input->getOption(type) !== hidden">
		{label $input /}
		{input $input}
		{inputError $input}
	</div>
</form>

A használt önlezáró páros {label .../} tagek a PHP kódban lévő űrlap definícióból származó címkéket jelenítik meg.

Ezt az általános sablont mentse el például a basic-form.latte fájlba, és az űrlap megjelenítéséhez elég csak beilleszteni és átadni az űrlap nevét (vagy példányát) a $form paraméterbe:

{include basic-form.latte, form: signInForm}

Ha egy adott űrlap megjelenítésekor bele szeretne szólni a formájába, és például egy elemet másképp szeretne megjeleníteni, akkor a legegyszerűbb út, ha a sablonban előre elkészít blokkokat, amelyeket később felül lehet írni. A blokkoknak lehetnek dinamikus nevek is, így beléjük lehet illeszteni a megjelenítendő elem nevét is. Például:

...
	{label $input /}
	{block "input-{$input->name}"}{input $input}{/block}
...

Egy username nevű elemhez így létrejön az input-username blokk, amelyet könnyen felül lehet írni a {embed} tag használatával:

{embed basic-form.latte, form: signInForm}
	{block input-username}
		<span class=important>
			{include parent}
		</span>
	{/block}
{/embed}

Alternatívaként a basic-form.latte sablon teljes tartalmát definiálni lehet blokként, beleértve a $form paramétert is:

{define basic-form, $form}
	<form n:name=$form class=form>
		...
	</form>
{/define}

Ennek köszönhetően kissé egyszerűbb lesz a hívása:

{embed basic-form, signInForm}
	...
{/embed}

A blokkot pedig elég egyetlen helyen importálni, a layout sablon elején:

{import basic-form.latte}

Speciális esetek

Ha csak az űrlap belső részét kell megjeleníteni a <form> HTML tagek nélkül, például snippek küldésekor, rejtse el őket az n:tag-if attribútummal:

<form n:name=signInForm n:tag-if=false>
	<div>
		<label n:name=username>Username: <input n:name=username></label>
		{inputError username}
	</div>
</form>

Az elemek megjelenítésében az űrlap konténeren belül segít a {formContainer} tag.

<p>Melyik híreket szeretné megkapni:</p>

{formContainer emailNews}
<ul>
	<li>{input sport} {label sport /}</li>
	<li>{input science} {label science /}</li>
</ul>
{/formContainer}

Megjelenítés Latte nélkül

Az űrlap megjelenítésének legegyszerűbb módja a következő hívás:

$form->render();

Az így megjelenített űrlap kinézetét a Renderer és az egyes elemek konfigurálásával lehet befolyásolni.

Manuális megjelenítés

Minden űrlap elem rendelkezik metódusokkal, amelyek generálják az űrlap mező és a címke HTML kódját. Ezt visszaadhatják vagy stringként, vagy Nette\Utils\Html objektumként:

  • getControl(): Html|string visszaadja az elem HTML kódját
  • getLabel($caption = null): Html|string|null visszaadja a címke HTML kódját, ha létezik

Az űrlapot így elemenként lehet megjeleníteni:

<?php $form->render('begin') ?>
<?php $form->render('errors') ?>

<div>
	<?= $form['name']->getLabel() ?>
	<?= $form['name']->getControl() ?>
	<span class=error><?= htmlspecialchars($form['name']->getError()) ?></span>
</div>

<div>
	<?= $form['age']->getLabel() ?>
	<?= $form['age']->getControl() ?>
	<span class=error><?= htmlspecialchars($form['age']->getError()) ?></span>
</div>

// ...

<?php $form->render('end') ?>

Míg egyes elemeknél a getControl() egyetlen HTML elemet ad vissza (pl. <input>, <select> stb.), másoknál egy egész HTML kódrészletet (CheckboxList, RadioList). Ebben az esetben használhatja azokat a metódusokat, amelyek az egyes inputokat és címkéket generálják, minden elemhez külön:

  • getControlPart($key = null): ?Html visszaadja egy elem HTML kódját
  • getLabelPart($key = null): ?Html visszaadja egy elem címkéjének HTML kódját

Ezeknek a metódusoknak történelmi okokból get prefixük van, de jobb lenne a generate, mert minden híváskor új Html elemet hoznak létre és adnak vissza.

Renderer

Ez egy objektum, amely biztosítja az űrlap megjelenítését. Ezt a $form->setRenderer metódussal lehet beállítani. A vezérlés átadódik neki a $form->render() metódus hívásakor.

Ha nem állítunk be saját renderert, az alapértelmezett megjelenítő Nette\Forms\Rendering\DefaultFormRenderer kerül felhasználásra. Ez az űrlap elemeit HTML táblázat formájában jeleníti meg. A kimenet így néz ki:

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

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

<tr class="required">
	<th><label class="required" for="frm-age">Életkor:</label></th>

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

<tr>
	<th><label>Nem:</label></th>
	...

Az, hogy használjunk-e táblázatot az űrlap vázához, vitatható, és sok webdesigner más jelölést részesít előnyben. Például a definíciós listát. Ezért újrakonfiguráljuk a DefaultFormRenderer-t úgy, hogy az űrlapot lista formájában jelenítse meg. A konfiguráció a $wrappers tömb szerkesztésével történik. Az első index mindig a területet, a második pedig annak attribútumát jelenti. Az egyes területeket az ábra mutatja:

Alapértelmezés szerint az controls elemcsoport egy <table>-ba van csomagolva, minden pair egy táblázat sort (<tr>) képvisel, a label és control páros pedig cellák (<th> és <td>). Most megváltoztatjuk a csomagoló elemeket. A controls területet egy <dl> konténerbe helyezzük, a pair területet konténer nélkül hagyjuk, a label-t <dt>-be, végül a control-t <dd> tagekkel csomagoljuk:

$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();

Az eredmény ez a HTML kód:

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

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


	<dt><label class="required" for="frm-age">Életkor:</label></dt>

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


	<dt><label>Nem:</label></dt>
	...
</dl>

A wrappers tömbben számos további attribútumot lehet befolyásolni:

  • CSS osztályok hozzáadása az egyes űrlap elem típusokhoz
  • CSS osztállyal megkülönböztetni a páros és páratlan sorokat
  • vizuálisan megkülönböztetni a kötelező és választható elemeket
  • meghatározni, hogy a hibaüzenetek közvetlenül az elemeknél vagy az űrlap felett jelenjenek-e meg

Options

A Renderer viselkedését az egyes űrlap elemeken beállított options segítségével is lehet irányítani. Így lehet beállítani a leírást, amely a beviteli mező mellett jelenik meg:

$form->addText('phone', 'Szám:')
	->setOption('description', 'Ez a szám rejtve marad');

Ha HTML tartalmat szeretnénk elhelyezni benne, használjuk a Html osztályt:

use Nette\Utils\Html;

$form->addText('phone', 'Szám:')
	->setOption('description', Html::el('p')
		->setHtml('<a href="...">A szám megőrzésének feltételei</a>')
	);

A Html elemet a címke helyett is lehet használni: $form->addCheckbox('conditions', $label).

Elemek csoportosítása

A Renderer lehetővé teszi az elemek vizuális csoportokba (fieldsetekbe) való csoportosítását:

$form->addGroup('Személyes adatok');

Új csoport létrehozása után ez válik aktívvá, és minden újonnan hozzáadott elem egyúttal hozzáadódik ehhez a csoporthoz is. Tehát az űrlapot így lehet építeni:

$form = new Form;
$form->addGroup('Személyes adatok');
$form->addText('name', 'Neved:');
$form->addInteger('age', 'Korod:');
$form->addEmail('email', 'Email:');

$form->addGroup('Szállítási cím');
$form->addCheckbox('send', 'Szállítás címre');
$form->addText('street', 'Utca:');
$form->addText('city', 'Város:');
$form->addSelect('country', 'Ország:', $countries);

A Renderer először a csoportokat jeleníti meg, és csak utána azokat az elemeket, amelyek egyik csoportba sem tartoznak.

Támogatás a Bootstraphez

A példákban találhatók minták arra, hogyan konfigurálja a Renderert a Twitter Bootstrap 2, Bootstrap 3 és Bootstrap 4 számára.

HTML attribútumok

Tetszőleges HTML attribútumok beállításához az űrlap elemekhez használja a setHtmlAttribute(string $name, $value = true) metódust:

$form->addInteger('number', 'Szám:')
	->setHtmlAttribute('class', 'big-number');

$form->addSelect('rank', 'Rendezés:', ['ár', 'név'])
	->setHtmlAttribute('onchange', 'submit()'); // változáskor küldés


// A <form> elem attribútumainak beállításához
$form->setHtmlAttribute('id', 'myForm');

Az elem típusának specifikációja:

$form->addText('tel', 'Telefonod:')
	->setHtmlType('tel')
	->setHtmlAttribute('placeholder', 'írja be a telefonszámot');

A típus és más attribútumok beállítása csak vizuális célokat szolgál. A bemenetek helyességének ellenőrzése a szerveroldalon kell, hogy történjen, amit a megfelelő űrlap elem kiválasztásával és érvényesítési szabályok megadásával biztosíthat.

A rádió- vagy checkbox listák egyes elemeihez beállíthatunk HTML attribútumot különböző értékekkel mindegyikhez. Figyelje meg a kettőspontot a style: után, amely biztosítja az érték kiválasztását kulcs szerint:

$colors = ['r' => 'piros', 'g' => 'zöld', 'b' => 'kék'];
$styles = ['r' => 'background:red', 'g' => 'background:green'];
$form->addCheckboxList('colors', 'Színek:', $colors)
	->setHtmlAttribute('style:', $styles);

Kiírja:

<label><input type="checkbox" name="colors[]" style="background:red" value="r">piros</label>
<label><input type="checkbox" name="colors[]" style="background:green" value="g">zöld</label>
<label><input type="checkbox" name="colors[]" value="b">kék</label>

Logikai attribútumok, mint a readonly, beállításához használhatunk kérdőjeles írásmódot:

$form->addCheckboxList('colors', 'Színek:', $colors)
	->setHtmlAttribute('readonly?', 'r'); // több kulcshoz használjon tömböt, pl. ['r', 'g']

Kiírja:

<label><input type="checkbox" name="colors[]" readonly value="r">piros</label>
<label><input type="checkbox" name="colors[]" value="g">zöld</label>
<label><input type="checkbox" name="colors[]" value="b">kék</label>

Select boxok esetén a setHtmlAttribute() metódus a <select> elem attribútumait állítja be. Ha az egyes <option> elemek attribútumait szeretnénk beállítani, használjuk a setOptionAttribute() metódust. Működnek a fentebb említett kettőspontos és kérdőjeles írásmódok is:

$form->addSelect('colors', 'Színek:', $colors)
	->setOptionAttribute('style:', $styles);

Kiírja:

<select name="colors">
	<option value="r" style="background:red">piros</option>
	<option value="g" style="background:green">zöld</option>
	<option value="b">kék</option>
</select>

Prototípusok

A HTML attribútumok beállításának alternatív módja a minta módosítása, amelyből a HTML elem generálódik. A minta egy Html objektum, és a getControlPrototype() metódus adja vissza:

$input = $form->addInteger('number', 'Szám:');
$html = $input->getControlPrototype(); // <input>
$html->class('big-number');            // <input class="big-number">

Ezzel a módszerrel módosítható a címke mintája is, amelyet a getLabelPrototype() ad vissza:

$html = $input->getLabelPrototype(); // <label>
$html->class('distinctive');         // <label class="distinctive">

A Checkbox, CheckboxList és RadioList elemeknél befolyásolhatja annak az elemnek a mintáját, amely az egész elemet csomagolja. Ezt a getContainerPrototype() adja vissza. Alapértelmezett állapotban ez egy „üres” elem, tehát semmi sem jelenik meg, de azzal, hogy nevet adunk neki, megjelenítésre kerül:

$input = $form->addCheckbox('send');
$html = $input->getContainerPrototype();
$html->setName('div'); // <div>
$html->class('check'); // <div class="check">
echo $input->getControl();
// <div class="check"><label><input type="checkbox" name="send"></label></div>

CheckboxList és RadioList esetén befolyásolható az egyes elemek elválasztójának mintája is, amelyet a getSeparatorPrototype() metódus ad vissza. Alapértelmezett állapotban ez a <br> elem. Ha páros elemre változtatja, akkor az egyes elemeket csomagolni fogja elválasztás helyett. Továbbá befolyásolható az egyes elemek címkéjének HTML elem mintája is, amelyet a getItemLabelPrototype() ad vissza.

Fordítás

Ha többnyelvű alkalmazást programoz, valószínűleg szüksége lesz az űrlap különböző nyelvi változatokban történő megjelenítésére. A Nette Framework ehhez definiál egy fordítási interfészt Nette\Localization\Translator. A Nette-ben nincs alapértelmezett implementáció, választhat igényei szerint több kész megoldás közül, amelyeket a Componette oldalon talál. Dokumentációjukban megtudhatja, hogyan konfigurálja a fordítót.

Az űrlapok támogatják a szövegek fordítón keresztüli kiírását. Ezt a setTranslator() metódussal adjuk át nekik:

$form->setTranslator($translator);

Ettől a pillanattól kezdve nemcsak az összes címke, hanem az összes hibaüzenet vagy select box elem is lefordításra kerül egy másik nyelvre.

Az egyes űrlap elemeknél lehetőség van más fordító beállítására vagy a fordítás teljes kikapcsolására null értékkel:

$form->addSelect('carModel', 'Modell:', $cars)
	->setTranslator(null);

Az érvényesítési szabályoknál a fordítónak specifikus paraméterek is átadásra kerülnek, például a szabálynál:

$form->addPassword('password', 'Jelszó:')
	->addRule($form::MinLength, 'A jelszónak legalább %d karakter hosszúnak kell lennie', 8);

a fordító ezekkel a paraméterekkel hívódik meg:

$translator->translate('A jelszónak legalább %d karakter hosszúnak kell lennie', 8);

és így kiválaszthatja a karakter szó helyes többes számú alakját a szám alapján.

onRender esemény

Közvetlenül azelőtt, hogy az űrlap megjelenne, meghívathatjuk a kódunkat. Ez például kiegészítheti az űrlap elemeket HTML osztályokkal a helyes megjelenítés érdekében. A kódot az onRender tömbhöz adjuk hozzá:

$form->onRender[] = function ($form) {
	BootstrapCSS::initialize($form);
};
verzió: 4.0