mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Rewrite, tidy and format of Forms documentation
This commit is contained in:
parent
82b1b3d566
commit
9fbbf6d88a
1
docs/en/02_Developer_Guides/00_Model/09_Validation.md
Normal file
1
docs/en/02_Developer_Guides/00_Model/09_Validation.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
* stub, talk validate()
|
@ -1,586 +0,0 @@
|
|||||||
# Forms
|
|
||||||
|
|
||||||
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
|
|
||||||
form.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
A fully implemented form in SilverStripe includes a couple of classes that
|
|
||||||
individually have separate concerns.
|
|
||||||
|
|
||||||
* Controller — Takes care of assembling the form and receiving data from it.
|
|
||||||
* Form — Holds sets of fields, actions and validators.
|
|
||||||
* FormField — Fields that receive data or displays them, e.g input fields.
|
|
||||||
* FormActions — Buttons that execute actions.
|
|
||||||
* Validators — Validate the whole form.
|
|
||||||
|
|
||||||
Depending on your needs you can customize and override any of the above classes;
|
|
||||||
the defaults, however, are often sufficient.
|
|
||||||
|
|
||||||
## The Controller
|
|
||||||
|
|
||||||
Forms start at the controller. Here is a simple example on how to set up a form
|
|
||||||
in a controller.
|
|
||||||
|
|
||||||
**Page.php**
|
|
||||||
|
|
||||||
:::php
|
|
||||||
class Page_Controller extends ContentController {
|
|
||||||
|
|
||||||
private static $allowed_actions = array(
|
|
||||||
'HelloForm'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Template method
|
|
||||||
public function HelloForm() {
|
|
||||||
$fields = new FieldList();
|
|
||||||
$actions = new FieldList(
|
|
||||||
FormAction::create("doSayHello")->setTitle("Say hello")
|
|
||||||
);
|
|
||||||
$form = new Form($this, 'HelloForm', $fields, $actions);
|
|
||||||
// Load the form with previously sent data
|
|
||||||
$form->loadDataFrom($this->request->postVars());
|
|
||||||
return $form;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function doSayHello($data, Form $form) {
|
|
||||||
// Do something with $data
|
|
||||||
return $this->render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
The name of the form ("HelloForm") is passed into the `Form` constructor as a
|
|
||||||
second argument. It needs to match the method name.
|
|
||||||
|
|
||||||
Because forms need a URL, the `HelloForm()` method needs to be handled like any
|
|
||||||
other controller action. To grant it access through URLs, we add it to the
|
|
||||||
`$allowed_actions` array.
|
|
||||||
|
|
||||||
Form actions ("doSayHello"), on the other hand, should _not_ be included in
|
|
||||||
`$allowed_actions`; these are handled separately through
|
|
||||||
`Form->httpSubmission()`.
|
|
||||||
|
|
||||||
You can control access on form actions either by conditionally removing a
|
|
||||||
`FormAction` from the form construction, or by defining `$allowed_actions` in
|
|
||||||
your own `Form` class (more information in the
|
|
||||||
["controllers" topic](/topics/controllers)).
|
|
||||||
|
|
||||||
**Page.ss**
|
|
||||||
|
|
||||||
:::ss
|
|
||||||
<%-- place where you would like the form to show up --%>
|
|
||||||
<div>$HelloForm</div>
|
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
|
||||||
Be sure to add the Form name 'HelloForm' to your controller's $allowed_actions
|
|
||||||
array to enable form submissions.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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. These are functionally equivalent, but
|
|
||||||
allows PHP to chain operations like `setTitle()` without assigning the field
|
|
||||||
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>
|
|
||||||
|
|
||||||
## The Form
|
|
||||||
|
|
||||||
Form is the base class of all forms in a SilverStripe application. Forms in your
|
|
||||||
application can be created either by instantiating the Form class itself, or by
|
|
||||||
subclassing it.
|
|
||||||
|
|
||||||
### Instantiating a form
|
|
||||||
|
|
||||||
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
|
|
||||||
arguments:
|
|
||||||
|
|
||||||
* `$controller`: This must be an instance of the controller that contains the
|
|
||||||
form, often `$this`.
|
|
||||||
* `$name`: This must be the name of the method on that controller that is
|
|
||||||
called to return the form. The first two arguments allow the form object
|
|
||||||
to be re-created after submission. **It's vital that they be properly
|
|
||||||
set—if you ever have problems with a form action handler not working,
|
|
||||||
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:
|
|
||||||
|
|
||||||
:::php
|
|
||||||
// Controller action
|
|
||||||
public function MyCustomForm() {
|
|
||||||
$fields = new FieldList(
|
|
||||||
EmailField::create("Email"),
|
|
||||||
PasswordField::create("Password")
|
|
||||||
);
|
|
||||||
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
|
|
||||||
return new Form($this, "MyCustomForm", $fields, $actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
## Subclassing a form
|
|
||||||
|
|
||||||
It's the responsibility of your subclass's constructor to call
|
|
||||||
|
|
||||||
:::php
|
|
||||||
parent::__construct()
|
|
||||||
|
|
||||||
with the right parameters. You may choose to take $fields and $actions as
|
|
||||||
arguments if you wish, but $controller and $name must be passed—their values
|
|
||||||
depend on where the form is instantiated.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
class MyForm extends Form {
|
|
||||||
public function __construct($controller, $name) {
|
|
||||||
$fields = new FieldList(
|
|
||||||
EmailField::create("Email"),
|
|
||||||
PasswordField::create("Password")
|
|
||||||
);
|
|
||||||
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
|
|
||||||
|
|
||||||
parent::__construct($controller, $name, $fields, $actions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
The real difference, however, is that you can then define your controller
|
|
||||||
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.
|
|
||||||
|
|
||||||
**Page.php**
|
|
||||||
|
|
||||||
:::php
|
|
||||||
class Page_Controller extends ContentController {
|
|
||||||
|
|
||||||
private static $allowed_actions = array(
|
|
||||||
'HelloForm',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Template method
|
|
||||||
public function HelloForm() {
|
|
||||||
return new MyForm($this, 'HelloForm');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
**MyForm.php**
|
|
||||||
|
|
||||||
:::php
|
|
||||||
class MyForm extends Form {
|
|
||||||
|
|
||||||
public function __construct($controller, $name) {
|
|
||||||
$fields = new FieldList(
|
|
||||||
EmailField::create("Email"),
|
|
||||||
PasswordField::create("Password")
|
|
||||||
);
|
|
||||||
|
|
||||||
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
|
|
||||||
|
|
||||||
parent::__construct($controller, $name, $fields, $actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function login(array $data, Form $form) {
|
|
||||||
// Authenticate the user and redirect the user somewhere
|
|
||||||
Controller::curr()->redirectBack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
## The FormField classes
|
|
||||||
|
|
||||||
There are many classes extending `[api:FormField]`. There is a full overview at
|
|
||||||
[form field types](/reference/form-field-types).
|
|
||||||
|
|
||||||
|
|
||||||
### Using Form Fields
|
|
||||||
|
|
||||||
To get these fields automatically rendered into a form element, all you need to
|
|
||||||
do is create a new instance of the class, and add it to the `FieldList` of the
|
|
||||||
form.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$form = new Form(
|
|
||||||
$this, // controller
|
|
||||||
"SignupForm", // form name
|
|
||||||
new FieldList( // fields
|
|
||||||
TextField::create("FirstName")->setTitle('First name'),
|
|
||||||
TextField::create("Surname")->setTitle('Last name')->setMaxLength(50),
|
|
||||||
EmailField::create("Email")->setTitle("Email address")->setAttribute('type', 'email')
|
|
||||||
),
|
|
||||||
new FieldList( // actions
|
|
||||||
FormAction::create("signup")->setTitle("Sign up")
|
|
||||||
),
|
|
||||||
new RequiredFields( // validation
|
|
||||||
"Email", "FirstName"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
## Readonly
|
|
||||||
|
|
||||||
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 cannot be edited due to
|
|
||||||
permissions.
|
|
||||||
|
|
||||||
Readonly on a Form
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$myForm->makeReadonly();
|
|
||||||
|
|
||||||
|
|
||||||
Readonly on a FieldList
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$myFieldList->makeReadonly();
|
|
||||||
|
|
||||||
|
|
||||||
Readonly on a FormField
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$myReadonlyField = $myField->transform(new ReadonlyTransformation());
|
|
||||||
// shortcut
|
|
||||||
$myReadonlyField = $myField->performReadonlyTransformation();
|
|
||||||
|
|
||||||
|
|
||||||
## Custom form templates
|
|
||||||
|
|
||||||
You can use a custom form template to render with, instead of *Form.ss*
|
|
||||||
|
|
||||||
It's recommended you do this only if you have a lot of presentation text or
|
|
||||||
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 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
|
|
||||||
class MyForm extends Form {
|
|
||||||
|
|
||||||
public function __construct($controller, $name) {
|
|
||||||
$fields = new FieldList(
|
|
||||||
EmailField::create("Email"),
|
|
||||||
PasswordField::create("Password")
|
|
||||||
);
|
|
||||||
|
|
||||||
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
|
|
||||||
parent::__construct($controller, $name, $fields, $actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function login(array $data, Form $form) {
|
|
||||||
// Do something with $data
|
|
||||||
Controller::curr()->redirectBack();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function forTemplate() {
|
|
||||||
return $this->renderWith(array($this->class, 'Form'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
`MyForm->forTemplate()` tells the `[api:Form]` class to render with a template
|
|
||||||
of return value of `$this->class`, which in this case is *MyForm*. If the
|
|
||||||
template doesn't exist, then it falls back to using Form.ss.
|
|
||||||
|
|
||||||
*MyForm.ss* should then be placed into your *templates/Includes* directory for your project. Here is an example of
|
|
||||||
basic customisation, with two ways of presenting the field and its inline validation:
|
|
||||||
|
|
||||||
:::ss
|
|
||||||
<form $FormAttributes>
|
|
||||||
<% if $Message %>
|
|
||||||
<p id="{$FormName}_error" class="message $MessageType">$Message</p>
|
|
||||||
<% else %>
|
|
||||||
<p id="{$FormName}_error" class="message $MessageType" style="display: none"></p>
|
|
||||||
<% end_if %>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<div id="Email" class="field email">
|
|
||||||
<label class="left" for="{$FormName}_Email">Email</label>
|
|
||||||
$Fields.dataFieldByName(Email)
|
|
||||||
<span id="{$FormName}_error" class="message $Fields.dataFieldByName(Email).MessageType">
|
|
||||||
$Fields.dataFieldByName(Email).Message
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="Email" class="field password">
|
|
||||||
<label class="left" for="{$FormName}_Password">Password</label>
|
|
||||||
<% with $Fields.dataFieldByName(Password) %>
|
|
||||||
$field
|
|
||||||
<% if $Message %>
|
|
||||||
<p id="{$FormName}_error" class="message $MessageType">$Message</p>
|
|
||||||
<% end_if %>
|
|
||||||
<% end_with %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
$Fields.dataFieldByName(SecurityID)
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<% if $Actions %>
|
|
||||||
<div class="Actions">
|
|
||||||
<% loop $Actions %>$Field<% end_loop %>
|
|
||||||
</div>
|
|
||||||
<% end_if %>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
`$Fields.dataFieldByName(FirstName)` will return the form control contents of
|
|
||||||
`Field()` for the particular field object, in this case `EmailField->Field()` or
|
|
||||||
`PasswordField->Field()` which returns an `<input>` element with specific markup
|
|
||||||
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 methods of customising
|
|
||||||
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
|
|
||||||
|
|
||||||
The easiest way to customize form fields is adding CSS classes and additional attributes.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$field = TextField::create('MyText')
|
|
||||||
->addExtraClass('largeText');
|
|
||||||
->setAttribute('data-validation-regex', '[\d]*');
|
|
||||||
|
|
||||||
Will be rendered as:
|
|
||||||
|
|
||||||
:::html
|
|
||||||
<input type="text" name="MyText" class="text largeText" id="MyForm_MyCustomForm_MyText" data-validation-regex="[\d]*">
|
|
||||||
|
|
||||||
Each form field is rendered into a form via the
|
|
||||||
`[FormField->FieldHolder()](api:FormField)` method, which includes a container
|
|
||||||
`<div>` as well as a `<label>` element (if applicable).
|
|
||||||
|
|
||||||
You can also render each field without these structural elements through the
|
|
||||||
`[FormField->Field()](api:FormField)` method. To influence form rendering,
|
|
||||||
overriding these two methods is a good start.
|
|
||||||
|
|
||||||
In addition, most form fields are rendered through SilverStripe templates; for
|
|
||||||
example, `TextareaField` is rendered via
|
|
||||||
`framework/templates/forms/TextareaField.ss`.
|
|
||||||
|
|
||||||
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 any of
|
|
||||||
these methods:
|
|
||||||
|
|
||||||
- FormField->setTemplate()
|
|
||||||
- FormField->setFieldHolderTemplate()
|
|
||||||
- FormField->setSmallFieldHolderTemplate()
|
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
|
||||||
Caution: Not all FormFields consistently uses templates set by the above methods.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Securing forms against Cross-Site Request Forgery (CSRF)
|
|
||||||
|
|
||||||
SilverStripe tries to protect users against *Cross-Site Request Forgery (CSRF)*
|
|
||||||
by adding a hidden *SecurityID* parameter to each form. See
|
|
||||||
[secure-development](/topics/security) for details.
|
|
||||||
|
|
||||||
In addition, you should limit forms to the intended HTTP verb (mostly `GET` or `POST`)
|
|
||||||
to further reduce attack exposure, by using `[api:Form->setStrictFormMethodCheck()]`.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$myForm->setFormMethod('POST');
|
|
||||||
$myForm->setStrictFormMethodCheck(true);
|
|
||||||
$myForm->setFormMethod('POST', true); // alternative short notation
|
|
||||||
|
|
||||||
### Remove existing fields
|
|
||||||
|
|
||||||
If you want to remove certain fields from your subclass:
|
|
||||||
|
|
||||||
:::php
|
|
||||||
class MyCustomForm extends MyForm {
|
|
||||||
|
|
||||||
public function __construct($controller, $name) {
|
|
||||||
parent::__construct($controller, $name);
|
|
||||||
|
|
||||||
// remove a normal field
|
|
||||||
$this->Fields()->removeByName('MyFieldName');
|
|
||||||
|
|
||||||
// remove a field from a tab
|
|
||||||
$this->Fields()->removeFieldFromTab('TabName', 'MyFieldName');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
### Working with tabs
|
|
||||||
|
|
||||||
Adds a new text field called FavouriteColour next to the Content field in the CMS
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$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');
|
|
||||||
$fileField = FileField::create('MyFile');
|
|
||||||
$fileField->getValidator()->setAllowedExtensions($exts);
|
|
||||||
$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
|
|
||||||
|
|
||||||
* [Form Field Types](/reference/form-field-types)
|
|
||||||
* [MultiForm Module](http://silverstripe.org/multi-form-module)
|
|
||||||
* Model Validation with [api:DataObject->validate()]
|
|
||||||
|
|
||||||
## API Documentation
|
|
||||||
|
|
||||||
* `[api:Form]`
|
|
||||||
* `[api:FormField]`
|
|
||||||
* `[api:FieldList]`
|
|
||||||
* `[api:FormAction]`
|
|
311
docs/en/02_Developer_Guides/03_Forms/00_Introduction.md
Normal file
311
docs/en/02_Developer_Guides/03_Forms/00_Introduction.md
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
title: Introduction to Forms
|
||||||
|
summary: An introduction to creating a Form instance and handling submissions.
|
||||||
|
|
||||||
|
# Forms
|
||||||
|
|
||||||
|
The HTML `Form` is the most used way to interact with a user. SilverStripe provides classes to generate forms through
|
||||||
|
the [api:Form] class, [api:FormField] instances to capture data and submissions through [api:FormAction].
|
||||||
|
|
||||||
|
<div class="notice" markdown="1">
|
||||||
|
See the [Forms Tutorial](../../tutorials/forms/) for a step by step process of creating a `Form`
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Creating a Form
|
||||||
|
|
||||||
|
Creating a [api:Form] has the following signature.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$form = new Form(
|
||||||
|
$controller, // the Controller to render this form on
|
||||||
|
$name, // name of the method that returns this form on the controller
|
||||||
|
FieldList $fields, // list of FormField instances
|
||||||
|
FieldList $actions, // list of FormAction instances
|
||||||
|
$required // optional use of RequiredFields object
|
||||||
|
);
|
||||||
|
|
||||||
|
In practice, this looks like:
|
||||||
|
|
||||||
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Page_Controller extends ContentController {
|
||||||
|
|
||||||
|
private static $allowed_actions = array(
|
||||||
|
'HelloForm'
|
||||||
|
);
|
||||||
|
|
||||||
|
public function HelloForm() {
|
||||||
|
$fields = new FieldList(
|
||||||
|
TextField::create('Name', 'Your Name')
|
||||||
|
);
|
||||||
|
|
||||||
|
$actions = new FieldList(
|
||||||
|
FormAction::create("doSayHello")->setTitle("Say hello")
|
||||||
|
);
|
||||||
|
|
||||||
|
$required = new RequiredFields('Name')
|
||||||
|
|
||||||
|
$form = new Form($this, 'HelloForm', $fields, $actions, $required);
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doSayHello($data, Form $form) {
|
||||||
|
$form->sessionMessage('Hello '. $data['Name'], 'success');
|
||||||
|
|
||||||
|
return $this->redirectBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
**mysite/templates/Page.ss**
|
||||||
|
|
||||||
|
:::ss
|
||||||
|
$HelloForm
|
||||||
|
|
||||||
|
|
||||||
|
<div class="info" markdown="1">
|
||||||
|
The examples above use `FormField::create()` instead of the `new` operator (`new FormField()`). These are functionally
|
||||||
|
equivalent, but allows PHP to chain operations like `setTitle()` without assigning the field instance to a temporary
|
||||||
|
variable.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
When constructing the `Form` instance (`new Form($controller, $name)`) both controller and name are required. The
|
||||||
|
`$controller` and `$name` are used to allow SilverStripe to calculate the origin of the `Form object`. When a user
|
||||||
|
submits the `HelloForm` from your `contact-us` page the form submission will go to `contact-us/HelloForm` before any of
|
||||||
|
the [api:FormActions]. The URL is known as the `$controller` instance will know the 'contact-us' link and we provide
|
||||||
|
`HelloForm` as the `$name` of the form. `$name` **needs** to match the method name.
|
||||||
|
|
||||||
|
Because the `HelloForm()` method will be the location the user is taken to, it needs to be handled like any other
|
||||||
|
controller action. To grant it access through URLs, we add it to the `$allowed_actions` array.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
private static $allowed_actions = array(
|
||||||
|
'HelloForm'
|
||||||
|
);
|
||||||
|
|
||||||
|
<div class="notice" markdown="1">
|
||||||
|
Form actions (`doSayHello`), on the other hand, should _not_ be included in `$allowed_actions`; these are handled
|
||||||
|
separately through [api:Form::httpSubmission].
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## Adding FormFields
|
||||||
|
|
||||||
|
Fields in a [api:Form] are represented as a single [api:FieldList] instance containing subclasses of [api:FormField].
|
||||||
|
Some common examples are [api:TextField] or [api:DropdownField].
|
||||||
|
|
||||||
|
:::php
|
||||||
|
TextField::create($name, $title, $value);
|
||||||
|
|
||||||
|
<div class="info" markdown='1'>
|
||||||
|
A list of the common FormField subclasses is available on the [Common Subclasses](fields/common_subclasses) page.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
The fields are added to the [api:FieldList] `fields` property on the `Form` and can be modified at up to the point the
|
||||||
|
`Form` is rendered.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$fields = new FieldList(
|
||||||
|
TextField::create('Name'),
|
||||||
|
EmailField::create('Email')
|
||||||
|
);
|
||||||
|
|
||||||
|
$form = new Form($controller, 'MethodName', $fields, ...);
|
||||||
|
|
||||||
|
// or use `setFields`
|
||||||
|
$form->setFields($fields);
|
||||||
|
|
||||||
|
// to fetch the current fields..
|
||||||
|
$fields = $form->getFields();
|
||||||
|
|
||||||
|
A field can be appended to the [api:FieldList].
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$fields = $form->Fields();
|
||||||
|
|
||||||
|
// add a field
|
||||||
|
$fields->push(new TextField(..));
|
||||||
|
|
||||||
|
// insert a field before another one
|
||||||
|
$fields->insertBefore(new TextField(..), 'Email');
|
||||||
|
|
||||||
|
// insert a field after another one
|
||||||
|
$fields->insertAfter(new TextField(..), 'Name');
|
||||||
|
|
||||||
|
Fields can be fetched after they have been added in.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$email = $form->Fields()->dataFieldByName('Email');
|
||||||
|
$email->setTitle('Your Email Address');
|
||||||
|
|
||||||
|
Fields can be removed from the form.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$form->getFields()->removeByName('Email');
|
||||||
|
|
||||||
|
<div class="alert" markdown="1">
|
||||||
|
Forms can be tabbed (such as the CMS interface). In these cases, there are additional functions such as `addFieldToTab`
|
||||||
|
and `removeFieldByTab` to ensure the fields are on the correct interface. See [Tabbed Forms](tabbed_forms) for more
|
||||||
|
information on the CMS interface.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Modifying FormFields
|
||||||
|
|
||||||
|
Each [api:FormField] subclass has a number of methods you can call on it to customize its' behavior or HTML markup. The
|
||||||
|
default `FormField` object has several methods for doing common operations.
|
||||||
|
|
||||||
|
<div class="notice">
|
||||||
|
Most of the `set` operations will return the object back so methods can be chained.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$field = new TextField(..);
|
||||||
|
|
||||||
|
$field
|
||||||
|
->setMaxLength(100)
|
||||||
|
->setAttribute('placeholder', 'Enter a value..')
|
||||||
|
->setTitle('');
|
||||||
|
|
||||||
|
### Custom Templates
|
||||||
|
|
||||||
|
The [api:Form] HTML markup and each of the [api:FormField] instances are rendered into templates. You can provide custom
|
||||||
|
templates by using the `setTemplate` method on either the `Form` or `FormField`. For more details on providing custom
|
||||||
|
templates see [Form Templates](form_templates)
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$form = new Form(..);
|
||||||
|
|
||||||
|
$form->setTemplate('CustomForm');
|
||||||
|
|
||||||
|
// or, for a FormField
|
||||||
|
$field = new TextField(..);
|
||||||
|
|
||||||
|
$field->setTemplate('CustomTextField');
|
||||||
|
$field->setFieldHolderTemplate('CustomTextField_Holder');
|
||||||
|
|
||||||
|
## Adding FormActions
|
||||||
|
|
||||||
|
[api:FormAction] objects are displayed at the bottom of the `Form` in the form of a `button` or `input` tag. When a
|
||||||
|
user presses the button, the form is submitted to the corresponding method.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
FormAction::create($action, $title);
|
||||||
|
|
||||||
|
As with [api:FormField], the actions for a `Form` are stored within a [api:FieldList] instance in the `actions` property
|
||||||
|
on the form.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
public function MyForm() {
|
||||||
|
$fields = new FieldList(..);
|
||||||
|
|
||||||
|
$actions = new FieldList(
|
||||||
|
FormAction::create('doSubmitForm', 'Submit')
|
||||||
|
);
|
||||||
|
|
||||||
|
$form = new Form($controller, 'MyForm', $fields, $actions);
|
||||||
|
|
||||||
|
// Get the actions
|
||||||
|
$actions = $form->Actions();
|
||||||
|
|
||||||
|
// As actions is a FieldList, push, insertBefore, removeByName and other
|
||||||
|
// methods described for `Fields` also work for actions.
|
||||||
|
|
||||||
|
$actions->push(
|
||||||
|
FormAction::create('doSecondaryFormAction', 'Another Button')
|
||||||
|
);
|
||||||
|
|
||||||
|
$actions->removeByName('doSubmitForm');
|
||||||
|
$form->setActions($actions);
|
||||||
|
|
||||||
|
return $form
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doSubmitForm($data, $form) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doSecondaryFormAction($data, $form) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
The first `$action` argument for creating a `FormAction` is the name of the method to invoke when submitting the form
|
||||||
|
with the particular button. In the previous example, clicking the 'Another Button' would invoke the
|
||||||
|
`doSecondaryFormAction` method. This action can be defined (in order) on either:
|
||||||
|
|
||||||
|
* One of the `FormField` instances.
|
||||||
|
* The `Form` instance.
|
||||||
|
* The `Controller` instance.
|
||||||
|
|
||||||
|
<div class="notice">
|
||||||
|
If the `$action` method cannot be found on any of those or is marked as `private` or `protected`, an error will be
|
||||||
|
thrown.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
The `$action` method takes two arguments:
|
||||||
|
|
||||||
|
* `$data` an array containing the values of the form mapped from `$name` => '$value'
|
||||||
|
* `$form` the submitted [api:Form] instance.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Page_Controller extends ContentController {
|
||||||
|
|
||||||
|
private static $allowed_actions = array(
|
||||||
|
'MyForm'
|
||||||
|
);
|
||||||
|
|
||||||
|
public function MyForm() {
|
||||||
|
$fields = new FieldList(
|
||||||
|
TextField::create('Name'),
|
||||||
|
EmailField::create('Email')
|
||||||
|
);
|
||||||
|
|
||||||
|
$actions = new FieldList(
|
||||||
|
FormAction::create('doSubmitForm', 'Submit')
|
||||||
|
);
|
||||||
|
|
||||||
|
$form = new Form($controller, 'MyForm', $fields, $actions);
|
||||||
|
|
||||||
|
return $form
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doSubmitForm($data, $form) {
|
||||||
|
// Submitted data is available as a map.
|
||||||
|
echo $data['Name'];
|
||||||
|
echo $data['Email'];
|
||||||
|
|
||||||
|
// You can also fetch the value from the field.
|
||||||
|
echo $form->Fields()->dataFieldByName('Email')->Value();
|
||||||
|
|
||||||
|
// Using the Form instance you can get / set status such as error messages.
|
||||||
|
$form->sessionMessage("Successful!", 'good');
|
||||||
|
|
||||||
|
// After dealing with the data you can redirect the user back.
|
||||||
|
return $this->redirectBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Form validation is handled by the [api:Validator] class and the `validator` property on the `Form` object. The validator
|
||||||
|
is provided with a name of each of the [api:FormField]s to validate and each `FormField` instance is responsible for
|
||||||
|
validating its' own data value.
|
||||||
|
|
||||||
|
For more information, see the [Form Validation](validation) documentation.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$validator = new RequiredFields(array(
|
||||||
|
'Name', 'Email'
|
||||||
|
));
|
||||||
|
|
||||||
|
$form = new Form($this, 'MyForm', $fields, $actions, $validator);
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
* [api:Form]
|
||||||
|
* [api:FormField]
|
||||||
|
* [api:FieldList]
|
||||||
|
* [api:FormAction]
|
235
docs/en/02_Developer_Guides/03_Forms/01_Validation.md
Normal file
235
docs/en/02_Developer_Guides/03_Forms/01_Validation.md
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
title: Form Validation
|
||||||
|
summary: Validate form data through the server side validation API.
|
||||||
|
|
||||||
|
# Form Validation
|
||||||
|
|
||||||
|
SilverStripe provides server-side form validation out of the box through the [api:Validator] class and its' child class
|
||||||
|
[api:RequiredFields]. A single `Validator` instance is set on each `Form`. Validators are implemented as an argument to
|
||||||
|
the `[api:Form]` constructor or through the function `setValidator`.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Page_Controller extends ContentController {
|
||||||
|
|
||||||
|
private static $allowed_actions = array(
|
||||||
|
'MyForm'
|
||||||
|
);
|
||||||
|
|
||||||
|
public function MyForm() {
|
||||||
|
$fields = new FieldList(
|
||||||
|
TextField::create('Name'),
|
||||||
|
EmailField::create('Email')
|
||||||
|
);
|
||||||
|
|
||||||
|
$actions = new FieldList(
|
||||||
|
FormAction::create('doSubmitForm', 'Submit')
|
||||||
|
);
|
||||||
|
|
||||||
|
// the fields 'Name' and 'Email' are required.
|
||||||
|
$required = new RequiredFields(array(
|
||||||
|
'Name', 'Email'
|
||||||
|
));
|
||||||
|
|
||||||
|
// $required can be set as an argument
|
||||||
|
$form = new Form($controller, 'MyForm', $fields, $actions, $required);
|
||||||
|
|
||||||
|
// Or, through a setter.
|
||||||
|
$form->setValidator($required);
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doSubmitForm($data, $form) {
|
||||||
|
//..
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
In this example we will be required to input a value for `Name` and a valid email address for `Email` before the
|
||||||
|
`doSubmitForm` method is called.
|
||||||
|
|
||||||
|
<div class="info" markdown="1">
|
||||||
|
Each individual [api:FormField] instance is responsible for validating the submitted content through the
|
||||||
|
[api:FormField::validate] method. By default, this just checks the value exists. Fields like `EmailField` override
|
||||||
|
`validate` to check for a specific format.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Subclasses of `FormField` can define their own version of `validate` to provide custom validation rules such as the
|
||||||
|
above example with the `Email` validation. The `validate` method on `FormField` takes a single argument of the current
|
||||||
|
`Validator` instance.
|
||||||
|
|
||||||
|
<div class="notice" markdown="1">
|
||||||
|
The data value of the `FormField` submitted is not passed into validate. It is stored in the `value` property through
|
||||||
|
the `setValue` method.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
:::php
|
||||||
|
public function validate($validator) {
|
||||||
|
if($this->value == 10) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
The `validate` method should return `true` if the value passes any validation and `false` if SilverStripe should trigger
|
||||||
|
a validation error on the page.
|
||||||
|
|
||||||
|
<div class="notice" markdown="1">
|
||||||
|
You can also override the entire `Form` validation by subclassing `Form` and defining a `validate` method on the form.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Say we need a custom `FormField` which requires the user input a value in a `TextField` between 2 and 5. There would be
|
||||||
|
two ways to go about this:
|
||||||
|
|
||||||
|
A custom `FormField` which handles the validation. This means the `FormField` can be reused throughout the site and have
|
||||||
|
the same validation logic applied to it throughout.
|
||||||
|
|
||||||
|
**mysite/code/formfields/CustomNumberField.php**
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class CustomNumberField extends TextField {
|
||||||
|
|
||||||
|
public function validate($validator) {
|
||||||
|
if(!is_numeric($this->value)) {
|
||||||
|
$validator->validationError(
|
||||||
|
$this->name, "Not a number. This must be between 2 and 5", "validation", false
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if($this->value > 5 || $this->value < 2) {
|
||||||
|
$validator->validationError(
|
||||||
|
$this->name, "Your number must be between 2 and 5, "validation", false
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Or, an alternative approach to the custom class is to define the behavior inside the Form's action method. This is less
|
||||||
|
reusable and would not be possible within the `CMS` or other automated `UI` but does not rely on creating custom
|
||||||
|
`FormField` classes.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Page_Controller extends ContentController {
|
||||||
|
|
||||||
|
private static $allowed_actions = array(
|
||||||
|
'MyForm'
|
||||||
|
);
|
||||||
|
|
||||||
|
public function MyForm() {
|
||||||
|
$fields = new FieldList(
|
||||||
|
TextField::create('Name'),
|
||||||
|
EmailField::create('Email')
|
||||||
|
);
|
||||||
|
|
||||||
|
$actions = new FieldList(
|
||||||
|
FormAction::create('doSubmitForm', 'Submit')
|
||||||
|
);
|
||||||
|
|
||||||
|
$form = new Form($controller, 'MyForm', $fields, $actions);
|
||||||
|
|
||||||
|
return $form
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doSubmitForm($data, $form) {
|
||||||
|
// At this point, RequiredFields->validate() will have been called already,
|
||||||
|
// so we can assume that the values exist. Say we want to make sure that email hasn't already been used.
|
||||||
|
|
||||||
|
$check = Member::get()->filter('Email', $data['Email'])->first();
|
||||||
|
|
||||||
|
if($check) {
|
||||||
|
$form->addErrorMessage('Email', 'This email already exists', 'bad');
|
||||||
|
|
||||||
|
return $this->redirectBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$form->sessionMessage("You have been added to our mailing list", 'good');
|
||||||
|
|
||||||
|
return $this->redirectBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## Server-side validation messages
|
||||||
|
|
||||||
|
If a `FormField` fails to pass `validate()` the default error message is returned.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
'$Name' is required
|
||||||
|
|
||||||
|
Use `setCustomValidationMessage` to provide a custom message.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$field = new TextField(..);
|
||||||
|
$field->setCustomValidationMessage('Whoops, looks like you have missed me!');
|
||||||
|
|
||||||
|
## 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 like [Parsley.js](http://parsleyjs.org/) or
|
||||||
|
[jQuery.Validate](http://jqueryvalidation.org/). Most of these libraries work on HTML `data-` attributes or special
|
||||||
|
classes added to each input. For Parsley we can structure the form like.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$form = new Form(..);
|
||||||
|
$form->setAttribute('data-parsley-validate', true);
|
||||||
|
|
||||||
|
$field = $fields->dataFieldByName('Name');
|
||||||
|
|
||||||
|
$field->setAttribute('required', true);
|
||||||
|
$field->setAttribute('data-parsley-mincheck', '2');
|
||||||
|
|
||||||
|
|
||||||
|
## Model Validation
|
||||||
|
|
||||||
|
An alternative (or additional) approach to validation is to place it directly on the database model. SilverStripe
|
||||||
|
provides a `[api:DataObject->validate]` method to validate data at the model level. See
|
||||||
|
[Data Model Validation](../model/validation).
|
||||||
|
|
||||||
|
### Validation in the CMS
|
||||||
|
|
||||||
|
In the CMS, we're not creating the forms for editing CMS records. The `Form` instance is generated for us so we cannot
|
||||||
|
call `setValidator` easily. However, a `DataObject` can provide its' own `Validator` instance through the
|
||||||
|
`getCMSValidator()` method. The CMS interfaces such as [api:LeftAndMain], [api:ModelAdmin] and [api:GridField] will
|
||||||
|
respect the provided `Validator` and handle displaying error and success responses to the user.
|
||||||
|
|
||||||
|
<div class="info" markdown="1">
|
||||||
|
Again, custom error messages can be provided through the `FormField`
|
||||||
|
</div>
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Page extends SiteTree {
|
||||||
|
|
||||||
|
private static $db = array(
|
||||||
|
'MyRequiredField' => 'Text'
|
||||||
|
);
|
||||||
|
|
||||||
|
public function getCMSFields() {
|
||||||
|
$fields = parent::getCMSFields();
|
||||||
|
|
||||||
|
$fields->addFieldToTab('Root.Main',
|
||||||
|
TextField::create('MyRequiredField')->setCustomValidationMessage('You missed me.')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCMSValidator() {
|
||||||
|
return new RequiredFields(array(
|
||||||
|
'MyRequiredField'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
* [api:RequiredFields]
|
||||||
|
* [api:Validator]
|
@ -1,157 +0,0 @@
|
|||||||
# DateField
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
This `FormField` subclass lets you display an editable date, either in
|
|
||||||
a single text input field, or in three separate fields for day, month and year.
|
|
||||||
It also provides a calendar datepicker.
|
|
||||||
|
|
||||||
## Adding a DateField
|
|
||||||
|
|
||||||
The following example will add a simple DateField to your Page, allowing you to
|
|
||||||
enter a date manually.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
class Page extends SiteTree {
|
|
||||||
private static $db = array(
|
|
||||||
'MyDate' => 'Date',
|
|
||||||
);
|
|
||||||
|
|
||||||
public function getCMSFields() {
|
|
||||||
$fields = parent::getCMSFields();
|
|
||||||
|
|
||||||
$fields->addFieldToTab(
|
|
||||||
'Root.Main',
|
|
||||||
$myDate = new DateField('MyDate', 'Enter a date')
|
|
||||||
);
|
|
||||||
|
|
||||||
return $fields;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
## Custom Dateformat
|
|
||||||
|
|
||||||
You can define a custom dateformat for your Datefield based on [Zend_Date constants](http://framework.zend.com/manual/1.12/en/zend.date.constants.html).
|
|
||||||
|
|
||||||
:::php
|
|
||||||
// will display a date in the following format: 31-06-2012
|
|
||||||
DateField::create('MyDate')->setConfig('dateformat', 'dd-MM-yyyy');
|
|
||||||
|
|
||||||
Caution: If you're using natural language date formats like abbreviated month names
|
|
||||||
alongside the "showcalendar" option, you'll need to ensure the formats
|
|
||||||
between the calendar widget and the SilverStripe validation are consistent.
|
|
||||||
As an example for the 'de' locale, check `framework/thirdparty/jquery-ui/datepicker/i18n/jquery.ui.datepicker-de.js`
|
|
||||||
and compare it to `framework/thirdparty/Zend/Locale/Data/de.xml`
|
|
||||||
(see `<calendar type="gregorian">` in the XML data).
|
|
||||||
|
|
||||||
## Min and Max Dates
|
|
||||||
|
|
||||||
Set the minimum and maximum allowed datevalues using the `min` and `max`
|
|
||||||
configuration settings (in ISO format or strtotime() compatible). Example:
|
|
||||||
|
|
||||||
:::php
|
|
||||||
DateField::create('MyDate')
|
|
||||||
->setConfig('min', '-7 days')
|
|
||||||
->setConfig('max', '2012-12-31')
|
|
||||||
|
|
||||||
## Separate Day/Month/Year Fields
|
|
||||||
|
|
||||||
The following setting will display your DateField as `three input fields` for
|
|
||||||
day, month and year separately. Any custom dateformat settings will be ignored.
|
|
||||||
HTML5 placeholders 'day', 'month' and 'year' are enabled by default.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
DateField::create('MyDate')
|
|
||||||
->setConfig('dmyfields', true)
|
|
||||||
->setConfig('dmyseparator', '/') // set the separator
|
|
||||||
->setConfig('dmyplaceholders', 'true'); // enable HTML 5 Placeholders
|
|
||||||
|
|
||||||
## Formatting Hints
|
|
||||||
|
|
||||||
Its often not immediate apparent which format a field accepts,
|
|
||||||
and showing the technical format (e.g. `HH:mm:ss`) is of limited
|
|
||||||
use to the average user. An alternative is to show the current date
|
|
||||||
in the desired format alongside the field description as an example.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$dateField = DateField::create('MyDate');
|
|
||||||
// Show long format as text below the field
|
|
||||||
$dateField->setDescription(sprintf(
|
|
||||||
_t('FormField.Example', 'e.g. %s', 'Example format'),
|
|
||||||
Convert::raw2xml(Zend_Date::now()->toString($dateField->getConfig('dateformat')))
|
|
||||||
));
|
|
||||||
// Alternatively, set short format as a placeholder in the field
|
|
||||||
$dateField->setAttribute('placeholder', $dateField->getConfig('dateformat'));
|
|
||||||
|
|
||||||
Note: Fields scaffolded through `[api:DataObject::scaffoldCMSFields()]` automatically
|
|
||||||
have a description attached to them.
|
|
||||||
|
|
||||||
## Calendar Field
|
|
||||||
|
|
||||||
The following setting will add a Calendar to a single DateField, using the
|
|
||||||
`jQuery UI DatePicker widget`
|
|
||||||
|
|
||||||
:::php
|
|
||||||
DateField::create('MyDate')->setConfig('showcalendar', true);
|
|
||||||
|
|
||||||
|
|
||||||
### 'Safe' Dateformats to Use with the Calendar
|
|
||||||
|
|
||||||
The jQuery DatePicker doesn't support every constant available for Zend_Date.
|
|
||||||
If you choose to use the calendar, the following constants should at least be safe:
|
|
||||||
|
|
||||||
Constant | xxxxx
|
|
||||||
-------- | -----
|
|
||||||
d | numeric day of the month (without leading zero)
|
|
||||||
dd | numeric day of the month (with leading zero)
|
|
||||||
EEE | dayname, abbreviated
|
|
||||||
EEEE | dayname
|
|
||||||
M | numeric month of the year (without leading zero)
|
|
||||||
MM | numeric month of the year (with leading zero)
|
|
||||||
MMM | monthname, abbreviated
|
|
||||||
MMMM | monthname
|
|
||||||
y | year (4 digits)
|
|
||||||
yy | year (2 digits)
|
|
||||||
yyyy | year (4 digits)
|
|
||||||
|
|
||||||
### Calendar localization issues
|
|
||||||
|
|
||||||
Unfortunately the day- and monthname values in Zend Date do not always match
|
|
||||||
those in the existing jQuery UI locale files, so constants like `EEE` or `MMM`,
|
|
||||||
for day and monthnames could break validation. To fix this we had to slightly
|
|
||||||
alter the jQuery locale files, situated in
|
|
||||||
*/framework/thirdparty/jquery-ui/datepicker/i18n/*, to match Zend_Date.
|
|
||||||
|
|
||||||
At this moment not all locale files may be present. If a locale file is
|
|
||||||
missing, the DatePicker calendar will fallback to 'yyyy-MM-dd' whenever day-
|
|
||||||
and/or monthnames are used. After saving, the correct format will be displayed.
|
|
||||||
|
|
||||||
## Contributing jQuery Locale Files
|
|
||||||
|
|
||||||
If you find the jQuery locale file for your chosen locale is missing, the
|
|
||||||
following section will explain how to create one. If you wish to contribute
|
|
||||||
your file to the SilverStripe core, please check out the guide on
|
|
||||||
['contributing code'](http://doc.silverstripe.org/framework/en/trunk/misc/contributing/code).
|
|
||||||
|
|
||||||
### 1. Get the Sourcefile
|
|
||||||
|
|
||||||
You can find a list of locale files for the jQuery UI DatePicker
|
|
||||||
[in the jQuery source code](https://github.com/jquery/jquery-ui/tree/master/ui/i18n).
|
|
||||||
|
|
||||||
### 2. Find your Zend Locale File
|
|
||||||
|
|
||||||
The Zend locale files are located in */framework/thirdparty/Zend/Locale/Data/*.
|
|
||||||
Find the one that has the information for your locale.
|
|
||||||
|
|
||||||
### 3. Find the Date Values
|
|
||||||
|
|
||||||
You're looking for the `Gregorian` date values for monthnames and daynames in
|
|
||||||
the Zend locale file. Edit the DatePicker locale File so your *full day- and
|
|
||||||
monthnames* and *short monthnames* match. For your *short daynames*, use the
|
|
||||||
first three characters of the full name. Note that Zend dates are `case
|
|
||||||
sensitive`!
|
|
||||||
|
|
||||||
### 4. Filename
|
|
||||||
|
|
||||||
Use the original jQuery UI filename 'jquery.ui.datepicker-xx.js', where xx
|
|
||||||
stands for the locale.
|
|
69
docs/en/02_Developer_Guides/03_Forms/03_Form_Templates.md
Normal file
69
docs/en/02_Developer_Guides/03_Forms/03_Form_Templates.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
title: Form Templates
|
||||||
|
summary: Customize the generated HTML for a FormField or an entire Form.
|
||||||
|
|
||||||
|
# Form Templates
|
||||||
|
|
||||||
|
Most markup generated in SilverStripe can be replaced by custom templates. Both [api:Form] and [api:FormField] instances
|
||||||
|
can be rendered out using custom templates using `setTemplate`.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$form = new Form(..);
|
||||||
|
$form->setTemplate('MyCustomFormTemplate');
|
||||||
|
|
||||||
|
// or, just a field
|
||||||
|
$field = new TextField(..);
|
||||||
|
$field->setTemplate('MyCustomTextField');
|
||||||
|
|
||||||
|
Both `MyCustomTemplate.ss` and `MyCustomTextField.ss` should be located in **mysite/templates/Includes/**
|
||||||
|
|
||||||
|
<div class="notice" markdown="1">
|
||||||
|
It's recommended to copy the contents of the template you're going to replace and use that as a start. For instance, if
|
||||||
|
you want to create a `MyCustomFormTemplate` copy the contents of `Form.ss` to a `MyCustomFormTemplate.ss` file and
|
||||||
|
modify as you need.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
By default, Form and Fields follow the SilverStripe Template convention and are rendered into templates of the same
|
||||||
|
class name (i.e EmailField will attempt to render into `EmailField.ss` and if that isn't found, `TextField.ss` or
|
||||||
|
finally `FormField.ss`).
|
||||||
|
|
||||||
|
<div class="alert" markdown="1">
|
||||||
|
While you can override all templates using normal view inheritance (i.e defining a `Form.ss`) other modules may rely on
|
||||||
|
the core template structure. It is recommended to use `setTemplate` and unique templates for specific forms.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
For [api:FormField] instances, there are several other templates that are used on top of the main `setTemplate`.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$field = new TextField();
|
||||||
|
|
||||||
|
$field->setTemplate('CustomTextField');
|
||||||
|
// Sets the template for the <input> tag. i.e '<input $AttributesHTML />'
|
||||||
|
|
||||||
|
$field->setFieldHolderTemplate('CustomTextField_Holder');
|
||||||
|
// Sets the template for the wrapper around the text field. i.e
|
||||||
|
// '<div class="text">'
|
||||||
|
//
|
||||||
|
// The actual FormField is rendered into the holder via the `$Field`
|
||||||
|
// variable.
|
||||||
|
//
|
||||||
|
// setFieldHolder() is used in most `Form` instances and needs to output
|
||||||
|
// labels, error messages and the like.
|
||||||
|
|
||||||
|
$field->setSmallFieldHolderTemplate('CustomTextField_Holder_Small');
|
||||||
|
// Sets the template for the wrapper around the text field.
|
||||||
|
//
|
||||||
|
// The difference here is the small field holder template is used when the
|
||||||
|
// field is embedded within another field. For example, if the field is
|
||||||
|
// part of a `FieldGroup` or `CompositeField` alongside other fields.
|
||||||
|
|
||||||
|
All templates are rendered within the scope of the [api:FormField]. To understand more about Scope within Templates as
|
||||||
|
well as the available syntax, see the [Templates](../templates) documentation.
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
* [How to: Create a lightweight Form](how_tos/lightweight_form)
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
* [api:Form]
|
||||||
|
* [api:FormField]
|
76
docs/en/02_Developer_Guides/03_Forms/04_Form_Security.md
Normal file
76
docs/en/02_Developer_Guides/03_Forms/04_Form_Security.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
title: Form Security
|
||||||
|
summary: Ensure Forms are secure against Cross-Site Request Forgery attacks, bots and other malicious intent.
|
||||||
|
|
||||||
|
# Form Security
|
||||||
|
|
||||||
|
Whenever you are accepting or asking users to input data to your application there comes an added responsibility that it
|
||||||
|
should be done as safely as possible. Below outlines the things to consider when building your forms.
|
||||||
|
|
||||||
|
## Cross-Site Request Forgery (CSRF)
|
||||||
|
|
||||||
|
SilverStripe protect users against [Cross-Site Request Forgery](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
|
||||||
|
(known as `CSRF`) by adding a SecurityID [api:HiddenField] to each [api:Form] instance. The `SecurityID` contains a
|
||||||
|
random string generated by [api:SecurityToken] to identify the particular user request vs a third-party forging fake
|
||||||
|
requests.
|
||||||
|
|
||||||
|
<div class="info" markdown="1">
|
||||||
|
For more information on Cross-Site Request Forgery, consult the [OWASP](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
|
||||||
|
website.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
The `SecurityToken` automatically added looks something like:
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$form = new Form(..);
|
||||||
|
echo $form->getSecurityToken()->getValue();
|
||||||
|
|
||||||
|
// 'c443076989a7f24cf6b35fe1360be8683a753e2c'
|
||||||
|
|
||||||
|
This token value is passed through the rendered Form HTML as a [api:HiddenField].
|
||||||
|
|
||||||
|
:::html
|
||||||
|
<input type="hidden" name="SecurityID" value="c443076989a7f24cf6b35fe1360be8683a753e2c" class="hidden" />
|
||||||
|
|
||||||
|
The token should be present whenever a operation has a side effect such as a `POST` operation.
|
||||||
|
|
||||||
|
It can be safely disabled for `GET` requests as long as it does not modify the database (i.e a search form does not
|
||||||
|
normally require a security token).
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$form = new Form(..);
|
||||||
|
$form->disableSecurityToken();
|
||||||
|
|
||||||
|
<div class="alert" markdown="1">
|
||||||
|
Do not disable the SecurityID for forms that perform some modification to the users session. This will open your
|
||||||
|
application up to `CSRF` security holes.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Strict Form Submission
|
||||||
|
|
||||||
|
Forms should be limited to the intended HTTP verb (mostly `GET` or `POST`) to further reduce attack exposure. Without
|
||||||
|
this check, forms that rely on `GET` can be submitted via `POST` or `PUT` or vice-versa potentially leading to
|
||||||
|
application errors or edge cases.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$form = new Form(..);
|
||||||
|
|
||||||
|
$form->setFormMethod('POST');
|
||||||
|
$form->setStrictFormMethodCheck(true);
|
||||||
|
|
||||||
|
// or alternative short notation..
|
||||||
|
$form->setFormMethod('POST', true);
|
||||||
|
|
||||||
|
## Spam and Bot Attacks
|
||||||
|
|
||||||
|
SilverStripe has no built-in protection for detailing with bots, captcha or other spam protection methods. This
|
||||||
|
functionality is available as an additional [Spam Protection](https://github.com/silverstripe/silverstripe-spamprotection)
|
||||||
|
module if required. The module provides an consistent API for allowing third-party spam protection handlers such as
|
||||||
|
[Recaptcha](http://www.google.com/recaptcha/intro/) and [Mollom](https://mollom.com/) to work within the `Form` API.
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
* [Security](../security)
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
* [api:SecurityToken]
|
@ -1,435 +0,0 @@
|
|||||||
# GridField
|
|
||||||
|
|
||||||
Gridfield is SilverStripe's implementation of data grids. Its main purpose is to display tabular data
|
|
||||||
in a format that is easy to view and modify. It can be thought of as a HTML table with some tricks.
|
|
||||||
|
|
||||||
It's built in a way that provides developers with an extensible way to display tabular data in a
|
|
||||||
table and minimise the amount of code that needs to be written.
|
|
||||||
|
|
||||||
In order to quickly get data-focused UIs up and running,
|
|
||||||
you might also be interested in the [/reference/modeladmin](ModelAdmin) class
|
|
||||||
which is driven largely by the `GridField` class explained here.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The `GridField` is a flexible form field for creating tables of data. It was introduced in
|
|
||||||
SilverStripe 3.0 and replaced the `ComplexTableField`, `TableListField`, and `TableField` from
|
|
||||||
previous versions of SilverStripe.
|
|
||||||
|
|
||||||
Each GridField is built from a number of components. Without any components, a GridField has almost no
|
|
||||||
functionality. The components are responsible for formatting data to be readable and also modifying it.
|
|
||||||
|
|
||||||
A gridfield with only the `GridFieldDataColumn` component will display a set of read-only columns
|
|
||||||
taken from your list, without any headers or pagination. Large datasets don't fit to one
|
|
||||||
page, so you could add a `GridFieldPaginator` to paginatate the data. Sorting is supported by adding
|
|
||||||
a `GridFieldSortableHeader` that enables sorting on fields that can be sorted.
|
|
||||||
|
|
||||||
This document aims to explain the usage of GridFields with code examples.
|
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
|
||||||
GridField can only be used with datasets that are of the type `SS_List` such as `DataList`
|
|
||||||
or `ArrayList`
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Creating a base GridField
|
|
||||||
|
|
||||||
A gridfield is often setup from a `Controller` that will output a form to the user. Even if there
|
|
||||||
are no other HTML input fields for gathering data from users, the gridfield itself must have a
|
|
||||||
`Form` to support interactions with it.
|
|
||||||
|
|
||||||
Here is an example where we display a basic gridfield with the default settings:
|
|
||||||
|
|
||||||
:::php
|
|
||||||
class GridController extends Page_Controller {
|
|
||||||
|
|
||||||
private static $allowed_actions = array('index', 'AllPages');
|
|
||||||
|
|
||||||
public function index(SS_HTTPRequest $request) {
|
|
||||||
$this->Content = $this->AllPages();
|
|
||||||
return $this->render();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function AllPages() {
|
|
||||||
$gridField = new GridField('pages', 'All pages', SiteTree::get());
|
|
||||||
return new Form($this, "AllPages", new FieldList($gridField), new FieldList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
__Note:__ This is example code and the gridfield might not be styled nicely depending on the rest of
|
|
||||||
the css included.
|
|
||||||
|
|
||||||
This gridfield will only contain a single column with the `Title` of each page. Gridfield by default
|
|
||||||
uses the `DataObject::$display_fields` for guessing what fields to display.
|
|
||||||
|
|
||||||
Instead of modifying a core `DataObject` we can tell the gridfield which fields to display by
|
|
||||||
setting the display fields on the `GridFieldDataColumns` component.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
public function AllPages() {
|
|
||||||
$gridField = new GridField('pages', 'All pages', SiteTree::get());
|
|
||||||
$dataColumns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
|
|
||||||
$dataColumns->setDisplayFields(array(
|
|
||||||
'Title' => 'Title',
|
|
||||||
'URLSegment'=> 'URL',
|
|
||||||
'LastEdited' => 'Changed'
|
|
||||||
));
|
|
||||||
return new Form($this, "AllPages", new FieldList($gridField), new FieldList());
|
|
||||||
}
|
|
||||||
|
|
||||||
We will now move onto what the `GridFieldConfig`s are and how to use them.
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
A gridfields's behaviour and look all depends on what config we're giving it. In the above example
|
|
||||||
we did not specify one, so it picked a default config called `GridFieldConfig_Base`.
|
|
||||||
|
|
||||||
A config object is a container for `GridFieldComponents` which contain the actual functionality and
|
|
||||||
view for the gridfield.
|
|
||||||
|
|
||||||
A config object can be either injected as the fourth argument of the GridField constructor,
|
|
||||||
`$config` or set at a later stage by using a setter:
|
|
||||||
|
|
||||||
:::php
|
|
||||||
// On initialisation:
|
|
||||||
$gridField = new GridField('pages', 'All pages', SiteTree::get(), GridFieldConfig_Base::create());
|
|
||||||
// By a setter after initialisation:
|
|
||||||
$gridField = new GridField('pages', 'All pages', SiteTree::get());
|
|
||||||
$gridField->setConfig(GridFieldConfig_Base::create());
|
|
||||||
|
|
||||||
By default the `[api:GridFieldConfig_Base]` constructor takes a single parameter to specify the number
|
|
||||||
of items displayed on each page.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
// I have lots of items, so increase the page size
|
|
||||||
$myConfig = GridFieldConfig_Base::create(40);
|
|
||||||
|
|
||||||
The default page size can also be tweaked via the config. (put in your mysite/_config/config.yml)
|
|
||||||
|
|
||||||
:::yaml
|
|
||||||
// For updating all gridfield defaults system wide
|
|
||||||
GridFieldPaginator:
|
|
||||||
default_items_per_page: 40
|
|
||||||
|
|
||||||
Note that for [/reference/modeladmin](ModelAdmin) sections the default 30 number of pages can be
|
|
||||||
controlled either by setting the base `ModelAdmin.page_length` config to the desired number, or
|
|
||||||
by overriding this value in a custom subclass.
|
|
||||||
|
|
||||||
The framework comes shipped with some base GridFieldConfigs:
|
|
||||||
|
|
||||||
### Table listing with GridFieldConfig_Base
|
|
||||||
|
|
||||||
A simple read-only and paginated view of records with sortable and searchable headers.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$gridField = new GridField('pages', 'All pages', SiteTree::get(), GridFieldConfig_Base::create());
|
|
||||||
|
|
||||||
The fields displayed are from `DataObject::getSummaryFields()`
|
|
||||||
|
|
||||||
### Viewing records with GridFieldConfig_RecordViewer
|
|
||||||
|
|
||||||
Similar to `GridFieldConfig_Base` with the addition support of:
|
|
||||||
|
|
||||||
- View read-only details of individual records.
|
|
||||||
|
|
||||||
The fields displayed in the read-only view is from `DataObject::getCMSFields()`
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$gridField = new GridField('pages', 'All pages', SiteTree::get(), GridFieldConfig_RecordViewer::create());
|
|
||||||
|
|
||||||
### Editing records with GridFieldConfig_RecordEditor
|
|
||||||
|
|
||||||
Similar to `GridFieldConfig_RecordViewer` with the addition support of:
|
|
||||||
|
|
||||||
- Viewing and changing an individual records data.
|
|
||||||
- Deleting a record
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$gridField = new GridField('pages', 'All pages', SiteTree::get(), GridFieldConfig_RecordEditor::create());
|
|
||||||
|
|
||||||
The fields displayed in the edit form are from `DataObject::getCMSFields()`
|
|
||||||
|
|
||||||
### Editing relations with GridFieldConfig_RelationEditor
|
|
||||||
|
|
||||||
Similar to `GridFieldConfig_RecordEditor`, but adds features to work on a record's has-many or
|
|
||||||
many-many relationships. As such, it expects the list used with the `GridField` to be a
|
|
||||||
`RelationList`. That is, the list returned by a has-many or many-many getter.
|
|
||||||
|
|
||||||
The relations can be:
|
|
||||||
|
|
||||||
- Searched for existing records and add a relationship
|
|
||||||
- Detach records from the relationship (rather than removing them from the database)
|
|
||||||
- Create new related records and automatically add them to the relationship.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$gridField = new GridField('images', 'Linked images', $this->Images(), GridFieldConfig_RelationEditor::create());
|
|
||||||
|
|
||||||
The fields displayed in the edit form are from `DataObject::getCMSFields()`
|
|
||||||
|
|
||||||
## Customizing Detail Forms
|
|
||||||
|
|
||||||
The `GridFieldDetailForm` component drives the record editing form which is usually configured
|
|
||||||
through the configs `GridFieldConfig_RecordEditor` and `GridFieldConfig_RelationEditor`
|
|
||||||
described above. It takes its fields from `DataObject->getCMSFields()`,
|
|
||||||
but can be customized to accept different fields via its `[api:GridFieldDetailForm->setFields()]` method.
|
|
||||||
|
|
||||||
The component also has the ability to load and save data stored on join tables
|
|
||||||
when two records are related via a "many_many" relationship, as defined through
|
|
||||||
`[api:DataObject::$many_many_extraFields]`. While loading and saving works transparently,
|
|
||||||
you need to add the necessary fields manually, they're not included in the `getCMSFields()` scaffolding.
|
|
||||||
|
|
||||||
These extra fields act like usual form fields, but need to be "namespaced"
|
|
||||||
in order for the gridfield logic to detect them as fields for relation extradata,
|
|
||||||
and to avoid clashes with the other form fields.
|
|
||||||
The namespace notation is `ManyMany[<extradata-field-name>]`, so for example
|
|
||||||
`ManyMany[MyExtraField]`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
:::php
|
|
||||||
|
|
||||||
class Player extends DataObject {
|
|
||||||
private static $db = array('Name' => 'Text');
|
|
||||||
public static $many_many = array('Teams' => 'Team');
|
|
||||||
public static $many_many_extraFields = array(
|
|
||||||
'Teams' => array('Position' => 'Text')
|
|
||||||
);
|
|
||||||
public function getCMSFields() {
|
|
||||||
$fields = parent::getCMSFields();
|
|
||||||
|
|
||||||
if($this->ID) {
|
|
||||||
$teamFields = singleton('Team')->getCMSFields();
|
|
||||||
$teamFields->addFieldToTab(
|
|
||||||
'Root.Main',
|
|
||||||
// Please follow the "ManyMany[<extradata-name>]" convention
|
|
||||||
new TextField('ManyMany[Position]', 'Current Position')
|
|
||||||
);
|
|
||||||
$config = GridFieldConfig_RelationEditor::create();
|
|
||||||
$config->getComponentByType('GridFieldDetailForm')->setFields($teamFields);
|
|
||||||
$gridField = new GridField('Teams', 'Teams', $this->Teams(), $config);
|
|
||||||
$fields->findOrMakeTab('Root.Teams')->replaceField('Teams', $gridField);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $fields;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Team extends DataObject {
|
|
||||||
private static $db = array('Name' => 'Text');
|
|
||||||
public static $many_many = array('Players' => 'Player');
|
|
||||||
}
|
|
||||||
|
|
||||||
## GridFieldComponents
|
|
||||||
|
|
||||||
The `GridFieldComponent` classes are the actual workers in a gridfield. They can be responsible for:
|
|
||||||
|
|
||||||
- Output some HTML to be rendered
|
|
||||||
- Manipulate data
|
|
||||||
- Recieve actions
|
|
||||||
- Display links
|
|
||||||
|
|
||||||
Components are added and removed from a config by setters and getters.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$config = GridFieldConfig::create();
|
|
||||||
|
|
||||||
// Add the base data columns to the gridfield
|
|
||||||
$config->addComponent(new GridFieldDataColumns());
|
|
||||||
$gridField = new GridField('pages', 'All pages', SiteTree::get(), $config);
|
|
||||||
|
|
||||||
It's also possible to insert a component before another component.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$config->addComponent(new GridFieldFilterHeader(), 'GridFieldDataColumns');
|
|
||||||
|
|
||||||
Adding multiple components in one call:
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$config->addComponents(new GridFieldDataColumns(), new GridFieldToolbarHeader());
|
|
||||||
|
|
||||||
Removing a component:
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$config->removeComponentsByType('GridFieldToolbarHeader');
|
|
||||||
|
|
||||||
For more information, see the [API for GridFieldConfig](http://api.silverstripe.org/3.0/framework/GridFieldConfig.html).
|
|
||||||
|
|
||||||
Here is a list of components for generic use:
|
|
||||||
|
|
||||||
- `[api:GridFieldToolbarHeader]`
|
|
||||||
- `[api:GridFieldSortableHeader]`
|
|
||||||
- `[api:GridFieldFilterHeader]`
|
|
||||||
- `[api:GridFieldDataColumns]`
|
|
||||||
- `[api:GridFieldDeleteAction]`
|
|
||||||
- `[api:GridFieldViewButton]`
|
|
||||||
- `[api:GridFieldEditButton]`
|
|
||||||
- `[api:GridFieldExportButton]`
|
|
||||||
- `[api:GridFieldPrintButton]`
|
|
||||||
- `[api:GridFieldPaginator]`
|
|
||||||
- `[api:GridFieldDetailForm]`
|
|
||||||
|
|
||||||
## Flexible Area Assignment through Fragments
|
|
||||||
|
|
||||||
GridField layouts can contain many components other than the table itself,
|
|
||||||
for example a search bar to find existing relations, a button to add those,
|
|
||||||
and buttons to export and print the current data. The GridField has certain
|
|
||||||
defined areas called "fragments" where these components can be placed.
|
|
||||||
The goal is for multiple components to share the same space, for example a header row.
|
|
||||||
|
|
||||||
Built-in components:
|
|
||||||
|
|
||||||
- `header`/`footer`: Renders in a `<thead>`/`<tfoot>`, should contain table markup
|
|
||||||
- `before`/`after`: Renders before/after the actual `<table>`
|
|
||||||
- `buttons-before-left`/`buttons-before-right`/`buttons-after-left`/`buttons-after-right`:
|
|
||||||
Renders in a shared row before the table. Requires [api:GridFieldButtonRow].
|
|
||||||
|
|
||||||
These built-ins can be used by passing the fragment names into the constructor
|
|
||||||
of various components. Note that some [api:GridFieldConfig] classes
|
|
||||||
will already have rows added to them. The following example will add a print button
|
|
||||||
at the bottom right of the table.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
$config->addComponent(new GridFieldButtonRow('after'));
|
|
||||||
$config->addComponent(new GridFieldPrintButton('buttons-after-right'));
|
|
||||||
|
|
||||||
Further down we'll explain how to write your own components using fragments.
|
|
||||||
|
|
||||||
## Creating a custom GridFieldComponent
|
|
||||||
|
|
||||||
A single component often uses a number of interfaces.
|
|
||||||
|
|
||||||
### GridField_HTMLProvider
|
|
||||||
|
|
||||||
Provides HTML for the header/footer rows in the table or before/after the template.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- A header html provider displays a header before the table
|
|
||||||
- A pagination html provider displays pagination controls under the table
|
|
||||||
- A filter html fields displays filter fields on top of the table
|
|
||||||
- A summary html field displays sums of a field at the bottom of the table
|
|
||||||
|
|
||||||
### GridField_ColumnProvider
|
|
||||||
|
|
||||||
Add a new column to the table display body, or modify existing columns. Used once per record/row.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- A data columns provider that displays data from the list in rows and columns.
|
|
||||||
- A delete button column provider that adds a delete button at the end of the row
|
|
||||||
|
|
||||||
### GridField_ActionProvider
|
|
||||||
|
|
||||||
Action providers runs actions, some examples are:
|
|
||||||
|
|
||||||
- A delete action provider that deletes a DataObject.
|
|
||||||
- An export action provider that will export the current list to a CSV file.
|
|
||||||
|
|
||||||
### GridField_DataManipulator
|
|
||||||
|
|
||||||
Modifies the data list. In general, the data manipulator will make use of `GridState` variables
|
|
||||||
to decide how to modify the data list.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- A paginating data manipulator can apply a limit to a list (show only 20 records)
|
|
||||||
- A sorting data manipulator can sort the Title in a descending order.
|
|
||||||
|
|
||||||
### GridField_URLHandler
|
|
||||||
|
|
||||||
Sometimes an action isn't enough, we need to provide additional support URLs for the grid. It
|
|
||||||
has a list of URL's that it can handle and the GridField passes request on to URLHandlers on matches.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- A pop-up form for editing a record's details.
|
|
||||||
- JSON formatted data used for javascript control of the gridfield.
|
|
||||||
|
|
||||||
## GridField_FormAction
|
|
||||||
|
|
||||||
This object is used for creating actions buttons, for example a delete button. When a user clicks on
|
|
||||||
a FormAction, the gridfield finds a `GridField_ActionProvider` that listens on that action.
|
|
||||||
`GridFieldDeleteAction` have a pretty basic implementation of how to use a Form action.
|
|
||||||
|
|
||||||
## GridField_SaveHandler
|
|
||||||
|
|
||||||
This is used to create a handler that is called when a form containing the grid
|
|
||||||
field is saved into a record. This is useful for performing actions when saving
|
|
||||||
the record.
|
|
||||||
|
|
||||||
### GridState
|
|
||||||
|
|
||||||
Gridstate is a class that is used to contain the current state and actions on the gridfield. It's
|
|
||||||
transfered between page requests by being inserted as a hidden field in the form.
|
|
||||||
|
|
||||||
A GridFieldComponent sets and gets data from the GridState.
|
|
||||||
|
|
||||||
Data within this object can be nested, allowing for organisation of information in a logical fashion. Additionally,
|
|
||||||
default values can be specified for any data field by invoking that field as a method, passing the default value
|
|
||||||
as the first parameter. If no default is specified then a nested `GridState_Data` is returned, which may be chained
|
|
||||||
to subsequently nest data values.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
:::php
|
|
||||||
|
|
||||||
public function getManipulatedData(GridField $gridField, SS_List $dataList) {
|
|
||||||
// Accesses the GridState, returns a nested GridState_Data, and retrieve a single field with a default of null
|
|
||||||
$objectID = $gridField->State->MyComponent->RecordID(null);
|
|
||||||
if($objectID) $dataList = $dataList->filter('ParentID', $objectID);
|
|
||||||
return $dataList;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
## Permissions
|
|
||||||
|
|
||||||
Since GridField is mostly used in the CMS, the controller managing a GridField instance
|
|
||||||
will already do some permission checks for you, and can decline display or executing
|
|
||||||
any logic on your field.
|
|
||||||
|
|
||||||
If you need more granular control, e.g. to consistently deny non-admins from deleting
|
|
||||||
records, use the `DataObject->can...()` methods
|
|
||||||
(see [DataObject permissions](/reference/dataobject#permissions)).
|
|
||||||
|
|
||||||
## Creating your own Fragments
|
|
||||||
|
|
||||||
Fragments are designated areas within a GridField which can be shared between component templates.
|
|
||||||
You can define your own fragments by using a `\$DefineFragment' placeholder in your components' template.
|
|
||||||
This example will simply create an area rendered before the table wrapped in a simple `<div>`.
|
|
||||||
|
|
||||||
:::php
|
|
||||||
class MyAreaComponent implements GridField_HTMLProvider {
|
|
||||||
public function getHTMLFragments( $gridField) {
|
|
||||||
return array(
|
|
||||||
'before' => '<div class="my-area">$DefineFragment(my-area)</div>'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
We're returning raw HTML from the component, usually this would be handled by a SilverStripe template.
|
|
||||||
Please note that in templates, you'll need to escape the dollar sign on `$DefineFragment`:
|
|
||||||
These are specially processed placeholders as opposed to native template syntax.
|
|
||||||
|
|
||||||
Now you can add other components into this area by returning them as an array from
|
|
||||||
your [api:GridFieldComponent->getHTMLFragments()] implementation:
|
|
||||||
|
|
||||||
:::php
|
|
||||||
class MyShareLinkComponent implements GridField_HTMLProvider {
|
|
||||||
public function getHTMLFragments( $gridField) {
|
|
||||||
return array(
|
|
||||||
'my-area' => '<a href>...</a>'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Your new area can also be used by existing components, e.g. the [api:GridFieldPrintButton]
|
|
||||||
|
|
||||||
:::php
|
|
||||||
new GridFieldPrintButton('my-component-area')
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
* [ModelAdmin: A UI driven by GridField](/reference/modeladmin)
|
|
||||||
* [Tutorial 5: Dataobject Relationship Management](/tutorials/5-dataobject-relationship-management)
|
|
||||||
* [How to add a custom action to a GridField row](/howto/gridfield-rowaction)
|
|
@ -0,0 +1,53 @@
|
|||||||
|
title: Form Transformations
|
||||||
|
summary: Provide read-only and disabled views of your Form data.
|
||||||
|
|
||||||
|
# Read-only and Disabled Forms
|
||||||
|
|
||||||
|
[api:Form] and [api:FormField] instances can be turned into a read-only version for things like confirmation pages or
|
||||||
|
when certain fields cannot be edited due to permissions. Creating the form is done the same way and markup is similar,
|
||||||
|
`readonly` mode converts the `input`, `select` and `textarea` tags to static HTML elements like `span`.
|
||||||
|
|
||||||
|
To make an entire [api:Form] read-only.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$form = new Form(..);
|
||||||
|
$form->makeReadonly();
|
||||||
|
|
||||||
|
|
||||||
|
To make all the fields within a [api:FieldList] read-only (i.e to make fields read-only but not buttons).
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$fields = new FieldList(..);
|
||||||
|
$fields = $fields->makeReadonly();
|
||||||
|
|
||||||
|
|
||||||
|
To make a [api:FormField] read-only you need to know the name of the form field or call it direct on the object
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$field = new TextField(..);
|
||||||
|
$field = $field->performReadonlyTransformation();
|
||||||
|
|
||||||
|
$fields = new FieldList(
|
||||||
|
$field
|
||||||
|
);
|
||||||
|
|
||||||
|
// Or,
|
||||||
|
$field = new TextField(..);
|
||||||
|
$field->setReadonly(true);
|
||||||
|
|
||||||
|
$fields = new FieldList(
|
||||||
|
$field
|
||||||
|
);
|
||||||
|
|
||||||
|
## Disabled FormFields
|
||||||
|
|
||||||
|
Disabling [api:FormField] instances, sets the `disabled` property on the class. This will use the same HTML markup as
|
||||||
|
a normal form, but set the `disabled` attribute on the `input` tag.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$field = new TextField(..);
|
||||||
|
$field->setDisabled(true);
|
||||||
|
|
||||||
|
echo $field->forTemplate();
|
||||||
|
|
||||||
|
// returns '<input type="text" class="text" .. disabled="disabled" />'
|
55
docs/en/02_Developer_Guides/03_Forms/06_Tabbed_Forms.md
Normal file
55
docs/en/02_Developer_Guides/03_Forms/06_Tabbed_Forms.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
title: Tabbed Forms
|
||||||
|
summary: Find out how CMS interfaces use jQuery UI tabs to provide nested FormFields.
|
||||||
|
|
||||||
|
# Tabbed Forms
|
||||||
|
|
||||||
|
SilverStripe's [api:FormScaffolder] can automatically generate [api:Form] instances for certain database models. In the
|
||||||
|
CMS and other scaffolded interfaces, it will output [api:TabSet] and [api:Tab] objects and use jQuery Tabs to split
|
||||||
|
parts of the data model.
|
||||||
|
|
||||||
|
<div class="notice" markdown="1">
|
||||||
|
All interfaces within the CMS such as [api:ModelAdmin] and [api:LeftAndMain] use tabbed interfaces by default.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
When dealing with tabbed forms, modifying the fields in the form has a few differences. Each [api:Tab] will be given a
|
||||||
|
name, and normally they all exist under the `Root` [api:TabSet].
|
||||||
|
|
||||||
|
<div class="notice" markdown="1">
|
||||||
|
[api:TabSet] instances can contain child [api:Tab] and further [api:TabSet] instances, however the CMS UI will only
|
||||||
|
display up to two levels of tabs in the interface. If you want to group data further than that, try [api:ToggleField].
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Adding a field from a tab.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$fields->addFieldToTab('Root.Main', new TextField(..));
|
||||||
|
|
||||||
|
## Removing a field from a tab
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$fields->removeFieldFromTab('Root.Main', 'Content');
|
||||||
|
|
||||||
|
## Creating a new tab
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$fields->addFieldToTab('Root.MyNewTab', new TextField(..));
|
||||||
|
|
||||||
|
## Moving a field between tabs
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$field = $fields->dataFieldByName('Content');
|
||||||
|
|
||||||
|
$fields->removeFieldFromTab('Root.Main', 'Content');
|
||||||
|
$fields->addFieldToTab('Root.MyContent', $content);
|
||||||
|
|
||||||
|
## Add multiple fields at once
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$fields->addFieldsToTab('Root.Content', array(
|
||||||
|
TextField::create('Name'),
|
||||||
|
TextField::create('Email')
|
||||||
|
));
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
* [api:FormScaffolder]
|
@ -1,6 +1,10 @@
|
|||||||
|
title: Common FormField Types
|
||||||
|
summary: A table containing a list of the common FormField subclasses.
|
||||||
|
|
||||||
# Form Field Types
|
# Form Field Types
|
||||||
|
|
||||||
This is a highlevel overview of available `[api:FormField]` subclasses. An automatically generated list is available through our [API]
|
This is a highlevel overview of available `[api:FormField]` subclasses. An automatically generated list is available
|
||||||
|
on the SilverStripe API documentation.
|
||||||
|
|
||||||
## Basic
|
## Basic
|
||||||
|
|
||||||
@ -28,7 +32,7 @@ This is a highlevel overview of available `[api:FormField]` subclasses. An autom
|
|||||||
* `[api:DatetimeField]`: Combined date- and time field.
|
* `[api:DatetimeField]`: Combined date- and time field.
|
||||||
* `[api:EmailField]`: Text input field with validation for correct email format according to RFC 2822.
|
* `[api:EmailField]`: Text input field with validation for correct email format according to RFC 2822.
|
||||||
* `[api:GroupedDropdownField]`: Grouped dropdown, using <optgroup> tags.
|
* `[api:GroupedDropdownField]`: Grouped dropdown, using <optgroup> tags.
|
||||||
* `[api:HtmlEditorField]`.
|
* `[api:HtmlEditorField]`: A WYSIWYG editor interface.
|
||||||
* `[api:MoneyField]`: A form field that can save into a `[api:Money]` database field.
|
* `[api:MoneyField]`: A form field that can save into a `[api:Money]` database field.
|
||||||
* `[api:NumericField]`: Text input field with validation for numeric values.
|
* `[api:NumericField]`: Text input field with validation for numeric values.
|
||||||
* `[api:OptionsetField]`: Set of radio buttons designed to emulate a dropdown.
|
* `[api:OptionsetField]`: Set of radio buttons designed to emulate a dropdown.
|
||||||
@ -66,6 +70,6 @@ doesn't necessarily have any visible styling.
|
|||||||
* `[api:DatalessField]` - Base class for fields which add some HTML to the form but don't submit any data or
|
* `[api:DatalessField]` - Base class for fields which add some HTML to the form but don't submit any data or
|
||||||
save it to the database
|
save it to the database
|
||||||
* `[api:HeaderField]`: Renders a simple HTML header element.
|
* `[api:HeaderField]`: Renders a simple HTML header element.
|
||||||
* `[api:HiddenField]`.
|
* `[api:HiddenField]` - Renders a hidden input field.
|
||||||
* `[api:LabelField]`: Simple label tag. This can be used to add extra text in your forms.
|
* `[api:LabelField]`: Simple label tag. This can be used to add extra text in your forms.
|
||||||
* `[api:LiteralField]`: Renders arbitrary HTML into a form.
|
* `[api:LiteralField]`: Renders arbitrary HTML into a form.
|
130
docs/en/02_Developer_Guides/03_Forms/Fields/02_DateField.md
Normal file
130
docs/en/02_Developer_Guides/03_Forms/Fields/02_DateField.md
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
title: DateField
|
||||||
|
summary: How to format and use the DateField class.
|
||||||
|
|
||||||
|
# DateField
|
||||||
|
|
||||||
|
This `FormField` subclass lets you display an editable date, either in a single text input field, or in three separate
|
||||||
|
fields for day, month and year. It also provides a calendar date picker.
|
||||||
|
|
||||||
|
The following example will add a simple DateField to your Page, allowing you to enter a date manually.
|
||||||
|
|
||||||
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Page extends SiteTree {
|
||||||
|
|
||||||
|
private static $db = array(
|
||||||
|
'MyDate' => 'Date',
|
||||||
|
);
|
||||||
|
|
||||||
|
public function getCMSFields() {
|
||||||
|
$fields = parent::getCMSFields();
|
||||||
|
|
||||||
|
$fields->addFieldToTab(
|
||||||
|
'Root.Main',
|
||||||
|
DateField::create('MyDate', 'Enter a date')
|
||||||
|
);
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## Custom Date Format
|
||||||
|
|
||||||
|
A custom date format for a [api:DateField] can be provided through `setConfig`.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
// will display a date in the following format: 31-06-2012
|
||||||
|
DateField::create('MyDate')->setConfig('dateformat', 'dd-MM-yyyy');
|
||||||
|
|
||||||
|
<div class="info" markdown="1">
|
||||||
|
The formats are based on [Zend_Date constants](http://framework.zend.com/manual/1.12/en/zend.date.constants.html).
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## Min and Max Dates
|
||||||
|
|
||||||
|
Sets the minimum and maximum allowed date values using the `min` and `max` configuration settings (in ISO format or
|
||||||
|
strtotime()).
|
||||||
|
|
||||||
|
:::php
|
||||||
|
DateField::create('MyDate')
|
||||||
|
->setConfig('min', '-7 days')
|
||||||
|
->setConfig('max', '2012-12-31')
|
||||||
|
|
||||||
|
## Separate Day / Month / Year Fields
|
||||||
|
|
||||||
|
The following setting will display your DateField as three input fields for day, month and year separately. HTML5
|
||||||
|
placeholders 'day', 'month' and 'year' are enabled by default.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
DateField::create('MyDate')
|
||||||
|
->setConfig('dmyfields', true)
|
||||||
|
->setConfig('dmyseparator', '/') // set the separator
|
||||||
|
->setConfig('dmyplaceholders', 'true'); // enable HTML 5 Placeholders
|
||||||
|
|
||||||
|
<div class="alert" markdown="1">
|
||||||
|
Any custom date format settings will be ignored.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Calendar Picker
|
||||||
|
|
||||||
|
The following setting will add a Calendar to a single DateField, using the jQuery UI DatePicker widget.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
DateField::create('MyDate')
|
||||||
|
->setConfig('showcalendar', true);
|
||||||
|
|
||||||
|
The jQuery DatePicker doesn't support every constant available for `Zend_Date`. If you choose to use the calendar, the
|
||||||
|
following constants should at least be safe:
|
||||||
|
|
||||||
|
Constant | xxxxx
|
||||||
|
-------- | -----
|
||||||
|
d | numeric day of the month (without leading zero)
|
||||||
|
dd | numeric day of the month (with leading zero)
|
||||||
|
EEE | dayname, abbreviated
|
||||||
|
EEEE | dayname
|
||||||
|
M | numeric month of the year (without leading zero)
|
||||||
|
MM | numeric month of the year (with leading zero)
|
||||||
|
MMM | monthname, abbreviated
|
||||||
|
MMMM | monthname
|
||||||
|
y | year (4 digits)
|
||||||
|
yy | year (2 digits)
|
||||||
|
yyyy | year (4 digits)
|
||||||
|
|
||||||
|
Unfortunately the day- and monthname values in Zend Date do not always match those in the existing jQuery UI locale
|
||||||
|
files, so constants like `EEE` or `MMM`, for day and month names could break validation. To fix this we had to slightly
|
||||||
|
alter the jQuery locale files, situated in */framework/thirdparty/jquery-ui/datepicker/i18n/*, to match Zend_Date.
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
At this moment not all locale files may be present. If a locale file is missing, the DatePicker calendar will fallback
|
||||||
|
to 'yyyy-MM-dd' whenever day - and/or monthnames are used. After saving, the correct format will be displayed.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Formatting Hints
|
||||||
|
|
||||||
|
It's often not immediate apparent which format a field accepts, and showing the technical format (e.g. `HH:mm:ss`) is
|
||||||
|
of limited use to the average user. An alternative is to show the current date in the desired format alongside the
|
||||||
|
field description as an example.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$dateField = DateField::create('MyDate');
|
||||||
|
|
||||||
|
// Show long format as text below the field
|
||||||
|
$dateField->setDescription(sprintf(
|
||||||
|
_t('FormField.Example', 'e.g. %s', 'Example format'),
|
||||||
|
Convert::raw2xml(Zend_Date::now()->toString($dateField->getConfig('dateformat')))
|
||||||
|
));
|
||||||
|
|
||||||
|
// Alternatively, set short format as a placeholder in the field
|
||||||
|
$dateField->setAttribute('placeholder', $dateField->getConfig('dateformat'));
|
||||||
|
|
||||||
|
<div class="notice" markdown="1">
|
||||||
|
Fields scaffolded through [api:DataObject::scaffoldCMSFields] automatically have a description attached to them.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
* [api:DateField]
|
394
docs/en/02_Developer_Guides/03_Forms/Fields/04_GridField.md
Normal file
394
docs/en/02_Developer_Guides/03_Forms/Fields/04_GridField.md
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
title: GridField
|
||||||
|
summary: How to use the GridField class for managing tabular data.
|
||||||
|
|
||||||
|
# GridField
|
||||||
|
|
||||||
|
[api:GridField] is SilverStripe's implementation of data grids. The main purpose of the `FormField` is to display
|
||||||
|
tabular data in a format that is easy to view and modify. It can be thought of as a HTML table with some tricks.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$field = new GridField($name, $title, $list);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="hint" markdown='1'>
|
||||||
|
GridField can only be used with `$list` data sets that are of the type `SS_List` such as `DataList` or `ArrayList`.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="notice" markdown="1">
|
||||||
|
[api:GridField] powers the automated data UI of [api:ModelAdmin]. For more information about `ModelAdmin` see the
|
||||||
|
[Customizing the CMS](../../customizing_the_cms) guide.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Each `GridField` is built from a number of components grouped into the [api:GridFieldConfig]. Without any components,
|
||||||
|
a `GridField` has almost no functionality. The `GridFieldConfig` instance and the attached [api:GridFieldComponent] are
|
||||||
|
responsible for all the user interactions including formatting data to be readable, modifying data and performing any
|
||||||
|
actions such as deleting records.
|
||||||
|
|
||||||
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Page extends SiteTree {
|
||||||
|
|
||||||
|
public function getCMSFields() {
|
||||||
|
$fields = parent::getCMSFields();
|
||||||
|
|
||||||
|
$fields->addFieldToTab('Root.Pages',
|
||||||
|
new GridField('Pages', 'All pages', SiteTree::get())
|
||||||
|
);
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
This will display a bare bones `GridField` instance under `Pages` tab in the CMS. As we have not specified the
|
||||||
|
`GridField` configuration, the default configuration is an instance of [api:GridFieldConfig_Base] which provides:
|
||||||
|
|
||||||
|
* [api:GridFieldToolbarHeader]
|
||||||
|
* [api:GridFieldSortableHeader]
|
||||||
|
* [api:GridFieldFilterHeader]
|
||||||
|
* [api:GridFieldDataColumns]
|
||||||
|
* [api:GridFieldPageCount]
|
||||||
|
* [api:GridFieldPaginator]
|
||||||
|
|
||||||
|
The configuration of those `GridFieldComponent` instances and the addition or subtraction of components is done through
|
||||||
|
the `getConfig()` method on `GridField`.
|
||||||
|
|
||||||
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Page extends SiteTree {
|
||||||
|
|
||||||
|
public function getCMSFields() {
|
||||||
|
$fields = parent::getCMSFields();
|
||||||
|
|
||||||
|
$fields->addFieldToTab('Root.Pages',
|
||||||
|
$grid = GridField('Pages', 'All pages', SiteTree::get())
|
||||||
|
);
|
||||||
|
|
||||||
|
// GridField configuration
|
||||||
|
$config = $gridField->getConfig();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Modification of existing components can be done by fetching that component.
|
||||||
|
// Consult the API documentation for each component to determine the configuration
|
||||||
|
// you can do.
|
||||||
|
//
|
||||||
|
$dataColumns = $config->getComponentByType('GridFieldDataColumns');
|
||||||
|
|
||||||
|
$dataColumns->setDisplayFields(array(
|
||||||
|
'Title' => 'Title',
|
||||||
|
'Link'=> 'URL',
|
||||||
|
'LastEdited' => 'Changed'
|
||||||
|
));
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
With the `GridFieldConfig` instance, we can modify the behavior of the `GridField`.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
// `GridFieldConfig::create()` will create an empty configuration (no components).
|
||||||
|
$config = GridFieldConfig::create();
|
||||||
|
|
||||||
|
// add a component
|
||||||
|
$config->addComponent(new GridFieldDataColumns());
|
||||||
|
|
||||||
|
// Update the GridField with our custom configuration
|
||||||
|
$gridField->setConfig($config);
|
||||||
|
|
||||||
|
`GridFieldConfig` provides a number of methods to make setting the configuration easier. We can insert a component
|
||||||
|
before another component by passing the second parameter.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$config->addComponent(new GridFieldFilterHeader(), 'GridFieldDataColumns');
|
||||||
|
|
||||||
|
We can add multiple components in one call.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$config->addComponents(
|
||||||
|
new GridFieldDataColumns(),
|
||||||
|
new GridFieldToolbarHeader()
|
||||||
|
);
|
||||||
|
|
||||||
|
Or, remove a component.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$config->removeComponentsByType('GridFieldDeleteAction');
|
||||||
|
|
||||||
|
Fetch a component to modify it later on.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$component = $config->getComponentByType('GridFieldFilterHeader')
|
||||||
|
|
||||||
|
|
||||||
|
Here is a list of components for use bundled with the core framework. Many more components are provided by third-party
|
||||||
|
modules and extensions.
|
||||||
|
|
||||||
|
- [api:GridFieldToolbarHeader]
|
||||||
|
- [api:GridFieldSortableHeader]
|
||||||
|
- [api:GridFieldFilterHeader]
|
||||||
|
- [api:GridFieldDataColumns]
|
||||||
|
- [api:GridFieldDeleteAction]
|
||||||
|
- [api:GridFieldViewButton]
|
||||||
|
- [api:GridFieldEditButton]
|
||||||
|
- [api:GridFieldExportButton]
|
||||||
|
- [api:GridFieldPrintButton]
|
||||||
|
- [api:GridFieldPaginator]
|
||||||
|
- [api:GridFieldDetailForm]
|
||||||
|
|
||||||
|
## Bundled GridFieldConfig
|
||||||
|
|
||||||
|
As a shortcut, `GridFieldConfig` subclasses can define a list of `GridFieldComponent` objects to use. This saves
|
||||||
|
developers manually adding each component.
|
||||||
|
|
||||||
|
### GridFieldConfig_Base
|
||||||
|
|
||||||
|
A simple read-only and paginated view of records with sortable and searchable headers.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$config = GridFieldConfig_Base::create();
|
||||||
|
|
||||||
|
$gridField->setConfig($config);
|
||||||
|
|
||||||
|
// Is the same as adding the following components..
|
||||||
|
// .. new GridFieldToolbarHeader()
|
||||||
|
// .. new GridFieldSortableHeader()
|
||||||
|
// .. new GridFieldFilterHeader()
|
||||||
|
// .. new GridFieldDataColumns()
|
||||||
|
// .. new GridFieldPageCount('toolbar-header-right')
|
||||||
|
// .. new GridFieldPaginator($itemsPerPage)
|
||||||
|
|
||||||
|
### GridFieldConfig_RecordViewer
|
||||||
|
|
||||||
|
Similar to `GridFieldConfig_Base` with the addition support of the ability to view a `GridFieldDetailForm` containing
|
||||||
|
a read-only view of the data record.
|
||||||
|
|
||||||
|
<div class="info" markdown="1">
|
||||||
|
The data row show must be a `DataObject` subclass. The fields displayed in the read-only view come from
|
||||||
|
`DataObject::getCMSFields()`.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert" markdown="1">
|
||||||
|
The `DataObject` class displayed must define a `canView()` method that returns a boolean on whether the user can view
|
||||||
|
this record.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$config = GridFieldConfig_RecordViewer::create();
|
||||||
|
|
||||||
|
$gridField->setConfig($config);
|
||||||
|
|
||||||
|
// Same as GridFieldConfig_Base with the addition of
|
||||||
|
// .. new GridFieldViewButton(),
|
||||||
|
// .. new GridFieldDetailForm()
|
||||||
|
|
||||||
|
### GridFieldConfig_RecordEditor
|
||||||
|
|
||||||
|
Similar to `GridFieldConfig_RecordViewer` with the addition support to edit or delete each of the records.
|
||||||
|
|
||||||
|
<div class="info" markdown="1">
|
||||||
|
The data row show must be a `DataObject` subclass. The fields displayed in the edit view come from
|
||||||
|
`DataObject::getCMSFields()`.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert" markdown="1">
|
||||||
|
Permission control for editing and deleting the record uses the `canEdit()` and `canDelete()` methods on the
|
||||||
|
`DataObject` object.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$config = GridFieldConfig_RecordEditor::create();
|
||||||
|
|
||||||
|
$gridField->setConfig($config);
|
||||||
|
|
||||||
|
// Same as GridFieldConfig_RecordViewer with the addition of
|
||||||
|
// .. new GridFieldAddNewButton(),
|
||||||
|
// .. new GridFieldEditButton(),
|
||||||
|
// .. new GridFieldDeleteAction()
|
||||||
|
|
||||||
|
### GridFieldConfig_RelationEditor
|
||||||
|
|
||||||
|
Similar to `GridFieldConfig_RecordEditor`, but adds features to work on a record's has-many or many-many relationships.
|
||||||
|
As such, it expects the list used with the `GridField` to be a instance of `RelationList`.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$config = GridFieldConfig_RelationEditor::create();
|
||||||
|
|
||||||
|
$gridField->setConfig($config);
|
||||||
|
|
||||||
|
This configuration adds the ability to searched for existing records and add a relationship
|
||||||
|
(`GridFieldAddExistingAutocompleter`).
|
||||||
|
|
||||||
|
Records created or deleted through the `GridFieldConfig_RelationEditor` automatically update the relationship in the
|
||||||
|
database.
|
||||||
|
|
||||||
|
## GridFieldDetailForm
|
||||||
|
|
||||||
|
The `GridFieldDetailForm` component drives the record viewing and editing form. It takes its' fields from
|
||||||
|
`DataObject->getCMSFields()` method but can be customized to accept different fields via the
|
||||||
|
[api:GridFieldDetailForm->setFields] method.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$form = $gridField->getConfig()->getComponentByType('GridFieldDetailForm');
|
||||||
|
$form->setFields(new FieldList(
|
||||||
|
new TextField('Title')
|
||||||
|
));
|
||||||
|
|
||||||
|
### many_many_extraFields
|
||||||
|
|
||||||
|
The component also has the ability to load and save data stored on join tables when two records are related via a
|
||||||
|
"many_many" relationship, as defined through [api:DataObject::$many_many_extraFields]. While loading and saving works
|
||||||
|
transparently, you need to add the necessary fields manually, they're not included in the `getCMSFields()` scaffolding.
|
||||||
|
|
||||||
|
These extra fields act like usual form fields, but need to be "namespaced" in order for the `GridField` logic to detect
|
||||||
|
them as fields for relation extra data, and to avoid clashes with the other form fields.
|
||||||
|
|
||||||
|
The namespace notation is `ManyMany[<extradata-field-name>]`, so for example `ManyMany[MyExtraField]`.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Team extends DataObject {
|
||||||
|
|
||||||
|
private static $db = array(
|
||||||
|
'Name' => 'Text'
|
||||||
|
);
|
||||||
|
|
||||||
|
public static $many_many = array(
|
||||||
|
'Players' => 'Player'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Player extends DataObject {
|
||||||
|
|
||||||
|
private static $db = array(
|
||||||
|
'Name' => 'Text'
|
||||||
|
);
|
||||||
|
|
||||||
|
public static $many_many = array(
|
||||||
|
'Teams' => 'Team'
|
||||||
|
);
|
||||||
|
|
||||||
|
public static $many_many_extraFields = array(
|
||||||
|
'Teams' => array(
|
||||||
|
'Position' => 'Text'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
public function getCMSFields() {
|
||||||
|
$fields = parent::getCMSFields();
|
||||||
|
|
||||||
|
if($this->ID) {
|
||||||
|
$teamFields = singleton('Team')->getCMSFields();
|
||||||
|
$teamFields->addFieldToTab(
|
||||||
|
'Root.Main',
|
||||||
|
// The "ManyMany[<extradata-name>]" convention
|
||||||
|
new TextField('ManyMany[Position]', 'Current Position')
|
||||||
|
);
|
||||||
|
|
||||||
|
$config = GridFieldConfig_RelationEditor::create();
|
||||||
|
$config->getComponentByType('GridFieldDetailForm')->setFields($teamFields);
|
||||||
|
|
||||||
|
$gridField = new GridField('Teams', 'Teams', $this->Teams(), $config);
|
||||||
|
$fields->findOrMakeTab('Root.Teams')->replaceField('Teams', $gridField);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Flexible Area Assignment through Fragments
|
||||||
|
|
||||||
|
`GridField` layouts can contain many components other than the table itself, for example a search bar to find existing
|
||||||
|
relations, a button to add those, and buttons to export and print the current data. The `GridField` has certain defined
|
||||||
|
areas called `fragments` where these components can be placed.
|
||||||
|
|
||||||
|
The goal is for multiple components to share the same space, for example a header row. The built-in components:
|
||||||
|
|
||||||
|
- `header`/`footer`: Renders in a `<thead>`/`<tfoot>`, should contain table markup
|
||||||
|
- `before`/`after`: Renders before/after the actual `<table>`
|
||||||
|
- `buttons-before-left`/`buttons-before-right`/`buttons-after-left`/`buttons-after-right`:
|
||||||
|
Renders in a shared row before the table. Requires [api:GridFieldButtonRow].
|
||||||
|
|
||||||
|
These built-ins can be used by passing the fragment names into the constructor of various components. Note that some
|
||||||
|
[api:GridFieldConfig] classes will already have rows added to them. The following example will add a print button at the
|
||||||
|
bottom right of the table.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
$config->addComponent(new GridFieldButtonRow('after'));
|
||||||
|
$config->addComponent(new GridFieldPrintButton('buttons-after-right'));
|
||||||
|
|
||||||
|
|
||||||
|
### Creating your own Fragments
|
||||||
|
|
||||||
|
Fragments are designated areas within a `GridField` which can be shared between component templates. You can define
|
||||||
|
your own fragments by using a `\$DefineFragment' placeholder in your components' template. This example will simply
|
||||||
|
create an area rendered before the table wrapped in a simple `<div>`.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class MyAreaComponent implements GridField_HTMLProvider {
|
||||||
|
|
||||||
|
public function getHTMLFragments( $gridField) {
|
||||||
|
return array(
|
||||||
|
'before' => '<div class="my-area">$DefineFragment(my-area)</div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="notice" markdown="1">
|
||||||
|
Please note that in templates, you'll need to escape the dollar sign on `\$DefineFragment`. These are specially
|
||||||
|
processed placeholders as opposed to native template syntax.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Now you can add other components into this area by returning them as an array from your
|
||||||
|
[api:GridFieldComponent->getHTMLFragments()] implementation:
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class MyShareLinkComponent implements GridField_HTMLProvider {
|
||||||
|
|
||||||
|
public function getHTMLFragments( $gridField) {
|
||||||
|
return array(
|
||||||
|
'my-area' => '<a href>...</a>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Your new area can also be used by existing components, e.g. the [api:GridFieldPrintButton]
|
||||||
|
|
||||||
|
:::php
|
||||||
|
new GridFieldPrintButton('my-component-area');
|
||||||
|
|
||||||
|
## Creating a Custom GridFieldComponent
|
||||||
|
|
||||||
|
Customizing a `GridField` is easy, applications and modules can provide their own `GridFieldComponent` instances to add
|
||||||
|
functionality. See [How to Create a GridFieldComponent](../how_tos/create_a_gridfield_component).
|
||||||
|
|
||||||
|
## Creating a Custom GridField_ActionProvider
|
||||||
|
|
||||||
|
[api:GridField_ActionProvider] provides row level actions such as deleting a record. See
|
||||||
|
[How to Create a GridField_ActionProvider](../how_tos/create_a_gridfield_actionprovider).
|
||||||
|
|
||||||
|
## Saving the GridField State
|
||||||
|
|
||||||
|
`GridState` is a class that is used to contain the current state and actions on the `GridField`. It's transfered
|
||||||
|
between page requests by being inserted as a hidden field in the form.
|
||||||
|
|
||||||
|
The `GridState_Component` sets and gets data from the `GridState`.
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
* [api:GridField]
|
||||||
|
* [api:GridFieldConfig]
|
||||||
|
* [api:GridFieldComponent]
|
@ -0,0 +1,151 @@
|
|||||||
|
title: How to Encapsulate Forms
|
||||||
|
|
||||||
|
# How to Encapsulate Forms
|
||||||
|
|
||||||
|
Form definitions can often get long, complex and often end up cluttering up a `Controller` definition. We may also want
|
||||||
|
to reuse the `Form` across multiple `Controller` classes rather than just one. A nice way to encapsulate the logic and
|
||||||
|
code for a `Form` is to create it as a subclass to `Form`. Let's look at a example of a `Form` which is on our
|
||||||
|
`Controller` but would be better written as a subclass.
|
||||||
|
|
||||||
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Page_Controller extends ContentController {
|
||||||
|
|
||||||
|
public function SearchForm() {
|
||||||
|
$fields = new FieldList(
|
||||||
|
HeaderField::create('Header', 'Step 1. Basics'),
|
||||||
|
OptionsetField::create('Type', '', array(
|
||||||
|
'foo' => 'Search Foo',
|
||||||
|
'bar' => 'Search Bar',
|
||||||
|
'baz' => 'Search Baz'
|
||||||
|
)),
|
||||||
|
|
||||||
|
CompositeField::create(
|
||||||
|
HeaderField::create('Header2', 'Step 2. Advanced '),
|
||||||
|
CheckboxSetField::create('Foo', 'Select Option', array(
|
||||||
|
'qux' => 'Search Qux'
|
||||||
|
)),
|
||||||
|
|
||||||
|
CheckboxSetField::create('Category', 'Category', array(
|
||||||
|
'Foo' => 'Foo',
|
||||||
|
'Bar' => 'Bar'
|
||||||
|
)),
|
||||||
|
|
||||||
|
NumericField::create('Minimum', 'Minimum'),
|
||||||
|
NumericField::create('Maximum', 'Maximum')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$actions = new FieldList(
|
||||||
|
FormAction::create('doSearchForm', 'Search')
|
||||||
|
);
|
||||||
|
|
||||||
|
$required = new RequiredFields(array(
|
||||||
|
'Type'
|
||||||
|
));
|
||||||
|
|
||||||
|
$form = new Form($this, 'SearchForm', $fields, $actions, $required);
|
||||||
|
$form->setFormMethod('GET');
|
||||||
|
|
||||||
|
$form->addExtraClass('no-action-styles');
|
||||||
|
$form->disableSecurityToken();
|
||||||
|
$form->loadDataFrom($_REQUEST);
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
..
|
||||||
|
}
|
||||||
|
|
||||||
|
Now that is a bit of code to include on our controller and generally makes the file look much more complex than it
|
||||||
|
should be. Good practice would be to move this to a subclass and create a new instance for your particular controller.
|
||||||
|
|
||||||
|
**mysite/code/forms/SearchForm.php**
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class SearchForm extends Form {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Our constructor only requires the controller and the name of the form
|
||||||
|
* method. We'll create the fields and actions in here.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct($controller, $name) {
|
||||||
|
$fields = new FieldList(
|
||||||
|
HeaderField::create('Header', 'Step 1. Basics'),
|
||||||
|
OptionsetField::create('Type', '', array(
|
||||||
|
'foo' => 'Search Foo',
|
||||||
|
'bar' => 'Search Bar',
|
||||||
|
'baz' => 'Search Baz'
|
||||||
|
)),
|
||||||
|
|
||||||
|
CompositeField::create(
|
||||||
|
HeaderField::create('Header2', 'Step 2. Advanced '),
|
||||||
|
CheckboxSetField::create('Foo', 'Select Option', array(
|
||||||
|
'qux' => 'Search Qux'
|
||||||
|
)),
|
||||||
|
|
||||||
|
CheckboxSetField::create('Category', 'Category', array(
|
||||||
|
'Foo' => 'Foo',
|
||||||
|
'Bar' => 'Bar'
|
||||||
|
)),
|
||||||
|
|
||||||
|
NumericField::create('Minimum', 'Minimum'),
|
||||||
|
NumericField::create('Maximum', 'Maximum')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$actions = new FieldList(
|
||||||
|
FormAction::create('doSearchForm', 'Search')
|
||||||
|
);
|
||||||
|
|
||||||
|
$required = new RequiredFields(array(
|
||||||
|
'Type'
|
||||||
|
));
|
||||||
|
|
||||||
|
// now we create the actual form with our fields and actions defined
|
||||||
|
// within this class
|
||||||
|
parent::__construct($controller, $name, $fields, $actions, $required);
|
||||||
|
|
||||||
|
// any modifications we need to make to the form.
|
||||||
|
$this->setFormMethod('GET');
|
||||||
|
|
||||||
|
$this->addExtraClass('no-action-styles');
|
||||||
|
$this->disableSecurityToken();
|
||||||
|
$this->loadDataFrom($_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Our controller will now just have to create a new instance of this form object. Keeping the file light and easy to read.
|
||||||
|
|
||||||
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Page_Controller extends ContentController {
|
||||||
|
|
||||||
|
private static $allowed_actions = array(
|
||||||
|
'SearchForm',
|
||||||
|
);
|
||||||
|
|
||||||
|
public function SearchForm() {
|
||||||
|
return new SearchForm($this, 'SearchForm');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Form actions can also be defined within your `Form` subclass to keep the entire form logic encapsulated.
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
* [Introduction to Forms](../introduction)
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
* [api:Form]
|
||||||
|
|
@ -0,0 +1,52 @@
|
|||||||
|
title: How to Create Lightweight Form
|
||||||
|
|
||||||
|
# How to Create Lightweight Form
|
||||||
|
|
||||||
|
Out of the box, SilverStripe provides a robust and reusable set of HTML markup for [api:FormFields], however this can
|
||||||
|
sometimes produce markup which is unnecessarily bloated.
|
||||||
|
|
||||||
|
For example, a basic search form. We want to use the [api:Form] API to handle the form but we may want to provide a
|
||||||
|
totally custom template to meet our needs. To do this, we'll provide the class with a unique template through
|
||||||
|
`setTemplate`.
|
||||||
|
|
||||||
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
public function SearchForm() {
|
||||||
|
$fields = new FieldList(
|
||||||
|
TextField::create('q')
|
||||||
|
);
|
||||||
|
|
||||||
|
$actions = new FieldList(
|
||||||
|
FormAction::create('doSearch', 'Search')
|
||||||
|
);
|
||||||
|
|
||||||
|
$form = new Form($this, 'SearchForm', $fields, $actions);
|
||||||
|
$form->setTemplate('SearchForm');
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
**mysite/templates/Includes/SearchForm.ss**
|
||||||
|
|
||||||
|
:::ss
|
||||||
|
<form $FormAttributes>
|
||||||
|
<fieldset>
|
||||||
|
$Fields.dataFieldByName(q)
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div class="Actions">
|
||||||
|
<% loop $Actions %>$Field<% end_loop %>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
`SearchForm.ss` will be executed within the scope of the `Form` object so has access to any of the methods and
|
||||||
|
properties on [api:Form] such as `$Fields` and `$Actions`.
|
||||||
|
|
||||||
|
<div class="notice">
|
||||||
|
To understand more about Scope or the syntax for custom templates, read the [Templates](../../templates) guide.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
A single component often uses a number of interfaces.
|
||||||
|
|
||||||
|
### GridField_HTMLProvider
|
||||||
|
|
||||||
|
Provides HTML for the header/footer rows in the table or before/after the template.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- A header html provider displays a header before the table
|
||||||
|
- A pagination html provider displays pagination controls under the table
|
||||||
|
- A filter html fields displays filter fields on top of the table
|
||||||
|
- A summary html field displays sums of a field at the bottom of the table
|
||||||
|
|
||||||
|
### GridField_ColumnProvider
|
||||||
|
|
||||||
|
Add a new column to the table display body, or modify existing columns. Used once per record/row.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- A data columns provider that displays data from the list in rows and columns.
|
||||||
|
- A delete button column provider that adds a delete button at the end of the row
|
||||||
|
|
||||||
|
### GridField_ActionProvider
|
||||||
|
|
||||||
|
Action providers runs actions, some examples are:
|
||||||
|
|
||||||
|
- A delete action provider that deletes a DataObject.
|
||||||
|
- An export action provider that will export the current list to a CSV file.
|
||||||
|
|
||||||
|
### GridField_DataManipulator
|
||||||
|
|
||||||
|
Modifies the data list. In general, the data manipulator will make use of `GridState` variables
|
||||||
|
to decide how to modify the data list.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- A paginating data manipulator can apply a limit to a list (show only 20 records)
|
||||||
|
- A sorting data manipulator can sort the Title in a descending order.
|
||||||
|
|
||||||
|
### GridField_URLHandler
|
||||||
|
|
||||||
|
Sometimes an action isn't enough, we need to provide additional support URLs for the grid. It
|
||||||
|
has a list of URL's that it can handle and the GridField passes request on to URLHandlers on matches.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- A pop-up form for editing a record's details.
|
||||||
|
- JSON formatted data used for javascript control of the gridfield.
|
@ -1,8 +1,22 @@
|
|||||||
title: Forms
|
title: Forms
|
||||||
summary: Capture user information through Forms. This guide will work through making forms, custom form fields and adding fields to your data.
|
summary: Capture user information through Forms. This guide will work through how to create SilverStripe forms, adding and modifying fields and how to handle form submissions.
|
||||||
|
introduction: This guide will work through how to create SilverStripe forms, adding and modifying fields and how to handle form submissions.
|
||||||
|
|
||||||
[CHILDREN]
|
The [api:Form] class provides a way to create interactive forms in your web application with very little effort.
|
||||||
|
SilverStripe handles generating the correct semantic HTML markup for the form and each of the fields, as well as the
|
||||||
|
framework for dealing with submissions and validation.
|
||||||
|
|
||||||
## How-to
|
[CHILDREN Exclude="How_Tos,Fields"]
|
||||||
|
|
||||||
[CHILDREN How_To]
|
## FormField Documentation
|
||||||
|
|
||||||
|
[CHILDREN Folder="Fields"]
|
||||||
|
|
||||||
|
## How to's
|
||||||
|
|
||||||
|
[CHILDREN How_Tos]
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
* [api:Form]
|
||||||
|
* [api:FormField]
|
@ -1,3 +1,6 @@
|
|||||||
|
title: UploadField
|
||||||
|
summary: How to use the UploadField class for uploading assets.
|
||||||
|
|
||||||
# UploadField
|
# UploadField
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
Loading…
Reference in New Issue
Block a user