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