Forms 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(). If we set no custom renderer, Nette\Forms\Rendering\DefaultFormRenderer is used. All you have to write is:

$form->render();

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

$form->render();

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

Latte

Templating engine Latte makes it much easier to render forms and their elements:

{form myForm}
<table>
<tr n:foreach="$form->controls as $name => $field">
	<th>{label $name /}<th>
	<td>{input $name}</td>
	<td n:ifcontent>{inputError $name}</td>
</tr>
</table>
{/form}

The {form} or <form n:name> tag starts rendering of given form. It also creates a local variable $form with a form object. The closing {/form} or </form> tag renders all undrawn hidden fields before completes the form.

{input} tag renders any element including select boxes and textareas. {label} is optionally paired tag, ie. {label}Age{/label}, in that case the text is translated too. Tag {inputError} prints error message for form input.

It's also possible to “breathe life” into a raw HTML input with n:name attribute tag, which links it with the form input using its identification:

<form n:name=myForm>
	<p>Name: <input size="50" n:name="name"></p>
	<p>Password: <input size="30" n:name="password"></p>
</form>

If you only need to render the inner content of a form without <form> & </form> HTML tags, for example, in an AJAX request, you can open and close the form with {formContext formName} … {/formContext}. It works similarly to {form} in a logical sense, here it allows you to use other tags to draw form elements, but at the same time it doesn't draw anything.

Code Proposal {formPrint}

You can also generate Latte code for a form using the {formPrint} tag. If you place it in a template, the code of form is displayed instead of the normal template. Then simply select and copy the code into your project.

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 to manually render a 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 = 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 controls using setHtmlAttribute(string $name, $value = true):

$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>
$form->getElementPrototype()->id = 'myForm';
$form->getElementPrototype()->target = '_top';

Setting input type:

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

We can set HTML attribute to individual items in radio or checkbox lists with different values for each of them. Note the colon after style: to ensure that the value is selected by key:

$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue'];
$styles = ['r' => 'background:red', 'g' => 'background:green'];
$form->addCheckboxList('colors', 'Colors:', $colors)
	->setHtmlAttribute('style:', $styles);

Renders:

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

For a logical HTML attribute (which has no value, such as readonly), you can use a question mark:

$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue'];
$form->addCheckboxList('colors', 'Colors:', $colors)
	->setHtmlAttribute('readonly?', 'r'); // use array for multiple keys, e.g. ['r', 'g']

Renders:

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

For selectboxes, the setHtmlAttribute() method sets the attributes of the <select> element. If we want to set the attributes for each <option>, we will use the method addOptionAttributes(). Also, the colon and question mark used above work:

$form->addSelect('colors', 'Colors:', $colors)
	->addOptionAttributes(['style:' => $styles]);

Renders:

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

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

Without Latte

Each element has getLabel() and getControl() methods which return HTML code of label and the element itself. Nette provides getter and setter property access as if you are accessing the attribute itself.

<?php $form->render('begin') ?>
<?php $form->render('errors') ?>

<table>
<tr class="required">
	<th><?php echo $form['name']->label // Calls getLabel() ?></th>
	<td><?php echo $form['name']->control // Calls getControl()  ?></td>
</tr>

<tr class="required">
	<th><?php echo $form['age']->label ?></th>
	<td><?php echo $form['age']->control ?></td>
</tr>

// ...

</table>

<?php $form->render('end') ?>

Event onRender

Just before the form is rendered, we can have our code invoked. This can, for example, add HTML classes to the form elements for proper display. We add the code to the onRender array:

$form->onRender[] = function ($form) {
	BootstrapCSS::initialize($form);
};
version: 4.0 3.x 2.x