What's New in Nette 2.1: Forms

10 years ago by Jan Smitka  

Forms have always been a key part of the framework. Ever since the first version, forms have allowed developers to easily define input elements, render them, and process user-submitted data. Nette 2.1 brings several new features and addresses the number of issues and restrictions from previous versions.

This article covers the most significant improvements:

  • new low-level access to form data
  • improved rendering using n: attributes
  • new features in the form validation

Low-Level Access

In previous versions of Nette, all form fields had to be defined during form initialization. This is a great feature for strictly defined forms, e.g. a registration dialog, but it has been rather limiting when creating dynamic forms. The developer had to build the entire form when it was created, or create a custom control that handled the dynamic data.

For example, if we want to create a table with a selection checkbox on each row, the most straightforward approach is to iterate the table rows twice: once in your form definition, and once when you were rendering the form.

In the new version, however, it is no longer necessary to iterate twice. You can simply render any HTML input you like and access its value through the new Form::getHttpData() method.

Suppose we have the following input inside the template of your form:

<input type="text" name="example" />

Even though the input of this name is not defined in your form, we can access it's user-submitted value by calling getHttpData():

$exampleValue = $form->getHttpData(Form::DATA_LINE, 'example');

The returned data is automatically sanitized, so you will always get a valid UTF-8 encoded string.

The first argument of the method is the type of the expected input. There are three types, all defined as constants of the Form class:

DATA_TEXT
Multi-line text input, e.g. a textarea. All newlines are normalized to \n, no further processing is performed.
DATA_LINE
Single-line text input, e.g. text, or password. All newlines are replaced by spaces and the trailing and leading spaces are trimmed.
DATA_FILE
Uploaded file, returns an instance of FileUpload.

The second argument of the method is the name of the input. If you specify a name with trailing [], the method returns an array of all values of the given name. In the example with selection checkboxes for each row, we could easily render each checkbox by a single foreach pass.

<tr n:foreach="$items as $item">
	<td><input type="checkbox" name="selection[]" value="{$item->id}" /></td>
</tr>

Then we can get IDs of all selected items by simply calling getHttpData() with the same name, as we specified in the template.

$exampleValue = $form->getHttpData(Form::DATA_LINE, 'selection[]');

This enables us to create dynamic forms simply and securely.

Please note that these dynamic values cannot be validated. If you need some kind of validation, you have to perform it yourself when retrieving the value. Also, you have to render the input elements completely manually. If you don't need to create a form with a dynamic number of inputs, you are better off with defining all input fields when creating the form.

Rendering With n:name Attributes

Nette 2.0 introduced new “form macros”: {form}, {label /} and {input}. This set of macros enables developers and coders to manually render the form and its inputs without messing around with custom form renderers and significantly simplifies embedding form controls in custom HTML markup.

The new version brings a new n:name attribute. It allows you to connect any HTML input field with an input defined in the form, or the form itself when applied to the <form> element.

The rendered element will have all required attributes, including name, value, and any defined validation rules. Attributes that are already defined on the input are not overwritten, even the name and value attributes, so make sure you removed them before adding n:name.

A simple sign-in form could be rendered as follows:

<form n:name="loginForm">
	<label n:name="username" class="required">Username:</label>
	<input n:name="username" size="40" placeholder="enter your username" />

	<label n:name="password" class="required">Password:</label>
	<input n:name="password" size="40" placeholder="******" />

	<button n:name="submit">Sign me in!</button>
</form>

The n:name attribute is contextual – for instance, when you add it to the <form>, it starts the rendering of form component with the given name and adds necessary attributes to the element. The same applies to the <label> element where it renders the input label. When applied to any other element, it renders the control itself.

Please note that in the current version the n:name attribute does not change nor validate the name of the element, so if you apply n:name of a input defined as a textarea to <input /> element, you will not get a textarea, just a broken <input>.

When rendering textarea, select and multiselect inputs, you don't have to worry about theirs inner value or <option> children – they will be rendered automatically. In fact, any child nodes will be overwritten. Just apply the n:name to an empty element:

<textarea n:name="text"></textarea>
<select n:name="select"></select>

In the upcoming 2.1.1 release it is possible to omit the closing tag:

<textarea n:name="text" />
<select n:name="select" />

However, in the current release, this will render malformed HTML.

Form Validation

Nette 2.1 also introduces several improvements in form validation. They are not as significant as improvements in rendering, but they are very useful.

First of all, several validation rules now modify the value of user-submitted input. Numeric validation rules like Form::INTEGER and Form::FLOAT automatically convert the user-submitted value to the proper data type, and Form::URL rule prepends the http:// prefix to values without any schema. For example, blog.nette.org would be automatically changed to https://blog.nette.org.

Additionally, all rule parameters can be now bound to the value of any other input. In previous versions, you could only specify equality of two controls using by the Form::EQUALS rule, but now you can reference another control in any parameter. This could be useful for the Form::RANGE rule:

$form->addText('min', 'Minimum value:');
$form->addText('max', 'Maximum value:');
$form->addText('value', 'Value from range:')
	->addRule(Form::RANGE, "Please enter any value between %d and %d.",
		array($form['min'], array(['max']));

Last but not least, you can set which controls should be validated when the user sends the form using a specific button. The SubmitButton::setValidationScope() method accepts an array with names of controls, which will be validated. If you pass the NULL value (or TRUE, as in previous versions), all form controls will be validated. This is the default behavior.

// when publishing an article, all controls has to be validated
$form->addSubmit('publish')->setValidationScope(NULL); // not necessary
// when deleting an article, no fields are validated
$form->addSubmit('delete')->setValidationScope(FALSE);
// when displaying an preview, only the article title and text is required
$form->addSubmit('preview')->setValidationScope(array(
	$form['title'], $form['text']
));

Conclusion

In Nette 2.1, forms were improved in both data processing and rendering. This article covered only the most significant changes. For a detailed list of changes and improvements, head to the Migrating to version 2.1 section. Before upgrading, please check your code for minor incompatibilities in the rendering of checkboxes using the {input} and {label /} macros.