Formulare Rendering

Das Erscheinungsbild von Formularen kann sehr unterschiedlich sein. In der Praxis können wir auf zwei Extreme stoßen. Einerseits besteht die Notwendigkeit, eine Reihe von Formularen in einer Anwendung darzustellen, die sich optisch ähneln, und wir schätzen die einfache Darstellung ohne Vorlage mit $form->render(). Dies ist normalerweise bei Verwaltungsschnittstellen der Fall.

Andererseits gibt es verschiedene Formulare, von denen jedes einzelne einzigartig ist. Ihr Aussehen wird am besten durch die HTML-Sprache in der Vorlage beschrieben. Und natürlich werden wir neben den beiden genannten Extremen auch viele Formulare finden, die irgendwo dazwischen liegen.

Rendering mit Latte

Das Latte-Templating-System erleichtert das Rendern von Formularen und ihren Elementen grundlegend. Wir werden zunächst zeigen, wie man Formulare manuell, Element für Element, rendern kann, um die volle Kontrolle über den Code zu erhalten. Später werden wir zeigen, wie man dieses Rendering automatisieren kann.

Mit der Methode Nette\Forms\Blueprint::latte($form) können Sie sich den Vorschlag einer Lattenvorlage für das Formular generieren lassen, der dann auf der Browser-Seite ausgegeben wird. Dann brauchen Sie den Code nur noch mit einem Klick auszuwählen und in Ihr Projekt zu kopieren.

{control}

Der einfachste Weg, ein Formular zu rendern, ist, es in eine Vorlage zu schreiben:

{control signInForm}

Das Aussehen des gerenderten Formulars kann durch die Konfiguration des Renderers und einzelner Steuerelemente verändert werden.

n:name

Es ist sehr einfach, die Formulardefinition in PHP-Code mit HTML-Code zu verknüpfen. Fügen Sie einfach die n:name Attribute hinzu. So einfach ist das!

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>

Das Aussehen des resultierenden HTML-Codes liegt ganz in Ihrer Hand. Wenn Sie das Attribut n:name mit <select>, <button> oder <textarea> Elementen verwenden, wird deren interner Inhalt automatisch ausgefüllt. Darüber hinaus erzeugt der <form n:name> Tag eine lokale Variable $form mit dem gezeichneten Formularobjekt und das schließende </form> zeichnet alle nicht gezeichneten versteckten Elemente (das gleiche gilt für {form} ... {/form}).

