Edit
Lang

Forms

Nette\Forms greatly facilitates creating and processing web forms. What it can really do?

  • validate sent data both client-side (JavaScript) and server-side
  • provide high level of security
  • multiple render modes
  • translations, i18n

Nette Framework puts a great effort to be safe and since forms are the most common user input, Nette forms are as good as impenetrable. All is maintained dynamically and transparently, nothing has to be set manually. Well known vulnerabilities such as Cross Site Scripting (XSS) and Cross-Site Request Forgery (CSRF) are filtered, as well as special control characters. All inputs are checked for UTF-8 validity. Every multiple-choice, select boxe and similar are checked for forged values upon validating. Sounds good? Let's try it out.

With Nette/Forms you can diminish routine tasks like double validation on both server and client side. You can also avoid common mistakes and security issues.

First form

Let's create a simple registration form for our app. Forms are added into presenters using component factories:

use Nette\Application\UI;

class HomepagePresenter extends UI\Presenter
{

    // ...

    protected function createComponentRegistrationForm()
    {
        $form = new UI\Form;
        $form->addText('name', 'Name:');
        $form->addPassword('password', 'Password:');
        $form->addSubmit('login', 'Sign up');
        $form->onSuccess[] = array($this, 'registrationFormSucceeded');
        return $form;
    }

    // called after form is successfully submitted
    public function registrationFormSucceeded(UI\Form $form, $values)
    {
        // ...
        $this->flashMessage('You have successfully signed up.');
        $this->redirect('Homepage:');
    }
}

render in template is done using control macro:

{control registrationForm}

and the result should look like this:

We have created a simple form, which will call registrationFormSucceeded() after being submitted and successfully validated. The form itself is passed as the first parameter. The submitted values are passed as the second parameter contained in a Nette\Utils\ArrayHash object. If you prefer a simple array, you can typehint the second parameter array $values. You can also use $values = $form->getValues() to retrieve the submitted values.

The rendered form follows basic web accessibility guidelines. All labels are generated as <label> elements and are associated with their inputs, clicking on the label moves cursor on the input.

Data stored in $values do not contain values of form buttons, so they're ready for more operations (such as inserting to database). It is noteworthy that whitespace at both left and right side of the input is removed. Just try it out: put your name into the forn field, add few spaces and submit it: the name will be trimmed.

Although we mentioned validation, our form has none. Let's fix it. In order to require user's name, call setRequired() method on the form item. You can pass an error message as optional argument and it will be displayed if user does not fill his name in:

$form->addText('name', 'Name:')
    ->setRequired('Please fill your name.');

Try submitting a form without the name – the message is displayed unless you meet the validation rules. The form is validated on both the client and server side.

If you don't use nette/sandbox, you need to link netteForms.js in order to enable JavaScript validation. You can find the file in src/assets folder.

<script src="netteForms.js"></script>

Nette Framework adds required class to all mandatory elements. Adding the following style will turn label of name input to red.

<style>
.required label { color: maroon }
</style>

Even though setting classes might be handy, we need solid data, don't we? We will add yet another validation rule, this time with addRule() method. First argument sets what should the value look like, second one is the error message again, shown if the validation is not passed. Preset validation rules will do this time, but you will learn how to create your own in no time.

Form will get another input age with condition, that it has to be a number (Form::INTEGER) and in certain boundaries (Form::RANGE). This time we will utilize a third argument of addRule(), the range itself:

$form->addText('age', 'Age:')
    ->addRule(Form::INTEGER, 'Your age must be an integer.')
    ->addRule(Form::RANGE, 'You must be older 18 years and be under 120.', array(18, 120));

Obviously we could do a little refactoring. The error message has those numbers entered duplicately, which is far from ideal. If we were creating a multilingual forms a the message containing numbers would have to be translated into several languages, any further change would be hard. That's the reason why you can use a substitute characters in this format:

->addRule(Form::RANGE, 'You must be older %d years and be under %d.', array(18, 120));

