Merge pull request #2867 from oddnoc/form-docs-consolidation

Combine form validation topic with forms topic
This commit is contained in:
Ingo Schommer 2014-03-03 10:32:04 +13:00
commit ae31362e21
4 changed files with 235 additions and 219 deletions

View File

@ -720,7 +720,7 @@ an object, not for displaying the objects contained in the relation.
## Validation and Constraints ## Validation and Constraints
Traditionally, validation in SilverStripe has been mostly handled on the Traditionally, validation in SilverStripe has been mostly handled on the
controller through [form validation](/topics/form-validation). controller through [form validation](/topics/forms#form-validation).
While this is a useful approach, it can lead to data inconsistencies if the While this is a useful approach, it can lead to data inconsistencies if the
record is modified outside of the controller and form context. record is modified outside of the controller and form context.

View File

@ -1,159 +0,0 @@
# Form Validation
SilverStripe provides PHP form validation out of the box,
but doesn't come with any built-in JavaScript validation
(the previously used `Validator.js` approach has been deprecated).
## Required Fields
Validators are implemented as an argument to the `[api:Form]` constructor,
and are subclasses of the abstract `[api:Validator]` base class.
The only implementation which comes with SilverStripe is
the `[api:RequiredFields]` class, which ensures fields are filled out
when the form is submitted.
:::php
public function Form() {
$form = new Form($this, 'Form',
new FieldList(
new TextField('MyRequiredField'),
new TextField('MyOptionalField')
),
new FieldList(
new FormAction('submit', 'Submit form')
),
new RequiredFields(array('MyRequiredField'))
);
// Optional: Add a CSS class for custom styling
$form->dataFieldByName('MyRequiredField')->addExtraClass('required');
return $form;
}
## Form Field Validation
Form fields are responsible for validating the data they process,
through the `[api:FormField->validate()] method. There are many fields
for different purposes (see ["form field types"](/reference/form-field-types) for a full list).
## Adding your own validation messages
In many cases, you want to add PHP validation which is more complex than
validating the format or existence of a single form field input.
For example, you might want to have dependent validation on
a postcode which depends on the country you've selected in a different field.
There's two ways to go about this: Either you can attach a custom error message
to a specific field, or a generic message for the whole form.
Example: Validate postcodes based on the selected country (on the controller).
:::php
class MyController extends Controller {
private static $allowed_actions = array('Form');
public function Form() {
return Form::create($this, 'Form',
new FieldList(
new NumericField('Postcode'),
new CountryDropdownField('Country')
),
new FieldList(
new FormAction('submit', 'Submit form')
),
new RequiredFields(array('Country'))
);
}
public function submit($data, $form) {
// At this point, RequiredFields->validate() will have been called already,
// so we can assume that the values exist.
// German postcodes need to be five digits
if($data['Country'] == 'de' && isset($data['Postcode']) && strlen($data['Postcode']) != 5) {
$form->addErrorMessage('Postcode', 'Need five digits for German postcodes', 'bad');
return $this->redirectBack();
}
// Global validation error (not specific to form field)
if($data['Country'] == 'IR' && isset($data['Postcode']) && $data['Postcode']) {
$form->sessionMessage("Ireland doesn't have postcodes!", 'bad');
return $this->redirectBack();
}
// continue normal processing...
}
}
## JavaScript Validation
While there are no built-in JavaScript validation handlers in SilverStripe,
the `FormField` API is flexible enough to provide the information required
in order to plug in custom libraries.
### HTML5 attributes
HTML5 specifies some built-in form validations ([source](http://www.w3.org/wiki/HTML5_form_additions)),
which are evaluated by modern browsers without any need for JavaScript.
SilverStripe supports this by allowing to set custom attributes on fields.
:::php
// Markup contains <input type="text" required />
TextField::create('MyText')->setAttribute('required', true);
// Markup contains <input type="url" pattern="https?://.+" />
TextField::create('MyText')
->setAttribute('type', 'url')
->setAttribute('pattern', 'https?://.+')
### HTML5 metadata
In addition, HTML5 elements can contain custom data attributes with the `data-` prefix.
These are general purpose attributes, but can be used to hook in your own validation.
:::php
// Validate a specific date format (in PHP)
// Markup contains <input type="text" data-dateformat="dd.MM.yyyy" />
DateField::create('MyDate')->setConfig('dateformat', 'dd.MM.yyyy');
// Limit extensions on upload (in PHP)
// Markup contains <input type="file" data-allowed-extensions="jpg,jpeg,gif" />
$exts = array('jpg', 'jpeg', 'gif');
$validator = new Upload_Validator();
$validator->setAllowedExtensions($exts);
$upload = Upload::create()->setValidator($validator);
$fileField = FileField::create('MyFile')->setUpload(new);
$fileField->setAttribute('data-allowed-extensions', implode(',', $exts));
Note that these examples don't have any effect on the client as such,
but are just a starting point for custom validation with JavaScript.
## Model Validation
An alternative (or additional) approach to validation is to place it directly
on the model. SilverStripe provides a `[api:DataObject->validate()]` method for this purpose.
Refer to the ["datamodel" topic](/topics/datamodel#validation-and-constraints) for more information.
## Validation in the CMS
Since you're not creating the forms for editing CMS records,
SilverStripe provides you with a `getCMSValidator()` method on your models
to return a `[api:Validator]` instance.
:::php
class Page extends SiteTree {
private static $db = array('MyRequiredField' => 'Text');
public function getCMSValidator() {
return new RequiredFields(array('MyRequiredField'));
}
}
## Subclassing Validator
To create your own validator, you need to subclass validator and define two methods:
* **javascript()** Should output a snippet of JavaScript that will get called to perform javascript validation.
* **php($data)** Should return true if the given data is valid, and call $this->validationError() if there were any
errors.
## Related
* Model Validation with [api:DataObject->validate()]

View File

@ -1,6 +1,6 @@
# Forms # Forms
HTML forms are in practice the most used way to communicate with a browser. HTML forms are in practice the most used way to interact with a user.
SilverStripe provides classes to generate and handle the actions and data from a SilverStripe provides classes to generate and handle the actions and data from a
form. form.
@ -9,14 +9,14 @@ form.
A fully implemented form in SilverStripe includes a couple of classes that A fully implemented form in SilverStripe includes a couple of classes that
individually have separate concerns. individually have separate concerns.
* Controller - Takes care of assembling the form and receiving data from it. * Controller—Takes care of assembling the form and receiving data from it.
* Form - Holds sets of fields, actions and validators. * Form—Holds sets of fields, actions and validators.
* FormField - Fields that receive data or displays them, e.g input fields. * FormField —Fields that receive data or displays them, e.g input fields.
* FormActions - Often submit buttons that executes actions. * FormActions—Often submit buttons that executes actions.
* Validators - Validate the whole form, see [Form validation](form-validation.md) topic for more information. * Validators—Validate the whole form.
Depending on your needs you can customize and override any of the above classes, Depending on your needs you can customize and override any of the above classes;
however the defaults are often sufficient. the defaults, however, are often sufficient.
## The Controller ## The Controller
@ -53,12 +53,13 @@ in a controller.
The name of the form ("HelloForm") is passed into the `Form` constructor as a The name of the form ("HelloForm") is passed into the `Form` constructor as a
second argument. It needs to match the method name. second argument. It needs to match the method name.
Since forms need a URL, the `HelloForm()` method needs to be handled like any Because forms need a URL, the `HelloForm()` method needs to be handled like any
other controller action. In order to whitelist its access through URLs, we add other controller action. To grant it access through URLs, we add it to the
it to the `$allowed_actions` array. `$allowed_actions` array.
Form actions ("doSayHello") on the other hand should NOT be included here, these Form actions ("doSayHello"), on the other hand, should _not_ be included in
are handled separately through `Form->httpSubmission()`. `$allowed_actions`; these are handled separately through
`Form->httpSubmission()`.
You can control access on form actions either by conditionally removing a You can control access on form actions either by conditionally removing a
`FormAction` from the form construction, or by defining `$allowed_actions` in `FormAction` from the form construction, or by defining `$allowed_actions` in
@ -68,19 +69,21 @@ your own `Form` class (more information in the
**Page.ss** **Page.ss**
:::ss :::ss
<!-- place where you would like the form to show up --> <%-- place where you would like the form to show up --%>
<div>$HelloForm</div> <div>$HelloForm</div>
<div class="warning" markdown='1'> <div class="warning" markdown='1'>
Be sure to add the Form name 'HelloForm' to the Controller::$allowed_actions() Be sure to add the Form name 'HelloForm' to your controller's $allowed_actions
to be sure that form submissions get through to the correct action. array to enable form submissions.
</div> </div>
<div class="notice" markdown='1'> <div class="notice" markdown='1'>
You'll notice that we've used a new notation for creating form fields, using `create()` instead of the `new` operator. You'll notice that we've used a new notation for creating form fields, using
These are functionally equivalent, but allows PHP to chain operations like `setTitle()` without assigning the field `create()` instead of the `new` operator. These are functionally equivalent, but
instance to a temporary variable. For in-depth information on the create syntax, see the [Injector](/reference/injector) allows PHP to chain operations like `setTitle()` without assigning the field
documentation or the API documentation for `[api:Object]`::create(). instance to a temporary variable. For in-depth information on the create syntax,
see the [Injector](/reference/injector) documentation or the API documentation
for `[api:Object]`::create().
</div> </div>
## The Form ## The Form
@ -95,13 +98,18 @@ Creating a form is a matter of defining a method to represent that form. This
method should return a form object. The constructor takes the following method should return a form object. The constructor takes the following
arguments: arguments:
* `$controller`: This must be an instance of the controller that contains the form, often `$this`. * `$controller`: This must be an instance of the controller that contains the
* `$name`: This must be the name of the method on that controller that is called to return the form. The first two form, often `$this`.
fields allow the form object to be re-created after submission. **It's vital that they are properly set - if you ever * `$name`: This must be the name of the method on that controller that is
have problems with form action handler not working, check that these values are correct.** called to return the form. The first two arguments allow the form object
* `$fields`: A `[api:FieldList]` containing `[api:FormField]` instances make up fields in the form. to be re-created after submission. **It's vital that they be properly
* `$actions`: A `[api:FieldList]` containing the `[api:FormAction]` objects - the buttons at the bottom. set—if you ever have problems with a form action handler not working,
* `$validator`: An optional `[api:Validator]` for validation of the form. check that these values are correct.**
* `$fields`: A `[api:FieldList]` containing `[api:FormField]` instances make
up fields in the form.
* `$actions`: A `[api:FieldList]` containing the `[api:FormAction]` objects -
the buttons at the bottom.
* `$validator`: An optional `[api:Validator]` for validation of the form.
Example: Example:
@ -119,13 +127,14 @@ Example:
## Subclassing a form ## Subclassing a form
It's the responsibility of your subclass' constructor to call It's the responsibility of your subclass's constructor to call
:::php :::php
parent::__construct() parent::__construct()
with the right parameters. You may choose to take $fields and $actions as arguments if you wish, but $controller and with the right parameters. You may choose to take $fields and $actions as
$name must be passed - their values depend on where the form is instantiated. arguments if you wish, but $controller and $name must be passed—their values
depend on where the form is instantiated.
:::php :::php
class MyForm extends Form { class MyForm extends Form {
@ -141,8 +150,9 @@ $name must be passed - their values depend on where the form is instantiated.
} }
The real difference, however, is that you can then define your controller methods within the form class itself. This The real difference, however, is that you can then define your controller
means that the form takes responsibilities from the controller and manage how to parse and use the form methods within the form class itself. This means that the form takes
responsibilities from the controller and manage how to parse and use the form
data. data.
**Page.php** **Page.php**
@ -211,10 +221,10 @@ form.
) )
); );
## Readonly ## Readonly
You can turn a form or individual fields into a readonly version. This is handy You can turn a form or individual fields into a readonly version. This is handy
in the case of confirmation pages or when certain fields can be edited due to in the case of confirmation pages or when certain fields cannot be edited due to
permissions. permissions.
Readonly on a Form Readonly on a Form
@ -241,10 +251,13 @@ Readonly on a FormField
You can use a custom form template to render with, instead of *Form.ss* You can use a custom form template to render with, instead of *Form.ss*
It's recommended you only do this if you've got a lot of presentation text, graphics that surround the form fields. This It's recommended you do this only if you have a lot of presentation text or
is better than defining those as *LiteralField* objects, as it doesn't clutter the data layer with presentation junk. graphics that surround the form fields. This is better than defining those as
*LiteralField* objects, as it doesn't clutter the data layer with presentation
junk.
First of all, you need to create your form on it's own class, that way you can define a custom template using a `forTemplate()` method on your Form class. First you need to create your own form class extending Form; that way you can
define a custom template using a `forTemplate()` method on your Form class.
:::php :::php
class MyForm extends Form { class MyForm extends Form {
@ -305,14 +318,16 @@ your project. Here is an example of basic customization:
<% end_if %> <% end_if %>
</form> </form>
`$Fields.dataFieldByName(FirstName)` will return the form control contents of `Field()` for the particular field object, `$Fields.dataFieldByName(FirstName)` will return the form control contents of
in this case `EmailField->Field()` or `PasswordField->Field()` which returns an `<input>` element with specific markup `Field()` for the particular field object, in this case `EmailField->Field()` or
for the type of field. Pass in the name of the field as the first parameter, as done above, to render it into the `PasswordField->Field()` which returns an `<input>` element with specific markup
template. for the type of field. Pass in the name of the field as the first parameter, as
done above, to render it into the template.
To find more methods, have a look at the `[api:Form]` class and `[api:FieldList]` class as there is a lot of different To find more methods, have a look at the `[api:Form]` class and
methods of customising the form templates. An example is that you could use `<% loop $Fields %>` instead of specifying `[api:FieldList]` class as there is a lot of different methods of customising
each field manually, as we've done above. the form templates. An example is that you could use `<% loop $Fields %>`
instead of specifying each field manually, as we've done above.
### Custom form field templates ### Custom form field templates
@ -333,19 +348,20 @@ Each form field is rendered into a form via the
`<div>` as well as a `<label>` element (if applicable). `<div>` as well as a `<label>` element (if applicable).
You can also render each field without these structural elements through the You can also render each field without these structural elements through the
`[FormField->Field()](api:FormField)` method. In order to influence the form `[FormField->Field()](api:FormField)` method. To influence form rendering,
rendering, overloading these two methods is a good start. overriding these two methods is a good start.
In addition, most form fields are rendered through SilverStripe templates, e.g. In addition, most form fields are rendered through SilverStripe templates; for
`TextareaField` is rendered via `framework/templates/forms/TextareaField.ss`. example, `TextareaField` is rendered via
`framework/templates/forms/TextareaField.ss`.
These templates can be overwritten globally by placing a template with the same These templates can be overridden globally by placing a template with the same
name in your `mysite` directory, or set on a form field instance via anyone of name in your `mysite` directory, or set on a form field instance via any of
these methods: these methods:
- FormField->setTemplate() - FormField->setTemplate()
- FormField->setFieldHolderTemplate() - FormField->setFieldHolderTemplate()
- FormField->getSmallFieldHolderTemplate() - FormField->getSmallFieldHolderTemplate()
<div class="hint" markdown='1'> <div class="hint" markdown='1'>
Caution: Not all FormFields consistently uses templates set by the above methods. Caution: Not all FormFields consistently uses templates set by the above methods.
@ -358,7 +374,7 @@ by adding a hidden *SecurityID* parameter to each form. See
[secure-development](/topics/security) for details. [secure-development](/topics/security) for details.
In addition, you should limit forms to the intended HTTP verb (mostly `GET` or `POST`) In addition, you should limit forms to the intended HTTP verb (mostly `GET` or `POST`)
to further reduce attack surface, by using `[api:Form->setStrictFormMethodCheck()]`. to further reduce attack exposure, by using `[api:Form->setStrictFormMethodCheck()]`.
:::php :::php
$myForm->setFormMethod('POST'); $myForm->setFormMethod('POST');
@ -391,12 +407,172 @@ Adds a new text field called FavouriteColour next to the Content field in the CM
:::php :::php
$this->Fields()->addFieldToTab('Root.Content', new TextField('FavouriteColour'), 'Content'); $this->Fields()->addFieldToTab('Root.Content', new TextField('FavouriteColour'), 'Content');
## Form Validation
SilverStripe provides PHP form validation out of the box, but doesn't come with
any built-in JavaScript validation (the previously used `Validator.js` approach
has been deprecated).
### Required Fields
Validators are implemented as an argument to the `[api:Form]` constructor, and
are subclasses of the abstract `[api:Validator]` base class. The only
implementation that comes with SilverStripe is the `[api:RequiredFields]` class,
which ensures that fields are filled out when the form is submitted.
:::php
public function Form() {
$form = new Form($this, 'Form',
new FieldList(
new TextField('MyRequiredField'),
new TextField('MyOptionalField')
),
new FieldList(
new FormAction('submit', 'Submit form')
),
new RequiredFields(array('MyRequiredField'))
);
// Optional: Add a CSS class for custom styling
$form->dataFieldByName('MyRequiredField')->addExtraClass('required');
return $form;
}
### Form Field Validation
Form fields are responsible for validating the data they process, through the
`[api:FormField->validate()]` method. There are many fields for different
purposes (see ["form field types"](/reference/form-field-types) for a full list).
### Adding your own validation messages
In many cases, you want to add PHP validation that is more complex than
validating the format or existence of a single form field input. For example,
you might want to have dependent validation on a postcode which depends on the
country you've selected in a different field.
There are two ways to go about this: attach a custom error message to a specific
field, or a generic message to the whole form.
Example: Validate postcodes based on the selected country (on the controller).
:::php
class MyController extends Controller {
private static $allowed_actions = array('Form');
public function Form() {
return Form::create($this, 'Form',
new FieldList(
new NumericField('Postcode'),
new CountryDropdownField('Country')
),
new FieldList(
new FormAction('submit', 'Submit form')
),
new RequiredFields(array('Country'))
);
}
public function submit($data, $form) {
// At this point, RequiredFields->validate() will have been called already,
// so we can assume that the values exist.
// German postcodes need to be five digits
if($data['Country'] == 'de' && isset($data['Postcode']) && strlen($data['Postcode']) != 5) {
$form->addErrorMessage('Postcode', 'Need five digits for German postcodes', 'bad');
return $this->redirectBack();
}
// Global validation error (not specific to form field)
if($data['Country'] == 'IR' && isset($data['Postcode']) && $data['Postcode']) {
$form->sessionMessage("Ireland doesn't have postcodes!", 'bad');
return $this->redirectBack();
}
// continue normal processing...
}
}
### JavaScript Validation
Although there are no built-in JavaScript validation handlers in SilverStripe,
the `FormField` API is flexible enough to provide the information required in
order to plug in custom libraries.
#### HTML5 attributes
HTML5 specifies some built-in form validations
([source](http://www.w3.org/wiki/HTML5_form_additions)), which are evaluated by
modern browsers without any need for JavaScript. SilverStripe supports this by
allowing to set custom attributes on fields.
:::php
// Markup contains <input type="text" required />
TextField::create('MyText')->setAttribute('required', true);
// Markup contains <input type="url" pattern="https?://.+" />
TextField::create('MyText')
->setAttribute('type', 'url')
->setAttribute('pattern', 'https?://.+')
#### HTML5 metadata
In addition, HTML5 elements can contain custom data attributes with the `data-`
prefix. These are general-purpose attributes, but can be used to hook in your
own validation.
:::php
// Validate a specific date format (in PHP)
// Markup contains <input type="text" data-dateformat="dd.MM.yyyy" />
DateField::create('MyDate')->setConfig('dateformat', 'dd.MM.yyyy');
// Limit extensions on upload (in PHP)
// Markup contains <input type="file" data-allowed-extensions="jpg,jpeg,gif" />
$exts = array('jpg', 'jpeg', 'gif');
$validator = new Upload_Validator();
$validator->setAllowedExtensions($exts);
$upload = Upload::create()->setValidator($validator);
$fileField = FileField::create('MyFile')->setUpload(new);
$fileField->setAttribute('data-allowed-extensions', implode(',', $exts));
Note that these examples don't have any effect on the client as such, but are
just a starting point for custom validation with JavaScript.
### Model Validation
An alternative (or additional) approach to validation is to place it directly on
the model. SilverStripe provides a `[api:DataObject->validate()]` method for
this purpose. Refer to the
["datamodel" topic](/topics/datamodel#validation-and-constraints) for more information.
### Validation in the CMS
Since you're not creating the forms for editing CMS records, SilverStripe
provides you with a `getCMSValidator()` method on your models to return a
`[api:Validator]` instance.
:::php
class Page extends SiteTree {
private static $db = array('MyRequiredField' => 'Text');
public function getCMSValidator() {
return new RequiredFields(array('MyRequiredField'));
}
}
### Subclassing Validator
To create your own validator, you need to subclass validator and define two methods:
* **javascript()** Should output a snippet of JavaScript that will get called
to perform javascript validation.
* **php($data)** Should return true if the given data is valid, and call
$this->validationError() if there were any errors.
## Related ## Related
* [Form Field Types](/reference/form-field-types) * [Form Field Types](/reference/form-field-types)
* [MultiForm Module](http://silverstripe.org/multi-form-module) * [MultiForm Module](http://silverstripe.org/multi-form-module)
* Model Validation with [api:DataObject->validate()]
## API Documentation ## API Documentation
* `[api:Form]` * `[api:Form]`
* `[api:FormField]` * `[api:FormField]`

View File

@ -16,8 +16,7 @@ It is where most documentation should live, and is the natural "second step" aft
* [Environment management](environment-management): Sharing configuration details (e.g. database login, passwords) with multiple websites via a `_ss_environment.php` file * [Environment management](environment-management): Sharing configuration details (e.g. database login, passwords) with multiple websites via a `_ss_environment.php` file
* [Error Handling](error-handling): Error messages and filesystem logs * [Error Handling](error-handling): Error messages and filesystem logs
* [Files and Images](files): File and Image management in the database and how to manipulate images * [Files and Images](files): File and Image management in the database and how to manipulate images
* [Form Validation](form-validation): Built-in validation on form fields, and how to extend it * [Forms & form validation](forms): Create your own form, add fields and create your own form template using the existing `Form` class
* [Forms](forms): Create your own form, add fields and create your own form template using the existing `Form` class
* [Internationalization (i18n)](i18n): Displaying templates and PHP code in different languages using i18n * [Internationalization (i18n)](i18n): Displaying templates and PHP code in different languages using i18n
* [Javascript](javascript): Best practices for developing with JavaScript in SilverStripe * [Javascript](javascript): Best practices for developing with JavaScript in SilverStripe
* [Module Development](module-development): Creating a module (also known as "extension" or "plugin") to contain reusable functionality * [Module Development](module-development): Creating a module (also known as "extension" or "plugin") to contain reusable functionality