Version 2.4

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 are tiny sweet forms, every 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 a 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 and Bootstrap 3

Manual Rendering

You can render forms manually for better control over the generated code. Place the form inside {form myForm} and {/form} pair macros. Inputs can be rendered using {input myInput} macro, 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.

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 macros {input gender:$key} and {label gender:$key}. The trick is using the colon. For a simple checkbox, use {input myCheckbox:}.

Macro 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 also extend the forms with labels, classes and do other things.

Pay attention to the order of conditions, rules and usage of the extensions.

Setting class or JavaScript attributes:

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

Setting input type (HTML5):

$form->addText('email', 'Your e-mail:')
    ->setHtmlType('email')
    ->setHtmlAttribute('placeholder', 'Please, fill in your e-mail address');

Setting description (rendered after the input by default):

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

In order to add HTML content, you can 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).