Nette Framework supports HTML5, including new form elements. Thanks to that we can set the age input as numeric:

$form->addText('age', 'Age:')
    ->setType('number')
    ...

In the most advance browsers, namely Google Chrome, Safari and Opera, tiny arrows are rendered next to the input. Safari for iPhone shows a optimized keyboard with numbers.

Let's get back to password field, which should be required. Also, we could limit the minimal password length (Form::MIN_LENGTH), again using the substitute characters in the message:

$form->addPassword('password', 'Password:')
    ->setRequired('Pick a password')
    ->addRule(Form::MIN_LENGTH, 'Your password has to be at least %d long', 3);

We will add one more input, passwordVerify, where user will be prompted to enter his password once more, to check for typo. Using validation rules we will check if both fields contain the same value (Form::EQUAL). Notice the dynamic third argument, which is in fact the password control itself:

$form->addPassword('passwordVerify', 'Password again:')
    ->setRequired('Fill your password again to check for typo')
    ->addRule(Form::EQUAL, 'Password missmatch', $form['password']);

If the form would not be used for registration, but rather for editing records, it would be handy to set default values.

That's a complete, fully working registration form with both client-side and server-side validation. Automatically treats magic quotes, checks for invalid UTF-8 strings etc. Everything is ready and without a slighest effort on our side – Nette has taken care of it.

Examples are available to download. Try adding some more inputs described below. Many inspiring forms are also in /examples/Forms of the distribution package.

Form inputs

Overview of standard form inputs.

addText($name, $label = NULL)

Adds oneline text field (class TextInput). Automatically trims left and right side whitespace. Beside preset validation rules, the following ones are available:

Form::MIN_LENGTH minimal length
Form::MAX_LENGTH maximal length
Form::LENGTH exact length
Form::EMAIL is value a valid email address?
Form::URL is value a valid URL?
Form::PATTERN tests against regular expression entire value, somewhat as if it is inside ^ and a $
Form::INTEGER is value integer?
Form::FLOAT is value a floating point number?
Form::RANGE is value in the range?
Form::BLANK the item must not be filled
Form::NOT_EQUAL the value must not be equal to given value
$form->addText('zip', 'Postcode:')
    ->addRule(Form::PATTERN, 'Postcode must have exactly 5 numerals', '([0-9]\s*){5}');

The Form::INTEGER, NUMERIC a FLOAT rules automatically convert the value to integer (or float respectively). Also, Form::URL automatically completes the URL, for example nette.org will be completed to http://nette.org.

addPassword($name, $label = NULL)

Adds password field (class TextInput). Automatically trims left and right side whitespace. Redrawing the form renders the input empty. Supports the same set of validation rules as addText.

$form->addPassword('password', 'Password:')
    ->addRule(Form::MIN_LENGTH, 'Password has to be at least %d characters long', 3)
    ->addRule(Form::PATTERN, 'Password must contain a number', '.*[0-9].*');

addTextArea($name, $label = NULL)

Adds a multiline text field (class TextArea).upports the same set of validation rules as addText. Unlike oneline inputs, it does not trim the input's whitespace on either edge.

$form->addTextArea('note', 'Note:')
    ->addRule(Form::MAX_LENGTH, 'Your note is way too long', 10000);

addUpload($name, $label = NULL)

Adds file upload field (class UploadControl). Beside preset validation rules, the following ones are available:

Form::MAX_FILE_SIZE verifies maximal file size
Form::MIME_TYPE checks if MIME type is valid
Form::IMAGE checks if uploaded file is JPEG, PNG or GIF
$form->addUpload('thumbnail', 'Thumbnail:')
    ->addRule(Form::IMAGE, 'Thubnail must be JPEG, PNG or GIF');

addMultiUpload($name, $label = NULL)

Adds multiple file upload field. This method is available since version 2.2.3. In earlier versions, you can use third optional parameter of addUpload() to do the same.

