Nette Documentation Preview

syntax
Rendu des formulaires
*********************

L'apparence des formulaires peut être très variée. En pratique, nous pouvons rencontrer deux extrêmes. D'un côté, il y a le besoin de rendre dans une application quantité de formulaires visuellement identiques, et nous apprécions alors le rendu facile, sans template, à l'aide de `$form->render()`. C'est typiquement le cas des interfaces d'administration.

De l'autre côté, il y a des formulaires variés dont chacun est unique. Leur apparence se décrit le mieux en HTML, dans le template du formulaire. Et bien sûr, entre ces deux extrêmes, nous rencontrons de nombreux formulaires qui se situent quelque part au milieu.


Rendu avec Latte
================

Le système de templates [Latte |latte:] simplifie considérablement le rendu des formulaires et de leurs éléments. Nous montrerons d'abord comment rendre les formulaires manuellement, élément par élément, pour garder le contrôle total sur le code. Nous montrerons ensuite comment un tel rendu peut être [automatisé |#Rendu automatique].

Vous pouvez faire générer le template Latte du formulaire à l'aide de la méthode `Nette\Forms\Blueprint::latte($form)`, qui l'affiche dans la page du navigateur. Il vous suffit ensuite de sélectionner le code d'un clic et de le copier dans votre projet. .{data-version:3.1.15}


`{control}`
-----------

La façon la plus simple de rendre un formulaire est d'écrire dans le template :

```latte
{control signInForm}
```

L'apparence du formulaire ainsi rendu peut être influencée par la configuration du [#Renderer] et des [différents champs |#Attributs HTML].


`n:name`
--------

Relier la définition du formulaire en PHP au code HTML est extrêmement simple. Il suffit d'ajouter les attributs `n:name`. C'est aussi simple que ça !

```php
protected function createComponentSignInForm(): Form
{
	$form = new Form;
	$form->addText('username')->setRequired();
	$form->addPassword('password')->setRequired();
	$form->addSubmit('send');
	return $form;
}
```

```latte
<form n:name=signInForm class=form>
	<div>
		<label n:name=username>Nom d'utilisateur : <input n:name=username size=20 autofocus></label>
	</div>
	<div>
		<label n:name=password>Mot de passe : <input n:name=password></label>
	</div>
	<div>
		<input n:name=send class="btn btn-default">
	</div>
</form>
```

Vous avez le contrôle total sur l'apparence du code HTML obtenu. Si vous utilisez l'attribut `n:name` avec les éléments `<select>`, `<button>` ou `<textarea>`, leur contenu interne est rempli automatiquement. De plus, la balise `<form n:name>` crée une variable locale `$form` contenant l'objet du formulaire rendu, et la balise fermante `</form>` rend les champs cachés qui n'ont pas encore été rendus (il en va de même pour `{form} ... {/form}`).

Nous ne devons cependant pas oublier de rendre les éventuels messages d'erreur. Cela concerne aussi bien les erreurs ajoutées aux différents champs par la méthode `addError()` (rendues via `{inputError}`) que celles ajoutées directement au formulaire (renvoyées par `$form->getOwnErrors()`) :

```latte
<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>Nom d'utilisateur : <input n:name=username size=20 autofocus></label>
		<span class=error n:ifcontent>{inputError username}</span>
	</div>
	<div>
		<label n:name=password>Mot de passe : <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>
```

Les champs de formulaire plus complexes, comme RadioList ou CheckboxList, peuvent être rendus élément par élément de cette façon :

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


`{label}` `{input}`
-------------------

Vous préférez ne pas avoir à réfléchir, dans le template, à l'élément HTML à utiliser pour chaque champ, `<input>`, `<textarea>`, etc. ? La solution est la balise universelle `{input}` :

```latte
<form n:name=signInForm class=form>
	<ul class="errors" n:ifcontent>
		<li n:foreach="$form->getOwnErrors() as $error">{$error}</li>
	</ul>

	<div>
		{label username}Nom d'utilisateur : {input username, size: 20, autofocus: true}{/label}
		{inputError username}
	</div>
	<div>
		{label password}Mot de passe : {input password}{/label}
		{inputError password}
	</div>
	<div>
		{input send, class: "btn btn-default"}
	</div>
</form>
```

Si le formulaire utilise un traducteur, les labels rendus à partir de la définition du formulaire (par exemple `{label username /}`) sont traduits. Le texte écrit directement entre les balises `{label}` et `{/label}` ne l'est pas.

Là encore, les champs de formulaire plus complexes, comme RadioList ou CheckboxList, peuvent être rendus élément par élément :

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

Pour ne rendre que l'`<input>` d'un champ Checkbox, utilisez `{input myCheckbox:}`. Dans ce cas, séparez toujours les attributs HTML par une virgule : `{input myCheckbox:, class: required}`.


