Version 2.4

Form Validation

Form Field Validation

The following rules are supported by most built-in form fields:

Form::FILLED is element filled?
Form::REQUIRED alias of Form::FILLED
Form::EQUAL is the value equal to? mixed $value or $values[]
Form::NOT_EQUAL the value must not be equal to given value mixed $value or $values[]
Form::IS_IN checks if value is in array mixed $value or $values[]
Form::IS_NOT_IN checks if value is NOT in array mixed $value or $values[]
Form::VALID did input pass validation?  
Form::BLANK the item must not be filled  

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 a different element than the current one: Just replace addCondition() with addConditionOn() and specify the reference to another element as the first parameter. In the following case, the email will be required if the checkbox is ticked (ie. its Boolean value is true):

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

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

All conditions can be negated with ~ (a tilde), i.e. addCondition(~Form::NUMBER, ...) 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
// note: this is a real function, not a method in the presenter
function divisibilityValidator($item, $arg)
{
    return $item->value % $arg === 0;
}

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

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="/path/to/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>

If our PHP validation callback is a static method in a class, we create the name for JavaScript by removing all backslashes \ and by replacing the double colon by single underscore _, e.g. App\MyValidator::divisibilityValidator is converted to AppMyValidator_divisibilityValidator.

Modifying Input Values

Using addFilter method we can modify retrieved value before form is processed. We can combine addFilter with addCondition and addConditionOn methods.

$form->addText('zip', 'Postcode:')
    ->addCondition($form::FILLED)
    ->addFilter(function ($value) {
        return str_replace(' ', '', $value);
    });

When we later access values in form processing, postcode won't contain any space.

Postprocessing 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 on a form itself:

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

} catch (Nette\Security\AuthenticationException $e) {
    if ($e->getCode() === Nette\Security\IAuthenticator::INVALID_CREDENTIAL) {
        $form->addError('Invalid password.');
    }
}

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

Whole Form Validation

If you need custom validation functionality, you can define your own validation 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[] = [$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().

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([]). You can also validate the form partially by specifying items to be validated.

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

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

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

onValidate event on the form is always invoked and is not affected by the setValidationScope. onValidate event on the container is invoked only when this container is specified for partial validation.