$form->addMultiUpload('files', 'Files'); // Since 2.2.3
$form->addUpload('files', 'Files', $multiple = TRUE); // Before

addHidden($name, $default = NULL)

Adds hidden field (class HiddenField).

$form->addHidden('user_id');

addCheckbox($name, $caption = NULL)

Adds a checkbox (class Checkbox). The return value is either boolean TRUE or FALSE, as checked or not checked.

$form->addCheckbox('agree', 'I agree with terms')
    ->addRule(Form::EQUAL, 'You must agree with our terms', TRUE);

addRadioList($name, $label = NULL, array $items = NULL)

Adds radio buttons (class RadioList). Array of offered values is passed as the third argument.

$sex = array(
    'm' => 'male',
    'f' => 'female',
);
$form->addRadioList('gender', 'Gender:', $sex);

addCheckboxList($name, $label = NULL, array $items = NULL)

Adds list of checkboxes for selecting multiple elements. Array of values is passed as the third argument. The component checks if the submitted values are from the given range.

$form = new Form;
$form->addCheckboxList('colors', 'Colors:', array(
    'r' => 'red',
    'g' => 'green',
    'b' => 'blue',
));

addSelect($name, $label = NULL, array $items = NULL)

Adds select box (class SelectBox). Array of offered values is passsed as third argument. Might as well be two dimensional. The first item is often used as a call-to-action message, but worthless when actually selected – that's what method setPromt() is for.

$countries = array(
    'Europe' => array(
        'CZ' => 'Czech republic',
        'SK' => 'Slovakia',
        'GB' => 'United Kingdom',
    ),
    'CA' => 'Canada',
    'US' => 'USA',
    '?'  => 'other',
);
$form->addSelect('country', 'Country:', $countries)
    ->setPrompt('Pick a country');

addMultiSelect($name, $label = NULL, array $items = NULL)

Adds multichoice select box (class MultiSelectBox).

$form->addMultiSelect('options', 'Pick many:', $options);

addSubmit($name, $caption = NULL)

Adds submit button (class SubmitButton).

$form->addSubmit('submit', 'Register');

If you don't want to validate the form when a submit button is pressed (such as Cancel or Preview buttons), you can turn it off with setValidationScope(array()).

It's okay to add more than one submit button. To find out which of them was clicked, use

if ($form['submit']->isSubmittedBy()) {
    // ...
}

or

if ($form->isSubmitted() === $form['submit']) {
    // ...
}

addButton($name, $caption)

Adds button (class Button) without submit function. It is useful for binding other functionality to id, for example a JavaScript action.

$form->addButton('raise', 'Raise salary')
    ->setAttribute('onclick', 'raiseSalary()');

addImage($name, $alt = NULL)

Adds submit button in form of an image (class ImageButton).

$form->addImage('submit', '/path/to/image');

addContainer($name)

Adds a sub-form (class Container), or a container, which can be treated the same way as a form. That means you can use methods like setDefaults() or getValues().

$sub1 = $form->addContainer('first');
$sub1->addText('name', 'Your name:');
$sub1->addText('email', 'Email:');

$sub2 = $form->addContainer('second');
$sub2->addText('name', 'Your name:');
$sub2->addText('email', 'Email:');

Low-level forms

To add an item to the form, you don't have to call $form->addXyz(). Since version 2.1, form items can introduced exclusively in templates instead. This is useful if you, for example, need to generate dynamic items:

{foreach $items as $item}
    <p><input type=checkbox name="sel[]" value={$item->id}> {$item->name}</p>
{/foreach}

After submission, you can retrieve the values:

$values = $form->getHttpData($form::DATA_TEXT, 'sel[]');
$values = $form->getHttpData($form::DATA_TEXT | $form::DATA_KEYS, 'sel[]');

