Nette Documentation Preview

syntax
Рендеринг форм
**************

Внешний вид форм может быть очень разнообразным. На практике мы можем столкнуться с двумя крайностями. С одной стороны, в приложении необходимо отобразить ряд форм, визуально похожих друг на друга, и мы ценим простоту визуализации без шаблона с помощью `$form->render()`. Обычно это относится к административным интерфейсам.

С другой стороны, существуют различные формы, каждая из которых уникальна. Их внешний вид лучше всего описывать с помощью языка HTML в шаблоне. И конечно, помимо обеих упомянутых крайностей, мы встретим множество форм, которые находятся где-то посередине.


Рендеринг с помощью Latte .[#toc-rendering-with-latte]
======================================================

[Система шаблонов Latte |latte:] в корне облегчает отрисовку форм и их элементов. Сначала мы покажем, как отрисовывать формы вручную, элемент за элементом, чтобы получить полный контроль над кодом. Позже мы покажем, как [автоматизировать |#Automatic-Rendering] такой рендеринг.

Предложение шаблона Latte для формы можно сгенерировать с помощью метода `Nette\Forms\Blueprint::latte($form)`, который выведет его на страницу браузера. Затем достаточно выделить код щелчком мыши и скопировать его в свой проект. .{data-version:3.1.15}


`{control}`
-----------

Самый простой способ отобразить форму - написать ее в шаблоне:

```latte
{control signInForm}
```

Внешний вид отрисованной формы можно изменить, настроив [Renderer |#Renderer] и [отдельные элементы управления |#HTML-Attributes].


`n:name`
--------

Очень легко связать определение формы в PHP-коде с HTML-кодом. Просто добавьте атрибуты `n:name`. Вот как это просто!

```php
protected function createComponentSignInForm(): Form
{
	$form = new Form;
	$form->addText('username')->setRequired();
	$form->addPassword('password')->setRequired();
	$form->addSubmit('send');
	return $form;
}
```

```latte
<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()`):

```latte
<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, могут быть отображены элемент за элементом:

```latte
{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}`:

```latte
<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}` будет переведен.

Опять же, более сложные элементы формы, такие как RadioList или CheckboxList, могут выводиться поэлементно:

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

Чтобы отобразить `<input>` в элементе Checkbox, используйте `{input myCheckbox:}`. Атрибуты HTML должны быть разделены запятой `{input myCheckbox:, class: required}`.


`{inputError}`
--------------

Выводит сообщение об ошибке для элемента формы, если оно есть. Сообщение обычно оборачивается в HTML-элемент для стилизации.
Избежать вывода пустого элемента при отсутствии сообщения можно с помощью `n:ifcontent`:

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

Мы можем определить наличие ошибки с помощью метода `hasErrors()` и установить класс родительского элемента соответствующим образом:

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


`{form}`
--------

Теги `{form signInForm}...{/form}` являются альтернативой `<form n:name="signInForm">...</form>`.


Автоматический рендеринг .[#toc-automatic-rendering]
----------------------------------------------------

С помощью тегов `{input}` и `{label}` мы можем легко создать общий шаблон для любой формы. Он будет итерировать и последовательно выводить все элементы формы, за исключением скрытых элементов, которые выводятся автоматически при завершении формы с помощью тега `</form>` тега.
Он будет ожидать имя отрисованной формы в переменной `$form`.

```latte
<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`:

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

Если вы хотите повлиять на внешний вид одной конкретной формы и отрисовать один элемент по-другому, то самый простой способ - это подготовить в шаблоне блоки, которые можно перезаписать позже.
Блоки также могут иметь [динамические имена |latte:template-inheritance#Dynamic-Block-Names], поэтому вы можете вставить в них имя элемента, который нужно нарисовать. Например:

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

Для элемента, например, `username` создается блок `input-username`, который можно легко переопределить с помощью тега [{embed} |latte:template-inheritance#Unit-Inheritance]:

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

Альтернативно, все содержимое шаблона `basic-form.latte` может быть [определено |latte:template-inheritance#Definitions] как блок, включая параметр `$form`:

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

Это несколько упростит его использование:

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

Вам нужно будет импортировать блок только в одном месте, в начале шаблона макета:

```latte
{import basic-form.latte}
```


Особые случаи .[#toc-special-cases]
-----------------------------------

Если необходимо отобразить только внутреннюю часть формы без HTML-тегов `<form>`например, при отправке сниппетов, скройте их с помощью атрибута `n:tag-if`:

```latte
<form n:name=signInForm n:tag-if=false>
	<div>
		<label n:name=username>Имя пользователя: <input n:name=username></label>
		{inputError username}
	</div>
</form>
```

Тег `formContainer` помогает при отрисовке вводимых данных внутри контейнера формы.

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


Рендеринг без Latte .[#toc-rendering-without-latte]
===================================================

Самый простой способ отобразить форму - вызвать:

```php
$form->render();
```

Внешний вид отрисованной формы можно изменить, настроив [Renderer |#Renderer] и [отдельные элементы управления |#HTML-Attributes].


Рендеринг вручную .[#toc-manual-rendering]
------------------------------------------

Каждый элемент формы имеет методы, которые генерируют HTML код для поля и метки формы. Они могут возвращать его в виде строки или объекта [Nette\Utils\Html |utils:html-elements]:

- `getControl(): Html|string` возвращает HTML-код элемента
- `getLabel($caption = null): Html|string|null` возвращает HTML-код метки, если таковая имеется

Это позволяет отображать форму элемент за элементом:

```php
<?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>` и т.д.), для других он возвращает целый кусок HTML-кода (CheckboxList, RadioList).
В этом случае можно использовать методы, генерирующие отдельные входы и метки, для каждого элемента отдельно:

- `getControlPart($key = null): ?Html` возвращает HTML-код одного элемента.
- `getLabelPart($key = null): ?Html` возвращает HTML-код для метки отдельного элемента

.[note]
Эти методы имеют префикс `get` по историческим причинам, но `generate` было бы лучше, так как он создает и возвращает новый элемент `Html` при каждом вызове.


Рендерер .[#toc-renderer]
=========================

Это объект, обеспечивающий рендеринг формы. Он может быть установлен методом `$form->setRenderer`. Ему передается управление при вызове метода `$form->render()`.

Если мы не зададим пользовательский рендерер, будет использоваться рендерер по умолчанию [api:Nette\Forms\Rendering\DefaultFormRenderer]. В результате элементы формы будут отображены в виде HTML-таблицы. Вывод выглядит следующим образом:

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

Использовать таблицу или нет - решать вам, многие веб-дизайнеры предпочитают другую разметку, например, список. Мы можем настроить `DefaultFormRenderer` так, чтобы он вообще не выводился в таблицу. Мы просто должны установить соответствующие [$wrappers |api:Nette\Forms\Rendering\DefaultFormRenderer::$wrappers]. Первый индекс всегда представляет область, а второй - элемент. Все соответствующие области показаны на рисунке:

[* form-areas-en.webp *]

По умолчанию группа `controls` обернута в. `<table>`и каждый `pair` представляет собой строку таблицы `<tr>` содержащая пару `label` и `control` (ячейки `<th>` и `<td>`). Давайте изменим все эти элементы обертки. Мы завернем `controls` в `<dl>`, оставим `pair` в одиночестве, поместим `label` в `<dt>` и обернем `control` в `<dd>`:

```php
$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();
```

В результате получится следующий фрагмент:

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

Обертки могут влиять на многие атрибуты. Например:

- добавить специальные классы CSS к каждому вводу формы
- различать четные и нечетные строки
- по-разному рисовать обязательные и необязательные элементы
- установить, будут ли сообщения об ошибках показываться над формой или рядом с каждым элементом


Опции .[#toc-options]
---------------------

Поведением Renderer также можно управлять, устанавливая *опции* для отдельных элементов формы. Таким образом, вы можете установить всплывающую подсказку, которая будет отображаться рядом с полем ввода:

```php
$form->addText('phone', 'Номер:')
	->setOption('description', 'Этот номер останется скрытым');
```

Если мы хотим поместить в него HTML-контент, мы используем класс [Html |utils:html-elements].

```php
use Nette\Utils\Html;

$form->addText('phone', 'Телефон:')
	->setOption('description', Html::el('p')
		->setHtml('<a href="...">Условия предоставления услуг.</a>')
	);
```

.[tip]
Html-элемент также можно использовать вместо label: `$form->addCheckbox('conditions', $label)`.


Группировка входов .[#toc-grouping-inputs]
------------------------------------------

Поля ввода можно объединить в наборы визуальных полей, создав группу:

```php
$form->addGroup('Персональные данные');
```

Создание новой группы активирует её — все добавленные далее элементы добавляются в эту группу. Вы можете построить форму следующим образом:

```php
$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 .[#toc-bootstrap-support]
---------------------------------------------

Вы можете найти [примеры |https://github.com/nette/forms/tree/master/examples] настройки рендерера для [Twitter Bootstrap 2 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap2-rendering.php#L58], [Bootstrap 3 |https://github.com/nette/forms/blob/a0bc775b96b30780270bdec06396ca985168f11a/examples/bootstrap3-rendering.php#L58] и [Bootstrap 4 |https://github.com/nette/forms/blob/96b3e90/examples/bootstrap4-rendering.php]


Атрибуты HTML .[#toc-html-attributes]
=====================================

Вы можете установить любые атрибуты HTML для элементов управления формы с помощью `setHtmlAttribute(string $name, $value = true)`:

```php
$form->addInteger('number', 'Количество:')
	->setHtmlAttribute('class', 'bigNumbers');

$form->addSelect('rank', 'Заказать:', ['price', 'name'])
	->setHtmlAttribute('onchange', 'submit()'); // вызывает JS-функцию submit() при изменении


// применяется на <form>
$form->setHtmlAttribute('id', 'myForm');
```

Установка типа входа:

```php
$form->addText('tel', 'Ваш телефон:')
	->setHtmlType('tel')
	->setHtmlAttribute('placeholder', 'Пожалуйста, заполните ваш телефон');
```

Мы можем установить HTML-атрибут для отдельных элементов в списках радио- или чекбоксов с разными значениями для каждого из них.
Обратите внимание на двоеточие после `style:`, чтобы убедиться, что значение выбирается по ключу:

```php
$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue'];
$styles = ['r' => 'background:red', 'g' => 'background:green'];
$form->addCheckboxList('colors', 'Цвета:', $colors)
	->setHtmlAttribute('style:', $styles);
```

Отображается как:

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

Для логического атрибута HTML (который не имеет значения, например, `readonly`) можно использовать вопросительный знак:

```php
$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue'];
$form->addCheckboxList('colors', 'Цвета:', $colors)
	->setHtmlAttribute('readonly?', 'r'); // использовать массив для нескольких ключей, например ['r', 'g']
```

Отображается как:

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

Для селекбоксов метод `setHtmlAttribute()` устанавливает атрибуты элемента `<select>`. Если мы хотим установить атрибуты для каждого
`<option>`, мы будем использовать метод `setOptionAttribute()`. Кроме того, двоеточие и вопросительный знак, использованные выше, работают:

```php
$form->addSelect('colors', 'Цвета:', $colors)
	->setOptionAttribute('style:', $styles);
```

Отображается как:

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


Прототипы .[#toc-prototypes]
----------------------------

Альтернативным способом установки атрибутов HTML является изменение шаблона, на основе которого генерируется элемент HTML. Шаблон является объектом `Html` и возвращается методом `getControlPrototype()`:

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

Шаблон метки, возвращаемый методом `getLabelPrototype()`, также может быть изменен таким образом:

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

Для элементов Checkbox, CheckboxList и RadioList вы можете повлиять на шаблон элемента, который оборачивает элемент. Его возвращает `getContainerPrototype()`. По умолчанию это "пустой" элемент, поэтому ничего не отображается, но если дать ему имя, он будет отображаться:

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

В случае CheckboxList и RadioList можно также повлиять на шаблон разделителя элементов, возвращаемый методом `getSeparatorPrototype()`. По умолчанию это элемент `<br>`. Если вы измените его на парный элемент, он будет обволакивать отдельные элементы вместо того, чтобы разделять их.
Также можно влиять на шаблон HTML-элемента меток элементов, который возвращает метод `getItemLabelPrototype()`.


Перевод .[#toc-translating]
===========================

Если вы программируете многоязычное приложение, то вам, вероятно, потребуется отображать форму на разных языках. В Nette Framework для этого определен интерфейс перевода [api:Nette\Localization\Translator]. В Nette нет реализации по умолчанию, вы можете выбрать в соответствии с вашими потребностями из нескольких готовых решений, которые можно найти на [Componette |https://componette.org/search/localization]. В их документации описано, как настроить переводчик.

Форма поддерживает вывод текста через переводчик. Мы передаем его с помощью метода `setTranslator()`:

```php
$form->setTranslator($translator);
```

С этого момента не только все метки, но и все сообщения об ошибках или записи в поле выбора будут переведены на другой язык.

Можно установить другой переводчик для отдельных элементов формы или полностью отключить перевод с помощью `null`:

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

Для [правил валидации |validation] переводчику также передаются определенные параметры, например, для правила:

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

транслятор вызывается со следующими параметрами:

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

и таким образом может выбрать правильную форму множественного числа для слова `characters` по счету.


Событие onRender .[#toc-event-onrender]
=======================================

Непосредственно перед отображением формы мы можем вызвать наш код. Это может, например, добавить HTML-классы к элементам формы для правильного отображения. Добавим код в массив 'onRender':

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

Рендеринг форм

Внешний вид форм может быть очень разнообразным. На практике мы можем столкнуться с двумя крайностями. С одной стороны, в приложении необходимо отобразить ряд форм, визуально похожих друг на друга, и мы ценим простоту визуализации без шаблона с помощью $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>Имя пользователя: <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} будет переведен.

Опять же, более сложные элементы формы, такие как RadioList или CheckboxList, могут выводиться поэлементно:

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

Чтобы отобразить <input> в элементе Checkbox, используйте {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}

Если вы хотите повлиять на внешний вид одной конкретной формы и отрисовать один элемент по-другому, то самый простой способ – это подготовить в шаблоне блоки, которые можно перезаписать позже. Блоки также могут иметь динамические имена, поэтому вы можете вставить в них имя элемента, который нужно нарисовать. Например:

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

Вам нужно будет импортировать блок только в одном месте, в начале шаблона макета:

{import basic-form.latte}

Особые случаи

Если необходимо отобразить только внутреннюю часть формы без HTML-тегов <form>например, при отправке сниппетов, скройте их с помощью атрибута 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 помогает при отрисовке вводимых данных внутри контейнера формы.

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

Рендеринг без 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> и т.д.), для других он возвращает целый кусок HTML-кода (CheckboxList, RadioList). В этом случае можно использовать методы, генерирующие отдельные входы и метки, для каждого элемента отдельно:

  • getControlPart($key = null): ?Html возвращает HTML-код одного элемента.
  • getLabelPart($key = null): ?Html возвращает HTML-код для метки отдельного элемента

Эти методы имеют префикс get по историческим причинам, но generate было бы лучше, так как он создает и возвращает новый элемент Html при каждом вызове.

Рендерер

Это объект, обеспечивающий рендеринг формы. Он может быть установлен методом $form->setRenderer. Ему передается управление при вызове метода $form->render().

Если мы не зададим пользовательский рендерер, будет использоваться рендерер по умолчанию Nette\Forms\Rendering\DefaultFormRenderer. В результате элементы формы будут отображены в виде HTML-таблицы. Вывод выглядит следующим образом:

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

Использовать таблицу или нет – решать вам, многие веб-дизайнеры предпочитают другую разметку, например, список. Мы можем настроить DefaultFormRenderer так, чтобы он вообще не выводился в таблицу. Мы просто должны установить соответствующие $wrappers. Первый индекс всегда представляет область, а второй – элемент. Все соответствующие области показаны на рисунке:

По умолчанию группа 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();

В результате получится следующий фрагмент:

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

Обертки могут влиять на многие атрибуты. Например:

  • добавить специальные классы 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-элемент также можно использовать вместо label: $form->addCheckbox('conditions', $label).

Группировка входов

Поля ввода можно объединить в наборы визуальных полей, создав группу:

$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

Вы можете найти примеры настройки рендерера для Twitter Bootstrap 2, Bootstrap 3 и Bootstrap 4

Атрибуты HTML

Вы можете установить любые атрибуты HTML для элементов управления формы с помощью setHtmlAttribute(string $name, $value = true):

$form->addInteger('number', 'Количество:')
	->setHtmlAttribute('class', 'bigNumbers');

$form->addSelect('rank', 'Заказать:', ['price', 'name'])
	->setHtmlAttribute('onchange', 'submit()'); // вызывает JS-функцию submit() при изменении


// применяется на <form>
$form->setHtmlAttribute('id', 'myForm');

Установка типа входа:

$form->addText('tel', 'Ваш телефон:')
	->setHtmlType('tel')
	->setHtmlAttribute('placeholder', 'Пожалуйста, заполните ваш телефон');

Мы можем установить HTML-атрибут для отдельных элементов в списках радио- или чекбоксов с разными значениями для каждого из них. Обратите внимание на двоеточие после style:, чтобы убедиться, что значение выбирается по ключу:

$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue'];
$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">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>

Для логического атрибута HTML (который не имеет значения, например, readonly) можно использовать вопросительный знак:

$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue'];
$form->addCheckboxList('colors', 'Цвета:', $colors)
	->setHtmlAttribute('readonly?', 'r'); // использовать массив для нескольких ключей, например ['r', 'g']

Отображается как:

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

Для селекбоксов метод setHtmlAttribute() устанавливает атрибуты элемента <select>. Если мы хотим установить атрибуты для каждого <option>, мы будем использовать метод setOptionAttribute(). Кроме того, двоеточие и вопросительный знак, использованные выше, работают:

$form->addSelect('colors', 'Цвета:', $colors)
	->setOptionAttribute('style:', $styles);

Отображается как:

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

Прототипы

Альтернативным способом установки атрибутов 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');
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>

В случае CheckboxList и RadioList можно также повлиять на шаблон разделителя элементов, возвращаемый методом getSeparatorPrototype(). По умолчанию это элемент <br>. Если вы измените его на парный элемент, он будет обволакивать отдельные элементы вместо того, чтобы разделять их. Также можно влиять на шаблон HTML-элемента меток элементов, который возвращает метод getItemLabelPrototype().

Перевод

Если вы программируете многоязычное приложение, то вам, вероятно, потребуется отображать форму на разных языках. В Nette Framework для этого определен интерфейс перевода Nette\Localization\Translator. В Nette нет реализации по умолчанию, вы можете выбрать в соответствии с вашими потребностями из нескольких готовых решений, которые можно найти на Componette. В их документации описано, как настроить переводчик.

Форма поддерживает вывод текста через переводчик. Мы передаем его с помощью метода setTranslator():

$form->setTranslator($translator);

С этого момента не только все метки, но и все сообщения об ошибках или записи в поле выбора будут переведены на другой язык.

Можно установить другой переводчик для отдельных элементов формы или полностью отключить перевод с помощью null:

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

Для правил валидации переводчику также передаются определенные параметры, например, для правила:

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

транслятор вызывается со следующими параметрами:

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

и таким образом может выбрать правильную форму множественного числа для слова characters по счету.

Событие onRender

Непосредственно перед отображением формы мы можем вызвать наш код. Это может, например, добавить HTML-классы к элементам формы для правильного отображения. Добавим код в массив ‚onRender‘:

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