Rendern von Formularen
Das Aussehen von Formularen kann sehr vielfältig sein. In der Praxis können wir auf zwei Extreme stoßen. Auf der einen Seite
steht die Notwendigkeit, in der Anwendung eine Reihe von Formularen zu rendern, die sich visuell wie ein Ei dem anderen gleichen,
und wir schätzen das einfache Rendern ohne Vorlage mit $form->render()
. Dies ist normalerweise bei
Verwaltungsoberflächen der Fall.
Auf der anderen Seite gibt es vielfältige Formulare, bei denen gilt: jedes ein Unikat ist. Ihre Form beschreiben wir am besten mit der HTML-Sprache in der Formularvorlage. Und natürlich stoßen wir neben den beiden genannten Extremen auf viele Formulare, die sich irgendwo dazwischen bewegen.
Rendern mit Latte
Das Latte Template-System erleichtert das Rendern von Formularen und ihren Elementen erheblich. Zuerst zeigen wir, wie man Formulare manuell Element für Element rendert und so die volle Kontrolle über den Code erhält. Später zeigen wir, wie man ein solches Rendering automatisieren kann.
Den Entwurf der Latte-Vorlage für ein Formular können Sie sich mit der Methode
Nette\Forms\Blueprint::latte($form)
generieren lassen, die ihn auf der Browserseite ausgibt. Den Code können Sie
dann einfach per Klick markieren und in Ihr Projekt kopieren.
{control}
Der einfachste Weg, ein Formular zu rendern, ist, im Template zu schreiben:
{control signInForm}
Das Aussehen des so gerenderten Formulars kann durch Konfiguration des Renderers und der einzelnen Elemente beeinflusst werden.
n:name
Die Definition des Formulars im PHP-Code lässt sich extrem einfach mit dem HTML-Code verknüpfen. Es genügt, die Attribute
n:name
hinzuzufügen. 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>Passwort: <input n:name=password></label>
</div>
<div>
<input n:name=send class="btn btn-default">
</div>
</form>
Die Form des resultierenden HTML-Codes liegt vollständig in Ihren Händen. Wenn Sie das Attribut n:name
bei den
Elementen <select>
, <button>
oder <textarea>
verwenden, wird ihr innerer
Inhalt automatisch ergänzt. Das Tag <form n:name>
erstellt außerdem eine lokale Variable $form
mit dem Objekt des gerenderten Formulars, und das schließende </form>
rendert alle nicht gerenderten
versteckten Elemente (dasselbe gilt auch für {form} ... {/form}
).
Wir dürfen jedoch nicht vergessen, mögliche Fehlermeldungen zu rendern. Sowohl diejenigen, die mit der Methode
addError()
zu einzelnen Elementen hinzugefügt wurden (mithilfe von {inputError}
), als auch diejenigen,
die direkt zum Formular hinzugefügt wurden (von $form->getOwnErrors()
zurückgegeben):
<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>Passwort: <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 RadioList oder CheckboxList können so 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}
Möchten Sie nicht bei jedem Element überlegen, welches HTML-Element Sie im Template verwenden sollen, ob
<input>
, <textarea>
usw.? Die Lösung ist das universelle Tag {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}Passwort: {input password}{/label}
{inputError password}
</div>
<div>
{input send, class: "btn btn-default"}
</div>
</form>
Wenn das Formular einen Translator verwendet, wird der Text innerhalb der {label}
-Tags übersetzt.
Auch in diesem Fall können komplexere Formularelemente wie RadioList oder CheckboxList Element für Element gerendert werden:
{foreach $form[gender]->items as $key => $label}
{label gender:$key}{input gender:$key} {$label}{/label}
{/foreach}
Zum Rendern des reinen <input>
in einem Checkbox-Element verwenden Sie {input myCheckbox:}
.
HTML-Attribute trennen Sie in diesem Fall immer mit einem Komma {input myCheckbox:, class: required}
.
{inputError}
Gibt die Fehlermeldung für ein Formularelement aus, falls eine vorhanden ist. Die Meldung wird normalerweise zur Gestaltung in
ein HTML-Element verpackt. Das Rendern eines leeren Elements, wenn keine Meldung vorhanden ist, lässt sich elegant mit
n:ifcontent
verhindern:
<span class=error n:ifcontent>{inputError $input}</span>
Das Vorhandensein eines Fehlers können wir mit der Methode hasErrors()
feststellen und entsprechend die Klasse
des übergeordneten Elements setzen:
<div n:class="$form[username]->hasErrors() ? 'error'">
{input username}
{inputError username}
</div>
{form}
Die Tags {form signInForm}...{/form}
sind eine Alternative zu
<form n:name="signInForm">...</form>
.
Automatisches Rendering
Dank der Tags {input}
und {label}
können wir leicht eine allgemeine Vorlage für jedes beliebige
Formular erstellen. Es wird nacheinander alle seine Elemente iterieren und rendern, außer den versteckten Elementen, die beim
Schließen des Formulars mit dem Tag </form>
automatisch gerendert werden. Der Name des zu rendernden Formulars
wird in der Variablen $form
erwartet.
<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 paarigen Tags {label .../}
zeigen Beschriftungen an, die aus der
Formulardefinition im PHP-Code stammen.
Speichern Sie diese allgemeine Vorlage beispielsweise in der Datei basic-form.latte
und zum Rendern des Formulars
genügt es, sie zu inkludieren und den Namen (oder die Instanz) des Formulars an den Parameter $form
zu
übergeben:
{include basic-form.latte, form: signInForm}
Wenn Sie beim Rendern eines bestimmten Formulars in dessen Form eingreifen und beispielsweise ein Element anders rendern möchten, ist der einfachste Weg, im Template Blöcke vorzubereiten, die anschließend überschrieben werden können. Blöcke können auch dynamische Namen haben, sodass man auch den Namen des zu rendernden Elements einfügen kann. Zum Beispiel:
...
{label $input /}
{block "input-{$input->name}"}{input $input}{/block}
...
Für ein Element z. B. username
entsteht so der Block input-username
, der leicht mit dem Tag {embed} überschrieben werden kann:
{embed basic-form.latte, form: signInForm}
{block input-username}
<span class=important>
{include parent}
</span>
{/block}
{/embed}
Alternativ kann 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 sein Aufruf etwas einfacher:
{embed basic-form, signInForm}
...
{/embed}
Den Block genügt es dabei an einer einzigen Stelle zu importieren, und zwar am Anfang der Layout-Vorlage:
{import basic-form.latte}
Spezialfälle
Wenn Sie nur den inneren Teil des Formulars ohne die HTML-Tags <form>
rendern müssen, beispielsweise beim
Senden von Snippets, verbergen Sie sie mit dem 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>
Beim Rendern von Elementen innerhalb eines Formularcontainers hilft das Tag {formContainer}
.
<p>Welche Nachrichten möchten Sie erhalten:</p>
{formContainer emailNews}
<ul>
<li>{input sport} {label sport /}</li>
<li>{input science} {label science /}</li>
</ul>
{/formContainer}
Rendern ohne Latte
Der einfachste Weg, ein Formular zu rendern, ist der Aufruf:
$form->render();
Das Aussehen des so gerenderten Formulars kann durch Konfiguration des Renderers und der einzelnen Elemente beeinflusst werden.
Manuelles Rendern
Jedes Formularelement verfügt über Methoden, die den HTML-Code des Formularfeldes und der Beschriftung generieren. Sie können ihn entweder als Zeichenkette oder als Nette\Utils\Html-Objekt zurückgeben:
getControl(): Html|string
gibt den HTML-Code des Elements zurückgetLabel($caption = null): Html|string|null
gibt den HTML-Code der Beschriftung zurück, falls vorhanden
Das Formular kann so 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 bei einigen Elementen getControl()
ein einzelnes HTML-Element zurückgibt (z. B.
<input>
, <select>
usw.), gibt es bei anderen 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 generieren:
getControlPart($key = null): ?Html
gibt den HTML-Code eines einzelnen Elements zurückgetLabelPart($key = null): ?Html
gibt den HTML-Code der Beschriftung eines einzelnen Elements zurück
Diese Methoden haben aus historischen Gründen das Präfix get
, aber generate
wäre
besser, da bei jedem Aufruf ein neues Html
-Element erstellt und zurückgegeben wird.
Renderer
Dies ist ein Objekt, das das Rendern des Formulars sicherstellt. Es kann mit der Methode $form->setRenderer
festgelegt werden. Ihm wird die Kontrolle übergeben, wenn die Methode $form->render()
aufgerufen wird.
Wenn wir keinen eigenen Renderer festlegen, wird der Standard-Renderer Nette\Forms\Rendering\DefaultFormRenderer verwendet. Dieser rendert die Formularelemente in Form einer HTML-Tabelle. Die Ausgabe sieht so 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">Alter:</label></th>
<td><input type="text" class="text" name="age" id="frm-age" required value=""></td>
</tr>
<tr>
<th><label>Geschlecht:</label></th>
...
Ob man für das Formulargerüst eine Tabelle verwenden soll oder nicht, ist umstritten, und viele Webdesigner bevorzugen ein
anderes Markup. Zum Beispiel eine Definitionsliste. Wir konfigurieren daher den DefaultFormRenderer
so um, dass er
das Formular als Liste rendert. Die Konfiguration erfolgt durch Bearbeitung des Feldes $wrappers. Der
erste Index stellt immer den Bereich dar und der zweite sein Attribut. Die einzelnen Bereiche zeigt das Bild:

