Oblikovanje obrazcev

Videz oblik je lahko zelo raznolik. V praksi lahko naletimo na dve skrajnosti. Po eni strani je treba v aplikaciji prikazati vrsto obrazcev, ki so si vizualno podobni, pri čemer cenimo enostavno prikazovanje brez predloge z uporabo spletne strani $form->render(). To je običajno primer upravnih vmesnikov.

Po drugi strani pa obstajajo različni obrazci, pri katerih je vsak izmed njih edinstven. Njihov videz je najbolje opisati s pomočjo jezika HTML v predlogi. In seveda bomo poleg obeh omenjenih skrajnosti naleteli na številne obrazce, ki so nekje vmes.

Renderiranje z Latte

Sistem predlog Latte bistveno olajša upodabljanje obrazcev in njihovih elementov. Najprej bomo pokazali, kako ročno upodabljati obrazce, element za elementom, da bi pridobili popoln nadzor nad kodo. Kasneje bomo pokazali, kako takšno upodabljanje avtomatizirati.

Predlog Latte predloge za obrazec lahko generirate z metodo Nette\Forms\Blueprint::latte($form), ki ga bo izpisala na stran brskalnika. Nato morate kodo preprosto izbrati s klikom in jo kopirati v svoj projekt.

{control}

Obrazec najlažje prikažete tako, da ga zapišete v predlogo:

{control signInForm}

Videz izrisanega obrazca lahko spremenite z nastavitvijo Rendererja in posameznih kontrolnih elementov.

n:name

Definicijo obrazca v kodi PHP je zelo enostavno povezati s kodo HTML. Preprosto dodajte atribute n:name. Tako enostavno je to!

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>

Videz nastale kode HTML je povsem v vaših rokah. Če uporabite atribut n:name z <select>, <button> ali <textarea> elementi, se njihova notranja vsebina samodejno izpolni. Poleg tega je <form n:name> oznaka ustvari lokalno spremenljivko $form z narisanim objektom obrazca in zaključnim </form> izriše vse neizrisane skrite elemente (enako velja za {form} ... {/form}).

Ne smemo pa pozabiti na izris morebitnih sporočil o napakah. Tako tistih, ki so bila posameznim elementom dodana z metodo addError() (z uporabo {inputError}), kot tistih, ki so bila dodana neposredno v obrazec (vrnjena z $form->getOwnErrors()):

<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>

Kompleksnejše elemente obrazca, kot sta RadioList ali CheckboxList, lahko prikažemo element za elementom:

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

{label} {input}

Ali ne želite za vsak element razmisliti, kateri element HTML uporabiti zanj v predlogi, ali <input>, <textarea> itd. Rešitev je univerzalna oznaka {input}:

<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>

Če obrazec uporablja prevajalnik, bo besedilo znotraj značk {label} prevedeno.

Tudi v tem primeru se lahko kompleksnejši elementi obrazca, kot sta RadioList ali CheckboxList, preslikajo po posameznih elementih:

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

Če želite prikazati element <input> samega elementa Checkbox, uporabite {input myCheckbox:}. Atributi HTML morajo biti ločeni z vejico {input myCheckbox:, class: required}.

{inputError}

Izpiše sporočilo o napaki za element obrazca, če ga vsebuje. Sporočilo je običajno zavito v element HTML za oblikovanje. Izogibanje izrisu praznega elementa, če ni sporočila, je mogoče elegantno opraviti s n:ifcontent:

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

Z metodo hasErrors() lahko zaznamo prisotnost napake in ustrezno nastavimo razred nadrejenega elementa:

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

{form}

Oznake {form signInForm}...{/form} so alternativa za <form n:name="signInForm">...</form>.

Samodejno upodabljanje

Z oznakama {input} in {label} lahko preprosto ustvarimo splošno predlogo za kateri koli obrazec. Ta bo iterirala in zaporedno prikazala vse elemente, razen skritih elementov, ki se samodejno prikažejo, ko se obrazec zaključi z oznako </form> z oznako. V spremenljivki $form bo pričakovala ime izrisanega obrazca.

<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>

Uporabljene samozapiralne parne oznake {label .../} prikazujejo oznake, ki izhajajo iz definicije obrazca v kodi PHP.

To splošno predlogo lahko shranite v datoteko basic-form.latte, za prikaz obrazca pa jo samo vključite in v parameter $form posredujete ime (ali primerek) obrazca:

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

Če bi radi vplivali na videz posameznega obrazca in en element izrisali drugače, potem je najlažje, da v predlogi pripravite bloke, ki jih lahko pozneje prepišete. Bloki imajo lahko tudi dinamična imena, zato lahko vanje vstavite ime elementa, ki ga želite narisati. Na primer:

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

Za element npr. username to ustvari blok input-username, ki ga lahko enostavno spremenite z uporabo oznake {embed}:

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

Druga možnost je, da se celotna vsebina predloge basic-form.latte opredeli kot blok, vključno s parametrom $form:

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

Tako bo uporaba nekoliko lažja:

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

