You are browsing the unmaintained documentation for old Nette 2.0. See documentation for current Nette.

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

Why should you bother setting up framework for a simple web form? You won't have to take care about routine tasks such as writing two validation scripts (client and server) and your code will be safe against security breaches.

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.

First form

Let's create a simple registration form:

require 'Nette/loader.php';

use Nette\Forms\Form;

$form = new Form;

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

echo $form; // renders the form

Hint: all scripts must be saved with UTF-8 encoding.

Browser will render the form as follows:

We have created a simple form, which is sent to the very same page over POST request. Though we can easily change it. What if we would like another form to receive the data over GET at url /submit.php?

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

The first argument of every $form->addElement() represents a unique identification. Each $form input can be access as though it was an array, that is with square brackets. So in our example, with $form['name'] we would receive an instance of Nette\Forms\Controls\TextInput, the very first element of our form.

Rendered forms fulfill the main rule of accessibility – all labels are generated as <label> elements and are linked with the respective input. Clicking them moves the cursor caret to the field automatically.

Let's light up the form a bit. Asking if the $form->isSubmitted() will yeld whether the form is submitted and method isValid() tells us, whether is was submitted correctly. If it was, we would like to print the data out, so we will append the following snippet after the form declaration.

if ($form->isSubmitted() && $form->isValid()) {
    echo 'Form was submitted and passed validation';

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

It's appropriate to redirect to next page after sending and processing form. It prevents unwanted form re-submitting upon clicking Refresh or Back in history.

Class Nette\Forms\Form is not used in presenters. Instead, you should utilize it's descendant Nette\Application\UI\Form, which is handled slightly differently. More about this topic at presenter forms.

Data get from getValues() call 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.

Though we mentioned validation, yet our form has none. Let's fix it. We require users to tell us their name, so we should call a setRequired() method, which optional argument is an error message to show, if user does not fill his name in:

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

Try submitting a form without the name – you will se this very message until you meet the validation rules. All that is left for us is setting up JavaScript rules. Luckily it's an piece of cake. We only have to link netteForms.js, which is located at /client-side/forms in the distribution package.

<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 {#multi-language 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, $cols = NULL, $maxLength = 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->addText('zip', 'Postcode:')
    ->addRule(Form::PATTERN, 'Postcode must have exactly 5 numerals', '([0-9]\s*){5}');

addPassword($name, $label = NULL, $cols = NULL, $maxLength = 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');

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 third argument.

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

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 it off with setValidationScope(FALSE).

addImageButton($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), which is used as though it were a form. Methods such as setDefaults() or getValues() are all working.

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

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 passed 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
        ->addRule(Form::FILLED, '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 out that user's input is invalid upon already working with validated form, for example when inserting a new database row and stumble upon a duplicate key. 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());
}

JavaScript

Validation rules are transferred to the JavaScript part over HTML5 attributes data-nette-rules, which contain JavaScript objects 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 /client-side/forms. 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>

Presenter forms

Forms are added into presetners using a factory:

protected function createComponentSignInForm()
{
    $form = new Nette\Application\UI\Form;
    $form->addText('name', 'Name:');
    $form->addPassword('password', 'Password:');
    $form->addSubmit('login', 'Log in');
    $form->onSuccess[] = array($this, 'signInFormSubmitted');
    return $form;
}

function signInFormSubmitted($form)
{
    // called when form is submitted and successfully validated
}

If a form has more than one submit buttons which we have to differentiate, it's appropriate to set a onClick handler to the buttons itself, which is called before onSuccess event.

$form->addSubmit('login', 'Log in')
    ->onClick[] = array($this, 'signInFormSubmitted');

When a form is submitted with Enter*Return* key, the submit button is the very first one.

Event handlers onSuccess and onClick are called only if the form has passed the validation. You should not check for validity again, as it would be superfluous. Forms also has one more event, onSubmit, called always not depending on form being valid or not.

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

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

Manual rendering

DefaultFormRenderer is great for swift rendering of standard forms. What's left to do if you can't fit your form into $wrappers or a custom renderer? The most pragmatic solution is writing a HTML template.

As you already know, each form input is accessible as php arrays (ie. $form['name]). But what might be a surprise is that every element has a getLabel() and getControl() methods, returning HTML snippets of label, and control, respectively. Nette Framework makes it possible to access getters as though they were properties, so you can only write $form->label and $form->control. Let's put those together and here comes the manual rendering:

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

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

<table>
<tr class="required">
    <th><?php echo $form['name']->label ?></th>
    <td><?php echo $form['name']->control ?></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') ?>

You are in the control of the code now, rendering the form exactly the way you want it to be rendered.

Using Latte, the final template can be as pretty as this:

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

...

</table>
{/form signForm}

With rendering of inputs inside a form container helps macro formContainer.

{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 can you 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:input 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:input="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.