In the first parameter, you specify element type (DATA_FILE for type=file, DATA_LINE for one-line inputs like text, password or email and DATA_TEXT for the rest). The second parameter matches HTML attribute name. If you need to preserve keys, you can combine the first parameter with DATA_KEYS. This is useful for select, radioList or checkboxList.

getHttpData() returns sanitized input. In this case, it will always be array of valid UTF-8 strings, no matter what is sent by the form. It's an alternative to working with $_POST or $_GET directly if you want to recieve safe data.

Validation rules

The following rules are supported by most already mentioned inputs:

Form::FILLED is element filled?
Form::EQUAL is the value equal to?
Form::IS_IN checks if value is in array
Form::VALID did input pass validation?

We can set a custom error message to all validation rules, or a default one is used. Multilingual forms' messages are automatically translated.

The following special substitute strings can be utilized in error message text:

%label replaced by label text
%name replaced by input identification
%value replaced by submitted input value

Besides validation rules, conditions can be set. They are set much like rules, yet we use addRule() instead of addCondition() and of course we leave it without an error message (the condition just asks):

$form->addPassword('password', 'Password:')
    // if password is not longer than 5 characters ...
    ->addCondition(Form::MAX_LENGTH, 5)
        // ... then it must contain a number
        ->addRule(Form::PATTERN, 'Must contain number', '.*[0-9].*');

Condition can be linked to another element then the one it's used on. Replacing addCondition() with addConditionOn() and settings the other input as first argument will do. In the followin case, the email is only required if the checkbox is checked (ie. it's boolean value is TRUE):

$form->addCheckbox('newsletters', 'send me newsletters');

$form->addText('email', 'Email:')
    // if checkbox is checked ...
    ->addConditionOn($form['newsletters'], Form::EQUAL, TRUE)
        // ... require email
        ->setRequired('Fill your email address');

All rules and conditions can be negated with ~ (a tilde), ie. addRule(~Form::FILLED, ...) passes validation if field is not filled. Conditions can be grouped into complex structures with elseCondition() and endCondition() methods.

As you can see, language of validation rules and conditions is powerful. Even though all constructions work both server-side and client-side, in JavaScript.

We can add own validators. Methods addRule() and addCondition() do accept callback or lambda function as well:

// user validation: checks if $item is divisible by $arg
function divisibilityValidator($item, $arg)
{
    return $item->value % $arg === 0;
}

$form->addText('number', 'Number:')
    ->addRule('divisibilityValidator', 'Number must be divisible by %d.', 8);

Custom errors

We often find that user's input is wrong even though each of the form's elements is technically valid. For example stumbling upon a duplicate key when inserting a new database row. Or invalid login credentials. In that case, we can add the error manually with addError() method. It can be called either on a specific input or a form itself:

try {
    $values = $form->getValues();
    $this->user->login($values->username, $values->password);
    $this->redirect('Homepage:');

} catch (Nette\Security\AuthenticationException $e) {
    $form->addError($e->getMessage());
}

It's recommended to link the error directly to a form element because then the error will be displayed next to it, when using default renderer.

$form['date']->addError("We apologize but this date has been already taken.");

Global default messages

To globally change validation error messages, modify static array Nette\Forms\Rules::$defaultMessages.

Nette\Forms\Rules::$defaultMessages[Form::FILLED] = "All fields are obligatory.";

Custom validation functions

If you need custom validation functionality, you can define your own valiadtion functions for the whole form and bind them to onValidate event. Typically, this is used to validate a combination of values:

protected function createComponentSignInForm()
{
    $form = new Form();
    ...
    $form->onValidate[] = array($this, 'validateSignInForm');
    return $form;
}

public function validateSignInForm($form)
{
    $values = $form->getValues();

    if (...) { // validation logic
        $form->addError('Password does not match.');
    }
}

You can bind multiple functions to the event. The function is considered successful if it doesn't add any error using $form->addError().

JavaScript

