フォームのレンダリング

フォームの外観は非常に多様です。実際には、2つの極端なケースに遭遇する可能性があります。一方では、アプリケーションで視覚的に互いに似ている多くのフォームをレンダリングする必要があり、$form->render() を使用してテンプレートなしで簡単にレンダリングできることを評価します。これは通常、管理インターフェースの場合です。

一方、それぞれがオリジナルである多様なフォームがあります。それらの形状は、フォームテンプレートのHTML言語で最もよく記述されます。そしてもちろん、両方の極端なケースに加えて、その中間のどこかに位置する多くのフォームに遭遇します。

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

結果の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>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>

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

フォームがトランスレータを使用する場合、{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> の代替です。

自動レンダリング

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

特定のフォームをレンダリングするときにその形状に介入し、たとえば1つの要素を異なる方法でレンダリングしたい場合は、最も簡単な方法は、後で上書きできるブロックをテンプレートに事前に準備することです。 ブロックは動的な名前を持つこともできるため、レンダリングされる要素の名前を挿入することもできます。たとえば:

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

特殊なケース

HTMLタグ <form> なしでフォームの内部部分のみをレンダリングする必要がある場合、たとえばスニペットを送信する場合、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>

フォームコンテナ内の要素のレンダリングは、{formContainer} タグが役立ちます。

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

一部の要素では getControl() が単一のHTML要素(例:<input><select> など)を返しますが、他の要素(CheckboxList、RadioList)ではHTMLコード全体を返します。 その場合、各項目について個々の入力とラベルを生成するメソッドを使用できます:

  • getControlPart($key = null): ?Html 1つの項目のHTMLコードを返します
  • getLabelPart($key = null): ?Html 1つの項目のラベルの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>
	...

フォームの骨格にテーブルを使用するかどうかは議論の余地があり、多くのWebデザイナーは異なるマークアップを好みます。たとえば、定義リストです。したがって、DefaultFormRenderer を再構成して、フォームをリストとしてレンダリングします。構成は $wrappers 配列を編集することによって行われます。最初のインデックスは常に領域を表し、2番目はその属性を表します。個々の領域は画像で示されています:

デフォルトでは、要素グループ controls はテーブル <table> でラップされ、各 pair はテーブル行 <tr> を表し、labelcontrol のペアはセル <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クラスで区別する
  • 必須項目とオプション項目を視覚的に区別する
  • エラーメッセージを要素のすぐ隣に表示するか、フォームの上に表示するかを決定する

Options

Rendererの動作は、個々のフォーム要素に options を設定することによっても制御できます。これにより、入力フィールドの隣に表示される説明を設定できます:

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

Rendererは最初にグループをレンダリングし、次にどのグループにも属さない要素をレンダリングします。

Bootstrapのサポート

例ではTwitter Bootstrap 2Bootstrap 3Bootstrap 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> 要素です。ペア要素に変更すると、区切る代わりに個々の項目をラップします。 さらに、個々の項目のラベルのHTML要素のテンプレートに影響を与えることができます。これは getItemLabelPrototype() によって返されます。

翻訳

多言語アプリケーションをプログラミングしている場合、おそらくフォームを異なる言語バージョンでレンダリングする必要があります。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);

したがって、数に応じて単語 文字 の正しい複数形を選択できます。

onRenderイベント

フォームがレンダリングされる直前に、コードを実行させることができます。たとえば、フォーム要素に正しい表示のためのHTMLクラスを追加できます。コードを onRender 配列に追加します:

$form->onRender[] = function ($form) {
	BootstrapCSS::initialize($form);
};
バージョン: 4.0