フォームの描画
フォームの見た目はとても多様です。実務では 2
つの極端に出くわします。ひとつは、見た目のそろったたくさんのフォームをアプリケーションで描く必要がある場合で、そこでは
$form->render()
によるテンプレートなしの簡単な描画がありがたく思えます。管理画面がまさにその例です。
もうひとつは、ひとつひとつが違う多様なフォームです。その見た目は、フォームのテンプレートの HTML で書くのがいちばんです。そしてもちろん、この 2 つの極端のあいだのどこかに落ちるフォームにも数多く出会います。
Latte での描画
Latte のテンプレートシステムは、フォームとその要素の描画を大いに簡単にします。まず、コードを完全に思いどおりにするために、フォームを要素ごとに手で描く方法をお見せします。そのあとで、そうした描画を自動化する方法を見ます。
フォームの Latte のテンプレートは Nette\Forms\Blueprint::latte($form)
メソッドで生成でき、ブラウザのページに出力されます。あとはクリックしてコードを選び、プロジェクトにコピーするだけです。
{control}
フォームを描くいちばん単純な方法は、テンプレートに次のように書くことです。
{control signInForm}
描かれるフォームの見た目は、Rendererの設定と個々の要素で変えられます。
n:name
PHP のコードのフォームの定義と HTML
のコードを結び付けるのはきわめて簡単です。n:name
の属性を足すだけです。それだけです。
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>ユーザー名: <input n:name=username size=20 autofocus></label>
</div>
<div>
<label n:name=password>パスワード: <input n:name=password></label>
</div>
<div>
<input n:name=send class="btn btn-default">
</div>
</form>
できあがる HTML のコードの見た目を完全に思いどおりにできます。n:name 属性を
<select>、<button>、<textarea>
の要素で使うと、その中身は自動的に埋められます。さらに <form n:name>
のタグは、描かれるフォームのオブジェクトが入った局所変数 $form を作り、閉じる
</form> のタグは描かれていない隠しの要素を描きます({form} ... {/form}
でも同じです)。
とはいえ、エラーのメッセージを描くのを忘れてはいけません。これには addError()
メソッドで個々の要素に足されたエラー({inputError}
で描かれます)と、フォームそのものに足されたエラー($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>ユーザー名: <input n:name=username size=20 autofocus></label>
<span class=error n:ifcontent>{inputError username}</span>
</div>
<div>
<label n:name=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>
RadioList や CheckboxList のような、もっと込み入ったフォームの要素は、次のように項目ごとに描けます。
{foreach $form[gender]->getItems() as $key => $label}
<label n:name="gender:$key"><input n:name="gender:$key"> {$label}</label>
{/foreach}
{label} {input}
要素ごとにテンプレートでどの HTML 要素を使うか、<input> なのか
<textarea> なのかを考えたくないですか。その解が万能の
{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}ユーザー名: {input username, size: 20, autofocus: true}{/label}
{inputError username}
</div>
<div>
{label password}パスワード: {input password}{/label}
{inputError password}
</div>
<div>
{input send, class: "btn btn-default"}
</div>
</form>
フォームが翻訳器を使っているなら、フォームの定義から描かれるラベル(たとえば
{label username /})は翻訳されます。{label} と {/label}
のタグのあいだに直接書かれた文は翻訳されません。
ここでも、RadioList や CheckboxList のような、もっと込み入ったフォームの要素は項目ごとに描けます。
{foreach $form[gender]->items as $key => $label}
{label gender:$key}{input gender:$key} {$label}{/label}
{/foreach}
Checkbox の要素の <input> だけを描くには {input myCheckbox:}
を使います。この場合、HTML
の属性は必ずコンマで区切ってください。{input myCheckbox:, class: required}
のようにです。
{inputError}
フォームの要素のエラーのメッセージがあれば表示します。このメッセージは、体裁を整えるためにふつう
HTML
の要素で包みます。メッセージがないときに空の要素が描かれるのを防ぐには、n:ifcontent
を使うと優雅です。
<span class=error n:ifcontent>{inputError $input}</span>
エラーがあるかどうかは hasErrors()
メソッドで確かめられ、それに応じて親の要素のクラスを設定できます。
<div n:class="$form[username]->hasErrors() ? 'error'">
{input username}
{inputError username}
</div>
{form}
{form signInForm}...{/form} のタグは <form n:name="signInForm">...</form>
の代わりになります。引数があれば名前とコンマで区切ってください。{form signInForm, class: foo}
のようにです。
名前の前に置く scope
の語は、フォームをスタックに積むだけで(つまり {input} や {label}
などがそれに結び付きます)、<form>
のタグは描きません。フォームの一部を、たとえばスニペットの中で描くのに便利です。すでにフォームが有効なら、名前はそれを基準に解決されるので、{form scope}
は {formContainer} の代わりにもなります。
{form scope signInForm}
{input username}
{/form}
detached の語は空の <form></form>
を描き、すべての要素を HTML の form 属性でそれに結び付けます。おかげで、HTML
がふつうは禁じているフォームの中のフォームを置けます。切り離されたフォームには HTML の
id が要りますが、名前を与えれば自動的に生成されます(下の outerForm
のようにです)。
{form detached outerForm}
...
{/form}
自動的な描画
{input} と {label}
のタグのおかげで、どんなフォームにも使える汎用のテンプレートを簡単に作れます。それはすべての要素を順に回って描きます。ただし隠しの要素は除きます。それは
</form>
のタグでフォームを閉じたときに自動的に描かれるからです。このテンプレートは、描くフォームの名前が
$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>
ここで使っている自分で閉じる対のタグ {label .../} は、PHP
のコードのフォームの定義から来るラベルを表示します。
この汎用のテンプレートを、たとえば basic-form.latte
というファイルに保存します。フォームを描くには、それを取り込んで $form
のパラメータにフォームの名前(かインスタンス)を渡すだけです。
{include basic-form.latte, form: signInForm}
特定のフォームの描画の見た目だけを変えたい、たとえばある要素を違うふうに描きたいなら、いちばん簡単なのはテンプレートにあとから上書きできるブロックを用意することです。ブロックには動的な名前も付けられるので、描く要素の名前を差し込めます。たとえば次のようにです。
...
{label $input /}
{block "input-{$input->name}"}{input $input}{/block}
...
たとえば username という名前の要素なら input-username
というブロックができ、{embed}のタグで簡単に上書きできます。
{embed basic-form.latte, form: signInForm}
{block input-username}
<span class=important>
{include parent}
</span>
{/block}
{/embed}
あるいは basic-form.latte テンプレートの中身全体を、$form
のパラメータも含めてブロックとして定義できます。
{define basic-form, $form}
<form n:name=$form class=form>
...
</form>
{/define}
これで呼び出しが少し簡単になります。
{embed basic-form, signInForm}
...
{/embed}
このブロックは 1 か所、レイアウトのテンプレートの先頭で取り込むだけで済みます。
{import basic-form.latte}
特別な場合
たとえばスニペットを送るときなど、<form> の HTML
のタグなしでフォームの内側だけを描く必要があるなら、n:tag-if
属性でそれらを隠します。
<form n:name=signInForm n:tag-if=false>
<div>
<label n:name=username>ユーザー名: <input n:name=username></label>
{inputError username}
</div>
</form>
フォームのコンテナの中の要素を描くには、{formContainer} のタグ、あるいは新しい {form scope}が役に立ちます。
<p>どのニュースを受け取りますか:</p>
{formContainer emailNews}
<ul>
<li>{input sport} {label sport /}</li>
<li>{input science} {label science /}</li>
</ul>
{/formContainer}
Latte なしの描画
フォームを描くいちばん簡単な方法は、次を呼ぶことです。
$form->render();
描かれるフォームの見た目は、Rendererの設定と個々の要素で変えられます。
手での描画
フォームのそれぞれの要素には、フォームの項目とそのラベルの HTML のコードを生成するメソッドがあります。それらは文字列としても Nette\Utils\Htmlオブジェクトとしても返せます。
getControl(): Html|stringは要素の HTML のコードを返しますgetLabel($caption = null): Html|string|nullは、あればラベルの HTML のコードを返します
おかげでフォームを要素ごとに描けます。
<?php $form->render('begin') ?>
<?php $form->render('ownerrors') ?>
<div>
<?= $form['name']->getLabel() ?>
<?= $form['name']->getControl() ?>
<span class=error><?= htmlspecialchars((string) $form['name']->getError()) ?></span>
</div>
<div>
<?= $form['age']->getLabel() ?>
<?= $form['age']->getControl() ?>
<span class=error><?= htmlspecialchars((string) $form['age']->getError()) ?></span>
</div>
// ...
<?php $form->render('end') ?>
要素によっては getControl() がひとつの HTML 要素(たとえば <input> や
<select> など)を返しますが、HTML
のコードのひとまとまり(CheckboxList、RadioList)を返すものもあります。その場合は、項目ごとに個々の入力とラベルを生成するメソッドを使えます。
getControlPart($key = null): Htmlはひとつの項目の HTML のコードを返しますgetLabelPart($key = null): Htmlはひとつの項目のラベルの HTML のコードを返します
これらのメソッドに get
の接頭辞が付いているのは歴史的な事情によるもので、呼ばれるたびに新しい Html
要素を作って返すので、generate のほうがふさわしいでしょう。
Renderer
これはフォームを描くことを受け持つオブジェクトです。$form->setRenderer()
メソッドで設定できます。$form->render()
メソッドが呼ばれると、そこへ制御が渡ります。
独自の描画器を設定しなければ、既定の描画器 Nette\Forms\Rendering\DefaultFormRendererが使われます。これはフォームの要素を HTML の表に描きます。出力は次のようになります。
<table>
<tr class="required">
<th><label class="required" for="frm-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">年齢:</label></th>
<td><input type="text" class="text" name="age" id="frm-age" required value=""></td>
</tr>
<tr>
<th><label>性別:</label></th>
...
フォームの構造に表を使うべきかは議論の分かれるところで、多くのウェブデザイナーは定義リストのような別のマークアップを好みます。ですから
DefaultFormRenderer を設定し直して、フォームをリストとして描かせましょう。設定は $wrappers配列を書き換えて行います。最初の添字はいつも領域を、2
つめはその属性を表します。個々の領域は図のとおりです。

既定では controls のまとまりは <table> で包まれ、それぞれの
pair は表の行 <tr> を、label と control の組はセル
<th> と <td>
を表します。では包む要素を変えましょう。controls の領域を <dl>
のコンテナに入れ、pair の領域はコンテナなしにし、label は <dt>
に入れ、最後に control を <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();
その結果、次の HTML のコードになります。
<dl>
<dt><label class="required" for="frm-name">名前:</label></dt>
<dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd>
<dt><label class="required" for="frm-age">年齢:</label></dt>
<dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd>
<dt><label>性別:</label></dt>
...
</dl>
wrappers の配列では、ほかにも多くの属性に手を入れられます。
- フォームの要素の種類ごとに CSS のクラスを足す
- 奇数と偶数の行を CSS のクラスで区別する
- 必須の項目と省略できる項目を見た目で区別する
- エラーのメッセージを要素のすぐ隣に表示するか、フォームの上に表示するかを決める
オプション
Renderer の振る舞いは、個々のフォームの要素に オプション を設定しても操れます。こうして入力欄の隣に現れる説明を設定できます。
$form->addText('phone', '番号:')
->setOption('description', 'この番号は公開されません');
そこに HTML の内容を置きたいなら、Htmlクラスを使います。
use Nette\Utils\Html;
$form->addText('phone', '電話:')
->setOption('description', Html::el('p')
->setHtml('<a href="...">利用条件。</a>')
);
Html の要素はラベルの代わりにも使えます。$form->addCheckbox('conditions', $label)
のようにです。
入力をまとめる
Renderer では、要素を見た目のまとまり(fieldset)にまとめられます。
$form->addGroup('個人情報');
新しいまとまりを作るとそれが有効になり、新しく足された要素はそこにも足されます。ですからフォームは次のように組み立てられます。
$form = new Form;
$form->addGroup('個人情報');
$form->addText('name', 'お名前:');
$form->addInteger('age', '年齢:');
$form->addEmail('email', 'メール:');
$form->addGroup('お届け先');
$form->addCheckbox('send', '住所へ届ける');
$form->addText('street', '通り:');
$form->addText('city', '市:');
$form->addSelect('country', '国:', $countries);
描画器はまずまとまりを描き、そのあとどのまとまりにも属さない要素を描きます。
Bootstrap への対応
examples のディレクトリには、Twitter Bootstrap 2、Bootstrap 3、Bootstrap 4向けに Renderer を設定する例があります。
HTML の属性
フォームの要素に好きな HTML
の属性を設定するには、setHtmlAttribute(string $name, $value = true) メソッドを使います。
$form->addInteger('number', '番号:')
->setHtmlAttribute('class', 'big-number');
$form->addSelect('rank', '並び順:', ['価格', '名前'])
->setHtmlAttribute('onchange', 'submit()'); // 変わったらフォームを送信します
// <form> 要素そのものの属性を設定するには
$form->setHtmlAttribute('id', 'myForm');
要素の種類を指定します。
$form->addText('tel', '電話番号:')
->setHtmlType('tel')
->setHtmlAttribute('placeholder', '電話番号を入力してください');
種類やそのほかの属性の設定は見た目のためだけのものです。入力が正しいかの確認はサーバー側で行わなければならず、それはふさわしいフォームの要素を選び、検証の規則を指定することで果たします。
ラジオやチェックボックスの一覧の個々の項目には、項目ごとに違う値の HTML
の属性を設定できます。style:
のうしろのコロンに注目してください。これでキーに応じて値が選ばれます。
$colors = ['r' => '赤', 'g' => '緑', 'b' => '青'];
$styles = ['r' => 'background:red', 'g' => 'background:green'];
$form->addCheckboxList('colors', '色:', $colors)
->setHtmlAttribute('style:', $styles);
描かれるのは次のとおりです。
<label><input type="checkbox" name="colors[]" style="background:red" value="r">赤</label>
<label><input type="checkbox" name="colors[]" style="background:green" value="g">緑</label>
<label><input type="checkbox" name="colors[]" value="b">青</label>
readonly のような真偽の属性を設定するには、疑問符を使う書き方が使えます。
$form->addCheckboxList('colors', '色:', $colors)
->setHtmlAttribute('readonly?', 'r'); // 複数のキーには配列を使います。たとえば ['r', 'g']
描かれるのは次のとおりです。
<label><input type="checkbox" name="colors[]" readonly value="r">赤</label>
<label><input type="checkbox" name="colors[]" value="g">緑</label>
<label><input type="checkbox" name="colors[]" value="b">青</label>
セレクトボックスでは、setHtmlAttribute() メソッドは <select>
要素の属性を設定します。個々の <option>
要素に属性を設定したいなら、setOptionAttribute()
メソッドを使います。先ほどのコロンと疑問符の書き方もここで働きます。
$form->addSelect('colors', '色:', $colors)
->setOptionAttribute('style:', $styles);
描かれるのは次のとおりです。
<select name="colors">
<option value="r" style="background:red">赤</option>
<option value="g" style="background:green">緑</option>
<option value="b">青</option>
</select>
プロトタイプ
HTML の属性を設定するもうひとつの方法は、HTML
要素が生成されるもとになるひな型を変えることです。このひな型は Html
オブジェクトで、getControlPrototype() メソッドが返します。
$input = $form->addInteger('number', '番号:');
$html = $input->getControlPrototype(); // <input>
$html->class('big-number'); // <input class="big-number">
getLabelPrototype() が返すラベルのひな型も同じやり方で変えられます。
$html = $input->getLabelPrototype(); // <label>
$html->class('distinctive'); // <label class="distinctive">
Checkbox、CheckboxList、RadioList
の要素では、要素全体を包む要素のひな型に手を入れられます。これは
getContainerPrototype()
が返します。既定では「空の」要素なので何も描かれませんが、名前を与えれば描かれるようになります。
$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>
CheckboxList と RadioList では、getSeparatorPrototype()
メソッドが返す、個々の項目の区切りのひな型にも手を入れられます。既定では
<br>
の要素です。これを対の要素に変えると、項目を区切る代わりに個々の項目を包みます。さらに
getItemLabelPrototype() が返す、個々の項目のラベルの HTML
要素のひな型にも手を入れられます。
翻訳
多言語のアプリケーションを作っているなら、フォームを言語ごとに描く必要が出てくるでしょう。Nette Framework はそのために翻訳のインターフェース Nette\Localization\Translatorを定めています。Nette には既定の実装がないので、Componetteにあるできあいの解のいくつかから、必要に応じて選べます。翻訳器の設定の仕方はそれぞれのドキュメントに書かれています。
フォームは翻訳器を通した文の出力に対応しています。setTranslator()
メソッドで渡します。
$form->setTranslator($translator);
この時点から、すべてのラベルだけでなく、すべてのエラーのメッセージ、セレクトボックスの項目、入力のプレースホルダも対象の言語に翻訳されます。
フォームの要素ごとに違う翻訳器を設定することも、値を null
にして翻訳を完全に切ることもできます。
$form->addSelect('carModel', '車種:', $cars)
->setTranslator(null);
検証の規則では、翻訳器に特定のパラメータも渡されます。たとえば次の規則では、
$form->addPassword('password', 'パスワード:')
->addRule($form::MinLength, 'パスワードは %d 文字以上でなければなりません', 8);
翻訳器は次のパラメータで呼ばれます。
$translator->translate('パスワードは %d 文字以上でなければなりません', 8);
ですから数に応じて、characters という語の正しい複数形を選べます。
onRender イベント
フォームが描かれる直前に、自分のコードを呼ばせられます。このコードはたとえば、正しく表示させるためにフォームの要素に
HTML のクラスを足せます。コードは onRender 配列に足します。
$form->onRender[] = function ($form) {
BootstrapCSS::initialize($form);
};