What's New in Nette 2.1: Forms
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.
Sign in to submit a comment