Form Rendering

Forms' appearances can differ greatly. In fact, there are two extremes. One side is the need to render a set of very similar forms all over again, with little to none effort. Usually administrations and back-ends.

The other side is tiny sweet forms, each one being a piece of art. Their layout can best be written in HTML. Of course, besides those extremes, there are many forms just in between.

Default Renderer

Renderer automatically renders a form. It's set with setRenderer() method on a form and it gains control when calling $form->render() or echo $form. If we set no custom renderer, Nette\Forms\Rendering\DefaultFormRenderer is used. All you have to write is:

echo $form;

or in Latte:

{control form}

and the form is alive. All input fields are rendered into an HTML table. The output could look like this:

<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" 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" value="" /></td>
</tr>

<tr>
	<th><label>Gender:</label></th>
	...

Nicely formatted, isn't it? :-)

It's up to you, whether to use a table or not, and many web designers prefer different markups, for example a list. We may configure DefaultFormRenderer so it would not render into a table at all. We just have to set proper $wrappers. The first index always represents an area and the second one it's element. All respective areas are shown in the picture:

By default a group of controls is wrapped in <table>, and every pair is a table row <tr> containing a pair of label and control (cells <th> and <td>). Let's change all those wrapper elements. We will wrap controls into <dl>, leave pair by itself, put label into <dt> and wrap control into <dd>:

$renderer = $form->getRenderer();
$renderer->wrappers['controls']['container'] = 'dl';
$renderer->wrappers['pair']['container'] = null;
$renderer->wrappers['label']['container'] = 'dt';
$renderer->wrappers['control']['container'] = 'dd';

echo $form;

Results into the following snippet:

<dl>
	<dt><label class="required" for="frm-name">Name:</label></dt>

	<dd><input type="text" class="text" name="name" id="frm-name" value="" /></dd>


	<dt><label class="required" for="frm-age">Age:</label></dt>

	<dd><input type="text" class="text" name="age" id="frm-age" value="" /></dd>


	<dt><label>Gender:</label></dt>
	...
</dl>

Wrappers can affect many attributes. For example:

  • add special CSS classes to each form input
  • distinguish between odd and even lines
  • make required and optional draw differently
  • set, whether error messages are shown above the form or close to each element

Bootstrap Support

You can find examples of configuration forms for Twitter Bootstrap 2, Bootstrap 3 and Bootstrap 4

Manual Rendering

You can render forms manually for better control over the generated code. Place the form inside {form myForm} and {/form} pair tags. Inputs can be rendered using {input myInput} tag, which renders the input, and {label myInput /}, which renders its the label.

{form signForm}

<!-- Simple errors rendering -->
<ul class="errors" n:if="$form->hasErrors()">
	<li n:foreach="$form->errors as $error">{$error}</li>
</ul>

<table>
<tr class="required">
	<th>{label name /}</th>
	<td>{input name}</td>
</tr>

<!--If you need manualy render radiolist -->
<p>{input radioList:itemKey} | {input radioList:itemKeyTwo}</p>

...

</table>
{/form}

You can also connect a form with a template easily by using n:name attribute.

protected function createComponentSignInForm(): Form
{
	$form = new Form;
	$form->addText('user')->setRequired();
	$form->addPassword('password')->setRequired();
	$form->addSubmit('send');
	return $form;
}
<form n:name=signInForm class=form>
	<p><label n:name=user>Username: <input n:name=user size=20></label>
	<p><label n:name=password>Password: <input n:name=password></label>
	<p><input n:name=send class="btn btn-default">
</form>

You can also use n:name with <select>, <button> or <textarea> elements and the content.

You can render RadioList, Checkbox or CheckboxList by HTML elements individually. This is called partial rendering:

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

Or you can use basic tags {input gender:$key} and {label gender:$key}. The trick is using the colon. For a simple checkbox, use {input myCheckbox:}.

Tag formContainer helps with rendering of inputs inside a form container.

{form signForm}
<table>
	<th>Which news you wish to receive:</th>
	<td>
		{formContainer emailNews}
		<ul>
			<li>{input sport} {label sport /}</li>
			<li>{input science} {label science /}</li>
		</ul>
		{/formContainer}
	</td>
	...
</table>
{/form}

How to set more attributes to HTML elements? Methods getControl() and getLabel() return element as a Nette\Utils\Html objects, which can be easily adjusted. In Latte:

{form signForm class => 'big'}
<table>
<tr class="required">
	<th>{label name /}</th>
	<td>{input name cols => 40, autofocus => true}</td>
</tr>

Grouping Inputs

Input fields can be grouped into visual field-sets by creating a group:

$form->addGroup('Personal data');

Creating new group activates it – all elements added further are added to this group. You may build a form like this:

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

HTML Attributes

You can set any HTML attributes to form fields:

$form->addInteger('number', 'Number:')
	->setHtmlAttribute('class', 'bigNumbers');

$form->addSelect('rank', 'Order by:', ['price', 'name'])
	->setHtmlAttribute('onchange', 'submit()'); // calls JS function submit() on change


// applying on <form> - since nette/forms 3.0.3
$form->setHtmlAttribute('id', 'myForm');

// before 3.0.3
$form->getElementPrototype()->id = 'myForm';

Setting input type:

$form->addText('tel', 'Your telephone:')
	->setHtmlType('tel')
	->setHtmlAttribute('placeholder', 'Please, fill in your telephone');

Other setting

Setting description (rendered after the input by default):

$form->addText('phone', 'Number:')
	->setOption('description', 'This number will remain hidden');

If we want to place HTML content into it, we use Html class.

use Nette\Utils\Html;

$form->addText('phone', 'Phone:')
	->setOption('description', Html::el('p')
		->setHtml('This number remains hidden. <a href="...">Terms of service.</a>')
	);

Html element can be also used instead of label: $form->addCheckbox('conditions', $label).

Related blog posts