Redarea formularelor

Aspectul formelor poate fi foarte divers. În practică, putem întâlni două extreme. Pe de o parte, există necesitatea de a reda într-o aplicație o serie de formulare care să fie asemănătoare din punct de vedere vizual, iar noi apreciem redarea ușoară fără șablon cu ajutorul $form->render(). Acesta este, de obicei, cazul interfețelor administrative.

Pe de altă parte, există diverse formulare în care fiecare este unic. Aspectul lor este cel mai bine descris folosind limbajul HTML în șablon. Și, bineînțeles, pe lângă cele două extreme menționate, vom întâlni multe forme care se încadrează undeva la mijloc.

Renderizare cu Latte

Sistemul de modelare Latte facilitează în mod fundamental redarea formularelor și a elementelor acestora. În primul rând, vom arăta cum să redăm formularele manual, element cu element, pentru a obține un control total asupra codului. Ulterior vom arăta cum să automatizăm această redare.

Puteți obține propunerea unui șablon Latte pentru formularul generat cu ajutorul metodei Nette\Forms\Blueprint::latte($form), care îl va afișa în pagina de browser. Apoi, trebuie doar să selectați codul cu un clic și să îl copiați în proiectul dvs.

{control}

Cel mai simplu mod de a reda un formular este de a scrie într-un șablon:

{control signInForm}

Aspectul formularului redat poate fi modificat prin configurarea Renderer și a controalelor individuale.

n:name

Este extrem de ușor să legați definiția formularului din codul PHP cu codul HTML. Trebuie 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>

Aspectul codului HTML rezultat este în întregime în mâinile dumneavoastră. Dacă utilizați atributul n:name cu <select>, <button> sau <textarea> conținutul lor intern este completat automat. În plus, atributul <form n:name> creează o variabilă locală $form cu obiectul de formular desenat și cu eticheta de închidere </form> desenează toate elementele ascunse nedesenate (același lucru este valabil și pentru {form} ... {/form}).

Cu toate acestea, nu trebuie să uităm să redăm eventualele mesaje de eroare. Atât pe cele care au fost adăugate la elementele individuale prin metoda addError() (folosind {inputError}), cât și pe cele adăugate direct în 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 redate 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 vreți să vă gândiți pentru 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 traducător, textul din interiorul etichetelor {label} va fi tradus.

Din nou, elementele de formular mai complexe, cum ar fi RadioList sau CheckboxList, pot fi redate element cu element:

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

Pentru a reda elementul <input> însuși în elementul Checkbox, utilizați {input myCheckbox:}. Atributele HTML trebuie să fie separate prin virgulă {input myCheckbox:, class: required}.

{inputError}

Tipărește un mesaj de eroare pentru elementul de formular, dacă acesta are unul. Mesajul este de obicei inclus într-un element HTML pentru stilizare. Evitarea redării unui element gol în cazul în care nu există un mesaj poate fi realizată în mod elegant cu n:ifcontent:

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

Putem detecta prezența unei erori folosind metoda hasErrors() și putem seta clasa elementului părinte în mod corespunzător:

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

{form}

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

Redare automată

Cu ajutorul etichetelor {input} și {label}, putem crea cu ușurință un șablon generic pentru orice formular. Acesta va itera și va reda toate elementele sale secvențial, cu excepția elementelor ascunse, care sunt redate automat atunci când formularul este încheiat cu tag-ul </form> . Se va aștepta ca numele formularului redat să fie introdus î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>

Etichetele pereche cu închidere automată utilizate {label .../} afișează etichetele care provin din definiția formularului din codul PHP.

Puteți salva acest șablon generic în fișierul basic-form.latte, iar pentru a reda formularul, trebuie doar să îl includeți și să treceți numele formularului (sau instanța) la parametrul $form:

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