Blok morate uvoziti le na enem mestu, na začetku predloge za postavitev:

{import basic-form.latte}

Posebni primeri

Če želite prikazati samo notranji del obrazca brez oznak HTML <form>na primer pri pošiljanju izsekov, jih skrijte s pomočjo atributa n:tag-if:

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

Oznaka formContainer pomaga pri izrisovanju vnosov znotraj vsebnika obrazca.

<p>Which news you wish to receive:</p>

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

Renderiranje brez Latte

Obrazec najlažje prikažete tako, da pokličete:

$form->render();

Videz izrisanega obrazca lahko spremenite z nastavitvijo Rendererja in posameznih kontrolnih elementov.

Ročno upodabljanje

Vsak element obrazca ima metode, ki generirajo kodo HTML za polje in oznako obrazca. Vrnejo jo lahko kot niz ali objekt Nette\Utils\Html:

  • getControl(): Html|string vrne kodo HTML elementa
  • getLabel($caption = null): Html|string|null vrne kodo HTML oznake, če obstaja

To omogoča prikaz obrazca po posameznih elementih:

<?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') ?>

Medtem ko za nekatere elemente getControl() vrne en sam element HTML (npr. <input>, <select> itd.), za druge vrne celoten del kode HTML (CheckboxList, RadioList). V tem primeru lahko uporabite metode, ki generirajo posamezne vhode in oznake, za vsak element posebej:

  • getControlPart($key = null): ?Html vrne kodo HTML posameznega elementa
  • getLabelPart($key = null): ?Html vrne kodo HTML za oznako posameznega elementa

Tem metodam je iz zgodovinskih razlogov dodana predpona get, vendar bi bila generate boljša, saj ob vsakem klicu ustvari in vrne nov element Html.

Renderer

To je objekt, ki zagotavlja upodabljanje obrazca. Nastavite ga lahko z metodo $form->setRenderer. Ob klicu metode $form->render() se mu posreduje nadzor.

Če ne nastavimo lastnega upodabljavca, bo uporabljen privzeti upodabljavec Nette\Forms\Rendering\DefaultFormRenderer. Ta bo elemente obrazca prikazal kot tabelo HTML. Izpis je videti takole:

<table>
<tr class="required">
	<th><label class="required" for="frm-name">Name:</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">Age:</label></th>

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

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

Mnogi spletni oblikovalci imajo raje drugačno označevanje, na primer seznam. Spletno mesto DefaultFormRenderer lahko konfiguriramo tako, da se sploh ne bo izrisalo v tabelo. Nastaviti moramo le ustrezne ovitke $wrappers. Prvi indeks vedno predstavlja območje, drugi pa element. Vsa ustrezna območja so prikazana na sliki:

Privzeto je skupina controls ovita v <table>, vsak naslov pair pa je vrstica tabele. <tr> ki vsebuje par label in control (celice <th> in <td>). Spremenimo vse te ovijalne elemente. controls bomo zavili v <dl>, pair bomo pustili samega, label bomo vstavili v <dt> in zavijemo control v <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();

Rezultat je naslednji delček:

<dl>
	<dt><label class="required" for="frm-name">Name:</label></dt>

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


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

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


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

Obalčki lahko vplivajo na številne atribute. Na primer:

  • dodajte posebne razrede CSS vsakemu vnosu obrazca
  • razlikovanje med lihimi in sodimi vrsticami
  • poskrbite, da se obvezni in neobvezni elementi rišejo drugače.
  • določite, ali se sporočila o napakah prikazujejo nad obrazcem ali blizu vsakega elementa

Možnosti

Obnašanje programa Renderer lahko nadzorujete tudi z nastavitvijo opcij na posameznih elementih obrazca. Tako lahko nastavite namig, ki se prikaže ob vnosnem polju:

$form->addText('phone', 'Number:')
	->setOption('description', 'This number will remain hidden');

Če želimo vanj vstaviti vsebino HTML, uporabimo razred Html.

use Nette\Utils\Html;

$form->addText('phone', 'Phone:')
	->setOption('description', Html::el('p')
		->setHtml('<a href="...">Terms of service.</a>')
	);

Element Html lahko uporabimo tudi namesto etikete: $form->addCheckbox('conditions', $label).

Združevanje vhodnih podatkov v skupine

Renderer omogoča združevanje elementov v vizualne skupine (fieldsets):

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

Ustvarjanje nove skupine jo aktivira – vsi elementi, ki so dodani naprej, so dodani v to skupino. Obrazec lahko sestavite takole:

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

Renderer najprej izriše skupine, nato pa elemente, ki ne pripadajo nobeni skupini.

Podpora Bootstrap

Najdete lahko primere konfiguracije Rendererja za Twitter Bootstrap 2, Bootstrap 3 in Bootstrap 4

Atributi HTML

Z uporabo spletne strani setHtmlAttribute(string $name, $value = true) lahko kontrolnim elementom obrazca nastavite poljubne atribute HTML:

$form->addInteger('number', 'Number:')
	->setHtmlAttribute('class', 'big-number');

