Randarea formularelor

Aspectul formularelor poate fi foarte divers. În practică, putem întâlni două extreme. Pe de o parte, există nevoia de a randa în aplicație o serie de formulare care sunt vizual asemănătoare ca două picături de apă și apreciem randarea ușoară fără șablon folosind $form->render(). Acesta este de obicei cazul interfețelor de administrare.

Pe de altă parte, există formulare diverse, unde regula este: fiecare piesă este originală. Forma lor este cel mai bine descrisă folosind limbajul HTML în șablonul formularului. Și, desigur, pe lângă cele două extreme menționate, vom întâlni multe formulare care se situează undeva între.

Randarea cu Latte

Sistemul de șabloane Latte facilitează fundamental randarea formularelor și a elementelor acestora. Mai întâi, vom arăta cum să randăm formularele manual, element cu element, obținând astfel control deplin asupra codului. Mai târziu, vom arăta cum se poate automatiza o astfel de randare.

Puteți genera designul șablonului Latte al formularului folosind metoda Nette\Forms\Blueprint::latte($form), care îl va afișa în pagina browserului. Apoi, este suficient să selectați codul cu un clic și să-l copiați în proiect.

{control}

Cel mai simplu mod de a randa un formular este să scrieți în șablon:

{control signInForm}

Puteți influența aspectul formularului randat astfel prin configurarea Rendererului și a elementelor individuale.

n:name

Definirea formularului în codul PHP poate fi extrem de ușor legată de codul HTML. Este suficient doar să adăugați atributele n:name. Atât de simplu este!

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>

Aveți control deplin asupra aspectului codului HTML rezultat. Dacă utilizați atributul n:name la elementele <select>, <button> sau <textarea>, conținutul lor intern se va completa automat. Tag-ul <form n:name> creează, în plus, o variabilă locală $form cu obiectul formularului randat, iar tag-ul de închidere </form> randează toate elementele hidden nerandate (același lucru este valabil și pentru {form} ... {/form}).

