Skip to content
Browse files

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

Thanks to @willmorgan for getting this discussion started
(see #1814).
  • Loading branch information...
1 parent 6ca27cd commit bfff11eb9c45d6ceed8a399b82a10449df9bc748 @chillu chillu committed
View
40 admin/code/CMSForm.php
@@ -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
10 admin/code/LeftAndMain.php
@@ -1211,7 +1211,10 @@ public function getEditForm($id = null, $fields = null) {
$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 @@ public function getEditForm($id = null, $fields = null) {
* @return Form
*/
public function EmptyForm() {
- $form = new Form(
+ $form = CMSForm::create(
$this,
"EditForm",
new FieldList(
@@ -1282,7 +1285,8 @@ public function EmptyForm() {
// )
),
new FieldList()
- );
+ )->setHTMLID('Form_EditForm');
+ $form->setResponseNegotiator($this->getResponseNegotiator());
$form->unsetValidator();
$form->addExtraClass('cms-edit-form');
$form->addExtraClass('root-form');
View
5 admin/code/ModelAdmin.php
@@ -155,12 +155,13 @@ public function getEditForm($id = null, $fields = null) {
$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
5 admin/code/SecurityAdmin.php
@@ -154,12 +154,13 @@ public function getEditForm($id = null, $fields = null) {
$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
3 docs/en/changelogs/3.1.0.md
@@ -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
18 docs/en/reference/cms-architecture.md
@@ -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
46 forms/Form.php
@@ -344,9 +344,36 @@ public function httpSubmission($request) {
// 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 @@ public function httpSubmission($request) {
}
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);
}
/**

0 comments on commit bfff11e

Please sign in to comment.
Something went wrong with that request. Please try again.