Renderizzazione dei Form
L'aspetto dei form può essere molto vario. In pratica, possiamo incontrare due estremi. Da un lato, c'è la necessità di
renderizzare nell'applicazione una serie di form che sono visivamente simili come due gocce d'acqua, e apprezzeremo la facile
renderizzazione senza template tramite $form->render()
. Questo è solitamente il caso delle interfacce di
amministrazione.
Dall'altro lato, ci sono form diversi, dove vale la regola: ogni pezzo è un originale. La loro forma è meglio descritta dal linguaggio HTML nel template del form. E naturalmente, oltre ai due estremi menzionati, incontreremo molti form che si trovano da qualche parte nel mezzo.
Renderizzazione tramite Latte
Il sistema di templating Latte facilita enormemente la renderizzazione dei form e dei loro elementi. Prima mostreremo come renderizzare i form manualmente, elemento per elemento, ottenendo così il pieno controllo sul codice. Successivamente mostreremo come tale renderizzazione possa essere automatizzata.
Puoi farti generare il design del template Latte del form tramite il metodo
Nette\Forms\Blueprint::latte($form)
, che lo stamperà nella pagina del browser. Basta quindi cliccare per selezionare
il codice e copiarlo nel progetto.
{control}
Il modo più semplice per renderizzare un form è scrivere nel template:
{control signInForm}
È possibile influenzare l'aspetto di un form così renderizzato configurando il Renderer e i singoli elementi.
n:name
La definizione del form nel codice PHP può essere collegata molto facilmente al codice HTML. Basta aggiungere gli attributi
n:name
. È così facile!
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>
Avete il pieno controllo sull'aspetto del codice HTML risultante. Se utilizzate l'attributo n:name
sugli elementi
<select>
, <button>
o <textarea>
, il loro contenuto interno verrà
completato automaticamente. Il tag <form n:name>
crea inoltre una variabile locale $form
con
l'oggetto del form disegnato e il tag di chiusura </form>
renderizza tutti gli elementi nascosti non
renderizzati (lo stesso vale anche per {form} ... {/form}
).
Non dobbiamo però dimenticare di renderizzare eventuali messaggi di errore. Sia quelli aggiunti ai singoli elementi con il
metodo addError()
(tramite {inputError}
), sia quelli aggiunti direttamente al form (restituiti da
$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>
Elementi del form più complessi, come RadioList o CheckboxList, possono essere renderizzati in questo modo per singoli elementi:
{foreach $form[gender]->getItems() as $key => $label}
<label n:name="gender:$key"><input n:name="gender:$key"> {$label}</label>
{/foreach}
{label}
{input}
Non vuoi pensare per ogni elemento quale elemento HTML usare nel template, se <input>
,
<textarea>
, ecc.? La soluzione è il tag universale {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>
Se il form utilizza un traduttore, il testo all'interno dei tag {label}
verrà tradotto.
Anche in questo caso, elementi del form più complessi, come RadioList o CheckboxList, possono essere renderizzati per singoli elementi:
{foreach $form[gender]->items as $key => $label}
{label gender:$key}{input gender:$key} {$label}{/label}
{/foreach}
Per renderizzare solo l'<input>
nell'elemento Checkbox, usa {input myCheckbox:}
. Gli attributi
HTML in questo caso vanno sempre separati da virgola {input myCheckbox:, class: required}
.
{inputError}
Stampa il messaggio di errore per l'elemento del form, se ne ha uno. Il messaggio viene solitamente racchiuso in un elemento
HTML per lo styling. Evitare la renderizzazione di un elemento vuoto, se il messaggio non c'è, è possibile elegantemente tramite
n:ifcontent
:
<span class=error n:ifcontent>{inputError $input}</span>
Possiamo verificare la presenza di un errore con il metodo hasErrors()
e in base a ciò impostare una classe
sull'elemento genitore:
<div n:class="$form[username]->hasErrors() ? 'error'">
{input username}
{inputError username}
</div>
{form}
I tag {form signInForm}...{/form}
sono un'alternativa a
<form n:name="signInForm">...</form>
.
Renderizzazione automatica
Grazie ai tag {input}
e {label}
possiamo facilmente creare un template generico per qualsiasi form.
Itererà e renderizzerà gradualmente tutti i suoi elementi, ad eccezione degli elementi nascosti, che verranno renderizzati
automaticamente alla chiusura del form con il tag </form>
. Il nome del form da renderizzare sarà atteso nella
variabile $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>
I tag di coppia auto-chiudenti {label .../}
utilizzati visualizzano le etichette provenienti dalla definizione
del form nel codice PHP.
Salvate questo template generico ad esempio nel file basic-form.latte
e per renderizzare il form basta includerlo
e passare il nome (o l'istanza) del form al parametro $form
:
{include basic-form.latte, form: signInForm}
Se durante la renderizzazione di un particolare form voleste intervenire sulla sua forma e ad esempio renderizzare un elemento diversamente, allora il modo più semplice è preparare nel template dei blocchi che potranno essere successivamente sovrascritti. I blocchi possono avere anche nomi dinamici, quindi è possibile inserirvi anche il nome dell'elemento renderizzato. Ad esempio:
...
{label $input /}
{block "input-{$input->name}"}{input $input}{/block}
...
Per l'elemento ad esempio username
verrà così creato il blocco input-username
, che può essere
facilmente sovrascritto utilizzando il tag {embed}:
{embed basic-form.latte, form: signInForm}
{block input-username}
<span class=important>
{include parent}
</span>
{/block}
{/embed}
In alternativa, l'intero contenuto del template basic-form.latte
può essere definito come blocco, incluso il parametro
$form
:
{define basic-form, $form}
<form n:name=$form class=form>
...
</form>
{/define}
Grazie a ciò, la sua chiamata sarà leggermente più semplice:
{embed basic-form, signInForm}
...
{/embed}
Il blocco basta importarlo in un unico punto, all'inizio del template di layout:
{import basic-form.latte}
Casi speciali
Se hai bisogno di renderizzare solo la parte interna del form senza i tag HTML <form>
, ad esempio quando
invii snippet, nascondili usando l'attributo 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>
Con la renderizzazione degli elementi all'interno del contenitore del form aiuta il tag {formContainer}
.
<p>Quali notizie desideri ricevere:</p>
{formContainer emailNews}
<ul>
<li>{input sport} {label sport /}</li>
<li>{input science} {label science /}</li>
</ul>
{/formContainer}
Renderizzazione senza Latte
Il modo più semplice per renderizzare un form è chiamare:
$form->render();
È possibile influenzare l'aspetto di un form così renderizzato configurando il Renderer e i singoli elementi.
Renderizzazione manuale
Ogni elemento del form dispone di metodi che generano il codice HTML del campo del form e dell'etichetta. Possono restituirlo sia come stringa che come oggetto Nette\Utils\Html:
getControl(): Html|string
restituisce il codice HTML dell'elementogetLabel($caption = null): Html|string|null
restituisce il codice HTML dell'etichetta, se esiste
Il form può quindi essere renderizzato per singoli elementi:
<?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') ?>
Mentre per alcuni elementi getControl()
restituisce un singolo elemento HTML (es. <input>
,
<select>
, ecc.), per altri restituisce un intero pezzo di codice HTML (CheckboxList, RadioList). In tal caso,
potete utilizzare metodi che generano singoli input ed etichette, per ogni elemento separatamente:
getControlPart($key = null): ?Html
restituisce il codice HTML di un singolo elementogetLabelPart($key = null): ?Html
restituisce il codice HTML dell'etichetta di un singolo elemento
Questi metodi hanno per motivi storici il prefisso get
, ma sarebbe stato meglio
generate
, perché ad ogni chiamata creano e restituiscono un nuovo elemento Html
.
Renderer
È un oggetto che si occupa della renderizzazione del form. Può essere impostato con il metodo
$form->setRenderer
. Gli viene passato il controllo quando viene chiamato il metodo
$form->render()
.
Se non impostiamo un renderer personalizzato, verrà utilizzato il renderer predefinito Nette\Forms\Rendering\DefaultFormRenderer. Questo renderizza gli elementi del form sotto forma di tabella HTML. L'output è simile a questo:
<table>
<tr class="required">
<th><label class="required" for="frm-name">Nome:</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">Età:</label></th>
<td><input type="text" class="text" name="age" id="frm-age" required value=""></td>
</tr>
<tr>
<th><label>Sesso:</label></th>
...
Se utilizzare o meno una tabella per la struttura del form è discutibile e molti web designer preferiscono un markup diverso.
Ad esempio, una lista di definizioni. Riconfigureremo quindi DefaultFormRenderer
in modo che renderizzi il form sotto
forma di lista. La configurazione viene eseguita modificando l'array $wrappers. Il primo
indice rappresenta sempre l'area e il secondo il suo attributo. Le singole aree sono illustrate nell'immagine:

Standardmente il gruppo di elementi controls
è avvolto da una tabella <table>
, ogni
pair
rappresenta una riga della tabella <tr>
e la coppia label
e control
sono le celle <th>
e <td>
. Ora cambieremo gli elementi contenitori. L'area
controls
la inseriremo nel contenitore <dl>
, l'area pair
la lasceremo senza
contenitore, label
la inseriremo in <dt>
e infine control
lo avvolgeremo con i tag
<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();
Il risultato è questo codice HTML:
<dl>
<dt><label class="required" for="frm-name">Nome:</label></dt>
<dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd>
<dt><label class="required" for="frm-age">Età:</label></dt>
<dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd>
<dt><label>Sesso:</label></dt>
...
</dl>
Nell'array wrappers è possibile influenzare tutta una serie di altri attributi:
- aggiungere classi CSS a singoli tipi di elementi del form
- distinguere con classi CSS le righe pari e dispari
- distinguere visivamente gli elementi obbligatori e facoltativi
- determinare se i messaggi di errore verranno visualizzati direttamente accanto agli elementi o sopra il form
Options
Il comportamento del Renderer può essere controllato anche impostando options sui singoli elementi del form. In questo modo è possibile impostare una descrizione che verrà visualizzata accanto al campo di input:
$form->addText('phone', 'Numero:')
->setOption('description', 'Questo numero rimarrà nascosto');
Se vogliamo inserirvi contenuto HTML, utilizziamo la classe Html
use Nette\Utils\Html;
$form->addText('phone', 'Numero:')
->setOption('description', Html::el('p')
->setHtml('<a href="...">Condizioni di conservazione del tuo numero</a>')
);
L'elemento Html può essere utilizzato anche al posto dell'etichetta:
$form->addCheckbox('conditions', $label)
.
Raggruppamento degli elementi
Il Renderer consente di raggruppare gli elementi in gruppi visivi (fieldset):
$form->addGroup('Dati personali');
Dopo aver creato un nuovo gruppo, questo diventa attivo e ogni elemento appena aggiunto viene aggiunto anche ad esso. Quindi il form può essere costruito in questo modo:
$form = new Form;
$form->addGroup('Dati personali');
$form->addText('name', 'Il tuo nome:');
$form->addInteger('age', 'La tua età:');
$form->addEmail('email', 'Email:');
$form->addGroup('Indirizzo di spedizione');
$form->addCheckbox('send', 'Spedisci all\'indirizzo');
$form->addText('street', 'Via:');
$form->addText('city', 'Città:');
$form->addSelect('country', 'Paese:', $countries);
Il Renderer renderizza prima i gruppi e solo dopo gli elementi che non appartengono a nessun gruppo.
Supporto per Bootstrap
Negli esempi troverete esempi su come configurare il Renderer per Twitter Bootstrap 2, Bootstrap 3 e Bootstrap 4
Attributi HTML
Per impostare attributi HTML arbitrari degli elementi del form, utilizziamo il metodo
setHtmlAttribute(string $name, $value = true)
:
$form->addInteger('number', 'Numero:')
->setHtmlAttribute('class', 'big-number');
$form->addSelect('rank', 'Ordina per:', ['prezzo', 'nome'])
->setHtmlAttribute('onchange', 'submit()'); // invia alla modifica
// Per impostare gli attributi del <form> stesso
$form->setHtmlAttribute('id', 'myForm');
Specifica del tipo di elemento:
$form->addText('tel', 'Il tuo telefono:')
->setHtmlType('tel')
->setHtmlAttribute('placeholder', 'scrivi il telefono');
L'impostazione del tipo e di altri attributi serve solo a scopi visivi. La verifica della correttezza degli input deve avvenire lato server, cosa che si garantisce scegliendo l'elemento del form appropriato e indicando le regole di validazione.
Alle singole voci nelle liste radio o checkbox possiamo impostare un attributo HTML con valori diversi per ciascuna di esse.
Notate i due punti dopo style:
, che assicurano la scelta del valore in base alla chiave:
$colors = ['r' => 'rosso', 'g' => 'verde', 'b' => 'blu'];
$styles = ['r' => 'background:red', 'g' => 'background:green'];
$form->addCheckboxList('colors', 'Colori:', $colors)
->setHtmlAttribute('style:', $styles);
Stampa:
<label><input type="checkbox" name="colors[]" style="background:red" value="r">rosso</label>
<label><input type="checkbox" name="colors[]" style="background:green" value="g">verde</label>
<label><input type="checkbox" name="colors[]" value="b">blu</label>
Per impostare attributi logici, come readonly
, possiamo usare la scrittura con il punto interrogativo:
$form->addCheckboxList('colors', 'Colori:', $colors)
->setHtmlAttribute('readonly?', 'r'); // per più chiavi usa un array, es. ['r', 'g']
Stampa:
<label><input type="checkbox" name="colors[]" readonly value="r">rosso</label>
<label><input type="checkbox" name="colors[]" value="g">verde</label>
<label><input type="checkbox" name="colors[]" value="b">blu</label>
Nel caso dei selectbox, il metodo setHtmlAttribute()
imposta gli attributi dell'elemento
<select>
. Se vogliamo impostare attributi ai singoli <option>
, usiamo il metodo
setOptionAttribute()
. Funzionano anche le scritture con i due punti e il punto interrogativo indicate sopra:
$form->addSelect('colors', 'Colori:', $colors)
->setOptionAttribute('style:', $styles);
Stampa:
<select name="colors">
<option value="r" style="background:red">rosso</option>
<option value="g" style="background:green">verde</option>
<option value="b">blu</option>
</select>
Prototipi
Un modo alternativo per impostare gli attributi HTML consiste nel modificare il prototipo da cui viene generato l'elemento
HTML. Il prototipo è un oggetto Html
e viene restituito dal metodo getControlPrototype()
:
$input = $form->addInteger('number', 'Numero:');
$html = $input->getControlPrototype(); // <input>
$html->class('big-number'); // <input class="big-number">
In questo modo è possibile modificare anche il prototipo dell'etichetta, restituito da getLabelPrototype()
:
$html = $input->getLabelPrototype(); // <label>
$html->class('distinctive'); // <label class="distinctive">
Per gli elementi Checkbox, CheckboxList e RadioList è possibile influenzare il prototipo dell'elemento che avvolge l'intero
elemento. Viene restituito da getContainerPrototype()
. Nello stato predefinito è un elemento “vuoto”, quindi non
viene renderizzato nulla, ma impostandogli un nome, verrà renderizzato:
$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>
Nel caso di CheckboxList e RadioList è possibile influenzare anche il prototipo del separatore delle singole voci, restituito
dal metodo getSeparatorPrototype()
. Nello stato predefinito è l'elemento <br>
. Se lo si modifica
in un elemento di coppia, avvolgerà le singole voci invece di separarle. Inoltre, è possibile influenzare il prototipo
dell'elemento HTML dell'etichetta delle singole voci, restituito da getItemLabelPrototype()
.
Traduzione
Se programmi un'applicazione multilingue, probabilmente avrai bisogno di renderizzare il form in diverse versioni linguistiche. Nette Framework definisce a tale scopo un'interfaccia per la traduzione Nette\Localization\Translator. In Nette non c'è un'implementazione predefinita, puoi scegliere in base alle tue esigenze tra diverse soluzioni pronte, che trovi su Componette. Nella loro documentazione scoprirai come configurare il traduttore.
I form supportano la stampa di testi tramite il traduttore. Glielo passiamo tramite il metodo
setTranslator()
:
$form->setTranslator($translator);
Da questo momento, non solo tutte le etichette, ma anche tutti i messaggi di errore o le voci dei select box verranno tradotti in un'altra lingua.
Per i singoli elementi del form è possibile impostare un traduttore diverso o disattivare completamente la traduzione con il
valore null
:
$form->addSelect('carModel', 'Modello:', $cars)
->setTranslator(null);
Alle regole di validazione vengono passati al traduttore anche parametri specifici, ad esempio per la regola:
$form->addPassword('password', 'Password:')
->addRule($form::MinLength, 'La password deve avere almeno %d caratteri', 8);
viene chiamato il traduttore con questi parametri:
$translator->translate('La password deve avere almeno %d caratteri', 8);
e quindi può scegliere la forma plurale corretta della parola caratteri
in base al numero.
Evento onRender
Poco prima che il form venga renderizzato, possiamo far chiamare il nostro codice. Questo può, ad esempio, aggiungere classi
HTML agli elementi del form per una corretta visualizzazione. Aggiungiamo il codice all'array onRender
:
$form->onRender[] = function ($form) {
BootstrapCSS::initialize($form);
};