$form->addSelect('rank', 'Order by:', ['price', 'name'])
	->setHtmlAttribute('onchange', 'submit()'); // ob spremembi pokliče funkcijo JS submit().


// uporaba na <form>
$form->setHtmlAttribute('id', 'myForm');

Nastavitev vrste vnosa:

$form->addText('tel', 'Your telephone:')
	->setHtmlType('tel')
	->setHtmlAttribute('placeholder', 'Please, fill in your telephone');

V seznamih z radijskimi ali potrditvenimi polji lahko posameznim elementom nastavimo atribut HTML z različnimi vrednostmi za vsakega od njih. Upoštevajte dvopičje za style:, ki zagotavlja, da je vrednost izbrana po ključu:

$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue'];
$styles = ['r' => 'background:red', 'g' => 'background:green'];
$form->addCheckboxList('colors', 'Colors:', $colors)
	->setHtmlAttribute('style:', $styles);

Renders:

<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label>
<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label>
<label><input type="checkbox" name="colors[]" value="b">blue</label>

Za logični atribut HTML (ki nima vrednosti, na primer readonly), lahko uporabite vprašalnik:

$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue'];
$form->addCheckboxList('colors', 'Colors:', $colors)
	->setHtmlAttribute('readonly?', 'r'); // uporabite polje za več ključev, npr. ['r', 'g']

Renders:

<label><input type="checkbox" name="colors[]" readonly value="r">red</label>
<label><input type="checkbox" name="colors[]" value="g">green</label>
<label><input type="checkbox" name="colors[]" value="b">blue</label>

Pri izbirnih poljih metoda setHtmlAttribute() nastavi atribute <select> elementa. Če želimo nastaviti atribute za vsako <option>, bomo uporabili metodo setOptionAttribute(). Tudi zgoraj uporabljena dvopičje in vprašalno znamenje delujeta:

$form->addSelect('colors', 'Colors:', $colors)
	->setOptionAttribute('style:', $styles);

Renders:

<select name="colors">
	<option value="r" style="background:red">red</option>
	<option value="g" style="background:green">green</option>
	<option value="b">blue</option>
</select>

Prototipi

Alternativni način določanja atributov HTML je spreminjanje predloge, iz katere se ustvari element HTML. Predloga je predmet Html in jo vrne metoda getControlPrototype():

$input = $form->addInteger('number');
$html = $input->getControlPrototype(); // <input>
$html->class('big-number');            // <input class="big-number">

Na ta način je mogoče spremeniti tudi predlogo za etiketo, ki jo vrne metoda getLabelPrototype():

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

Za elemente Checkbox, CheckboxList in RadioList lahko vplivate na predlogo elementa, ki obdaja element. Vrne jo getContainerPrototype(). Privzeto je to “prazen” element, zato se ne prikaže nič, vendar se bo s podelitvijo imena prikazal:

$input = $form->addCheckbox('send');
echo $input->getControl();
// <label><input type="checkbox" name="send"></label>

$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>

V primeru CheckboxList in RadioList je mogoče vplivati tudi na vzorec ločevalnika elementov, ki ga vrne metoda getSeparatorPrototype(). Privzeto je to element <br>. Če ga spremenite v parni element, bo zavil posamezne elemente, namesto da bi jih ločil. Prav tako je mogoče vplivati na predlogo elementa HTML za oznake elementov, ki jo vrne metoda getItemLabelPrototype().

Prevajanje

Če programirate večjezično aplikacijo, boste verjetno morali obrazec prikazati v različnih jezikih. Okvir Nette v ta namen opredeljuje vmesnik za prevajanje Nette\Localization\Translator. V Nette ni privzete implementacije, glede na svoje potrebe lahko izbirate med več pripravljenimi rešitvami, ki jih najdete na portalu Componette. V njihovi dokumentaciji je opisano, kako konfigurirati prevajalnik.

Obrazec podpira izpis besedila prek prevajalnika. Predamo ga z uporabo metode setTranslator():

$form->setTranslator($translator);

Odslej bodo v drug jezik prevedene ne le vse oznake, temveč tudi vsa sporočila o napakah ali vnosi v izbirna polja.

Za posamezne elemente obrazca je mogoče nastaviti drugačen prevajalnik ali popolnoma onemogočiti prevajanje s null:

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

Pri pravilih potrjevanja se prevajalniku posredujejo tudi določeni parametri, na primer za pravilo:

$form->addPassword('password', 'Password:')
	->addRule($form::MinLength, 'Password has to be at least %d characters long', 8)

se prevajalnik pokliče z naslednjimi parametri:

$translator->translate('Password has to be at least %d characters long', 8);

in tako lahko izbere pravilno množinsko obliko za besedo characters po številu.

Dogodek onRender

Tik preden se obrazec izriše, lahko sprožimo našo kodo. Ta lahko na primer elementom obrazca doda razrede HTML za pravilen prikaz. Kodo dodamo v polje onRender:

$form->onRender[] = function ($form) {
	BootstrapCSS::initialize($form);
};
različica: 4.0