Nette Documentation Preview

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

L'aspect des formes peut être très varié. Dans la pratique, nous pouvons rencontrer deux extrêmes. D'une part, il est nécessaire de rendre une série de formulaires dans une application qui sont visuellement similaires les uns aux autres, et nous apprécions le rendu facile sans modèle à l'aide de `$form->render()`. C'est généralement le cas des interfaces administratives.

D'autre part, il existe différents formulaires dont chacun est unique. Leur apparence est mieux décrite en utilisant le langage HTML dans le modèle. Et bien sûr, en plus des deux extrêmes mentionnés, nous rencontrerons de nombreux formulaires qui se situent quelque part entre les deux.


Rendu avec Latte .[#toc-rendering-with-latte]
=============================================

Le [système de templates Latte |latte:] facilite fondamentalement le rendu des formulaires et de leurs éléments. Nous allons d'abord montrer comment rendre les formulaires manuellement, élément par élément, afin d'avoir un contrôle total sur le code. Plus tard, nous montrerons comment [automatiser |#Automatic rendering] ce rendu.

Vous pouvez obtenir la proposition d'un modèle Latte pour le formulaire généré à l'aide de la méthode `Nette\Forms\Blueprint::latte($form)`, qui l'affichera sur 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 un modèle :

```latte
{control signInForm}
```

L'aspect du formulaire rendu peut être modifié en configurant le [Renderer |#Renderer] et les [contrôles individuels |#HTML Attributes].


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

Il est extrêmement facile de lier la définition du formulaire dans le code PHP avec le code HTML. Il suffit d'ajouter les attributs `n:name`. C'est aussi simple que cela !

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

L'aspect du code HTML résultant est entièrement entre vos mains. Si vous utilisez l'attribut `n:name` avec `<select>`, `<button>` ou `<textarea>` leur contenu interne est automatiquement rempli.
En outre, la balise `<form n:name>` crée une variable locale `$form` avec l'objet de formulaire dessiné et la balise de fermeture `</form>` dessine tous les éléments cachés non dessinés (il en va de même pour `{form} ... {/form}`).

Il ne faut cependant pas oublier de rendre les éventuels messages d'erreur. Aussi bien ceux qui ont été ajoutés aux éléments individuels par la méthode `addError()` (en utilisant `{inputError}`) que ceux ajoutés directement au formulaire (renvoyés 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>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>
```

Les éléments de formulaire plus complexes, tels que RadioList ou CheckboxList, peuvent être rendus élément par élément :

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


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

Ne voulez-vous pas penser pour chaque élément à l'élément HTML à utiliser pour lui dans le modèle, que ce soit `<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}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>
```

Si le formulaire utilise un traducteur, le texte contenu dans les balises `{label}` sera traduit.

Là encore, les éléments de formulaire plus complexes, tels que 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 rendre l'élément `<input>` dans l'élément Checkbox, utilisez `{input myCheckbox:}`. Les attributs HTML doivent être séparés par une virgule `{input myCheckbox:, class: required}`.


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

Imprime un message d'erreur pour l'élément de formulaire, s'il en a un. Le message est généralement enveloppé dans un élément HTML pour le style.
Pour éviter de rendre un élément vide s'il n'y a pas de message, il est possible de le faire de manière élégante avec `n:ifcontent`:

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

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

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


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

Les étiquettes `{form signInForm}...{/form}` sont une alternative à `<form n:name="signInForm">...</form>`.


Rendu automatique .[#toc-automatic-rendering]
---------------------------------------------

Avec les balises `{input}` et `{label}`, nous pouvons facilement créer un modèle générique pour n'importe quel formulaire. Il itérera et rendra tous ses éléments séquentiellement, à l'exception des éléments cachés, qui sont rendus automatiquement lorsque le formulaire est terminé par la balise `</form>` .
Il attendra le nom du formulaire rendu 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 à paire auto-fermante utilisées `{label .../}` affichent les étiquettes provenant de la définition du formulaire dans le code PHP.

Vous pouvez enregistrer ce modèle générique dans le fichier `basic-form.latte` et 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 souhaitez influencer l'apparence d'un formulaire particulier et dessiner un élément différemment, le moyen le plus simple est de préparer des blocs dans le modèle qui peuvent être écrasés ultérieurement.
Les blocs peuvent également avoir des [noms dynamiques |latte:template-inheritance#dynamic-block-names], vous pouvez donc y insérer le nom de l'élément à dessiner. Par exemple :

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

Pour l'élément `username`, cela crée le bloc `input-username`, qui peut facilement être remplacé par la balise [{embed} |latte:template-inheritance#unit-inheritance]:

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

Par ailleurs, tout le contenu du modèle `basic-form.latte` peut être [défini |latte:template-inheritance#definitions] comme un bloc, y compris le paramètre `$form`:

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

Cela rendra son utilisation légèrement plus facile :

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

Vous ne devez importer le bloc qu'à un seul endroit, au début du modèle de mise en page :

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


Cas particuliers .[#toc-special-cases]
--------------------------------------

Si vous devez rendre uniquement la partie intérieure du formulaire sans balises HTML, par exemple lors de l'envoi d'extraits, masquez-les à l'aide de l'attribut . `<form>`par exemple lors de l'envoi d'extraits, masquez-les à l'aide de l'attribut `n:tag-if`:

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

La balise `formContainer` aide à rendre les entrées à l'intérieur d'un conteneur de formulaire.

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


Rendu sans Latte .[#toc-rendering-without-latte]
================================================

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

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

L'apparence du formulaire rendu peut être modifiée en configurant le [Renderer |#Renderer] et les [contrôles individuels |#HTML Attributes].


Rendu manuel .[#toc-manual-rendering]
-------------------------------------

Chaque élément de formulaire possède des méthodes qui génèrent le code HTML pour le champ de formulaire et l'étiquette. Elles peuvent le renvoyer sous la forme d'une chaîne ou d'un objet [Nette\Utils\Html |utils:html-elements]:

- `getControl(): Html|string` renvoie le code HTML de l'élément
- `getLabel($caption = null): Html|string|null` renvoie le code HTML de l'étiquette, le cas échéant.

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

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

Alors que pour certains éléments, `getControl()` renvoie un seul élément HTML (par ex. `<input>`, `<select>` etc.), pour d'autres, il renvoie un morceau entier de code HTML (CheckboxList, RadioList).
Dans ce cas, vous pouvez utiliser des méthodes qui génèrent des entrées et des étiquettes individuelles, pour chaque élément séparément :

- `getControlPart($key = null): ?Html` renvoie le code HTML d'un seul élément
- `getLabelPart($key = null): ?Html` renvoie le code HTML de l'étiquette d'un seul élément.

.[note]
Ces méthodes sont préfixées par `get` pour des raisons historiques, mais `generate` serait plus approprié, car il crée et renvoie un nouvel élément `Html` à chaque appel.


Renderer .[#toc-renderer]
=========================

C'est un objet qui assure le rendu du formulaire. Il peut être défini par la méthode `$form->setRenderer`. Le contrôle lui est transmis lorsque la méthode `$form->render()` est appelée.

Si nous ne définissons pas de moteur de rendu personnalisé, le moteur de rendu par défaut [api:Nette\Forms\Rendering\DefaultFormRenderer] sera utilisé. Il rendra les éléments du formulaire sous la forme d'un tableau HTML. Le résultat ressemble à ceci :

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

C'est à vous de décider si vous voulez utiliser un tableau ou non, et de nombreux concepteurs de sites Web préfèrent des balises différentes, par exemple une liste. Nous pouvons configurer `DefaultFormRenderer` pour qu'il ne soit pas rendu dans un tableau du tout. Il suffit de définir les [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers] appropriés. Le premier indice représente toujours une zone et le second un élément. Toutes les zones respectives sont indiquées dans l'image :

[* form-areas-en.webp *]

Par défaut, un groupe de `controls` est entouré de `<table>`et chaque `pair` est une ligne de tableau `<tr>` contenant une paire de `label` et `control` (cellules `<th>` et `<td>`). Changeons tous ces éléments d'habillage. Nous allons envelopper `controls` dans `<dl>`laisser `pair` seul, mettre `label` dans `<dt>` et transformer `control` en `<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();
```

Ce qui donne l'extrait suivant :

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

Les wrappers peuvent affecter de nombreux attributs. Par exemple :

- ajouter des classes CSS spéciales à chaque entrée du formulaire
- distinguer les lignes paires et impaires
- faire en sorte que les éléments obligatoires et facultatifs soient dessinés différemment
- déterminer si les messages d'erreur doivent être affichés au-dessus du formulaire ou près de chaque élément.


Options .[#toc-options]
-----------------------

Le comportement du Renderer peut également être contrôlé en définissant des *options* sur des éléments de formulaire individuels. Ainsi, vous pouvez définir l'infobulle qui s'affiche à côté du champ de saisie :

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

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

```php
use Nette\Utils\Html;

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

.[tip]
L'élément Html peut également être utilisé à la place de l'étiquette : `$form->addCheckbox('conditions', $label)`.


Regroupement des entrées .[#toc-grouping-inputs]
------------------------------------------------

Le Renderer permet de regrouper des éléments en groupes visuels (fieldsets) :

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

La création d'un nouveau groupe l'active - tous les éléments ajoutés par la suite sont ajoutés à ce groupe. Vous pouvez construire un formulaire comme ceci :

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

Le moteur de rendu dessine d'abord les groupes, puis les éléments qui n'appartiennent à aucun groupe.


Support Bootstrap .[#toc-bootstrap-support]
-------------------------------------------

Vous trouverez des [exemples |https://github.com/nette/forms/tree/master/examples] de configuration du moteur de rendu 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 .[#toc-html-attributes]
======================================

Vous pouvez définir n'importe quel attribut HTML pour les contrôles de formulaires en utilisant `setHtmlAttribute(string $name, $value = true)`:

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

$form->addSelect('rank', 'Order by:', ['price', 'name'])
	->setHtmlAttribute('onchange', 'submit()'); // appelle la fonction JS submit() en cas de modification.


// application sur <form>
$form->setHtmlAttribute('id', 'myForm');
```

Définition du type d'entrée :

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

Nous pouvons définir l'attribut HTML à des éléments individuels dans des listes de radio ou de cases à cocher avec des valeurs différentes pour chacun d'eux.
Notez les deux points après `style:` pour vous assurer que la valeur est sélectionnée par la clé :

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

Renders :

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

Pour un attribut HTML logique (qui n'a pas de valeur, comme `readonly`), vous pouvez utiliser un point d'interrogation :

```php
$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue'];
$form->addCheckboxList('colors', 'Colors:', $colors)
	->setHtmlAttribute('readonly?', 'r'); // utiliser un tableau pour les clés multiples, par exemple ['r', 'g'].
```

Rendu :

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

Pour les boîtes de sélection, la méthode `setHtmlAttribute()` définit les attributs de l'élément `<select>` de l'élément. Si nous voulons définir les attributs pour chaque
`<option>`nous utiliserons la méthode `setOptionAttribute()`. De même, les deux points et le point d'interrogation utilisés ci-dessus fonctionnent :

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

Rendu :

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


Prototypes .[#toc-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é. Le modèle est un objet `Html` et est renvoyé par la méthode `getControlPrototype()`:

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

Le modèle d'étiquette renvoyé par `getLabelPrototype()` peut également être modifié de cette manière :

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

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

```php
$input = $form->addCheckbox('send');
echo $input->getControl();
// <label><input type="checkbox" name="send"></label>

$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 des CheckboxList et RadioList, il est également possible d'influencer le modèle de séparateur d'éléments renvoyé par la méthode `getSeparatorPrototype()`. Par défaut, il s'agit d'un élément `<br>`. Si vous le changez en un élément de paire, il enveloppera les éléments individuels au lieu de les séparer.
Il est également possible d'influencer le modèle d'élément HTML des étiquettes d'éléments, qui renvoie `getItemLabelPrototype()`.


Traduire .[#toc-translating]
============================

Si vous programmez une application multilingue, vous aurez probablement besoin de rendre le formulaire dans différentes langues. Le Framework Nette définit une interface de traduction à cet effet [api:Nette\Localization\Translator]. Il n'y a pas d'implémentation par défaut dans Nette, vous pouvez choisir en fonction de vos besoins parmi plusieurs solutions prêtes à l'emploi que vous pouvez trouver sur [Componette |https://componette.org/search/localization]. Leur documentation vous indique comment configurer le traducteur.

Le formulaire supporte la sortie de texte par le traducteur. Nous le passons en utilisant la méthode `setTranslator()`:

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

À partir de maintenant, non seulement toutes les étiquettes, mais aussi tous les messages d'erreur ou les entrées des boîtes de sélection seront traduits dans une autre langue.

Il est possible de définir un traducteur différent pour chaque élément de formulaire ou de désactiver complètement la traduction à l'aide de `null`:

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

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

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

le traducteur est appelé avec les paramètres suivants :

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

et peut ainsi choisir la forme plurielle correcte pour le mot `characters` par comptage.


Événement onRender .[#toc-event-onrender]
=========================================

Juste avant que le formulaire ne soit rendu, nous pouvons faire appel à notre code. Celui-ci peut, par exemple, ajouter des classes HTML aux éléments du formulaire pour un affichage correct. Nous ajoutons le code au tableau `onRender`:

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

Rendu des formulaires

L'aspect des formes peut être très varié. Dans la pratique, nous pouvons rencontrer deux extrêmes. D'une part, il est nécessaire de rendre une série de formulaires dans une application qui sont visuellement similaires les uns aux autres, et nous apprécions le rendu facile sans modèle à l'aide de $form->render(). C'est généralement le cas des interfaces administratives.

D'autre part, il existe différents formulaires dont chacun est unique. Leur apparence est mieux décrite en utilisant le langage HTML dans le modèle. Et bien sûr, en plus des deux extrêmes mentionnés, nous rencontrerons de nombreux formulaires qui se situent quelque part entre les deux.

Rendu avec Latte

Le système de templates Latte facilite fondamentalement le rendu des formulaires et de leurs éléments. Nous allons d'abord montrer comment rendre les formulaires manuellement, élément par élément, afin d'avoir un contrôle total sur le code. Plus tard, nous montrerons comment automatiser ce rendu.

Vous pouvez obtenir la proposition d'un modèle Latte pour le formulaire généré à l'aide de la méthode Nette\Forms\Blueprint::latte($form), qui l'affichera sur 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 un modèle :

{control signInForm}

L'aspect du formulaire rendu peut être modifié en configurant le Renderer et les contrôles individuels.

n:name

Il est extrêmement facile de lier la définition du formulaire dans le code PHP avec le code HTML. Il suffit d'ajouter les attributs n:name. C'est aussi simple que cela !

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>

L'aspect du code HTML résultant est entièrement entre vos mains. Si vous utilisez l'attribut n:name avec <select>, <button> ou <textarea> leur contenu interne est automatiquement rempli. En outre, la balise <form n:name> crée une variable locale $form avec l'objet de formulaire dessiné et la balise de fermeture </form> dessine tous les éléments cachés non dessinés (il en va de même pour {form} ... {/form}).

Il ne faut cependant pas oublier de rendre les éventuels messages d'erreur. Aussi bien ceux qui ont été ajoutés aux éléments individuels par la méthode addError() (en utilisant {inputError}) que ceux ajoutés directement au formulaire (renvoyés 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>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>

Les éléments de formulaire plus complexes, tels que RadioList ou CheckboxList, peuvent être rendus élément par élément :

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

{label} {input}

Ne voulez-vous pas penser pour chaque élément à l'élément HTML à utiliser pour lui dans le modèle, que ce soit <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}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>

Si le formulaire utilise un traducteur, le texte contenu dans les balises {label} sera traduit.

Là encore, les éléments de formulaire plus complexes, tels que 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 rendre l'élément <input> dans l'élément Checkbox, utilisez {input myCheckbox:}. Les attributs HTML doivent être séparés par une virgule {input myCheckbox:, class: required}.

{inputError}

Imprime un message d'erreur pour l'élément de formulaire, s'il en a un. Le message est généralement enveloppé dans un élément HTML pour le style. Pour éviter de rendre un élément vide s'il n'y a pas de message, il est possible de le faire de manière élégante avec n:ifcontent:

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

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

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

{form}

Les étiquettes {form signInForm}...{/form} sont une alternative à <form n:name="signInForm">...</form>.

Rendu automatique

Avec les balises {input} et {label}, nous pouvons facilement créer un modèle générique pour n'importe quel formulaire. Il itérera et rendra tous ses éléments séquentiellement, à l'exception des éléments cachés, qui sont rendus automatiquement lorsque le formulaire est terminé par la balise </form> . Il attendra le nom du formulaire rendu 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 à paire auto-fermante utilisées {label .../} affichent les étiquettes provenant de la définition du formulaire dans le code PHP.

Vous pouvez enregistrer ce modèle générique dans le fichier basic-form.latte et 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 souhaitez influencer l'apparence d'un formulaire particulier et dessiner un élément différemment, le moyen le plus simple est de préparer des blocs dans le modèle qui peuvent être écrasés ultérieurement. Les blocs peuvent également avoir des noms dynamiques, vous pouvez donc y insérer le nom de l'élément à dessiner. Par exemple :

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

Pour l'élément username, cela crée le bloc input-username, qui peut facilement être remplacé par la balise {embed}:

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

Par ailleurs, tout le contenu du modèle basic-form.latte peut être défini comme un bloc, y compris le paramètre $form:

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

Cela rendra son utilisation légèrement plus facile :

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

Vous ne devez importer le bloc qu'à un seul endroit, au début du modèle de mise en page :

{import basic-form.latte}

Cas particuliers

Si vous devez rendre uniquement la partie intérieure du formulaire sans balises HTML, par exemple lors de l'envoi d'extraits, masquez-les à l'aide de l'attribut . <form>par exemple lors de l'envoi d'extraits, masquez-les à l'aide de l'attribut 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>

La balise formContainer aide à rendre les entrées à l'intérieur d'un conteneur de formulaire.

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

Rendu sans Latte

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

$form->render();

L'apparence du formulaire rendu peut être modifiée en configurant le Renderer et les contrôles individuels.

Rendu manuel

Chaque élément de formulaire possède des méthodes qui génèrent le code HTML pour le champ de formulaire et l'étiquette. Elles peuvent le renvoyer sous la forme d'une chaîne ou d'un objet Nette\Utils\Html:

  • getControl(): Html|string renvoie le code HTML de l'élément
  • getLabel($caption = null): Html|string|null renvoie le code HTML de l'étiquette, le cas échéant.

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

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

Alors que pour certains éléments, getControl() renvoie un seul élément HTML (par ex. <input>, <select> etc.), pour d'autres, il renvoie un morceau entier de code HTML (CheckboxList, RadioList). Dans ce cas, vous pouvez utiliser des méthodes qui génèrent des entrées et des étiquettes individuelles, pour chaque élément séparément :

  • getControlPart($key = null): ?Html renvoie le code HTML d'un seul élément
  • getLabelPart($key = null): ?Html renvoie le code HTML de l'étiquette d'un seul élément.

Ces méthodes sont préfixées par get pour des raisons historiques, mais generate serait plus approprié, car il crée et renvoie un nouvel élément Html à chaque appel.

Renderer

C'est un objet qui assure le rendu du formulaire. Il peut être défini par la méthode $form->setRenderer. Le contrôle lui est transmis lorsque la méthode $form->render() est appelée.

Si nous ne définissons pas de moteur de rendu personnalisé, le moteur de rendu par défaut Nette\Forms\Rendering\DefaultFormRenderer sera utilisé. Il rendra les éléments du formulaire sous la forme d'un tableau HTML. Le résultat ressemble à ceci :

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

C'est à vous de décider si vous voulez utiliser un tableau ou non, et de nombreux concepteurs de sites Web préfèrent des balises différentes, par exemple une liste. Nous pouvons configurer DefaultFormRenderer pour qu'il ne soit pas rendu dans un tableau du tout. Il suffit de définir les $wrappers appropriés. Le premier indice représente toujours une zone et le second un élément. Toutes les zones respectives sont indiquées dans l'image :

Par défaut, un groupe de controls est entouré de <table>et chaque pair est une ligne de tableau <tr> contenant une paire de label et control (cellules <th> et <td>). Changeons tous ces éléments d'habillage. Nous allons envelopper controls dans <dl>laisser pair seul, mettre label dans <dt> et transformer control en <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();

Ce qui donne l'extrait suivant :

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

Les wrappers peuvent affecter de nombreux attributs. Par exemple :

  • ajouter des classes CSS spéciales à chaque entrée du formulaire
  • distinguer les lignes paires et impaires
  • faire en sorte que les éléments obligatoires et facultatifs soient dessinés différemment
  • déterminer si les messages d'erreur doivent être affichés au-dessus du formulaire ou près de chaque élément.

Options

Le comportement du Renderer peut également être contrôlé en définissant des options sur des éléments de formulaire individuels. Ainsi, vous pouvez définir l'infobulle qui s'affiche à côté du champ de saisie :

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

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

use Nette\Utils\Html;

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

L'élément Html peut également être utilisé à la place de l'étiquette : $form->addCheckbox('conditions', $label).

Regroupement des entrées

Le Renderer permet de regrouper des éléments en groupes visuels (fieldsets) :

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

La création d'un nouveau groupe l'active – tous les éléments ajoutés par la suite sont ajoutés à ce groupe. Vous pouvez construire un formulaire comme ceci :

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

Le moteur de rendu dessine d'abord les groupes, puis les éléments qui n'appartiennent à aucun groupe.

Support Bootstrap

Vous trouverez des exemples de configuration du moteur de rendu pour Twitter Bootstrap 2, Bootstrap 3 et Bootstrap 4.

Attributs HTML

Vous pouvez définir n'importe quel attribut HTML pour les contrôles de formulaires en utilisant setHtmlAttribute(string $name, $value = true):

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

$form->addSelect('rank', 'Order by:', ['price', 'name'])
	->setHtmlAttribute('onchange', 'submit()'); // appelle la fonction JS submit() en cas de modification.


// application sur <form>
$form->setHtmlAttribute('id', 'myForm');

Définition du type d'entrée :

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

Nous pouvons définir l'attribut HTML à des éléments individuels dans des listes de radio ou de cases à cocher avec des valeurs différentes pour chacun d'eux. Notez les deux points après style: pour vous assurer que la valeur est sélectionnée par la clé :

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

Renders :

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

Pour un attribut HTML logique (qui n'a pas de valeur, comme readonly), vous pouvez utiliser un point d'interrogation :

$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue'];
$form->addCheckboxList('colors', 'Colors:', $colors)
	->setHtmlAttribute('readonly?', 'r'); // utiliser un tableau pour les clés multiples, par exemple ['r', 'g'].

Rendu :

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

Pour les boîtes de sélection, la méthode setHtmlAttribute() définit les attributs de l'élément <select> de l'élément. Si nous voulons définir les attributs pour chaque <option>nous utiliserons la méthode setOptionAttribute(). De même, les deux points et le point d'interrogation utilisés ci-dessus fonctionnent :

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

Rendu :

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

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é. Le modèle est un objet Html et est renvoyé par la méthode getControlPrototype():

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

Le modèle d'étiquette renvoyé par getLabelPrototype() peut également être modifié de cette manière :

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

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

$input = $form->addCheckbox('send');
echo $input->getControl();
// <label><input type="checkbox" name="send"></label>

$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 des CheckboxList et RadioList, il est également possible d'influencer le modèle de séparateur d'éléments renvoyé par la méthode getSeparatorPrototype(). Par défaut, il s'agit d'un élément <br>. Si vous le changez en un élément de paire, il enveloppera les éléments individuels au lieu de les séparer. Il est également possible d'influencer le modèle d'élément HTML des étiquettes d'éléments, qui renvoie getItemLabelPrototype().

Traduire

Si vous programmez une application multilingue, vous aurez probablement besoin de rendre le formulaire dans différentes langues. Le Framework Nette définit une interface de traduction à cet effet Nette\Localization\Translator. Il n'y a pas d'implémentation par défaut dans Nette, vous pouvez choisir en fonction de vos besoins parmi plusieurs solutions prêtes à l'emploi que vous pouvez trouver sur Componette. Leur documentation vous indique comment configurer le traducteur.

Le formulaire supporte la sortie de texte par le traducteur. Nous le passons en utilisant la méthode setTranslator():

$form->setTranslator($translator);

À partir de maintenant, non seulement toutes les étiquettes, mais aussi tous les messages d'erreur ou les entrées des boîtes de sélection seront traduits dans une autre langue.

Il est possible de définir un traducteur différent pour chaque élément de formulaire ou de désactiver complètement la traduction à l'aide de null:

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

Pour les règles de validation, des paramètres spécifiques sont également transmis au traducteur, par exemple pour la règle :

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

le traducteur est appelé avec les paramètres suivants :

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

et peut ainsi choisir la forme plurielle correcte pour le mot characters par comptage.

Événement onRender

Juste avant que le formulaire ne soit rendu, nous pouvons faire appel à notre code. Celui-ci peut, par exemple, ajouter des classes HTML aux éléments du formulaire pour un affichage correct. Nous ajoutons le code au tableau onRender:

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