Wir dürfen jedoch nicht vergessen, mögliche Fehlermeldungen zu rendern. Sowohl solche, die durch die Methode addError() zu einzelnen Elementen hinzugefügt wurden (mit {inputError}), als auch solche, die direkt zum Formular hinzugefügt wurden (zurückgegeben von $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>

Komplexere Formularelemente, wie z. B. RadioList oder CheckboxList, können Element für Element gerendert werden:

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

{label} {input}

Wollen Sie nicht für jedes Element überlegen, welches HTML-Element Sie dafür in der Vorlage verwenden wollen, ob <input>, <textarea> usw.? Die Lösung ist der universelle {input} Tag:

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

Wenn das Formular einen Übersetzer verwendet, wird der Text innerhalb der {label} Tags übersetzt.

Auch hier gilt, dass komplexere Formularelemente wie RadioList oder CheckboxList Element für Element gerendert werden können:

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

Zum Rendern des <input> selbst im Element Checkbox zu rendern, verwenden Sie {input myCheckbox:}. HTML-Attribute müssen durch ein Komma getrennt werden {input myCheckbox:, class: required}.

{inputError}

Gibt eine Fehlermeldung für das Formularelement aus, wenn es eine hat. Die Meldung wird normalerweise in ein HTML-Element zur Gestaltung eingeschlossen. Die Vermeidung der Darstellung eines leeren Elements, wenn es keine Meldung gibt, kann elegant mit n:ifcontent erfolgen:

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

Wir können das Vorhandensein eines Fehlers mit der Methode hasErrors() erkennen und die Klasse des übergeordneten Elements entsprechend einstellen:

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

{form}

Tags {form signInForm}...{/form} sind eine Alternative zu <form n:name="signInForm">...</form>.

Automatisches Rendering

Mit den Tags {input} und {label} können wir auf einfache Weise eine generische Vorlage für ein beliebiges Formular erstellen. Sie durchläuft alle Elemente des Formulars und rendert sie nacheinander, mit Ausnahme der versteckten Elemente, die automatisch gerendert werden, wenn das Formular mit dem </form> Tag beendet wird. Sie erwartet den Namen des gerenderten Formulars in der Variablen $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>

Die verwendeten selbstschließenden Paar-Tags {label .../} zeigen die Bezeichnungen aus der Formulardefinition im PHP-Code an.

Sie können diese generische Vorlage in der Datei basic-form.latte speichern und das Formular rendern, indem Sie es einbinden und den Formularnamen (oder die Instanz) an den Parameter $form übergeben:

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

Wenn Sie das Aussehen eines bestimmten Formulars beeinflussen und ein Element anders zeichnen möchten, ist es am einfachsten, in der Vorlage Blöcke vorzubereiten, die später überschrieben werden können. Blöcke können auch dynamische Namen haben, so dass Sie den Namen des zu zeichnenden Elements in sie einfügen können. Zum Beispiel:

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

Für das Element z.B. username wird der Block input-username erstellt, der mit dem Tag {embed} einfach überschrieben werden kann:

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

Alternativ kann auch der gesamte Inhalt der Vorlage basic-form.latte als Block definiert werden, einschließlich des Parameters $form:

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

Dadurch wird die Verwendung etwas einfacher:

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

Sie brauchen den Block nur noch an einer Stelle zu importieren, nämlich am Anfang der Layout-Vorlage:

{import basic-form.latte}

Besondere Fälle

Wenn Sie nur den inneren Teil des Formulars ohne HTML-Tags darstellen wollen <form>darstellen wollen, z. B. beim Senden von Snippets, blenden Sie diese mit dem Attribut n:tag-if aus:

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

Das Tag formContainer hilft beim Rendern von Eingaben innerhalb eines Formular-Containers.

<p>Which news you wish to receive:</p>

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

Rendering ohne Latte

Der einfachste Weg, ein Formular zu rendern, ist der Aufruf:

$form->render();

Das Aussehen des gerenderten Formulars kann durch Konfiguration des Renderers und einzelner Steuerelemente geändert werden.

Manuelles Rendering

Jedes Formularelement hat Methoden, die den HTML-Code für das Formularfeld und die Beschriftung erzeugen. Sie können ihn entweder als String oder als Nette\Utils\Html-Objekt zurückgeben:

  • getControl(): Html|string gibt den HTML-Code des Elements zurück
  • getLabel($caption = null): Html|string|null gibt den HTML-Code der Beschriftung zurück, falls vorhanden

So kann das Formular Element für Element gerendert werden:

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

Während für einige Elemente getControl() ein einzelnes HTML-Element zurückgibt (z. B. <input>, <select> usw.), gibt es für andere ein ganzes Stück HTML-Code zurück (CheckboxList, RadioList). In diesem Fall können Sie Methoden verwenden, die einzelne Eingaben und Beschriftungen für jedes Element separat erzeugen:

  • getControlPart($key = null): ?Html gibt den HTML-Code eines einzelnen Elements zurück
  • getLabelPart($key = null): ?Html gibt den HTML-Code für die Beschriftung eines einzelnen Eintrags zurück

Aus historischen Gründen wird diesen Methoden das Präfix get vorangestellt, aber generate wäre besser, da es bei jedem Aufruf ein neues Element Html erzeugt und zurückgibt.

Renderer

Es handelt sich um ein Objekt, das das Rendering des Formulars ermöglicht. Es kann durch die Methode $form->setRenderer gesetzt werden. Es wird beim Aufruf der Methode $form->render() an die Kontrolle übergeben.

Wenn wir keinen benutzerdefinierten Renderer festlegen, wird der Standard-Renderer Nette\Forms\Rendering\DefaultFormRenderer verwendet. Dieser rendert die Formularelemente als HTML-Tabelle. Die Ausgabe sieht wie folgt aus:

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

Es bleibt Ihnen überlassen, ob Sie eine Tabelle verwenden oder nicht, und viele Webdesigner bevorzugen andere Auszeichnungen, zum Beispiel eine Liste. Wir können DefaultFormRenderer so konfigurieren, dass es überhaupt nicht in eine Tabelle gerendert wird. Wir müssen nur die richtigen $wrappers setzen. Der erste Index steht immer für einen Bereich und der zweite für ein Element. Alle entsprechenden Bereiche sind im Bild zu sehen:

Standardmäßig wird eine Gruppe von controls in <table>eingeschlossen, und jede pair ist eine Tabellenzeile <tr> mit einem Paar von label und control (Zellen <th> und <td>). Lassen Sie uns all diese Umhüllungselemente ändern. Wir packen controls in <dl>ein, belassen pair für sich, fügen label in <dt> und verpacken control in <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();

Das Ergebnis ist der folgende Ausschnitt:

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

Wrapper können viele Attribute beeinflussen. Zum Beispiel:

  • spezielle CSS-Klassen zu jeder Formulareingabe hinzufügen
  • Unterscheidung zwischen ungeraden und geraden Zeilen
  • Erforderliche und optionale Zeichen unterschiedlich gestalten
  • einstellen, ob Fehlermeldungen oberhalb des Formulars oder in der Nähe der einzelnen Elemente angezeigt werden

Optionen

Das Verhalten von Renderer kann auch durch das Setzen von Optionen für einzelne Formularelemente gesteuert werden. Auf diese Weise können Sie den Tooltip einstellen, der neben dem Eingabefeld angezeigt wird:

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

Wenn wir einen HTML-Inhalt darin platzieren wollen, verwenden wir die Klasse Html.

use Nette\Utils\Html;

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

Das Html-Element kann auch anstelle des Labels verwendet werden: $form->addCheckbox('conditions', $label).

Eingaben gruppieren

Renderer ermöglicht die Gruppierung von Elementen in visuelle Gruppen (Fieldsets):

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

Durch das Erstellen einer neuen Gruppe wird diese aktiviert – alle weiteren Elemente werden dieser Gruppe hinzugefügt. Sie können ein Formular wie folgt erstellen:

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

Der Renderer zeichnet zuerst die Gruppen und dann die Elemente, die zu keiner Gruppe gehören.

Bootstrap-Unterstützung

Sie können Beispiele für die Konfiguration des Renderers für Twitter Bootstrap 2, Bootstrap 3 und Bootstrap 4 finden

HTML-Attribute

Mit setHtmlAttribute(string $name, $value = true) können Sie beliebige HTML-Attribute für Formularsteuerelemente festlegen:

$form->addInteger('number', 'Zahl:')
	->setHtmlAttribute('class', 'Große Zahl');

$form->addSelect('rank', 'Sortieren nach:', ['Preis', 'Name'])
	->setHtmlAttribute('onchange', 'submit()'); // ruft JS-Funktion submit() bei Änderung auf


// Anwendung auf <form>
$form->setHtmlAttribute('id', 'myForm');

Einstellung des Eingabetyps:

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

Wir können HTML-Attribute für einzelne Elemente in Radio- oder Checkbox-Listen mit unterschiedlichen Werten für jedes dieser Elemente festlegen. Beachten Sie den Doppelpunkt nach style:, um sicherzustellen, dass der Wert nach Schlüssel ausgewählt wird:

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

Für ein logisches HTML-Attribut (das keinen Wert hat, wie readonly) können Sie ein Fragezeichen verwenden:

$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue'];
$form->addCheckboxList('colors', 'Colors:', $colors)
	->setHtmlAttribute('readonly?', 'r'); // use array for multiple keys, e.g. ['r', 'g']

Renders:

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

Bei Selectboxen setzt die Methode setHtmlAttribute() die Attribute des <select> Elements. Wenn wir die Attribute für jedes der folgenden Elemente setzen wollen <option>setzen wollen, verwenden wir die Methode setOptionAttribute(). Auch der Doppelpunkt und das Fragezeichen, die oben verwendet wurden, funktionieren:

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

Rendert:

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

Prototypen

Eine alternative Möglichkeit, HTML-Attribute zu setzen, besteht darin, die Vorlage zu ändern, aus der das HTML-Element erzeugt wird. Das Template ist ein Html Objekt und wird von der Methode getControlPrototype() zurückgegeben:

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

Die von getLabelPrototype() zurückgegebene Etikettenvorlage kann ebenfalls auf diese Weise geändert werden:

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

Für Checkbox-, CheckboxList- und RadioList-Elemente können Sie die Elementvorlage beeinflussen, die das Element umhüllt. Sie wird von getContainerPrototype() zurückgegeben. Standardmäßig ist es ein “leeres” Element, so dass nichts gerendert wird, aber wenn Sie ihm einen Namen geben, wird es gerendert:

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

Im Falle von CheckboxList und RadioList ist es auch möglich, das von der Methode getSeparatorPrototype() zurückgegebene Elementtrennmuster zu beeinflussen. Standardmäßig ist es ein Element <br>. Wenn Sie es in ein Paarelement ändern, umhüllt es die einzelnen Elemente, anstatt sie zu trennen. Es ist auch möglich, die HTML-Elementvorlage der Elementbeschriftungen zu beeinflussen, die getItemLabelPrototype() zurückgibt.

Übersetzen

Wenn Sie eine mehrsprachige Anwendung programmieren, müssen Sie das Formular wahrscheinlich in verschiedenen Sprachen darstellen. Das Nette Framework definiert zu diesem Zweck eine Übersetzungsschnittstelle Nette\Localization\Translator. Es gibt keine Standardimplementierung in Nette, Sie können je nach Bedarf aus mehreren fertigen Lösungen wählen, die Sie auf Componette finden. Die Dokumentation beschreibt, wie Sie den Übersetzer konfigurieren können.

Das Formular unterstützt die Ausgabe von Text durch den Übersetzer. Wir übergeben ihn mit der Methode setTranslator():

$form->setTranslator($translator);

Von nun an werden nicht nur alle Beschriftungen, sondern auch alle Fehlermeldungen oder Selectbox-Einträge in eine andere Sprache übersetzt.

Es ist möglich, für einzelne Formularelemente einen anderen Übersetzer einzustellen oder die Übersetzung mit null komplett zu deaktivieren:

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

Für Validierungsregeln werden auch spezifische Parameter an den Übersetzer übergeben, zum Beispiel für die Regel:

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

Der Übersetzer wird mit den folgenden Parametern aufgerufen:

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

und kann so die korrekte Pluralform für das Wort characters nach Anzahl wählen.

Ereignis onRender

Kurz bevor das Formular gerendert wird, können wir unseren Code aufrufen lassen. Dieser kann zum Beispiel HTML-Klassen zu den Formularelementen hinzufügen, damit sie richtig angezeigt werden. Wir fügen den Code in das Array onRender ein:

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