`{inputError}`
--------------

Affiche le message d'erreur d'un champ de formulaire, s'il en existe un. Le message est généralement enveloppé dans un élément HTML pour la mise en forme. On peut élégamment éviter le rendu d'un élément vide en l'absence de message à l'aide de `n:ifcontent` :

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

Nous pouvons détecter la présence d'une erreur avec la méthode `hasErrors()` et définir en conséquence la classe de l'élément parent :

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


`{form}`
--------

Les balises `{form signInForm}...{/form}` sont une alternative à `<form n:name="signInForm">...</form>`. Séparez les éventuels arguments du nom par une virgule : `{form signInForm, class: foo}`.

.{data-version:3.3.0}
Le mot-clé `scope` placé avant le nom se contente de pousser le formulaire sur la pile (pour que `{input}`, `{label}`, etc. s'y rattachent), mais ne rend pas la balise `<form>`. C'est pratique pour rendre une partie d'un formulaire, par exemple dans un snippet. Si un formulaire est déjà actif, le nom est résolu relativement à lui, si bien que `{form scope}` remplace aussi `{formContainer}` :

```latte
{form scope signInForm}
	{input username}
{/form}
```

.{data-version:3.3.0}
Le mot-clé `detached` rend un `<form></form>` vide et relie chaque champ à lui via l'attribut HTML `form`. Cela vous permet de placer un formulaire à l'intérieur d'un autre formulaire, ce que le HTML interdit par ailleurs. Le formulaire détaché doit avoir un `id` HTML, qui est généré automatiquement lorsque vous lui donnez un nom (comme `outerForm` ci-dessous) :

```latte
{form detached outerForm}
	...
{/form}
```


Rendu automatique
-----------------

Grâce aux balises `{input}` et `{label}`, nous pouvons facilement créer un template générique pour n'importe quel formulaire. Il parcourra et rendra tous ses champs, à l'exception des champs cachés, qui sont rendus automatiquement à la fermeture du formulaire par la balise `</form>`. Il attend le nom du formulaire à rendre dans la variable `$form`.

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

Les balises paires auto-fermantes `{label .../}` employées ici affichent les labels provenant de la définition du formulaire dans le code PHP.

Enregistrez ce template générique, par exemple, dans le fichier `basic-form.latte`. Pour rendre le formulaire, il suffit de l'inclure et de passer le nom du formulaire (ou son instance) au paramètre `$form` :

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

Si vous voulez modifier l'apparence d'un formulaire précis lors du rendu, par exemple rendre un champ différemment, le plus simple est de préparer dans le template des blocs que l'on pourra ensuite redéfinir. Les blocs peuvent aussi avoir des [noms dynamiques |latte:template-inheritance#Noms de blocs dynamiques], ce qui vous permet d'y insérer le nom du champ rendu. Par exemple :

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

Pour un champ nommé par exemple `username`, cela crée le bloc `input-username`, qui peut être facilement redéfini à l'aide de la balise [{embed} |latte:template-inheritance#Héritage unitaire] :

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

Le contenu entier du template `basic-form.latte` peut aussi être [défini |latte:template-inheritance#Définitions] comme un bloc, paramètre `$form` compris :

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

Cela rend son appel légèrement plus simple :

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

Le bloc n'a besoin d'être importé qu'à un seul endroit, au début du template de layout :

```latte
{import basic-form.latte}
```


Cas particuliers
----------------

Si vous avez besoin de ne rendre que la partie interne du formulaire, sans les balises HTML `<form>`, par exemple lors de l'envoi de snippets, masquez-les à l'aide de l'attribut `n:tag-if` :

```latte
<form n:name=signInForm n:tag-if=false>
	<div>
		<label n:name=username>Nom d'utilisateur : <input n:name=username></label>
		{inputError username}
	</div>
</form>
```

La balise `{formContainer}`, ou la plus récente [`{form scope}` |#{form}], aide à rendre les champs situés dans un conteneur du formulaire.

```latte
<p>Quelles actualités souhaitez-vous recevoir :</p>

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


Rendu sans Latte
================

La façon la plus simple de rendre un formulaire est d'appeler :

```php
$form->render();
```

L'apparence du formulaire ainsi rendu peut être influencée par la configuration du [#Renderer] et des [différents champs |#Attributs HTML].


Rendu manuel
------------

Chaque champ de formulaire possède des méthodes qui génèrent le code HTML du champ et de son label. Elles peuvent le renvoyer soit sous forme de chaîne, soit sous forme d'objet [Nette\Utils\Html |utils:html-elements] :

- `getControl(): Html|string` renvoie le code HTML du champ
- `getLabel($caption = null): Html|string|null` renvoie le code HTML du label, s'il existe

Cela permet de rendre le formulaire élément par élément :

```php
<?php $form->render('begin') ?>
<?php $form->render('ownerrors') ?>

<div>
	<?= $form['name']->getLabel() ?>
	<?= $form['name']->getControl() ?>
	<span class=error><?= htmlspecialchars((string) $form['name']->getError()) ?></span>
</div>

<div>
	<?= $form['age']->getLabel() ?>
	<?= $form['age']->getControl() ?>
	<span class=error><?= htmlspecialchars((string) $form['age']->getError()) ?></span>
</div>

// ...

<?php $form->render('end') ?>
```

Tandis que, pour certains champs, `getControl()` renvoie un unique élément HTML (par exemple `<input>`, `<select>`, etc.), pour d'autres il renvoie un morceau de code HTML complet (CheckboxList, RadioList). Dans ce cas, vous pouvez utiliser les méthodes qui génèrent séparément les inputs et les labels de chaque élément :

- `getControlPart($key = null): Html` renvoie le code HTML d'un seul élément
- `getLabelPart($key = null): Html` renvoie le code HTML du label d'un seul élément

.[note]
Ces méthodes portent le préfixe `get` pour des raisons historiques, mais `generate` serait plus approprié, car elles créent et renvoient un nouvel élément `Html` à chaque appel.


Renderer
========

C'est un objet chargé du rendu du formulaire. Il se définit à l'aide de la méthode `$form->setRenderer()`. Le contrôle lui est passé lors de l'appel de la méthode `$form->render()`.

Si nous ne définissons pas de renderer personnalisé, le renderer par défaut [api:Nette\Forms\Rendering\DefaultFormRenderer] sera utilisé. Il rend les champs du formulaire dans un tableau HTML. Le résultat ressemble à ceci :

```latte
<table>
<tr class="required">
	<th><label class="required" for="frm-name">Nom :</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">Âge :</label></th>

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

<tr>
	<th><label>Genre :</label></th>
	...
```

L'usage d'un tableau pour la structure du formulaire est discutable, et beaucoup de web designers préfèrent un balisage différent, par exemple une liste de définitions. Nous allons donc reconfigurer `DefaultFormRenderer` pour qu'il rende le formulaire sous forme de liste. La configuration se fait en modifiant le tableau [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Le premier index représente toujours une zone, et le second son attribut. Les différentes zones sont représentées sur l'image :

[* form-areas-en.webp *]

Par défaut, le groupe `controls` est enveloppé dans `<table>`, chaque `pair` représente une ligne de tableau `<tr>`, et le couple `label` et `control` correspond aux cellules `<th>` et `<td>`. Nous allons maintenant changer les éléments d'enveloppe. Nous placerons la zone `controls` dans un conteneur `<dl>`, laisserons la zone `pair` sans conteneur, mettrons le `label` dans `<dt>` et envelopperons enfin le `control` par des balises `<dd>` :

```php
$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 en résulte le code HTML suivant :

```latte
<dl>
	<dt><label class="required" for="frm-name">Nom :</label></dt>

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


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

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


	<dt><label>Genre :</label></dt>
	...
</dl>
```

Le tableau wrappers permet d'influencer bien d'autres attributs :

- ajouter des classes CSS aux différents types de champs de formulaire
- distinguer les lignes paires et impaires par des classes CSS
- distinguer visuellement les éléments obligatoires et facultatifs
- déterminer si les messages d'erreur s'affichent directement à côté des champs ou au-dessus du formulaire


Options
-------

Le comportement du Renderer peut aussi être piloté en définissant des *options* sur les différents champs du formulaire. Vous pouvez ainsi définir une description qui apparaît à côté du champ de saisie :

```php
$form->addText('phone', 'Numéro :')
	->setOption('description', 'Ce numéro restera masqué');
```

Si nous voulons y placer du contenu HTML, nous utilisons la classe [Html |utils:html-elements] :

```php
use Nette\Utils\Html;

$form->addText('phone', 'Téléphone :')
	->setOption('description', Html::el('p')
		->setHtml('<a href="...">Conditions d\'utilisation.</a>')
	);
```

.[tip]
Un élément Html peut aussi être utilisé à la place d'un label : `$form->addCheckbox('conditions', $label)`.


Regrouper les champs
--------------------

Le Renderer permet de regrouper les champs en groupes visuels (fieldsets) :

```php
$form->addGroup('Données personnelles');
```

Après la création d'un nouveau groupe, celui-ci devient actif et chaque champ nouvellement ajouté y est également ajouté. Le formulaire peut donc être construit ainsi :

```php
$form = new Form;
$form->addGroup('Données personnelles');
$form->addText('name', 'Votre nom :');
$form->addInteger('age', 'Votre âge :');
$form->addEmail('email', 'E-mail :');

$form->addGroup('Adresse de livraison');
$form->addCheckbox('send', 'Livrer à l\'adresse');
$form->addText('street', 'Rue :');
$form->addText('city', 'Ville :');
$form->addSelect('country', 'Pays :', $countries);
```

Le renderer dessine d'abord les groupes, puis les champs qui n'appartiennent à aucun groupe.


Prise en charge de Bootstrap
----------------------------

Vous trouverez dans le [répertoire des exemples |https://github.com/nette/forms/tree/master/examples] des exemples montrant comment configurer le Renderer pour [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] et [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php].


Attributs HTML
==============

Pour définir n'importe quels attributs HTML sur les champs de formulaire, utilisez la méthode `setHtmlAttribute(string $name, $value = true)` :

```php
$form->addInteger('number', 'Nombre :')
	->setHtmlAttribute('class', 'big-number');

$form->addSelect('rank', 'Trier par :', ['prix', 'nom'])
	->setHtmlAttribute('onchange', 'submit()'); // envoie le formulaire au changement


// Pour définir les attributs de l'élément <form> lui-même
$form->setHtmlAttribute('id', 'myForm');
```

Indiquer le type du champ :

```php
$form->addText('tel', 'Votre téléphone :')
	->setHtmlType('tel')
	->setHtmlAttribute('placeholder', 'Veuillez indiquer votre téléphone');
```

.[warning]
Définir le type et les autres attributs n'a qu'un but visuel. La vérification de la validité des données doit avoir lieu côté serveur, ce que vous assurez en choisissant un [champ de formulaire |controls] approprié et en indiquant des [règles de validation |validation].

Pour les différents éléments d'une liste de boutons radio ou de cases à cocher, nous pouvons définir un attribut HTML avec des valeurs différentes pour chacun. Remarquez les deux-points après `style:`, qui font que la valeur est choisie d'après la clé :

```php
$colors = ['r' => 'rouge', 'g' => 'vert', 'b' => 'bleu'];
$styles = ['r' => 'background:red', 'g' => 'background:green'];
$form->addCheckboxList('colors', 'Couleurs :', $colors)
	->setHtmlAttribute('style:', $styles);
```

Rend :

```latte
<label><input type="checkbox" name="colors[]" style="background:red" value="r">rouge</label>
<label><input type="checkbox" name="colors[]" style="background:green" value="g">vert</label>
<label><input type="checkbox" name="colors[]" value="b">bleu</label>
```

Pour définir des attributs booléens, comme `readonly`, nous pouvons utiliser la notation avec un point d'interrogation :

```php
$form->addCheckboxList('colors', 'Couleurs :', $colors)
	->setHtmlAttribute('readonly?', 'r'); // utilisez un tableau pour plusieurs clés, par ex. ['r', 'g']
```

Rend :

```latte
<label><input type="checkbox" name="colors[]" readonly value="r">rouge</label>
<label><input type="checkbox" name="colors[]" value="g">vert</label>
<label><input type="checkbox" name="colors[]" value="b">bleu</label>
```

Pour les listes déroulantes, la méthode `setHtmlAttribute()` définit les attributs de l'élément `<select>`. Si nous voulons définir les attributs des différents éléments `<option>`, nous utilisons la méthode `setOptionAttribute()`. Les notations avec les deux-points et le point d'interrogation évoquées plus haut fonctionnent également :

```php
$form->addSelect('colors', 'Couleurs :', $colors)
	->setOptionAttribute('style:', $styles);
```

Rend :

```latte
<select name="colors">
	<option value="r" style="background:red">rouge</option>
	<option value="g" style="background:green">vert</option>
	<option value="b">bleu</option>
</select>
```


Prototypes
----------

Une autre façon de définir les attributs HTML consiste à modifier le modèle à partir duquel l'élément HTML est généré. Ce modèle est un objet `Html` et il est renvoyé par la méthode `getControlPrototype()` :

```php
$input = $form->addInteger('number', 'Nombre :');
$html = $input->getControlPrototype(); // <input>
$html->class('big-number');            // <input class="big-number">
```

Le modèle du label, renvoyé par `getLabelPrototype()`, peut lui aussi être modifié de cette façon :

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

Pour les champs Checkbox, CheckboxList et RadioList, vous pouvez influencer le modèle de l'élément qui enveloppe le champ entier. Il est renvoyé par `getContainerPrototype()`. Par défaut, c'est un élément "vide", donc rien n'est rendu, mais si vous lui donnez un nom, il sera rendu :

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

Dans le cas de CheckboxList et RadioList, vous pouvez aussi influencer le modèle du séparateur des différents éléments, renvoyé par la méthode `getSeparatorPrototype()`. Par défaut, c'est l'élément `<br>`. Si vous le changez en élément pair, il enveloppera les différents éléments au lieu de les séparer. Vous pouvez en outre influencer le modèle de l'élément HTML des labels des différents éléments, renvoyé par `getItemLabelPrototype()`.


Traduction
==========

Si vous développez une application multilingue, vous aurez sans doute besoin de rendre le formulaire dans différentes versions linguistiques. Nette Framework définit à cet effet une interface de traduction : [api:Nette\Localization\Translator]. Nette n'a pas d'implémentation par défaut ; vous pouvez choisir, selon vos besoins, parmi plusieurs solutions toutes prêtes disponibles sur [Componette |https://componette.org/search/localization]. Leur documentation explique comment configurer le traducteur.

Les formulaires prennent en charge l'affichage des textes via le traducteur. Nous le leur passons à l'aide de la méthode `setTranslator()` :

```php
$form->setTranslator($translator);
```

À partir de ce moment, non seulement tous les labels, mais aussi tous les messages d'erreur, les éléments des listes déroulantes et les placeholders des champs seront traduits dans la langue cible.

Il est possible de définir un traducteur différent pour chaque champ du formulaire, ou de désactiver complètement la traduction en définissant la valeur `null` :

```php
$form->addSelect('carModel', 'Modèle :', $cars)
	->setTranslator(null);
```

Pour les [règles de validation |validation], des paramètres spécifiques sont également passés au traducteur. Par exemple, pour la règle :

```php
$form->addPassword('password', 'Mot de passe :')
	->addRule($form::MinLength, 'Le mot de passe doit comporter au moins %d caractères', 8);
```

le traducteur est appelé avec ces paramètres :

```php
$translator->translate('Le mot de passe doit comporter au moins %d caractères', 8);
```

et il peut donc choisir la forme plurielle correcte du mot `caractères` en fonction du nombre.


Événement onRender
==================

Juste avant le rendu du formulaire, nous pouvons faire appeler notre propre code. Celui-ci peut par exemple ajouter des classes HTML aux champs du formulaire pour un affichage correct. Nous ajoutons ce code au tableau `onRender` :

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

Rendu des formulaires

L'apparence des formulaires peut être très variée. En pratique, nous pouvons rencontrer deux extrêmes. D'un côté, il y a le besoin de rendre dans une application quantité de formulaires visuellement identiques, et nous apprécions alors le rendu facile, sans template, à l'aide de $form->render(). C'est typiquement le cas des interfaces d'administration.

De l'autre côté, il y a des formulaires variés dont chacun est unique. Leur apparence se décrit le mieux en HTML, dans le template du formulaire. Et bien sûr, entre ces deux extrêmes, nous rencontrons de nombreux formulaires qui se situent quelque part au milieu.

Rendu avec Latte

Le système de templates Latte simplifie considérablement le rendu des formulaires et de leurs éléments. Nous montrerons d'abord comment rendre les formulaires manuellement, élément par élément, pour garder le contrôle total sur le code. Nous montrerons ensuite comment un tel rendu peut être automatisé.

Vous pouvez faire générer le template Latte du formulaire à l'aide de la méthode Nette\Forms\Blueprint::latte($form), qui l'affiche dans la page du navigateur. Il vous suffit ensuite de sélectionner le code d'un clic et de le copier dans votre projet.

{control}

La façon la plus simple de rendre un formulaire est d'écrire dans le template :

{control signInForm}

L'apparence du formulaire ainsi rendu peut être influencée par la configuration du Renderer et des différents champs.

n:name

Relier la définition du formulaire en PHP au code HTML est extrêmement simple. Il suffit d'ajouter les attributs n:name. C'est aussi simple que ça !

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>Nom d'utilisateur : <input n:name=username size=20 autofocus></label>
	</div>
	<div>
		<label n:name=password>Mot de passe : <input n:name=password></label>
	</div>
	<div>
		<input n:name=send class="btn btn-default">
	</div>
</form>

Vous avez le contrôle total sur l'apparence du code HTML obtenu. Si vous utilisez l'attribut n:name avec les éléments <select>, <button> ou <textarea>, leur contenu interne est rempli automatiquement. De plus, la balise <form n:name> crée une variable locale $form contenant l'objet du formulaire rendu, et la balise fermante </form> rend les champs cachés qui n'ont pas encore été rendus (il en va de même pour {form} ... {/form}).

Nous ne devons cependant pas oublier de rendre les éventuels messages d'erreur. Cela concerne aussi bien les erreurs ajoutées aux différents champs par la méthode addError() (rendues via {inputError}) que celles ajoutées directement au formulaire (renvoyées par $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>Nom d'utilisateur : <input n:name=username size=20 autofocus></label>
		<span class=error n:ifcontent>{inputError username}</span>
	</div>
	<div>
		<label n:name=password>Mot de passe : <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>

Les champs de formulaire plus complexes, comme RadioList ou CheckboxList, peuvent être rendus élément par élément de cette façon :

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

{label} {input}

Vous préférez ne pas avoir à réfléchir, dans le template, à l'élément HTML à utiliser pour chaque champ, <input>, <textarea>, etc. ? La solution est la balise universelle {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}Nom d'utilisateur : {input username, size: 20, autofocus: true}{/label}
		{inputError username}
	</div>
	<div>
		{label password}Mot de passe : {input password}{/label}
		{inputError password}
	</div>
	<div>
		{input send, class: "btn btn-default"}
	</div>
</form>

Si le formulaire utilise un traducteur, les labels rendus à partir de la définition du formulaire (par exemple {label username /}) sont traduits. Le texte écrit directement entre les balises {label} et {/label} ne l'est pas.

Là encore, les champs de formulaire plus complexes, comme RadioList ou CheckboxList, peuvent être rendus élément par élément :

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

Pour ne rendre que l'<input> d'un champ Checkbox, utilisez {input myCheckbox:}. Dans ce cas, séparez toujours les attributs HTML par une virgule : {input myCheckbox:, class: required}.

{inputError}

Affiche le message d'erreur d'un champ de formulaire, s'il en existe un. Le message est généralement enveloppé dans un élément HTML pour la mise en forme. On peut élégamment éviter le rendu d'un élément vide en l'absence de message à l'aide de n:ifcontent :

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

Nous pouvons détecter la présence d'une erreur avec la méthode hasErrors() et définir en conséquence la classe de l'élément parent :

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

{form}

Les balises {form signInForm}...{/form} sont une alternative à <form n:name="signInForm">...</form>. Séparez les éventuels arguments du nom par une virgule : {form signInForm, class: foo}.

Le mot-clé scope placé avant le nom se contente de pousser le formulaire sur la pile (pour que {input}, {label}, etc. s'y rattachent), mais ne rend pas la balise <form>. C'est pratique pour rendre une partie d'un formulaire, par exemple dans un snippet. Si un formulaire est déjà actif, le nom est résolu relativement à lui, si bien que {form scope} remplace aussi {formContainer} :

{form scope signInForm}
	{input username}
{/form}

Le mot-clé detached rend un <form></form> vide et relie chaque champ à lui via l'attribut HTML form. Cela vous permet de placer un formulaire à l'intérieur d'un autre formulaire, ce que le HTML interdit par ailleurs. Le formulaire détaché doit avoir un id HTML, qui est généré automatiquement lorsque vous lui donnez un nom (comme outerForm ci-dessous) :

{form detached outerForm}
	...
{/form}

Rendu automatique

Grâce aux balises {input} et {label}, nous pouvons facilement créer un template générique pour n'importe quel formulaire. Il parcourra et rendra tous ses champs, à l'exception des champs cachés, qui sont rendus automatiquement à la fermeture du formulaire par la balise </form>. Il attend le nom du formulaire à rendre dans la variable $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>

Les balises paires auto-fermantes {label .../} employées ici affichent les labels provenant de la définition du formulaire dans le code PHP.

Enregistrez ce template générique, par exemple, dans le fichier basic-form.latte. Pour rendre le formulaire, il suffit de l'inclure et de passer le nom du formulaire (ou son instance) au paramètre $form :

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

Si vous voulez modifier l'apparence d'un formulaire précis lors du rendu, par exemple rendre un champ différemment, le plus simple est de préparer dans le template des blocs que l'on pourra ensuite redéfinir. Les blocs peuvent aussi avoir des noms dynamiques, ce qui vous permet d'y insérer le nom du champ rendu. Par exemple :

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

Pour un champ nommé par exemple username, cela crée le bloc input-username, qui peut être facilement redéfini à l'aide de la balise {embed} :

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

Le contenu entier du template basic-form.latte peut aussi être défini comme un bloc, paramètre $form compris :

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

Cela rend son appel légèrement plus simple :

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

Le bloc n'a besoin d'être importé qu'à un seul endroit, au début du template de layout :

{import basic-form.latte}

Cas particuliers

Si vous avez besoin de ne rendre que la partie interne du formulaire, sans les balises HTML <form>, par exemple lors de l'envoi de snippets, masquez-les à l'aide de l'attribut n:tag-if :

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

La balise {formContainer}, ou la plus récente {form scope}, aide à rendre les champs situés dans un conteneur du formulaire.

<p>Quelles actualités souhaitez-vous recevoir :</p>

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

Rendu sans Latte

La façon la plus simple de rendre un formulaire est d'appeler :

$form->render();

L'apparence du formulaire ainsi rendu peut être influencée par la configuration du Renderer et des différents champs.

Rendu manuel

Chaque champ de formulaire possède des méthodes qui génèrent le code HTML du champ et de son label. Elles peuvent le renvoyer soit sous forme de chaîne, soit sous forme d'objet Nette\Utils\Html :

  • getControl(): Html|string renvoie le code HTML du champ
  • getLabel($caption = null): Html|string|null renvoie le code HTML du label, s'il existe

Cela permet de rendre le formulaire élément par élément :

<?php $form->render('begin') ?>
<?php $form->render('ownerrors') ?>

<div>
	<?= $form['name']->getLabel() ?>
	<?= $form['name']->getControl() ?>
	<span class=error><?= htmlspecialchars((string) $form['name']->getError()) ?></span>
</div>

<div>
	<?= $form['age']->getLabel() ?>
	<?= $form['age']->getControl() ?>
	<span class=error><?= htmlspecialchars((string) $form['age']->getError()) ?></span>
</div>

// ...

<?php $form->render('end') ?>

Tandis que, pour certains champs, getControl() renvoie un unique élément HTML (par exemple <input>, <select>, etc.), pour d'autres il renvoie un morceau de code HTML complet (CheckboxList, RadioList). Dans ce cas, vous pouvez utiliser les méthodes qui génèrent séparément les inputs et les labels de chaque élément :

  • getControlPart($key = null): Html renvoie le code HTML d'un seul élément
  • getLabelPart($key = null): Html renvoie le code HTML du label d'un seul élément

Ces méthodes portent le préfixe get pour des raisons historiques, mais generate serait plus approprié, car elles créent et renvoient un nouvel élément Html à chaque appel.

Renderer

C'est un objet chargé du rendu du formulaire. Il se définit à l'aide de la méthode $form->setRenderer(). Le contrôle lui est passé lors de l'appel de la méthode $form->render().

Si nous ne définissons pas de renderer personnalisé, le renderer par défaut Nette\Forms\Rendering\DefaultFormRenderer sera utilisé. Il rend les champs du formulaire dans un tableau HTML. Le résultat ressemble à ceci :

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

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

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

L'usage d'un tableau pour la structure du formulaire est discutable, et beaucoup de web designers préfèrent un balisage différent, par exemple une liste de définitions. Nous allons donc reconfigurer DefaultFormRenderer pour qu'il rende le formulaire sous forme de liste. La configuration se fait en modifiant le tableau $wrappers. Le premier index représente toujours une zone, et le second son attribut. Les différentes zones sont représentées sur l'image :

Par défaut, le groupe controls est enveloppé dans <table>, chaque pair représente une ligne de tableau <tr>, et le couple label et control correspond aux cellules <th> et <td>. Nous allons maintenant changer les éléments d'enveloppe. Nous placerons la zone controls dans un conteneur <dl>, laisserons la zone pair sans conteneur, mettrons le label dans <dt> et envelopperons enfin le control par des balises <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 en résulte le code HTML suivant :

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

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


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

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


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

Le tableau wrappers permet d'influencer bien d'autres attributs :

  • ajouter des classes CSS aux différents types de champs de formulaire
  • distinguer les lignes paires et impaires par des classes CSS
  • distinguer visuellement les éléments obligatoires et facultatifs
  • déterminer si les messages d'erreur s'affichent directement à côté des champs ou au-dessus du formulaire

Options

Le comportement du Renderer peut aussi être piloté en définissant des options sur les différents champs du formulaire. Vous pouvez ainsi définir une description qui apparaît à côté du champ de saisie :

$form->addText('phone', 'Numéro :')
	->setOption('description', 'Ce numéro restera masqué');

Si nous voulons y placer du contenu HTML, nous utilisons la classe Html :

use Nette\Utils\Html;

$form->addText('phone', 'Téléphone :')
	->setOption('description', Html::el('p')
		->setHtml('<a href="...">Conditions d\'utilisation.</a>')
	);

Un élément Html peut aussi être utilisé à la place d'un label : $form->addCheckbox('conditions', $label).

Regrouper les champs

Le Renderer permet de regrouper les champs en groupes visuels (fieldsets) :

$form->addGroup('Données personnelles');

Après la création d'un nouveau groupe, celui-ci devient actif et chaque champ nouvellement ajouté y est également ajouté. Le formulaire peut donc être construit ainsi :

$form = new Form;
$form->addGroup('Données personnelles');
$form->addText('name', 'Votre nom :');
$form->addInteger('age', 'Votre âge :');
$form->addEmail('email', 'E-mail :');

$form->addGroup('Adresse de livraison');
$form->addCheckbox('send', 'Livrer à l\'adresse');
$form->addText('street', 'Rue :');
$form->addText('city', 'Ville :');
$form->addSelect('country', 'Pays :', $countries);

Le renderer dessine d'abord les groupes, puis les champs qui n'appartiennent à aucun groupe.

Prise en charge de Bootstrap

Vous trouverez dans le répertoire des exemples des exemples montrant comment configurer le Renderer pour Twitter Bootstrap 2, Bootstrap 3 et Bootstrap 4.

Attributs HTML

Pour définir n'importe quels attributs HTML sur les champs de formulaire, utilisez la méthode setHtmlAttribute(string $name, $value = true) :

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

$form->addSelect('rank', 'Trier par :', ['prix', 'nom'])
	->setHtmlAttribute('onchange', 'submit()'); // envoie le formulaire au changement


// Pour définir les attributs de l'élément <form> lui-même
$form->setHtmlAttribute('id', 'myForm');

Indiquer le type du champ :

$form->addText('tel', 'Votre téléphone :')
	->setHtmlType('tel')
	->setHtmlAttribute('placeholder', 'Veuillez indiquer votre téléphone');

Définir le type et les autres attributs n'a qu'un but visuel. La vérification de la validité des données doit avoir lieu côté serveur, ce que vous assurez en choisissant un champ de formulaire approprié et en indiquant des règles de validation.

Pour les différents éléments d'une liste de boutons radio ou de cases à cocher, nous pouvons définir un attribut HTML avec des valeurs différentes pour chacun. Remarquez les deux-points après style:, qui font que la valeur est choisie d'après la clé :

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

Rend :

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

Pour définir des attributs booléens, comme readonly, nous pouvons utiliser la notation avec un point d'interrogation :

$form->addCheckboxList('colors', 'Couleurs :', $colors)
	->setHtmlAttribute('readonly?', 'r'); // utilisez un tableau pour plusieurs clés, par ex. ['r', 'g']

Rend :

<label><input type="checkbox" name="colors[]" readonly value="r">rouge</label>
<label><input type="checkbox" name="colors[]" value="g">vert</label>
<label><input type="checkbox" name="colors[]" value="b">bleu</label>

Pour les listes déroulantes, la méthode setHtmlAttribute() définit les attributs de l'élément <select>. Si nous voulons définir les attributs des différents éléments <option>, nous utilisons la méthode setOptionAttribute(). Les notations avec les deux-points et le point d'interrogation évoquées plus haut fonctionnent également :

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

Rend :

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

Prototypes

Une autre façon de définir les attributs HTML consiste à modifier le modèle à partir duquel l'élément HTML est généré. Ce modèle est un objet Html et il est renvoyé par la méthode getControlPrototype() :

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

Le modèle du label, renvoyé par getLabelPrototype(), peut lui aussi être modifié de cette façon :

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

Pour les champs Checkbox, CheckboxList et RadioList, vous pouvez influencer le modèle de l'élément qui enveloppe le champ entier. Il est renvoyé par getContainerPrototype(). Par défaut, c'est un élément „vide“, donc rien n'est rendu, mais si vous lui donnez un nom, il sera rendu :

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

Dans le cas de CheckboxList et RadioList, vous pouvez aussi influencer le modèle du séparateur des différents éléments, renvoyé par la méthode getSeparatorPrototype(). Par défaut, c'est l'élément <br>. Si vous le changez en élément pair, il enveloppera les différents éléments au lieu de les séparer. Vous pouvez en outre influencer le modèle de l'élément HTML des labels des différents éléments, renvoyé par getItemLabelPrototype().

Traduction

Si vous développez une application multilingue, vous aurez sans doute besoin de rendre le formulaire dans différentes versions linguistiques. Nette Framework définit à cet effet une interface de traduction : Nette\Localization\Translator. Nette n'a pas d'implémentation par défaut ; vous pouvez choisir, selon vos besoins, parmi plusieurs solutions toutes prêtes disponibles sur Componette. Leur documentation explique comment configurer le traducteur.

Les formulaires prennent en charge l'affichage des textes via le traducteur. Nous le leur passons à l'aide de la méthode setTranslator() :

$form->setTranslator($translator);

À partir de ce moment, non seulement tous les labels, mais aussi tous les messages d'erreur, les éléments des listes déroulantes et les placeholders des champs seront traduits dans la langue cible.

Il est possible de définir un traducteur différent pour chaque champ du formulaire, ou de désactiver complètement la traduction en définissant la valeur null :

$form->addSelect('carModel', 'Modèle :', $cars)
	->setTranslator(null);

Pour les règles de validation, des paramètres spécifiques sont également passés au traducteur. Par exemple, pour la règle :

$form->addPassword('password', 'Mot de passe :')
	->addRule($form::MinLength, 'Le mot de passe doit comporter au moins %d caractères', 8);

le traducteur est appelé avec ces paramètres :

$translator->translate('Le mot de passe doit comporter au moins %d caractères', 8);

et il peut donc choisir la forme plurielle correcte du mot caractères en fonction du nombre.

Événement onRender

Juste avant le rendu du formulaire, nous pouvons faire appeler notre propre code. Celui-ci peut par exemple ajouter des classes HTML aux champs du formulaire pour un affichage correct. Nous ajoutons ce code au tableau onRender :

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