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ückgetLabel($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ückgetLabelPart($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
Um beliebige HTML-Attribute für Formularelemente zu setzen, verwenden Sie die Methode
setHtmlAttribute(string $name, $value = true)
:
$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
// Zum Festlegen von Attributen der <form> selbst
$form->setHtmlAttribute('id', 'myForm');
Angabe der Art des Elements:
$form->addText('tel', 'Your telephone:')
->setHtmlType('tel')
->setHtmlAttribute('placeholder', 'Please, fill in your telephone');
Das Setzen des Typs und anderer Attribute dient nur der visuellen Darstellung. Die Überprüfung der Korrektheit der Eingabe muss auf dem Server erfolgen, was Sie durch die Wahl eines geeigneten Formularsteuerelements und die Festlegung von Validierungsregeln sicherstellen können.
Für einzelne Elemente in Radio- oder Checkbox-Listen können wir ein HTML-Attribut mit unterschiedlichen Werten für jedes
dieser Elemente setzen. Beachten Sie den Doppelpunkt nach style:
, der sicherstellt, dass der Wert anhand des
Schlüssels 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 das Setzen von booleschen Attributen, wie z. B. readonly
, können wir die Notation mit einem Fragezeichen
verwenden:
$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');
$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);
};