Standardmäßig ist die Gruppe der Elemente controls
von einer Tabelle <table>
umschlossen,
jedes pair
stellt eine Tabellenzeile <tr>
dar, und die Paare label
und
control
sind Zellen <th>
und <td>
. Nun ändern wir die umschließenden
Elemente. Den Bereich controls
legen wir in einen Container <dl>
, den Bereich pair
lassen wir ohne Container, label
legen wir in <dt>
und schließlich control
umschließen wir mit <dd>
-Tags:
$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 dieser HTML-Code:
<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">Alter:</label></dt>
<dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd>
<dt><label>Geschlecht:</label></dt>
...
</dl>
Im Wrappers-Array können viele weitere Attribute beeinflusst werden:
- CSS-Klassen zu einzelnen Typen von Formularelementen hinzufügen
- ungerade und gerade Zeilen durch CSS-Klassen unterscheiden
- Pflichtfelder und optionale Felder visuell unterscheiden
- bestimmen, ob Fehlermeldungen direkt bei den Elementen oder über dem Formular angezeigt werden
Options
Das Verhalten des Renderers kann auch durch das Setzen von options auf einzelnen Formularelementen gesteuert werden. So kann eine Beschreibung festgelegt werden, die neben dem Eingabefeld ausgegeben wird:
$form->addText('phone', 'Nummer:')
->setOption('description', 'Diese Nummer bleibt verborgen');
Wenn wir darin HTML-Inhalt platzieren möchten, verwenden wir die Klasse Html
use Nette\Utils\Html;
$form->addText('phone', 'Nummer:')
->setOption('description', Html::el('p')
->setHtml('<a href="...">Bedingungen zur Speicherung Ihrer Nummer</a>')
);
Ein Html-Element kann auch anstelle eines Labels verwendet werden:
$form->addCheckbox('conditions', $label)
.
Gruppierung von Elementen
Der Renderer ermöglicht das Gruppieren von Elementen in visuelle Gruppen (Fieldsets):
$form->addGroup('Persönliche Daten');
Nachdem eine neue Gruppe erstellt wurde, wird diese aktiv, und jedes neu hinzugefügte Element wird gleichzeitig auch zu ihr hinzugefügt. Das Formular kann also auf diese Weise aufgebaut werden:
$form = new Form;
$form->addGroup('Persönliche Daten');
$form->addText('name', 'Ihr Name:');
$form->addInteger('age', 'Ihr Alter:');
$form->addEmail('email', 'E-Mail:');
$form->addGroup('Lieferadresse');
$form->addCheckbox('send', 'An Adresse liefern');
$form->addText('street', 'Straße:');
$form->addText('city', 'Stadt:');
$form->addSelect('country', 'Land:', $countries);
Der Renderer rendert zuerst die Gruppen und erst danach die Elemente, die zu keiner Gruppe gehören.
Unterstützung für Bootstrap
In den Beispielen finden Sie Beispiele, wie Sie den Renderer für Twitter Bootstrap 2, Bootstrap 3 und Bootstrap 4 konfigurieren.
HTML-Attribute
Um beliebige HTML-Attribute für Formularelemente festzulegen, verwenden wir die Methode
setHtmlAttribute(string $name, $value = true)
:
$form->addInteger('number', 'Nummer:')
->setHtmlAttribute('class', 'big-number');
$form->addSelect('rank', 'Sortieren nach:', ['Preis', 'Name'])
->setHtmlAttribute('onchange', 'submit()'); // bei Änderung senden
// Um Attribute des <form>-Elements selbst festzulegen
$form->setHtmlAttribute('id', 'myForm');
Spezifikation des Elementtyps:
$form->addText('tel', 'Ihr Telefon:')
->setHtmlType('tel')
->setHtmlAttribute('placeholder', 'Telefonnummer eingeben');
Die Einstellung des Typs und anderer Attribute dient nur zu visuellen Zwecken. Die Überprüfung der Richtigkeit der Eingaben muss serverseitig erfolgen, was durch die Wahl des geeigneten Formularelements und die Angabe von Validierungsregeln sichergestellt wird.
Einzelnen Elementen in Radio- oder Checkbox-Listen können wir HTML-Attribute mit unterschiedlichen Werten für jedes von ihnen
zuweisen. Beachten Sie den Doppelpunkt nach style:
, der die Auswahl des Wertes nach Schlüssel sicherstellt:
$colors = ['r' => 'rot', 'g' => 'grün', 'b' => 'blau'];
$styles = ['r' => 'background:red', 'g' => 'background:green'];
$form->addCheckboxList('colors', 'Farben:', $colors)
->setHtmlAttribute('style:', $styles);
Gibt aus:
<label><input type="checkbox" name="colors[]" style="background:red" value="r">rot</label>
<label><input type="checkbox" name="colors[]" style="background:green" value="g">grün</label>
<label><input type="checkbox" name="colors[]" value="b">blau</label>
Um logische Attribute wie readonly
festzulegen, können wir eine Schreibweise mit Fragezeichen verwenden:
$form->addCheckboxList('colors', 'Farben:', $colors)
->setHtmlAttribute('readonly?', 'r'); // für mehrere Schlüssel verwenden Sie ein Array, z. B. ['r', 'g']
Gibt aus:
<label><input type="checkbox" name="colors[]" readonly value="r">rot</label>
<label><input type="checkbox" name="colors[]" value="g">grün</label>
<label><input type="checkbox" name="colors[]" value="b">blau</label>
Bei Selectboxen setzt die Methode setHtmlAttribute()
Attribute des <select>
-Elements. Wenn wir
Attribute für einzelne <option>
setzen möchten, verwenden wir die Methode setOptionAttribute()
.
Auch Schreibweisen mit Doppelpunkt und Fragezeichen, wie oben erwähnt, funktionieren:
$form->addSelect('colors', 'Farben:', $colors)
->setOptionAttribute('style:', $styles);
Gibt aus:
<select name="colors">
<option value="r" style="background:red">rot</option>
<option value="g" style="background:green">grün</option>
<option value="b">blau</option>
</select>
Prototypen
Eine alternative Methode zum Festlegen von HTML-Attributen besteht darin, die Vorlage zu ändern, aus der das HTML-Element
generiert wird. Die Vorlage ist ein Html
-Objekt und wird von der Methode getControlPrototype()
zurückgegeben:
$input = $form->addInteger('number', 'Nummer:');
$html = $input->getControlPrototype(); // <input>
$html->class('big-number'); // <input class="big-number">
Auf diese Weise kann auch die Vorlage der Beschriftung geändert werden, die von getLabelPrototype()
zurückgegeben wird:
$html = $input->getLabelPrototype(); // <label>
$html->class('distinctive'); // <label class="distinctive">
Bei den Elementen Checkbox, CheckboxList und RadioList können Sie die Vorlage des Elements beeinflussen, das das gesamte
Element umschließt. Sie wird von getContainerPrototype()
zurückgegeben. Standardmäßig ist dies ein „leeres“
Element, sodass nichts gerendert wird, aber indem wir ihm einen Namen geben, wird es gerendert:
$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>
Im Fall von CheckboxList und RadioList kann auch die Vorlage des Trennzeichens der einzelnen Elemente beeinflusst werden, das
von der Methode getSeparatorPrototype()
zurückgegeben wird. Standardmäßig ist dies das Element
<br>
. Wenn Sie es in ein paarweises Element ändern, wird es die einzelnen Elemente umschließen anstatt sie zu
trennen. Weiterhin kann die Vorlage des HTML-Elements der Beschriftung bei den einzelnen Elementen beeinflusst werden, das von
getItemLabelPrototype()
zurückgegeben wird.
Übersetzung
Wenn Sie eine mehrsprachige Anwendung programmieren, müssen Sie das Formular wahrscheinlich in verschiedenen Sprachversionen rendern. Das Nette Framework definiert zu diesem Zweck eine Schnittstelle für die Übersetzung Nette\Localization\Translator. In Nette gibt es keine Standardimplementierung, Sie können je nach Bedarf aus mehreren fertigen Lösungen wählen, die Sie auf Componette finden. In deren Dokumentation erfahren Sie, wie Sie den Translator konfigurieren.
Formulare unterstützen die Ausgabe von Texten über den Translator. Wir übergeben ihn ihnen mit der Methode
setTranslator()
:
$form->setTranslator($translator);
Ab diesem Zeitpunkt werden nicht nur alle Beschriftungen, sondern auch alle Fehlermeldungen oder Elemente von Select-Boxen in eine andere Sprache übersetzt.
Bei einzelnen Formularelementen ist es dabei möglich, einen anderen Übersetzer einzustellen oder die Übersetzung mit dem
Wert null
vollständig zu deaktivieren:
$form->addSelect('carModel', 'Modell:', $cars)
->setTranslator(null);
Bei Validierungsregeln werden dem Translator auch spezifische Parameter übergeben, beispielsweise bei der Regel:
$form->addPassword('password', 'Passwort:')
->addRule($form::MinLength, 'Das Passwort muss mindestens %d Zeichen lang sein', 8);
wird der Translator mit diesen Parametern aufgerufen:
$translator->translate('Das Passwort muss mindestens %d Zeichen lang sein', 8);
und kann somit die richtige Pluralform des Wortes Zeichen
entsprechend der Anzahl wählen.
Ereignis onRender
Kurz bevor das Formular gerendert wird, können wir unseren Code aufrufen lassen. Dieser kann beispielsweise den
Formularelementen HTML-Klassen für die korrekte Anzeige hinzufügen. Den Code fügen wir zum Array
onRender
hinzu:
$form->onRender[] = function ($form) {
BootstrapCSS::initialize($form);
};