Dacă doriți să influențați aspectul unui anumit formular și să desenați un element în mod diferit, cel mai simplu este să pregătiți blocuri în șablon care pot fi suprascrise ulterior. De asemenea, blocurile pot avea nume dinamice, astfel încât puteți introduce în ele numele elementului care urmează să fie desenat. De exemplu:

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

Pentru elementul de exemplu username, se creează blocul input-username, care poate fi ușor de înlocuit prin utilizarea tag-ului {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}

Acest lucru îl va face puțin mai ușor de utilizat:

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

Trebuie doar să importați blocul într-un singur loc, la începutul modelului de machetă:

{import basic-form.latte}

Cazuri speciale

Dacă aveți nevoie să redați doar partea interioară a formularului fără etichete HTML <form>de exemplu, atunci când trimiteți fragmente, 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 redarea intrărilor în interiorul unui container de formular.

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

Rendering fără Latte

Cel mai simplu mod de a reda un formular este de a apela:

$form->render();

Aspectul formularului redat poate fi modificat prin configurarea Renderer și a controalelor individuale.

Redare manuală

Fiecare element de formular are metode care generează codul HTML pentru câmpul și eticheta formularului. Acestea îl pot returna fie sub forma unui șir de caractere, fie sub forma unui 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ă.

Acest lucru permite ca formularul să fie redat 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 pentru unele elemente, getControl() returnează un singur element HTML (de exemplu. <input>, <select> etc.), pentru altele se returnează o întreagă bucată de cod HTML (CheckboxList, RadioList). În acest caz, puteți utiliza metode care generează intrări și etichete individuale, pentru fiecare element în parte:

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

Aceste metode sunt prefixate cu get din motive istorice, dar generate ar fi mai bine, deoarece creează și returnează un nou element Html la fiecare apel.

Redator

Este un obiect care asigură redarea formularului. Acesta poate fi setat prin metoda $form->setRenderer. Este trecut în control atunci când este apelată metoda $form->render().

În cazul în care nu setează un renderizator personalizat, se va utiliza renderizatorul implicit Nette\Forms\Rendering\DefaultFormRenderer. Aceasta va reda elementele formularului sub forma unui tabel HTML. Rezultatul arată astfel:

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

Depinde de dvs. dacă doriți să utilizați un tabel sau nu, iar mulți designeri web preferă diferite marcaje, de exemplu o listă. Putem configura DefaultFormRenderer astfel încât să nu fie redat deloc într-un tabel. Trebuie doar să setăm $wrappers corespunzătoare. Primul index reprezintă întotdeauna o zonă, iar al doilea un element. Toate zonele respective sunt prezentate în imagine:

În mod implicit, un grup de controls este învelit în <table>, iar fiecare pair este un rând de tabel <tr> care conține o pereche de label și control (celule <th> și <td>). Haideți să schimbăm toate aceste elemente de înfășurare. Vom transforma controls în <dl>, vom lăsa pair de unul singur, vom pune label în <dt> și vom transforma control în <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();

Rezultă următorul fragment:

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

Wrappers pot afecta multe atribute. De exemplu:

  • adăugați clase CSS speciale la fiecare intrare din formular
  • faceți distincția între liniile pare și impare
  • faceți ca obligatoriu și opțional să fie desenate diferit
  • setați, dacă mesajele de eroare sunt afișate deasupra formularului sau în apropierea fiecărui element

Opțiuni

Comportamentul lui Renderer poate fi controlat și prin setarea opțiunilor pe elementele individuale ale formularului. Astfel, puteți seta tooltip-ul care este afișat lângă câmpul de intrare:

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

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

use Nette\Utils\Html;

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

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

Gruparea intrărilor

Renderer permite gruparea elementelor în grupuri vizuale (fieldsets):

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

Crearea unui nou grup îl activează – toate elementele adăugate ulterior sunt adăugate la acest grup. Puteți construi un formular în felul următor:

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

Dispozitivul de redare desenează mai întâi grupurile și apoi elementele care nu aparțin niciunui grup.