Nu trebuie însă să uităm de randarea posibilelor mesaje de eroare. Atât cele care au fost adăugate la elementele individuale prin metoda addError() (folosind {inputError}), cât și cele adăugate direct la formular (returnate de $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>

Elementele de formular mai complexe, cum ar fi RadioList sau CheckboxList, pot fi randate astfel, element cu element:

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

{label} {input}

Nu doriți să vă gândiți la fiecare element ce element HTML să folosiți pentru el în șablon, dacă <input>, <textarea> etc.? Soluția este tag-ul universal {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>

Dacă formularul utilizează un translator, textul din interiorul tag-urilor {label} va fi tradus.

Chiar și în acest caz, elementele de formular mai complexe, cum ar fi RadioList sau CheckboxList, pot fi randate element cu element:

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

Pentru a randa doar <input> în elementul Checkbox, utilizați {input myCheckbox:}. Atributele HTML în acest caz separați-le întotdeauna cu virgulă {input myCheckbox:, class: required}.

{inputError}

Afișează mesajul de eroare pentru elementul formularului, dacă are unul. Mesajul este de obicei încapsulat într-un element HTML pentru stilizare. Puteți preveni randarea elementului gol, dacă nu există mesaj, elegant folosind n:ifcontent:

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

Putem verifica prezența unei erori cu metoda hasErrors() și, în funcție de aceasta, seta clasa elementului părinte:

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

{form}

Tag-urile {form signInForm}...{/form} sunt o alternativă la <form n:name="signInForm">...</form>.

Randare automată

Datorită tag-urilor {input} și {label}, putem crea cu ușurință un șablon generic pentru orice formular. Acesta va itera și va randa succesiv toate elementele sale, cu excepția elementelor hidden, care se randează automat la închiderea formularului cu tag-ul </form>. Numele formularului randat va fi așteptat în variabila $form.

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

Tag-urile pereche auto-închise {label .../} utilizate afișează etichetele provenite din definirea formularului în codul PHP.

Salvați acest șablon generic, de exemplu, în fișierul basic-form.latte și pentru a randa formularul, este suficient să-l includeți și să transmiteți numele (sau instanța) formularului în parametrul $form:

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

Dacă doriți să interveniți în aspectul unui anumit formular în timpul randării și, de exemplu, să randați un element diferit, cea mai simplă cale este să pregătiți blocuri în șablon, care vor putea fi ulterior suprascrise. Blocurile pot avea și nume dinamice, astfel încât se poate insera în ele și numele elementului randat. De exemplu:

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

Pentru elementul, de exemplu, username, se va crea astfel blocul input-username, care poate fi ușor suprascris folosind tag-ul {embed}:

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

Alternativ, întregul conținut al șablonului basic-form.latte poate fi definit ca un bloc, inclusiv parametrul $form:

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

Datorită acestui fapt, apelarea sa va fi puțin mai simplă:

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

Blocul trebuie importat într-un singur loc, și anume la începutul șablonului de layout:

{import basic-form.latte}

Cazuri speciale

Dacă aveți nevoie să randați doar partea interioară a formularului fără tag-urile HTML <form>, de exemplu la trimiterea snippet-urilor, ascundeți-le folosind atributul 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>

Tag-ul {formContainer} ajută la randarea elementelor din interiorul containerului formularului.

<p>Ce știri doriți să primiți:</p>

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

Randare fără Latte

Cel mai simplu mod de a randa un formular este să apelați:

$form->render();

Puteți influența aspectul formularului randat astfel prin configurarea Rendererului și a elementelor individuale.

Randare manuală

Fiecare element de formular dispune de metode care generează codul HTML al câmpului formularului și al etichetei. Îl pot returna fie ca șir, fie ca obiect Nette\Utils\Html:

  • getControl(): Html|string returnează codul HTML al elementului
  • getLabel($caption = null): Html|string|null returnează codul HTML al etichetei, dacă există

Formularul poate fi astfel randat element cu element:

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

În timp ce la unele elemente getControl() returnează un singur element HTML (de ex. <input>, <select> etc.), la altele returnează o întreagă bucată de cod HTML (CheckboxList, RadioList). Într-un astfel de caz, puteți utiliza metode care generează input-uri și etichete individuale, pentru fiecare element în parte:

  • getControlPart($key = null): ?Html returnează codul HTML al unui element
  • getLabelPart($key = null): ?Html returnează codul HTML al etichetei unui element

Aceste metode au din motive istorice prefixul get, dar mai bun ar fi generate, deoarece la fiecare apelare creează și returnează un nou element Html.

Renderer

Este un obiect care asigură randarea formularului. Acesta poate fi setat cu metoda $form->setRenderer. I se transmite controlul la apelarea metodei $form->render().

Dacă nu setăm propriul renderer, va fi utilizat rendererul implicit Nette\Forms\Rendering\DefaultFormRenderer. Acesta randează elementele formularului sub formă de tabel HTML. Ieșirea arată astfel:

<table>
<tr class="required">
	<th><label class="required" for="frm-name">Nume:</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">Vârstă:</label></th>

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

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

Dacă să folosim sau nu un tabel pentru structura formularului este discutabil, iar mulți webdesigneri preferă alt markup. De exemplu, o listă de definiții. Vom reconfigura, prin urmare, DefaultFormRenderer astfel încât să randeze formularul sub formă de listă. Configurarea se realizează prin editarea array-ului $wrappers. Primul index reprezintă întotdeauna zona, iar al doilea atributul său. Zonele individuale sunt ilustrate în imagine:

În mod standard, grupul de elemente controls este încapsulat într-un tabel <table>, fiecare pair reprezintă un rând de tabel <tr>, iar perechea label și control sunt celule <th> și <td>. Acum vom schimba elementele încapsulatoare. Vom insera zona controls într-un container <dl>, vom lăsa zona pair fără container, vom insera label în <dt> și, în final, vom încapsula control cu tag-urile <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();

Rezultatul este acest cod HTML:

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

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


	<dt><label class="required" for="frm-age">Vârstă:</label></dt>

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


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

În array-ul wrappers se pot influența multe alte atribute:

  • adăugarea claselor CSS la tipurile individuale de elemente de formular
  • distingerea rândurilor pare și impare prin clasa CSS
  • diferențierea vizuală a elementelor obligatorii și opționale
  • determinarea dacă mesajele de eroare se afișează direct lângă elemente sau deasupra formularului

Opțiuni

Comportamentul Rendererului poate fi controlat și prin setarea opțiunilor pe elementele individuale ale formularului. Astfel, se poate seta o descriere care se va afișa lângă câmpul de intrare:

$form->addText('phone', 'Număr:')
	->setOption('description', 'Acest număr va rămâne ascuns');

Dacă dorim să plasăm conținut HTML în el, utilizăm clasa Html

use Nette\Utils\Html;

$form->addText('phone', 'Număr:')
	->setOption('description', Html::el('p')
		->setHtml('<a href="...">Condițiile de păstrare a numărului dumneavoastră</a>')
	);

Elementul Html poate fi utilizat și în locul etichetei: $form->addCheckbox('conditions', $label).

Gruparea elementelor

Rendererul permite gruparea elementelor în grupuri vizuale (fieldset-uri):

$form->addGroup('Date personale');

După crearea unui nou grup, acesta devine activ și fiecare element nou adăugat este, de asemenea, adăugat în el. Deci, formularul poate fi construit în acest mod:

$form = new Form;
$form->addGroup('Date personale');
$form->addText('name', 'Numele dumneavoastră:');
$form->addInteger('age', 'Vârsta dumneavoastră:');
$form->addEmail('email', 'Email:');

$form->addGroup('Adresa de livrare');
$form->addCheckbox('send', 'Livrează la adresă');
$form->addText('street', 'Stradă:');
$form->addText('city', 'Oraș:');
$form->addSelect('country', 'Țara:', $countries);

Rendererul randează mai întâi grupurile și abia apoi elementele care nu aparțin niciunui grup.

Suport pentru Bootstrap

În exemple veți găsi exemple despre cum să configurați Rendererul pentru Twitter Bootstrap 2, Bootstrap 3 și Bootstrap 4

Atribute HTML

Pentru a seta atribute HTML arbitrare ale elementelor formularului, folosim metoda setHtmlAttribute(string $name, $value = true):

$form->addInteger('number', 'Număr:')
	->setHtmlAttribute('class', 'big-number');

$form->addSelect('rank', 'Sortare după:', ['preț', 'nume'])
	->setHtmlAttribute('onchange', 'submit()'); // trimite la modificare


// Pentru a seta atributele formularului <form> în sine
$form->setHtmlAttribute('id', 'myForm');

Specificarea tipului elementului:

$form->addText('tel', 'Telefonul dumneavoastră:')
	->setHtmlType('tel')
	->setHtmlAttribute('placeholder', 'scrieți telefonul');

Setarea tipului și a altor atribute servește doar în scopuri vizuale. Verificarea corectitudinii intrărilor trebuie să aibă loc pe server, ceea ce asigurați prin alegerea elementului de formular adecvat și specificarea regulilor de validare.

Elementelor individuale din listele radio sau checkbox le putem seta un atribut HTML cu valori diferite pentru fiecare dintre ele. Observați două puncte după style:, care asigură alegerea valorii după cheie:

$colors = ['r' => 'roșu', 'g' => 'verde', 'b' => 'albastru'];
$styles = ['r' => 'background:red', 'g' => 'background:green'];
$form->addCheckboxList('colors', 'Culori:', $colors)
	->setHtmlAttribute('style:', $styles);

Afișează:

<label><input type="checkbox" name="colors[]" style="background:red" value="r">roșu</label>
<label><input type="checkbox" name="colors[]" style="background:green" value="g">verde</label>
<label><input type="checkbox" name="colors[]" value="b">albastru</label>

Pentru a seta atribute logice, cum ar fi readonly, putem folosi notația cu semn de întrebare:

$form->addCheckboxList('colors', 'Culori:', $colors)
	->setHtmlAttribute('readonly?', 'r'); // pentru mai multe chei utilizați un array, de ex. ['r', 'g']

Afișează:

<label><input type="checkbox" name="colors[]" readonly value="r">roșu</label>
<label><input type="checkbox" name="colors[]" value="g">verde</label>
<label><input type="checkbox" name="colors[]" value="b">albastru</label>

În cazul selectbox-urilor, metoda setHtmlAttribute() setează atributele elementului <select>. Dacă dorim să setăm atributele elementelor individuale <option>, folosim metoda setOptionAttribute(). Funcționează și notațiile cu două puncte și semn de întrebare menționate mai sus:

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

Afișează:

<select name="colors">
	<option value="r" style="background:red">roșu</option>
	<option value="g" style="background:green">verde</option>
	<option value="b">albastru</option>
</select>

Prototipuri

O modalitate alternativă de setare a atributelor HTML constă în modificarea șablonului din care se generează elementul HTML. Șablonul este un obiect Html și este returnat de metoda getControlPrototype():

$input = $form->addInteger('number', 'Număr:');
$html = $input->getControlPrototype(); // <input>
$html->class('big-number');            // <input class="big-number">

În acest mod se poate modifica și șablonul etichetei, returnat de getLabelPrototype():

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

La elementele Checkbox, CheckboxList și RadioList puteți influența șablonul elementului care încapsulează întregul element. Acesta este returnat de getContainerPrototype(). În starea implicită, este un element „gol”, deci nu se randează nimic, dar prin setarea numelui său, va începe să se randeze:

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

În cazul CheckboxList și RadioList, se poate influența și șablonul separatorului elementelor individuale, returnat de metoda getSeparatorPrototype(). În starea implicită, este elementul <br>. Dacă îl schimbați într-un element pereche, va încapsula elementele individuale în loc să le separe. Și, în plus, se poate influența șablonul elementului HTML al etichetei la elementele individuale, returnat de getItemLabelPrototype().

Traducere

Dacă programați o aplicație multilingvă, probabil veți avea nevoie să randați formularul în diferite versiuni lingvistice. Nette Framework definește în acest scop o interfață pentru traducere Nette\Localization\Translator. În Nette nu există o implementare implicită, puteți alege în funcție de nevoile dumneavoastră din mai multe soluții gata făcute, pe care le găsiți pe Componette. În documentația lor veți afla cum să configurați translatorul.

Formularele suportă afișarea textelor prin translator. Îl transmitem folosind metoda setTranslator():

$form->setTranslator($translator);

De acum înainte, nu numai toate etichetele, ci și toate mesajele de eroare sau elementele select box-urilor vor fi traduse într-o altă limbă.

La elementele individuale ale formularului este posibil să setați un alt traducător sau să dezactivați complet traducerea cu valoarea null:

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

La regulile de validare, translatorului i se transmit și parametri specifici, de exemplu la regula:

$form->addPassword('password', 'Parolă:')
	->addRule($form::MinLength, 'Parola trebuie să aibă cel puțin %d caractere', 8);

se apelează translatorul cu acești parametri:

$translator->translate('Parola trebuie să aibă cel puțin %d caractere', 8);

și, prin urmare, poate alege forma corectă de plural a cuvântului caractere în funcție de număr.

Evenimentul onRender

Chiar înainte ca formularul să fie randat, putem lăsa să fie apelat codul nostru. Acesta poate, de exemplu, să completeze elementele formularului cu clase HTML pentru afișarea corectă. Adăugăm codul în array-ul onRender:

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