Изобразяване на форми

Външният вид на формите може да бъде много разнообразен. На практика можем да се сблъскаме с две крайности. От една страна, има нужда от визуализиране на поредица от формуляри в дадено приложение, които визуално си приличат един на друг, и оценяваме лесното визуализиране без шаблон с помощта на $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 всеки път, когато бъде извикан.

Renderer

Това е обектът, който осигурява визуализирането на формата. Тя може да бъде настроена чрез метода $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: $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', 'Number:')
	->setHtmlAttribute('class', 'bigNumbers');

$form->addSelect('rank', 'Order:', ['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>

За задаване на булеви атрибути, като например readonly, можем да използваме означението с въпросителен знак:

$form->addCheckboxList('colors', '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');
$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);
};
версия: 4.0