Suport Bootstrap

Puteți găsi exemple de configurare a Renderer pentru Twitter Bootstrap 2, Bootstrap 3 și Bootstrap 4

Atribute HTML

Pentru a seta atribute HTML arbitrare pentru elementele de formular, utilizați metoda setHtmlAttribute(string $name, $value = true):

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

$form->addSelect('rank', 'Order by:', ['price', 'name'])
	->setHtmlAttribute('onchange', 'submit()'); // solicită funcția JS submit() la schimbare


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

Specificarea tipului de element:

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

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

Pentru elementele individuale din listele de tip radio sau checkbox, putem seta un atribut HTML cu valori diferite pentru fiecare dintre ele. Observați cele două puncte după style:, care asigură că valoarea este selectată pe baza cheii:

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

Redă:

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

Pentru stabilirea atributelor booleene, cum ar fi readonly, se poate utiliza notația cu semnul întrebării:

$form->addCheckboxList('colors', 'Colors:', $colors)
	->setHtmlAttribute('readonly?', 'r'); // se utilizează matrice pentru mai multe chei, de exemplu ['r', 'g']

Redă:

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

Pentru casetele de selectare, metoda setHtmlAttribute() setează atributele elementelor de tip <select> element. Dacă dorim să setăm atributele pentru fiecare <option>, vom folosi metoda setOptionAttribute(). De asemenea, funcționează și cele două puncte și semnul de întrebare folosite mai sus:

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

Redă:

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

Prototipuri

O modalitate alternativă de a seta atributele HTML este de a modifica șablonul din care este generat elementul HTML. Șablonul este un obiect Html și este returnat de metoda getControlPrototype():

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

Șablonul de etichetă returnat de getLabelPrototype() poate fi, de asemenea, modificat în acest mod:

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

Pentru elementele Checkbox, CheckboxList și RadioList puteți influența șablonul elementului care înfășoară elementul. Acesta este returnat de getContainerPrototype(). În mod implicit, este un element “gol”, deci nu este redat nimic, dar dacă i se dă un nume, acesta va fi redat:

$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 este, de asemenea, posibil să se influențeze modelul separatorului de elemente returnat de metoda getSeparatorPrototype(). În mod implicit, acesta este un element <br>. Dacă îl schimbați într-un element pereche, acesta va înfășura elementele individuale în loc să le separe. De asemenea, este posibil să se influențeze șablonul elementului HTML al etichetelor elementelor, care returnează getItemLabelPrototype().

Traducerea

Dacă programați o aplicație multilingvă, probabil că va trebui să redați formularul în diferite limbi. Cadrul Nette Framework definește o interfață de traducere în acest scop Nette\Localization\Translator. Nu există o implementare implicită în Nette, puteți alege în funcție de nevoile dumneavoastră din mai multe soluții gata făcute pe care le puteți găsi pe Componette. Documentația acestora vă indică modul de configurare a traducătorului.

Formularul suportă ieșirea de text prin intermediul traducătorului. Îl transmitem folosind metoda setTranslator():

$form->setTranslator($translator);

De acum înainte, nu numai toate etichetele, ci și toate mesajele de eroare sau intrările din căsuțele de selectare vor fi traduse în altă limbă.

Este posibil să setați un traducător diferit pentru elementele individuale ale formularului sau să dezactivați complet traducerea cu null:

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

Pentru regulile de validare, parametrii specifici sunt, de asemenea, trecuți traducătorului, de exemplu pentru regula:

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

traducătorul este apelat cu următorii parametri:

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

și astfel poate alege forma corectă de plural pentru cuvântul characters by count.

Evenimentul onRender

Chiar înainte ca formularul să fie redat, putem invoca codul nostru. Acesta poate, de exemplu, să adauge clase HTML la elementele formularului pentru o afișare corectă. Adăugăm codul în matricea onRender:

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