DOCS Update docs to reference PageController without an underscore, implement some PSR-2

This commit is contained in:
Robbie Averill 2016-12-30 12:17:15 +13:00
parent a996e20e79
commit c620063608
20 changed files with 1031 additions and 819 deletions

View File

@ -335,13 +335,22 @@ types right now, we will go into much more detail in the [next tutorial](/tutori
Create a new file *HomePage.php* in *mysite/code*. Copy the following code into it:
:::php
<?php
class HomePage extends Page {
}
class HomePage_Controller extends Page_Controller {
}
```php
<?php
use Page;
use PageController;
class HomePage extends Page
{
}
class HomePageController extends PageController
{
}
```
Every page type also has a database table corresponding to it. Every time we modify the database, we need to rebuild it.
We can do this by going to `http://localhost/your_site_name/dev/build`.

View File

@ -18,59 +18,67 @@ We will create a poll on the home page that asks the user their favourite web br
## Creating the form
The poll we will be creating on our homepage will ask the user for their name and favourite web browser. It will then collate the results into a bar graph. We create the form in a method on *HomePage_Controller*.
The poll we will be creating on our homepage will ask the user for their name and favourite web browser. It will then collate the results into a bar graph. We create the form in a method on *HomePageController*.
**mysite/code/HomePage.php**
**mysite/code/HomePageController.php**
```php
class HomePage_Controller extends Page_Controller {
private static $allowed_actions = array('BrowserPollForm');
```php
use PageController;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\OptionSetField;
use SilverStripe\Forms\TextField;
// ...
class HomePageController extends PageController
{
private static $allowed_actions = array('BrowserPollForm');
public function BrowserPollForm() {
// Create fields
$fields = new FieldList(
new TextField('Name'),
new OptionsetField('Browser', 'Your Favourite Browser', array(
'Firefox' => 'Firefox',
'Chrome' => 'Chrome',
'Internet Explorer' => 'Internet Explorer',
'Safari' => 'Safari',
'Opera' => 'Opera',
'Lynx' => 'Lynx'
))
);
// ...
// Create actions
$actions = new FieldList(
new FormAction('doBrowserPoll', 'Submit')
);
public function BrowserPollForm()
{
// Create fields
$fields = new FieldList(
new TextField('Name'),
new OptionsetField('Browser', 'Your Favourite Browser', array(
'Firefox' => 'Firefox',
'Chrome' => 'Chrome',
'Internet Explorer' => 'Internet Explorer',
'Safari' => 'Safari',
'Opera' => 'Opera',
'Lynx' => 'Lynx'
))
);
return new Form($this, 'BrowserPollForm', $fields, $actions);
}
// Create actions
$actions = new FieldList(
new FormAction('doBrowserPoll', 'Submit')
);
...
}
return new Form($this, 'BrowserPollForm', $fields, $actions);
}
...
// ...
}
// ...
```
Let's step through this code.
```php
// Create fields
$fields = new FieldList(
new TextField('Name'),
new OptionsetField('Browser', 'Your Favourite Browser', array(
'Firefox' => 'Firefox',
'Chrome' => 'Chrome',
'Internet Explorer' => 'Internet Explorer',
'Safari' => 'Safari',
'Opera' => 'Opera',
'Lynx' => 'Lynx'
))
);
// Create fields
$fields = new FieldList(
new TextField('Name'),
new OptionsetField('Browser', 'Your Favourite Browser', array(
'Firefox' => 'Firefox',
'Chrome' => 'Chrome',
'Internet Explorer' => 'Internet Explorer',
'Safari' => 'Safari',
'Opera' => 'Opera',
'Lynx' => 'Lynx'
))
);
```
First we create our form fields.
@ -81,10 +89,10 @@ argument is passed, as in this case, it is assumed the label is the same as the
The second field we create is an [api:OptionsetField]. This is a dropdown, and takes a third argument - an
array mapping the values to the options listed in the dropdown.
```php
$actions = new FieldList(
new FormAction('doBrowserPoll', 'Submit');
);
```php
$actions = new FieldList(
new FormAction('doBrowserPoll', 'Submit');
);
```
After creating the fields, we create the form actions. Form actions appear as buttons at the bottom of the form.
@ -94,7 +102,7 @@ All the form actions (in this case only one) are collected into a [api:FieldList
the fields.
```php
return new Form($this, 'BrowserPollForm', $fields, $actions);
return new Form($this, 'BrowserPollForm', $fields, $actions);
```
Finally we create the [api:Form] object and return it.
@ -107,14 +115,14 @@ Add the following code to the top of your home page template, just before `<div
**themes/simple/templates/Layout/HomePage.ss**
```ss
...
<div id="BrowserPoll">
<h2>Browser Poll</h2>
$BrowserPollForm
</div>
<div class="Content">
...
```ss
...
<div id="BrowserPoll">
<h2>Browser Poll</h2>
$BrowserPollForm
</div>
<div class="Content">
...
```
In order to make the graphs render correctly,
@ -123,49 +131,49 @@ Add the following code to the existing `form.css` file:
**themes/simple/css/form.css**
```css
/* BROWSER POLL */
#BrowserPoll {
float: right;
margin: 20px 10px 0 0;
width: 20%;
}
form FieldList {
border:0;
}
```css
/* BROWSER POLL */
#BrowserPoll {
float: right;
margin: 20px 10px 0 0;
width: 20%;
}
form FieldList {
border: 0;
}
#BrowserPoll .message {
float:left;
#BrowserPoll .message {
float: left;
display: block;
color:red;
background:#efefef;
border:1px solid #ccc;
padding:5px;
margin:5px;
}
color: red;
background: #efefef;
border: 1px solid #ccc;
padding: 5px;
margin: 5px;
}
#BrowserPoll h2 {
font-size: 1.5em;
line-height:2em;
color: #0083C8;
}
#BrowserPoll h2 {
font-size: 1.5em;
line-height:2em;
color: #0083C8;
}
#BrowserPoll .field {
padding:3px 0;
}
#BrowserPoll .field {
padding:3px 0;
}
#BrowserPoll input.text {
padding: 0;
font-size:1em;
}
#BrowserPoll input.text {
padding: 0;
font-size:1em;
}
#BrowserPoll .btn-toolbar {
padding:5px 0;
}
#BrowserPoll .btn-toolbar {
padding: 5px 0;
}
#BrowserPoll .bar {
background-color: #015581;
}
#BrowserPoll .bar {
background-color: #015581;
}
```
@ -183,29 +191,37 @@ If you recall, in the [second tutorial](/tutorials/extending_a_basic_site) we sa
**mysite/code/BrowserPollSubmission.php**
```php
<?php
class BrowserPollSubmission extends DataObject {
private static $db = array(
'Name' => 'Text',
'Browser' => 'Text'
);
}
```php
<?php
use SilverStripe\ORM\DataObject;
class BrowserPollSubmission extends DataObject
{
private static $db = array(
'Name' => 'Text',
'Browser' => 'Text'
);
}
```
If we then rebuild the database ([http://localhost/your_site_name/dev/build](http://localhost/your_site_name/dev/build)), we will see that the *BrowserPollSubmission* table is created. Now we just need to define 'doBrowserPoll' on *HomePage_Controller*:
If we then rebuild the database ([http://localhost/your_site_name/dev/build](http://localhost/your_site_name/dev/build)), we will see that the *BrowserPollSubmission* table is created. Now we just need to define 'doBrowserPoll' on *HomePageController*:
**mysite/code/HomePage.php**
**mysite/code/HomePageController.php**
```php
class HomePage_Controller extends Page_Controller {
// ...
public function doBrowserPoll($data, $form) {
$submission = new BrowserPollSubmission();
$form->saveInto($submission);
$submission->write();
return $this->redirectBack();
}
}
```php
use BrowserPollSubmission;
use PageController;
class HomePageController extends PageController
{
// ...
public function doBrowserPoll($data, $form) {
$submission = new BrowserPollSubmission();
$form->saveInto($submission);
$submission->write();
return $this->redirectBack();
}
}
```
A function that processes a form submission takes two arguments - the first is the data in the form, the second is the [api:Form] object.
@ -218,16 +234,17 @@ SilverStripe forms all have automatic validation on fields where it is logical.
SilverStripe provides the *RequiredFields* validator, which ensures that the fields specified are filled in before the form is submitted. To use it we create a new *RequiredFields* object with the name of the fields we wish to be required as the arguments, then pass this as a fifth argument to the Form constructor.
Change the end of the 'BrowserPollForm' function so it looks like this:
Add a namespace import for `SilverStripe\Forms\RequiredFields`, then change the end of the 'BrowserPollForm' function so it looks like this:
**mysite/code/HomePage.php**
```php
public function BrowserPollForm() {
// ...
$validator = new RequiredFields('Name', 'Browser');
return new Form($this, 'BrowserPollForm', $fields, $actions, $validator);
}
```php
public function BrowserPollForm()
{
// ...
$validator = new RequiredFields('Name', 'Browser');
return new Form($this, 'BrowserPollForm', $fields, $actions, $validator);
}
```
If we then open the homepage and attempt to submit the form without filling in the required fields errors should appear.
@ -242,34 +259,40 @@ The first thing to do is make it so a user can only vote once per session. If th
We can do this using a session variable. The [api:Session] class handles all session variables in SilverStripe. First modify the 'doBrowserPoll' to set the session variable 'BrowserPollVoted' when a user votes.
**mysite/code/HomePage.php**
**mysite/code/HomePageController.php**
```php
// ...
class HomePage_Controller extends Page_Controller {
// ...
public function doBrowserPoll($data, $form) {
$submission = new BrowserPollSubmission();
$form->saveInto($submission);
$submission->write();
Session::set('BrowserPollVoted', true);
return $this->redirectBack();
}
}
```php
// ...
class HomePageController extends PageController
{
// ...
public function doBrowserPoll($data, $form)
{
$submission = new BrowserPollSubmission();
$form->saveInto($submission);
$submission->write();
Session::set('BrowserPollVoted', true);
return $this->redirectBack();
}
}
```
Then we simply need to check if the session variable has been set in 'BrowserPollForm()', and to not return the form if
it is.
```php
// ...
class HomePage_Controller extends Page_Controller {
// ...
public function BrowserPollForm() {
if(Session::get('BrowserPollVoted')) return false;
// ...
}
}
```php
// ...
class HomePageController extends PageController
{
// ...
public function BrowserPollForm()
{
if (Session::get('BrowserPollVoted')) {
return false;
}
// ...
}
}
```
If you visit the home page now you will see you can only vote once per session; after that the form won't be shown. You can start a new session by closing and reopening your browser,
@ -281,47 +304,49 @@ Now that we're collecting data, it would be nice to show the results on the webs
In the [second tutorial](/tutorials/extending_a_basic_site), we got a collection of news articles for the home page by using the 'ArticleHolder::get()' function, which returns a [api:DataList]. We can get all submissions in the same fashion, through `BrowserPollSubmission::get()`. This list will be the starting point for our result aggregation.
Create the function 'BrowserPollResults' on the *HomePage_Controller* class.
Add the appropriate namespace imports, then create the function 'BrowserPollResults' on the *HomePageController* class.
**mysite/code/HomePage.php**
**mysite/code/HomePageController.php**
```php
public function BrowserPollResults() {
$submissions = new GroupedList(BrowserPollSubmission::get());
$total = $submissions->Count();
```php
public function BrowserPollResults()
{
$submissions = new GroupedList(BrowserPollSubmission::get());
$total = $submissions->Count();
$list = new ArrayList();
foreach($submissions->groupBy('Browser') as $browserName => $browserSubmissions) {
$percentage = (int) ($browserSubmissions->Count() / $total * 100);
$list->push(new ArrayData(array(
'Browser' => $browserName,
'Percentage' => $percentage
)));
}
return $list;
}
$list = new ArrayList();
foreach($submissions->groupBy('Browser') as $browserName => $browserSubmissions) {
$percentage = (int) ($browserSubmissions->Count() / $total * 100);
$list->push(new ArrayData(array(
'Browser' => $browserName,
'Percentage' => $percentage
)));
}
return $list;
}
```
This code introduces a few new concepts, so let's step through it.
```php
$submissions = new GroupedList(BrowserPollSubmission::get());
```php
$submissions = new GroupedList(BrowserPollSubmission::get());
```
First we get all of the `BrowserPollSubmission` records from the database. This returns the submissions as a [api:DataList]. Then we wrap it inside a [api:GroupedList], which adds the ability to group those records. The resulting object will behave just like the original `DataList`, though (with the addition of a `groupBy()` method).
```php
$total = $submissions->Count();
$total = $submissions->Count();
```
We get the total number of submissions, which is needed to calculate the percentages.
```php
$list = new ArrayList();
foreach($submissions->groupBy('Browser') as $browserName => $browserSubmissions) {
$percentage = (int) ($browserSubmissions->Count() / $total * 100);
$list->push(new ArrayData(array(
'Browser' => $browserName,
'Percentage' => $percentage
)));
}
```php
$list = new ArrayList();
foreach ($submissions->groupBy('Browser') as $browserName => $browserSubmissions) {
$percentage = (int) ($browserSubmissions->Count() / $total * 100);
$list->push(new ArrayData(array(
'Browser' => $browserName,
'Percentage' => $percentage
)));
}
```
Now we create an empty [api:ArrayList] to hold the data we'll pass to the template. Its similar to [api:DataList], but can hold arbitrary objects rather than just DataObject` instances. Then we iterate over the 'Browser' submissions field.
@ -334,21 +359,21 @@ The final step is to create the template to display our data. Change the 'Browse
**themes/simple/templates/Layout/HomePage.ss**
```ss
<div id="BrowserPoll">
<h2>Browser Poll</h2>
<% if $BrowserPollForm %>
$BrowserPollForm
<% else %>
<ul>
<% loop $BrowserPollResults %>
<li>
<div class="browser">$Browser: $Percentage%</div>
<div class="bar" style="width:$Percentage%">&nbsp;</div>
</li>
<% end_loop %>
</ul>
<% end_if %>
</div>
<div id="BrowserPoll">
<h2>Browser Poll</h2>
<% if $BrowserPollForm %>
$BrowserPollForm
<% else %>
<ul>
<% loop $BrowserPollResults %>
<li>
<div class="browser">$Browser: $Percentage%</div>
<div class="bar" style="width:$Percentage%">&nbsp;</div>
</li>
<% end_loop %>
</ul>
<% end_if %>
</div>
```
Here we first check if the *BrowserPollForm* is returned, and if it is display it. Otherwise the user has already voted,

View File

@ -22,11 +22,11 @@ make them editable in the CMS, and render them on the website.
This table shows some example data we'll be using:
| Project | Student | Mentor |
| ------- | ------- | ------- |
| Developer Toolbar | Jakob,Ofir | Mark,Sean |
| Project | Student | Mentor |
| ------- | ------- | ------- |
| Developer Toolbar | Jakob,Ofir | Mark,Sean |
| Behaviour Testing | Michal,Wojtek | Ingo, Sean |
| Content Personalization | Yuki | Philipp |
| Content Personalization | Yuki | Philipp |
| Module Management | Andrew | Marcus,Sam |
### Has-One and Has-Many Relationships: Project and Student
@ -38,29 +38,49 @@ Let's create the `Student` and `Project` objects.
**mysite/code/Student.php**
:::php
<?php
class Student extends DataObject {
private static $db = array(
'Name' => 'Varchar',
'University' => 'Varchar',
);
private static $has_one = array(
'Project' => 'Project'
);
}
```php
<?php
use SilverStripe\ORM\DataObject;
class Student extends DataObject
{
private static $db = array(
'Name' => 'Varchar',
'University' => 'Varchar',
);
private static $has_one = array(
'Project' => 'Project'
);
}
```
**mysite/code/Project.php**
:::php
<?php
class Project extends Page {
private static $has_many = array(
'Students' => 'Student'
);
}
class Project_Controller extends Page_Controller {
}
```php
<?php
use Page;
class Project extends Page
{
private static $has_many = array(
'Students' => 'Student'
);
}
```
**mysite/code/ProjectController.php**
```php
<?php
use PageController;
class ProjectController extends PageController
{
}
```
The relationships are defined through the `$has_one`
and `$has_many` properties on the objects.
@ -95,15 +115,30 @@ The restriction is enforced through the `$allowed_children` directive.
**mysite/code/ProjectsHolder.php**
:::php
<?php
class ProjectsHolder extends Page {
private static $allowed_children = array(
'Project'
);
}
class ProjectsHolder_Controller extends Page_Controller {
}
:::php
<?php
use Page;
class ProjectsHolder extends Page {
private static $allowed_children = array(
'Project'
);
}
```
**mysite/code/ProjectsHolderController.php
```php
<?php
use PageController;
class ProjectsHolderController extends PageController
{
}
```
You might have noticed that we don't specify the relationship
to a project. That's because it's already inherited from the parent implementation,
@ -128,32 +163,42 @@ All customization to fields for a page type are managed through a method called
**mysite/code/Project.php**
:::php
<?php
class Project extends Page {
// ...
public function getCMSFields() {
// Get the fields from the parent implementation
$fields = parent::getCMSFields();
// Create a default configuration for the new GridField, allowing record editing
$config = GridFieldConfig_RelationEditor::create();
// Set the names and data for our gridfield columns
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'Name' => 'Name',
'Project.Title'=> 'Project' // Retrieve from a has-one relationship
));
// Create a gridfield to hold the student relationship
$studentsField = new GridField(
'Students', // Field name
'Student', // Field title
$this->Students(), // List of all related students
$config
);
// Create a tab named "Students" and add our field to it
$fields->addFieldToTab('Root.Students', $studentsField);
return $fields;
}
}
```php
<?php
use Page;
use SilverStripe\Forms\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
class Project extends Page
{
// ...
public function getCMSFields()
{
// Get the fields from the parent implementation
$fields = parent::getCMSFields();
// Create a default configuration for the new GridField, allowing record editing
$config = GridFieldConfig_RelationEditor::create();
// Set the names and data for our gridfield columns
$config
->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDataColumns')
->setDisplayFields(array(
'Name' => 'Name',
'Project.Title'=> 'Project' // Retrieve from a has-one relationship
));
// Create a gridfield to hold the student relationship
$studentsField = new GridField(
'Students', // Field name
'Student', // Field title
$this->Students(), // List of all related students
$config
);
// Create a tab named "Students" and add our field to it
$fields->addFieldToTab('Root.Students', $studentsField);
return $fields;
}
}
```
This creates a tabular field, which lists related student records, one row at a time.
It's empty by default, but you can add new students as required,
@ -169,11 +214,11 @@ One example of this is the configuration of column names on our table:
We call `setDisplayFields()` directly on the component responsible for their rendering.
<div class="note" markdown="1">
Adding a `GridField` to a page type is a popular way to manage data,
but not the only one. If your data requires a dedicated interface
with more sophisticated search and management logic, consider
using the [ModelAdmin](/developer_guides/customising_the_admin_interface/modeladmin)
interface instead.
Adding a `GridField` to a page type is a popular way to manage data,
but not the only one. If your data requires a dedicated interface
with more sophisticated search and management logic, consider
using the [ModelAdmin](/developer_guides/customising_the_admin_interface/modeladmin)
interface instead.
</div>
![tutorial:tutorial5_project_creation.jpg](../_images/tutorial5_project_creation.jpg)
@ -200,26 +245,37 @@ The first step is to create the `Mentor` object and set the relation with the `P
**mysite/code/Mentor.php**
:::php
<?php
class Mentor extends DataObject {
private static $db = array(
'Name' => 'Varchar',
);
private static $belongs_many_many = array(
'Projects' => 'Project'
);
}
```php
<?php
use SilverStripe\ORM\DataObject;
class Mentor extends DataObject
{
private static $db = array(
'Name' => 'Varchar',
);
private static $belongs_many_many = array(
'Projects' => 'Project'
);
}
```
**mysite/code/Project.php**
:::php
class Project extends Page {
// ...
private static $many_many = array(
'Mentors' => 'Mentor'
);
}
```php
<?php
use Page;
class Project extends Page
{
// ...
private static $many_many = array(
'Mentors' => 'Mentor'
);
}
```
This code will create a relationship between the `Project` table and the `Mentor` table by storing the ids of the respective `Project` and `Mentor` in a another table named "Project_Mentors"
(after you've performed a `dev/build` command, of course).
@ -231,22 +287,31 @@ to configure it a bit differently.
**mysite/code/Project.php**
:::php
class Project extends Page {
// ...
public function getCMSFields() {
// ...
// Same setup, but for mentors
$mentorsField = new GridField(
'Mentors',
'Mentors',
$this->Mentors(),
GridFieldConfig_RelationEditor::create()
);
$fields->addFieldToTab('Root.Mentors', $mentorsField);
return $fields;
}
}
```php
<?php
use Page;
use SilverStripe\Forms\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
class Project extends Page
{
// ...
public function getCMSFields()
{
// ...
// Same setup, but for mentors
$mentorsField = new GridField(
'Mentors',
'Mentors',
$this->Mentors(),
GridFieldConfig_RelationEditor::create()
);
$fields->addFieldToTab('Root.Mentors', $mentorsField);
return $fields;
}
}
```
The important difference to our student management UI is the usage
of `$this->Mentor()` (rather than `Mentor::get()`). It will limit
@ -286,44 +351,45 @@ a named list of object.
**themes/simple/templates/Layout/ProjectsHolder.ss**
:::ss
<% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article>
<h1>$Title</h1>
<div class="content">
$Content
<table>
<thead>
<tr>
<th>Project</th>
<th>Students</th>
<th>Mentors</th>
</tr>
</thead>
<tbody>
<% loop $Children %>
<tr>
<td>
<a href="$Link">$Title</a>
</td>
<td>
<% loop $Students %>
$Name ($University)<% if $Last !=1 %>,<% end_if %>
<% end_loop %>
</td>
<td>
<% loop $Mentors %>
$Name<% if $Last !=1 %>,<% end_if %>
<% end_loop %>
</td>
</tr>
<% end_loop %>
</tbody>
</table>
</div>
</article>
</div>
```ss
<% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article>
<h1>$Title</h1>
<div class="content">
$Content
<table>
<thead>
<tr>
<th>Project</th>
<th>Students</th>
<th>Mentors</th>
</tr>
</thead>
<tbody>
<% loop $Children %>
<tr>
<td>
<a href="$Link">$Title</a>
</td>
<td>
<% loop $Students %>
$Name ($University)<% if not $Last %>, <% end_if %>
<% end_loop %>
</td>
<td>
<% loop $Mentors %>
$Name<% if not $Last %>, <% end_if %>
<% end_loop %>
</td>
</tr>
<% end_loop %>
</tbody>
</table>
</div>
</article>
</div>
```
Navigate to the holder page through your website navigation,
or the "Preview" feature in the CMS. You should see a list of all projects now.
@ -344,36 +410,37 @@ we can access the "Students" and "Mentors" relationships directly in the templat
**themes/simple/templates/Layout/Project.ss**
:::ss
<% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article>
<h1>$Title</h1>
<div class="content">
$Content
<h2>Students</h2>
<% if $Students %>
<ul>
<% loop $Students %>
<li>$Name ($University)</li>
<% end_loop %>
</ul>
<% else %>
<p>No students found</p>
<% end_if %>
<h2>Mentors</h2>
<% if $Mentors %>
<ul>
<% loop $Mentors %>
<li>$Name</li>
<% end_loop %>
</ul>
<% else %>
<p>No mentors found</p>
<% end_if %>
</div>
</article>
</div>
```ss
<% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article>
<h1>$Title</h1>
<div class="content">
$Content
<h2>Students</h2>
<% if $Students %>
<ul>
<% loop $Students %>
<li>$Name ($University)</li>
<% end_loop %>
</ul>
<% else %>
<p>No students found</p>
<% end_if %>
<h2>Mentors</h2>
<% if $Mentors %>
<ul>
<% loop $Mentors %>
<li>$Name</li>
<% end_loop %>
</ul>
<% else %>
<p>No mentors found</p>
<% end_if %>
</div>
</article>
</div>
```
Follow the link to a project detail from from your holder page,
or navigate to it through the submenu provided by the theme.
@ -387,17 +454,23 @@ by introducing a new template for them.
**themes/simple/templates/Includes/StudentInfo.ss**
:::ss
$Name ($University)
```ss
$Name ($University)
```
To use this template, we need to add a new method to our student class:
:::php
class Student extends DataObject {
function getInfo() {
return $this->renderWith('StudentInfo');
}
}
```php
use SilverStripe\ORM\DataObject;
class Student extends DataObject
{
public function getInfo()
{
return $this->renderWith('StudentInfo');
}
}
```
Replace the student template code in both `Project.ss`
and `ProjectHolder.ss` templates with the new placeholder, `$Info`.

View File

@ -98,7 +98,8 @@ Variables can come from your database fields, or custom methods you define on yo
**mysite/code/Page.php**
:::php
public function UsersIpAddress() {
public function UsersIpAddress()
{
return $this->getRequest()->getIP();
}
@ -112,8 +113,8 @@ Variables can come from your database fields, or custom methods you define on yo
</div>
The variables that can be used in a template vary based on the object currently in [scope](#scope). Scope defines what
object the methods get called on. For the standard `Page.ss` template the scope is the current [api:Page_Controller]
class. This object gives you access to all the database fields on [api:Page_Controller], its corresponding [api:Page]
object the methods get called on. For the standard `Page.ss` template the scope is the current [api:PageController]
class. This object gives you access to all the database fields on [api:PageController], its corresponding [api:Page]
record and any subclasses of those two.
**mysite/code/Layout/Page.ss**
@ -407,12 +408,12 @@ In the `<% loop %>` section, we saw an example of two **scopes**. Outside the `<
the scope of the top level `Page`. But inside the loop, we were in the scope of an item in the list (i.e the `Child`)
The scope determines where the value comes from when you refer to a variable. Typically the outer scope of a `Page.ss`
layout template is the [api:Page_Controller] that is currently being rendered.
layout template is the [api:PageController] that is currently being rendered.
When the scope is a `Page_Controller` it will automatically also look up any methods in the corresponding `Page` data
When the scope is a `PageController` it will automatically also look up any methods in the corresponding `Page` data
record. In the case of `$Title` the flow looks like
$Title --> [Looks up: Current Page_Controller and parent classes] --> [Looks up: Current Page and parent classes]
$Title --> [Looks up: Current PageController and parent classes] --> [Looks up: Current Page and parent classes]
The list of variables you could use in your template is the total of all the methods in the current scope object, parent
classes of the current scope object, and any [api:Extension] instances you have.

View File

@ -15,7 +15,7 @@ scope, and you can specify additional static methods to be available globally in
<div class="notice" markdown="1">
Want a quick way of knowing what scope you're in? Try putting `$ClassName` in your template. You should see a string
such as `Page` of the object that's in scope. The methods you can call on that object then are any functions, database
properties or relations on the `Page` class, `Page_Controller` class as well as anything from their subclasses **or**
properties or relations on the `Page` class, `PageController` class as well as anything from their subclasses **or**
extensions.
</div>

View File

@ -11,79 +11,88 @@ subclasses).
The following will render the given data into a template. Given the template:
**mysite/templates/Coach_Message.ss**
:::ss
<strong>$Name</strong> is the $Role on our team.
```ss
<strong>$Name</strong> is the $Role on our team.
```
Our application code can render into that view using `renderWith`. This method is called on the [api:ViewableData]
instance with a template name or an array of templates to render.
**mysite/code/Page.php**
:::php
$arrayData = new ArrayData(array(
'Name' => 'John',
'Role' => 'Head Coach'
));
```php
$arrayData = new ArrayData(array(
'Name' => 'John',
'Role' => 'Head Coach'
));
echo $arrayData->renderWith('Coach_Message');
echo $arrayData->renderWith('Coach_Message');
// returns "<strong>John</strong> is the Head Coach on our team."
// returns "<strong>John</strong> is the Head Coach on our team."
```
<div class="info" markdown="1">
Most classes in SilverStripe you want in your template extend `ViewableData` and allow you to call `renderWith`. This
includes [api:Controller], [api:FormField] and [api:DataObject] instances.
</div>
:::php
$controller->renderWith(array("MyController", "MyBaseController"));
```php
$controller->renderWith(array('MyController', 'MyBaseController'));
Member::currentUser()->renderWith('Member_Profile');
Member::currentUser()->renderWith('Member_Profile');
```
`renderWith` can be used to override the default template process. For instance, to provide an ajax version of a
template.
:::php
<?php
```php
<?php
class Page_Controller extends ContentController {
use SilverStripe\CMS\Controllers\ContentController;
private static $allowed_actions = array('iwantmyajax');
class PageController extends ContentController
{
private static $allowed_actions = array('iwantmyajax');
public function iwantmyajax() {
if(Director::is_ajax()) {
return $this->renderWith("AjaxTemplate");
} else {
return $this->httpError(404);
}
}
}
public function iwantmyajax()
{
if (Director::is_ajax()) {
return $this->renderWith('AjaxTemplate');
} else {
return $this->httpError(404);
}
}
}
```
Any data you want to render into the template that does not extend `ViewableData` should be wrapped in an object that
does, such as `ArrayData` or `ArrayList`.
:::php
<?php
```php
<?php
class Page_Controller extends ContentController {
use SilverStripe\CMS\Controllers\ContentController;
..
class PageController extends ContentController
{
// ..
public function iwantmyajax()
{
if (Director::is_ajax()) {
$experience = new ArrayList();
$experience->push(new ArrayData(array(
'Title' => 'First Job'
)));
public function iwantmyajax() {
if(Director::is_ajax()) {
$experience = new ArrayList();
$experience->push(new ArrayData(array(
'Title' => 'First Job'
)));
return $this->customise(new ArrayData(array(
'Name' => 'John',
'Role' => 'Head Coach',
'Experience' => $experience
)))->renderWith("AjaxTemplate");
} else {
return $this->httpError(404);
}
}
}
return $this->customise(new ArrayData(array(
'Name' => 'John',
'Role' => 'Head Coach',
'Experience' => $experience
)))->renderWith('AjaxTemplate');
} else {
return $this->httpError(404);
}
}
}
```

View File

@ -34,7 +34,7 @@ at http://yoursite.com/teams/ and the `players` custom action is at http://yours
<div class="info" markdown="1">
If you're using the `cms` module with and dealing with `Page` objects then for your custom `Page Type` controllers you
would extend `ContentController` or `Page_Controller`. You don't need to define the routes value as the `cms` handles
would extend `ContentController` or `PageController`. You don't need to define the routes value as the `cms` handles
routing.
</div>

View File

@ -4,11 +4,11 @@ summary: A more in depth look at how to map requests to particular controllers a
# Routing
Routing is the process of mapping URL's to [api:Controllers] and actions. In the introduction we defined a new custom route
for our `TeamsController` mapping any `teams` URL to our `TeamsController`
for our `TeamController` mapping any `teams` URL to our `TeamController`
<div class="info" markdown="1">
If you're using the `cms` module with and dealing with `Page` objects then for your custom `Page Type` controllers you
would extend `ContentController` or `Page_Controller`. You don't need to define the routes value as the `cms` handles
would extend `ContentController` or `PageController`. You don't need to define the routes value as the `cms` handles
routing.
</div>
@ -17,16 +17,17 @@ These routes by standard, go into a `routes.yml` file in your applications `_con
**mysite/_config/routes.yml**
:::yml
---
Name: mysiteroutes
After: framework/routes#coreroutes
---
Director:
rules:
'teams//$Action/$ID/$Name': 'TeamController'
'player/': 'PlayerController'
'': 'HomeController'
```yml
---
Name: mysiteroutes
After: framework/routes#coreroutes
---
Director:
rules:
'teams//$Action/$ID/$Name': 'TeamController'
'player/': 'PlayerController'
'': 'HomeController'
```
<div class="notice" markdown="1">
To understand the syntax for the `routes.yml` file better, read the [Configuration](../configuration) documentation.
@ -34,8 +35,9 @@ To understand the syntax for the `routes.yml` file better, read the [Configurati
## Parameters
:::yml
'teams//$Action/$ID/$Name': 'TeamController'
```yml
'teams//$Action/$ID/$Name': 'TeamController'
```
This route has defined that any URL beginning with `team` should create, and be handled by a `TeamController` instance.
@ -49,49 +51,49 @@ All Controllers have access to `$this->getRequest()` for the request object and
Here is what those parameters would look like for certain requests
:::php
// GET /teams/
```php
// GET /teams/
print_r($this->getRequest()->params());
print_r($this->getRequest()->params());
// Array
// (
// [Action] => null
// [ID] => null
// [Name] => null
// )
// Array
// (
// [Action] => null
// [ID] => null
// [Name] => null
// )
// GET /teams/players/
// GET /teams/players/
print_r($this->getRequest()->params());
print_r($this->getRequest()->params());
// Array
// (
// [Action] => 'players'
// [ID] => null
// [Name] => null
// )
// Array
// (
// [Action] => 'players'
// [ID] => null
// [Name] => null
// )
// GET /teams/players/1
// GET /teams/players/1
print_r($this->getRequest()->params());
print_r($this->getRequest()->params());
// Array
// (
// [Action] => 'players'
// [ID] => 1
// [Name] => null
// )
// Array
// (
// [Action] => 'players'
// [ID] => 1
// [Name] => null
// )
```
You can also fetch one parameter at a time.
:::php
// GET /teams/players/1/
echo $this->getRequest()->param('ID');
// returns '1'
```php
// GET /teams/players/1/
echo $this->getRequest()->param('ID');
// returns '1'
```
## URL Patterns
@ -108,26 +110,29 @@ A rule must always start with alphabetical ([A-Za-z]) characters or a $Variable
| `!` | **Require Variable** - Placing this after a parameter variable requires data to be present for the rule to match |
| `//` | **Shift Point** - Declares that only variables denoted with a $ are parsed into the $params AFTER this point in the regex |
:::yml
'teams/$Action/$ID/$OtherID': 'TeamController'
```yml
'teams/$Action/$ID/$OtherID': 'TeamController'
# /teams/
# /teams/players/
# /teams/
# /teams/
# /teams/players/
# /teams/
```
Standard URL handler syntax. For any URL that contains 'team' this rule will match and hand over execution to the
matching controller. The `TeamsController` is passed an optional action, id and other id parameters to do any more
decision making.
:::yml
'teams/$Action!/$ID!/': 'TeamController'
```yml
'teams/$Action!/$ID!/': 'TeamController'
```
This does the same matching as the previous example, any URL starting with `teams` will look at this rule **but** both
`$Action` and `$ID` are required. Any requests to `team/` will result in a `404` error rather than being handed off to
the `TeamController`.
:::yml
`admin/help//$Action/$ID`: 'AdminHelp'
```yml
'admin/help//$Action/$ID: 'AdminHelp'
```
Match an url starting with `/admin/help/`, but don't include `/help/` as part of the action (the shift point is set to
start parsing variables and the appropriate controller action AFTER the `//`).
@ -152,19 +157,22 @@ This is useful when you want to provide custom actions for the mapping of `teams
**mysite/code/controllers/TeamController.php**
:::php
<?php
```
<?php
class TeamController extends Controller {
use SilverStripe\Control\Controller;
private static $allowed_actions = array(
'payroll'
);
class TeamController extends Controller
{
private static $allowed_actions = array(
'payroll'
);
private static $url_handlers = array(
'staff/$ID/$Name' => 'payroll',
'coach/$ID/$Name' => 'payroll'
);
private static $url_handlers = array(
'staff/$ID/$Name' => 'payroll',
'coach/$ID/$Name' => 'payroll'
);
```
The syntax for the `$url_handlers` array users the same pattern matches as the `YAML` configuration rules.
@ -175,28 +183,34 @@ class specifies the URL pattern in `$url_handlers`. Notice that it defines 5
parameters.
:::php
class FeedController extends ContentController {
```php
use SilverStripe\CMS\Controllers\ContentController;
private static $allowed_actions = array('go');
private static $url_handlers = array(
'go/$UserName/$AuthToken/$Timestamp/$OutputType/$DeleteMode' => 'go'
);
public function go() {
$this->validateUser(
$this->getRequest()->param('UserName'),
$this->getRequest()->param('AuthToken')
);
/* more processing goes here */
}
class FeedController extends ContentController
{
private static $allowed_actions = array('go');
private static $url_handlers = array(
'go/$UserName/$AuthToken/$Timestamp/$OutputType/$DeleteMode' => 'go'
);
public function go()
{
$this->validateUser(
$this->getRequest()->param('UserName'),
$this->getRequest()->param('AuthToken')
);
/* more processing goes here */
}
}
The YAML rule, in contrast, is simple. It needs to provide only enough
information for the framework to choose the desired controller.
:::yaml
Director:
rules:
'feed': 'FeedController'
```yml
Director:
rules:
'feed': 'FeedController'
```
## Links

View File

@ -14,56 +14,66 @@ See the [Forms Tutorial](../../tutorials/forms/) for a step by step process of c
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
);
:::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
```php
<?php
class Page_Controller extends ContentController {
private static $allowed_actions = array(
'HelloForm'
);
public function HelloForm() {
$fields = new FieldList(
TextField::create('Name', 'Your Name')
);
use SilverStripe\CMS\Controllers\ContentController;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\RequiredFields;
use SilverStripe\Forms\TextField;
$actions = new FieldList(
FormAction::create("doSayHello")->setTitle("Say hello")
);
class PageController extends ContentController
{
private static $allowed_actions = array(
'HelloForm'
);
public function HelloForm()
{
$fields = new FieldList(
TextField::create('Name', 'Your Name')
);
$required = new RequiredFields('Name');
$actions = new FieldList(
FormAction::create('doSayHello')->setTitle('Say hello')
);
$form = new Form($this, 'HelloForm', $fields, $actions, $required);
return $form;
}
public function doSayHello($data, Form $form) {
$form->sessionMessage('Hello '. $data['Name'], 'success');
$required = new RequiredFields('Name');
return $this->redirectBack();
}
}
$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
```ss
$HelloForm
```
<div class="info" markdown="1">
The examples above use `FormField::create()` instead of the `new` operator (`new FormField()`). These are functionally
@ -80,10 +90,11 @@ the [api:FormActions]. The URL is known as the `$controller` instance will know
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'
);
```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
@ -96,8 +107,9 @@ separately through [api:Form::httpSubmission()].
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);
```php
TextField::create($name, $title, $value);
```
<div class="info" markdown='1'>
A list of the common FormField subclasses is available on the [Common Subclasses](field_types/common_subclasses/) page.
@ -106,48 +118,52 @@ A list of the common FormField subclasses is available on the [Common Subclasses
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')
);
```php
$fields = new FieldList(
TextField::create('Name'),
EmailField::create('Email')
);
$form = new Form($controller, 'MethodName', $fields, ...);
$form = new Form($controller, 'MethodName', $fields, ...);
// or use `setFields`
$form->setFields($fields);
// or use `setFields`
$form->setFields($fields);
// to fetch the current fields..
$fields = $form->getFields();
// to fetch the current fields..
$fields = $form->getFields();
```
A field can be appended to the [api:FieldList].
:::php
$fields = $form->Fields();
```php
$fields = $form->Fields();
// add a field
$fields->push(TextField::create(..));
// add a field
$fields->push(TextField::create(/* ... */));
// insert a field before another one
$fields->insertBefore(TextField::create(..), 'Email');
// insert a field before another one
$fields->insertBefore(TextField::create(/* ... */), 'Email');
// insert a field after another one
$fields->insertAfter(TextField::create(..), 'Name');
// insert a tab before the main content tab (used to position tabs in the CMS)
$fields->insertBefore(Tab::create(...), 'Main');
// Note: you need to create and position the new tab prior to adding fields via addFieldToTab()
// insert a field after another one
$fields->insertAfter(TextField::create(/* ... */), 'Name');
// insert a tab before the main content tab (used to position tabs in the CMS)
$fields->insertBefore(Tab::create(/* ... */), 'Main');
// Note: you need to create and position the new tab prior to adding fields via addFieldToTab()
```
Fields can be fetched after they have been added in.
:::php
$email = $form->Fields()->dataFieldByName('Email');
$email->setTitle('Your Email Address');
```php
$email = $form->Fields()->dataFieldByName('Email');
$email->setTitle('Your Email Address');
```
Fields can be removed from the form.
:::php
$form->getFields()->removeByName('Email');
```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`
@ -164,13 +180,14 @@ default `FormField` object has several methods for doing common operations.
Most of the `set` operations will return the object back so methods can be chained.
</div>
:::php
$field = new TextField(..);
```php
$field = new TextField(..);
$field
->setMaxLength(100)
->setAttribute('placeholder', 'Enter a value..')
->setTitle('');
$field
->setMaxLength(100)
->setAttribute('placeholder', 'Enter a value..')
->setTitle('');
```
### Custom Templates
@ -178,61 +195,67 @@ The [api:Form] HTML markup and each of the [api:FormField] instances are rendere
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(..);
```php
$form = new Form(..);
$form->setTemplate('CustomForm');
$form->setTemplate('CustomForm');
// or, for a FormField
$field = new TextField(..);
// or, for a FormField
$field = new TextField(..);
$field->setTemplate('CustomTextField');
$field->setFieldHolderTemplate('CustomTextField_Holder');
$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);
```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(..);
```php
public function MyForm()
{
$fields = new FieldList(/* .. */);
$actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit')
);
$actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit')
);
$form = new Form($controller, 'MyForm', $fields, $actions);
$form = new Form($controller, 'MyForm', $fields, $actions);
// Get the actions
$actions = $form->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.
// 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->push(
FormAction::create('doSecondaryFormAction', 'Another Button')
);
$actions->removeByName('doSubmitForm');
$form->setActions($actions);
$actions->removeByName('doSubmitForm');
$form->setActions($actions);
return $form
}
return $form
}
public function doSubmitForm($data, $form) {
//
}
public function doSubmitForm($data, $form)
{
//
}
public function doSecondaryFormAction($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
@ -252,45 +275,55 @@ 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
```php
<?php
class Page_Controller extends ContentController {
use SilverStripe\CMS\Controllers\ContentController;
use SilverStripe\Forms\EmailField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\TextField;
private static $allowed_actions = array(
'MyForm'
);
class PageController extends ContentController
{
private static $allowed_actions = array(
'MyForm'
);
public function MyForm() {
$fields = new FieldList(
TextField::create('Name'),
EmailField::create('Email')
);
public function MyForm()
{
$fields = new FieldList(
TextField::create('Name'),
EmailField::create('Email')
);
$actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit')
);
$actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit')
);
$form = new Form($controller, 'MyForm', $fields, $actions);
$form = new Form($controller, 'MyForm', $fields, $actions);
return $form
}
return $form
}
public function doSubmitForm($data, $form) {
// Submitted data is available as a map.
echo $data['Name'];
echo $data['Email'];
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();
// 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');
// 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();
}
}
// After dealing with the data you can redirect the user back.
return $this->redirectBack();
}
}
```
## Validation
@ -300,12 +333,14 @@ validating its' own data value.
For more information, see the [Form Validation](validation) documentation.
:::php
$validator = new RequiredFields(array(
'Name', 'Email'
));
```php
$validator = new RequiredFields(array(
'Name',
'Email'
));
$form = new Form($this, 'MyForm', $fields, $actions, $validator);
$form = new Form($this, 'MyForm', $fields, $actions, $validator);
```
## API Documentation

View File

@ -7,43 +7,53 @@ SilverStripe provides server-side form validation out of the box through the [ap
[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
```php
<?php
class Page_Controller extends ContentController {
use SilverStripe\CMS\Controllers\ContentController;
use SilverStripe\Forms\EmailField;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\RequiredFields;
private static $allowed_actions = array(
'MyForm'
);
class PageController extends ContentController
{
private static $allowed_actions = array(
'MyForm'
);
public function MyForm() {
$fields = new FieldList(
TextField::create('Name'),
EmailField::create('Email')
);
public function MyForm()
{
$fields = new FieldList(
TextField::create('Name'),
EmailField::create('Email')
);
$actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit')
);
$actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit')
);
// the fields 'Name' and 'Email' are required.
$required = new RequiredFields(array(
'Name', 'Email'
));
// 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);
// $required can be set as an argument
$form = new Form($controller, 'MyForm', $fields, $actions, $required);
// Or, through a setter.
$form->setValidator($required);
// Or, through a setter.
$form->setValidator($required);
return $form;
}
return $form;
}
public function doSubmitForm($data, $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.
@ -63,15 +73,17 @@ The data value of the `FormField` submitted is not passed into validate. It is s
the `setValue` method.
</div>
:::php
public function validate($validator) {
if($this->Value() == 10) {
$validator->validationError($this->Name(), 'This value cannot be 10');
return false;
}
```php
public function validate($validator)
{
if ((int) $this->Value() === 10) {
$validator->validationError($this->Name(), 'This value cannot be 10');
return false;
}
return true;
}
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. In addition a useful error message must be set on the given validator.
@ -86,80 +98,94 @@ 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**
**mysite/code/CustomNumberField.php**
:::php
<?php
```php
<?php
class CustomNumberField extends TextField {
use SilverStripe\Forms\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
);
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;
} elseif ($this->value > 5 || $this->value < 2) {
$validator->validationError(
$this->name, 'Your number must be between 2 and 5', 'validation', false
);
return false;
}
return false;
}
return true;
}
}
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
```php
<?php
class Page_Controller extends ContentController {
use SilverStripe\CMS\Controllers\ContentController;
use SilverStripe\Forms\EmailField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\TextField;
use SilverStripe\Security\Member;
private static $allowed_actions = array(
'MyForm'
);
class Page_Controller extends ContentController
{
private static $allowed_actions = array(
'MyForm'
);
public function MyForm() {
$fields = new FieldList(
TextField::create('Name'),
EmailField::create('Email')
);
public function MyForm()
{
$fields = new FieldList(
TextField::create('Name'),
EmailField::create('Email')
);
$actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit')
);
$actions = new FieldList(
FormAction::create('doSubmitForm', 'Submit')
);
$form = new Form($controller, 'MyForm', $fields, $actions);
$form = new Form($controller, 'MyForm', $fields, $actions);
return $form;
}
return $form;
}
public function doSubmitForm($data, $form) {
// At this point, RequiredFields->isValid() 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();
public function doSubmitForm($data, $form)
{
// At this point, RequiredFields->isValid() 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');
if ($check) {
$form->addErrorMessage('Email', 'This email already exists', 'bad');
return $this->redirectBack();
}
return $this->redirectBack();
}
$form->sessionMessage("You have been added to our mailing list", 'good');
return $this->redirectBack();
}
}
$form->sessionMessage('You have been added to our mailing list', 'good');
return $this->redirectBack();
}
}
```
## Exempt validation actions
In some cases you might need to disable validation for specific actions. E.g. actions which discard submitted
@ -167,32 +193,32 @@ data may not need to check the validity of the posted content.
You can disable validation on individual using one of two methods:
```php
$actions = new FieldList(
$action = FormAction::create('doSubmitForm', 'Submit')
);
$form = new Form($controller, 'MyForm', $fields, $actions);
:::php
$actions = new FieldList(
$action = FormAction::create('doSubmitForm', 'Submit')
);
$form = new Form($controller, 'MyForm', $fields, $actions);
// Disable actions on the form action themselves
$action->setValidationExempt(true);
// Alternatively, you can whitelist individual actions on the form object by name
$form->setValidationExemptActions(['doSubmitForm']);
// Disable actions on the form action themselves
$action->setValidationExempt(true);
// Alternatively, you can whitelist individual actions on the form object by name
$form->setValidationExemptActions(['doSubmitForm']);
```
## Server-side validation messages
If a `FormField` fails to pass `validate()` the default error message is returned.
:::php
'$Name' is required
```
'$Name' is required
```
Use `setCustomValidationMessage` to provide a custom message.
:::php
$field = new TextField(..);
$field->setCustomValidationMessage('Whoops, looks like you have missed me!');
```php
$field = new TextField(/* .. */);
$field->setCustomValidationMessage('Whoops, looks like you have missed me!');
## JavaScript validation
@ -201,15 +227,15 @@ to provide the information required in order to plug in custom libraries like [P
[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);
```php
$form = new Form(/* .. */);
$form->setAttribute('data-parsley-validate', true);
$field = $fields->dataFieldByName('Name');
$field->setAttribute('required', true);
$field->setAttribute('data-parsley-mincheck', '2');
$field = $fields->dataFieldByName('Name');
$field->setAttribute('required', true);
$field->setAttribute('data-parsley-mincheck', '2');
```
## Model Validation
@ -228,23 +254,26 @@ error message, or a [api:ValidationResult] object containing the list of errors
E.g.
```php
use SilverStripe\Control\Controller;
use SilverStripe\ORM\ValidationException;
:::php
class MyController extends Controller
class MyController extends Controller
{
public function doSave($data, $form)
{
public function doSave($data, $form) {
$success = $this->sendEmail($data);
// Example error handling
if (!$success) {
throw new ValidationException('Sorry, we could not email to that address');
}
// If success
return $this->redirect($this->Link('success'));
$success = $this->sendEmail($data);
// Example error handling
if (!$success) {
throw new ValidationException('Sorry, we could not email to that address');
}
// If success
return $this->redirect($this->Link('success'));
}
}
```
### Validation in the CMS
@ -257,28 +286,36 @@ respect the provided `Validator` and handle displaying error and success respons
Again, custom error messages can be provided through the `FormField`
</div>
:::php
<?php
```php
<?php
class Page extends SiteTree {
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\RequiredFields;
private static $db = array(
'MyRequiredField' => 'Text'
);
class Page extends SiteTree
{
private static $db = array(
'MyRequiredField' => 'Text'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
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'
));
}
$fields->addFieldToTab('Root.Main',
TextField::create('MyRequiredField')->setCustomValidationMessage('You missed me.')
);
}
public function getCMSValidator()
{
return new RequiredFields(array(
'MyRequiredField'
));
}
}
```
## API Documentation

View File

@ -431,7 +431,7 @@ code could be used:
:::php
class GalleryPage extends Page {}
class GalleryPage_Controller extends Page_Controller {
class GalleryPageController extends PageController {
private static $allowed_actions = array('Form');
public function Form() {
$fields = new FieldList(

View File

@ -12,7 +12,7 @@ code for a `Form` is to create it as a subclass to `Form`. Let's look at a examp
:::php
<?php
class Page_Controller extends ContentController {
class PageController extends ContentController {
public function SearchForm() {
$fields = new FieldList(
@ -128,7 +128,7 @@ Our controller will now just have to create a new instance of this form object.
:::php
<?php
class Page_Controller extends ContentController {
class PageController extends ContentController {
private static $allowed_actions = array(
'SearchForm',

View File

@ -8,7 +8,7 @@ Let's start by defining a new `ContactPage` page type:
<?php
class ContactPage extends Page {
}
class ContactPage_Controller extends Page_Controller {
class ContactPageController extends PageController {
private static $allowed_actions = array('Form');
public function Form() {
$fields = new FieldList(
@ -61,7 +61,7 @@ If you now create a ContactPage in the CMS (making sure you have rebuilt the dat
Now that we have a contact form, we need some way of collecting the data submitted. We do this by creating a function on the controller with the same name as the form action. In this case, we create the function 'submit' on the ContactPage_Controller class.
:::php
class ContactPage_Controller extends Page_Controller {
class ContactPageController extends PageController {
private static $allowed_actions = array('Form');
public function Form() {
// ...

View File

@ -83,7 +83,7 @@ If your caching logic is complex or re-usable, you can define a method on your c
fragment.
For example, a block that shows a collection of rotating slides needs to update whenever the relationship
`Page::$many_many = array('Slides' => 'Slide')` changes. In Page_Controller:
`Page::$many_many = array('Slides' => 'Slide')` changes. In `PageController`:
:::php
@ -151,7 +151,7 @@ heavy load:
<% cached 'blogstatistics', $Blog.ID if $HighLoad %>
By adding a `HighLoad` function to your `Page_Controller`, you could enable or disable caching dynamically.
By adding a `HighLoad` function to your `PageController`, you could enable or disable caching dynamically.
To cache the contents of a page for all anonymous users, but dynamically calculate the contents for logged in members,
use something like:

View File

@ -14,27 +14,35 @@ The simple usage, Permission::check("PERM_CODE") will detect if the currently lo
**Group ACLs**
* Call **Permission::check("MY_PERMISSION_CODE")** to see if the current user has MY_PERMISSION_CODE.
* MY_PERMISSION_CODE can be loaded into the Security admin on the appropriate group, using the "Permissions" tab.
* Call **Permission::check('MY_PERMISSION_CODE')** to see if the current user has MY_PERMISSION_CODE.
* `MY_PERMISSION_CODE` can be loaded into the Security admin on the appropriate group, using the "Permissions" tab.
## PermissionProvider
[api:PermissionProvider] is an interface which lets you define a method *providePermissions()*.
This method should return a map of permission code names with a human readable explanation of its purpose.
:::php
class Page_Controller implements PermissionProvider {
public function init() {
parent::init();
if(!Permission::check("VIEW_SITE")) Security::permissionFailure();
}
```php
use SilverStripe\Security\PermissionProvider;
public function providePermissions() {
return array(
"VIEW_SITE" => "Access the site",
);
}
}
class PageController implements PermissionProvider
{
public function init()
{
parent::init();
if (!Permission::check('VIEW_SITE')) {
Security::permissionFailure();
}
}
public function providePermissions()
{
return array(
'VIEW_SITE' => 'Access the site'
);
}
}
```
This can then be used to add a dropdown for permission codes to the security panel. Permission::get_all_codes() will be
@ -89,10 +97,11 @@ This works much like ADMIN permissions (see above)
You can check if a user has access to the CMS by simply performing a check against `CMS_ACCESS`.
:::php
if (Permission::checkMember($member, 'CMS_ACCESS')) {
//user can access the CMS
}
```php
if (Permission::checkMember($member, 'CMS_ACCESS')) {
//user can access the CMS
}
```
Internally, this checks that the user has any of the defined `CMS_ACCESS_*` permissions.

View File

@ -57,7 +57,7 @@ You can use [api:RSSFeed] to easily create a feed showing your latest Page updat
..
class Page_Controller extends ContentController {
class PageController extends ContentController {
private static $allowed_actions = array(
'rss'
@ -118,7 +118,7 @@ Then in our controller, we add a new action which returns a the XML list of `Pla
:::php
<?php
class Page_Controller extends ContentController {
class PageController extends ContentController {
private static $allowed_actions = array(
'players'

View File

@ -79,9 +79,9 @@ the `$fields` constructor parameter.
:::php
<?php
..
// ..
class Page_Controller extends ContentController {
class PageController extends ContentController {
public function SearchForm() {
$context = singleton('MyDataObject')->getCustomSearchContext();

View File

@ -59,7 +59,7 @@ authorised users, the following should be considered:
:::php
class Page_Controller extends ContentController {
class PageController extends ContentController {
public function init() {
parent::init();
// Whitelist any protected files on this page for the current user
@ -88,7 +88,7 @@ authorised users, the following should be considered:
:::php
class Page_Controller extends ContentController {
class PageController extends ContentController {
public function init() {
parent::init();
// Whitelist any protected files on this page for the current user

View File

@ -23,7 +23,7 @@ use SilverStripe\Core\Object;
* If you want to implement a FileField into a form element, you need to pass it an array of source data.
*
* <code>
* class ExampleForm_Controller extends Page_Controller {
* class ExampleFormController extends PageController {
*
* function Form() {
* $fields = new FieldList(

View File

@ -518,7 +518,7 @@ class Security extends Controller implements TemplateGlobalProvider
$tmpPage = new SiteTree();
$tmpPage->Title = $title;
/** @skipUpgrade */
$tmpPage->URLSegment = "Security";
$tmpPage->URLSegment = 'Security';
// Disable ID-based caching of the log-in page by making it a random number
$tmpPage->ID = -1 * rand(1, 10000000);