Validation rules are transferred to the JavaScript part over HTML5 attributes data-nette-rules, which contain JSON containing all rules and conditions. The validation itself is handled by another script, which hooks all submit events, iterates over all inputs and runs respective validations. Default implementation is netteForms.js file, which can be found at src/assets. All you have to do is link it with

<script src="netteForms.js"></script>`

Custom validation rules can be added by extending Nette.validators object:

<script>
Nette.validators.divisibilityValidator = function(elem, args, val) {
    return val % args === 0;
};
</script>

Disabling validation

In certain cases, you need to disable validation. If a submit button isn't supposed to run validation after submitting (for example Cancel or Preview button), you can disable the validation by calling $submit->setValidationScope(array()). You can also validate the form partially by specifying items to be validated.

$form->addText('name')->setRequired();

$details = $form->addContainer('details');
$details->addText('age')->setRequired('age');
$details->addText('age2')->setRequired('age2');

$form->addSubmit('send1'); // Validates the whole form
$form->addSubmit('send2')->setValidationScope(FALSE); // Validates nothing
$form->addSubmit('send3')->setValidationScope(array($form['name'])); // Validates only 'name' input
$form->addSubmit('send4')->setValidationScope(array($form['details']['age'])); // Validates only 'age' input
$form->addSubmit('send5')->setValidationScope(array($form['details'])); // Validates 'details' container

Processing forms

Accessing submitted value is done by method getValues(), which returns an instance of class ArrayHash or a simple array if we pass TRUE as the first parameter.

$values = $form->getValues();     // Nette\Utils\ArrayHash
$values = $form->getValues(TRUE); // array

Disabling inputs

In order to disable an input, you can call $control->setDisabled(TRUE).

$form->addText('email', 'E-mail:')->setDisabled(TRUE);

The input cannot be written to and its value won't be returned by getValues().

If you only need to remove input's value but not disabling it, you can use setOmitted(TRUE). This option is useful for ommiting antispam inputs for example.

$form->addText('antispam', 'Antispam:')->setOmitted(TRUE);

Other form extensions

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->addText('number', 'Number:')
    ->setAttribute('class', 'bigNumbers');

$form->addSelect('rank', 'Order by:', array('price', 'name'))
    ->setAttribute('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:')
    ->setType('email')
    ->setAttribute('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).

Forms in presenters

When using forms in presenters, we use class Nette\Application\UI\Form which is an extension of Nette\Forms\Form.

Using one form in multiple presenters

In a case when we need to use one form in multiple presenters, we have two options:

  1. either putting it into the presenters' hiearchy into a common ancestor and define a factory there
  2. or to define it in a separate factory class and create its instance in the presenters' factories.

Appropriate place for such class is e.g. app/forms/SignInFormFactory.php. Our factory class will look like this:

use Nette\Application\UI\Form;

class SignInFormFactory
{
    /**
     * @return Form
     */
    public function create()
    {
        $form = new Form;

        $form->addText('name', 'Name:');
        // ...
        $form->addSubmit('login', 'Log in');

        return $form;
    }
}

Now in each presenters' factory, which use our form, we create a form instance using our form factory class using create() method:

protected function createComponentSignInForm()
{
    $form = (new SignInFormFactory)->create();
    $form['login']->caption = 'Continue'; // we can also modify our form

    $form->onSuccess[] = array($this, 'signInFormSubmitted'); // and add event handlers

    return $form;
}

We could also process our submitted form on one place. It's as simple as moving our event handler to our factory class, renaming it from signInFormSubmitted to e.g. submitted. Or we can use an anonymous function as a handler:

use Nette\Application\UI\Form;

class SignInFormFactory
{
    /**
     * @return Form
     */
    public function create()
    {
        $form = new Form;

        $form->addText('name', 'Name:');
        ...
        $form->addSubmit('login', 'Log in');

        $form->onSuccess[] = function (Form $form, \stdClass $values) {
            // we process our submitted form here
        };

        return $form;
    }
}

Submitting a form

If a form contains multiple submit buttons we need to distinguish, it is useful to bind the handlers to onSubmit event, which is invoked immediately before onSuccess.

$form->addSubmit('login', 'Přihlásit se')
    ->onClick[] = array($this, 'signInFormSubmitted');

If a form is submitted by pressing enter key, the first submit button is invoked.

Handlers of onSuccess and onClick events are invoked only if the submitted values pass validation. You don't need to validate the input inside the handler functions. The form also has onSubmit event, which is invoked irrespectively of the validation.

Default values

There are two ways to set default values. Method setDefaults() on a form or a container:

$form->addText('name', 'Name:');
$form->addText('age', 'Age:');

$form->setDefaults(array(
    'name' => 'John',
    'age' => '33'
));

or method setDefaultValue() on a single input:

$form->addText('email', 'Email:')
    ->setDefaultValue('user@example.com');

Default values of select-boxes and radio lists are set with the key from passed array of values:

$form->addSelect('country', 'Country', array(
    'cz' => 'Czech republic',
    'sk' => 'Slovakia',
));
$form['country']->setDefaultValue('sk'); // country defaults to Slovakia

Another usefull option is the “emptyValue”. If value of the form field after form submit is same as its “emptyValue” the field behaves as not filled.

$form->addText('phone', 'Phone:')
    ->setEmptyValue('+42');

Form appearance

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.

DefaultFormRenderer

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;

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}

<!-- Jednoduché vykreslení chyb -->
<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>

...

</table>
{/form}

You can also connect a form with a template esaily 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>

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

<table>
<tr class="required">
    <th><label for="frm-name"></th>
    <td><input cols=40 n:name="name"></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->addText('age', 'Your age:');
$form->addText('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);

Cross-Site Request Forgery (CSRF) protection

Nette Framework protects your applications against Cross-Site Request Forgery (CSRF) attacks. An attacker lures a victim on a webpage, which quietly performs a request to server the victim is logged into. The server would not recognize whether the user send the request willingly.

The protection is pretty simple:

$form->addProtection('Security token has expired, please submit the form again');

Generating and validating authentication token can prevent this attack. It has a limited expiration time: a session lifespan. Thanks to that it does not prevent using the application in multiple windows (as long as it is the same session). The first argument is the error message shown, if the token has expired.

This protection should be added to all form which change sensitive data.

Multilingual Forms

If you are creating a multilingual application, you will need to render the very same form in many language mutations. Nette Framework forms have built-in support for translation. You simply set a translator the form, which is an object implementing Nette\Localization\ITranslator interface, having only one method translate().

class MyTranslator extends Nette\Object implements Nette\Localization\ITranslator
{
    /**
     * Translates the given string.
     * @param  string   message
     * @param  int      plural count
     * @return string
     */
    public function translate($message, $count = NULL)
    {
        return ...;
    }
}

$form->setTranslator($translator);

All labels, error messages and select values are transparently translated into another languages.

Nette/Forms standalone

Nette/Forms can be used without the whole framework as a standalone package. You can install it via Composer:

composer require nette/forms

The form is created easily like this:

use Nette\Forms\Form;

$form = new Form;

$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('send', 'Sign up');

echo $form; // renders the form

Such form is submitted through POST method to the same page. You can alter that easily:

$form = new Form;
$form->setAction('/submit.php');
$form->setMethod('GET');
...

We can find out whether the form was submitted and passed validation by calling $form->isSuccess(). If so, let's print out the data.

if ($form->isSuccess()) {
    echo 'Form was filled and submitted successfully';

    $values = $form->getValues();
    dump($values);
}

You can access form items like you access array. In our case, you can find the first text input on index $form['name'].

It's recommended to redirect to other page after you have processed the data. You can avoid duplicate form submission this way.

Rendering the form

Each element has getLabel() and getControl() methods which return HTML code of label and the element itself. Nette provides getter and setter property acces as if you are accesing 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') ?>