API New CMSForm class to allow validation responses in CMS (fixes #1777)

Thanks to @willmorgan for getting this discussion started
(see https://github.com/silverstripe/sapphire/pull/1814).
This commit is contained in:
Ingo Schommer 2013-05-10 15:00:13 +02:00
parent 6ca27cd257
commit bfff11eb9c
7 changed files with 98 additions and 29 deletions

40
admin/code/CMSForm.php Normal file
View File

@ -0,0 +1,40 @@
<?php
/**
* Deals with special form handling in CMS, mainly around {@link PjaxResponseNegotiator}
*/
class CMSForm extends Form {
/**
* Route validation error responses through response negotiator,
* so they return the correct markup as expected by the requesting client.
*/
protected function getValidationErrorResponse() {
$request = $this->getRequest();
$negotiator = $this->getResponseNegotiator();
if($request->isAjax() && $negotiator) {
$negotiator->setResponse(new SS_HTTPResponse($this));
return $negotiator->respond($request);
} else {
return parent::getValidationErrorResponse();
}
}
/**
* Sets the response negotiator
* @param ResponseNegotiator $negotiator The response negotiator to use
* @return Form The current form
*/
public function setResponseNegotiator($negotiator) {
$this->responseNegotiator = $negotiator;
return $this;
}
/**
* Gets the current response negotiator
* @return ResponseNegotiator|null
*/
public function getResponseNegotiator() {
return $this->responseNegotiator;
}
}

View File

@ -1211,7 +1211,10 @@ class LeftAndMain extends Controller implements PermissionProvider {
$actionsFlattened = $actions->dataFields();
if($actionsFlattened) foreach($actionsFlattened as $action) $action->setUseButtonTag(true);
$form = new Form($this, "EditForm", $fields, $actions);
$form = CMSForm::create(
$this, "EditForm", $fields, $actions
)->setHTMLID('Form_EditForm');
$form->setResponseNegotiator($this->getResponseNegotiator());
$form->addExtraClass('cms-edit-form');
$form->loadDataFrom($record);
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
@ -1264,7 +1267,7 @@ class LeftAndMain extends Controller implements PermissionProvider {
* @return Form
*/
public function EmptyForm() {
$form = new Form(
$form = CMSForm::create(
$this,
"EditForm",
new FieldList(
@ -1282,7 +1285,8 @@ class LeftAndMain extends Controller implements PermissionProvider {
// )
),
new FieldList()
);
)->setHTMLID('Form_EditForm');
$form->setResponseNegotiator($this->getResponseNegotiator());
$form->unsetValidator();
$form->addExtraClass('cms-edit-form');
$form->addExtraClass('root-form');

View File

@ -155,12 +155,13 @@ abstract class ModelAdmin extends LeftAndMain {
$listField->getConfig()->getComponentByType('GridFieldDetailForm')->setValidator($detailValidator);
}
$form = new Form(
$form = CMSForm::create(
$this,
'EditForm',
new FieldList($listField),
new FieldList()
);
)->setHTMLID('Form_EditForm');
$form->setResponseNegotiator($this->getResponseNegotiator());
$form->addExtraClass('cms-edit-form cms-panel-padded center');
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
$editFormAction = Controller::join_links($this->Link($this->sanitiseClassName($this->modelClass)), 'EditForm');

View File

@ -154,12 +154,13 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
$actions = new FieldList();
$form = new Form(
$form = CMSForm::create(
$this,
'EditForm',
$fields,
$actions
);
)->setHTMLID('Form_EditForm');
$form->setResponseNegotiator($this->getResponseNegotiator());
$form->addExtraClass('cms-edit-form');
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
// Tab nav in CMS is rendered through separate template

View File

@ -475,3 +475,6 @@ you can enable those warnings and future-proof your code already.
by `updateCMSFields`. See the [DataExtension Reference](/reference/dataextension) for more information.
* Magic quotes is now deprecated. Will trigger user_error on live sites, as well as an error on new installs
* Support for Apache 1.x is removed.
* Forms created in the CMS should now be instances of a new `CMSForm` class,
and have the CMS controller's response negotiator passed into them.
Example: `$form = new CMSForm(...); $form->setResponseNegotiator($this->getResponseNegotiator());`

View File

@ -106,12 +106,15 @@ In order to set the correct layout classes, we also need a custom template.
To obey the inheritance chain, we use `$this->getTemplatesWithSuffix('_EditForm')` for
selecting the most specific template (so `MyAdmin_EditForm.ss`, if it exists).
The form should be of type `CMSForm` rather than `Form`, since it allows the use
of a `PjaxResponseNegotiator` to handle its display.
Basic example form in a CMS controller subclass:
:::php
class MyAdmin extends LeftAndMain {
function getEditForm() {
$form = new Form(
return CMSForm::create(
$this,
'EditForm',
new FieldSet(
@ -125,11 +128,14 @@ Basic example form in a CMS controller subclass:
new FieldSet(
FormAction::create('doSubmit')
)
);
// Required for correct CMS layout
$form->addExtraClass('cms-edit-form');
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
return $form;
)
// JS and CSS use this identifier
->setHTMLID('Form_EditForm')
// Render correct responses on validation errors
->setResponseNegotiator($this->getResponseNegotiator());
// Required for correct CMS layout
->addExtraClass('cms-edit-form')
->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
}
}

View File

@ -344,9 +344,36 @@ class Form extends RequestHandler {
// Validate the form
if(!$this->validate()) {
if(Director::is_ajax()) {
// Special case for legacy Validator.js implementation (assumes eval'ed javascript collected through
// FormResponse)
return $this->getValidationErrorResponse();
}
// First, try a handler method on the controller (has been checked for allowed_actions above already)
if($this->controller->hasMethod($funcName)) {
return $this->controller->$funcName($vars, $this, $request);
// Otherwise, try a handler method on the form object.
} elseif($this->hasMethod($funcName)) {
return $this->$funcName($vars, $this, $request);
} elseif($field = $this->checkFieldsForAction($this->Fields(), $funcName)) {
return $field->$funcName($vars, $this, $request);
}
return $this->httpError(404);
}
/**
* Returns the appropriate response up the controller chain
* if {@link validate()} fails (which is checked prior to executing any form actions).
* By default, returns different views for ajax/non-ajax request, and
* handles 'appliction/json' requests with a JSON object containing the error messages.
* Behaviour can be influenced by setting {@link $redirectToFormOnValidationError}.
*
* @return SS_HTTPResponse|string
*/
protected function getValidationErrorResponse() {
$request = $this->getRequest();
if($request->isAjax()) {
// Special case for legacy Validator.js implementation
// (assumes eval'ed javascript collected through FormResponse)
$acceptType = $request->getHeader('Accept');
if(strpos($acceptType, 'application/json') !== FALSE) {
// Send validation errors back as JSON with a flag at the start
@ -372,19 +399,6 @@ class Form extends RequestHandler {
}
return $this->controller->redirectBack();
}
}
// First, try a handler method on the controller (has been checked for allowed_actions above already)
if($this->controller->hasMethod($funcName)) {
return $this->controller->$funcName($vars, $this, $request);
// Otherwise, try a handler method on the form object.
} elseif($this->hasMethod($funcName)) {
return $this->$funcName($vars, $this, $request);
} elseif($field = $this->checkFieldsForAction($this->Fields(), $funcName)) {
return $field->$funcName($vars, $this, $request);
}
return $this->httpError(404);
}
/**