diff --git a/admin/client/src/components/FormBuilder/FormBuilder.js b/admin/client/src/components/FormBuilder/FormBuilder.js index d72fd39a8..7e9ae764a 100644 --- a/admin/client/src/components/FormBuilder/FormBuilder.js +++ b/admin/client/src/components/FormBuilder/FormBuilder.js @@ -119,7 +119,6 @@ export class FormBuilderComponent extends SilverStripeComponent { if (typeof formSchema.id !== 'undefined') { const defaultData = { - ID: formSchema.schema.id, SecurityID: this.props.config.SecurityID, }; diff --git a/admin/client/src/components/HiddenField/HiddenField.js b/admin/client/src/components/HiddenField/HiddenField.js index 4abeff011..3b5fc5785 100644 --- a/admin/client/src/components/HiddenField/HiddenField.js +++ b/admin/client/src/components/HiddenField/HiddenField.js @@ -20,7 +20,7 @@ class HiddenField extends SilverStripeComponent { getInputProps() { return { className: ['hidden', this.props.extraClass].join(' '), - id: this.props.name, + id: this.props.id, name: this.props.name, onChange: this.props.onChange, type: 'hidden', @@ -42,7 +42,7 @@ HiddenField.propTypes = { extraClass: React.PropTypes.string, name: React.PropTypes.string.isRequired, onChange: React.PropTypes.func, - value: React.PropTypes.string, + value: React.PropTypes.any, }; export default HiddenField; diff --git a/admin/client/src/lib/backend.js b/admin/client/src/lib/backend.js index c8446ee72..6cee78b1c 100644 --- a/admin/client/src/lib/backend.js +++ b/admin/client/src/lib/backend.js @@ -84,6 +84,9 @@ class Backend { * endpoint({one: 1, two: 2, three: 3}); * // Calls http://example.org/1/2?three=3 with a HTTP body of '{"two": 2}' * ``` + * + * Custom HTTP headers can be passed to the second parameter of the endpoint + * * **urlReplacement** * * Can be used to replace template placeholders in the 'url' endpoint spec. @@ -274,11 +277,11 @@ class Backend { } ); - return (data = {}) => { - const headers = { + return (data = {}, headers = {}) => { + const mergedHeaders = Object.assign({}, headers, { Accept: refinedSpec.responseFormat, 'Content-Type': refinedSpec.payloadFormat, - }; + }); const mergedData = merge.recursive({}, refinedSpec.defaultData, data); @@ -302,8 +305,8 @@ class Backend { ); const args = refinedSpec.method.toLowerCase() === 'get' - ? [url, headers] - : [url, encodedData, headers]; + ? [url, mergedHeaders] + : [url, encodedData, mergedHeaders]; return this[refinedSpec.method.toLowerCase()](...args) .then(parseResponse); diff --git a/admin/client/src/state/forms/actions.js b/admin/client/src/state/forms/actions.js index 5c64c36e4..06f4d9c66 100644 --- a/admin/client/src/state/forms/actions.js +++ b/admin/client/src/state/forms/actions.js @@ -62,7 +62,7 @@ export function submitForm(submitApi, formId, fieldValues) { payload: {}, }); - submitApi(Object.assign({ ID: formId }, fieldValues)) + submitApi(Object.assign({ ID: formId }, fieldValues), { 'X-Formschema-Request': 'state' }) .then(() => { dispatch({ type: ACTION_TYPES.SUBMIT_FORM_SUCCESS, diff --git a/admin/code/CampaignAdmin.php b/admin/code/CampaignAdmin.php index d3866f830..921d275f7 100644 --- a/admin/code/CampaignAdmin.php +++ b/admin/code/CampaignAdmin.php @@ -25,12 +25,13 @@ class CampaignAdmin extends LeftAndMain implements PermissionProvider { private static $menu_title = 'Campaigns'; + private static $tree_class = 'ChangeSet'; + private static $url_handlers = [ 'GET sets' => 'readCampaigns', 'POST set/$ID/publish' => 'publishCampaign', 'POST set/$ID' => 'createCampaign', 'GET set/$ID/$Name' => 'readCampaign', - 'POST $ID' => 'updateCampaign', 'DELETE set/$ID' => 'deleteCampaign', ]; @@ -367,25 +368,6 @@ JSON; } } - /** - * REST endpoint to update a campaign. - * - * @param SS_HTTPRequest $request - * - * @return SS_HTTPResponse - */ - public function updateCampaign(SS_HTTPRequest $request) { - $id = $request->param('ID'); - - $response = new SS_HTTPResponse(); - $response->addHeader('Content-Type', 'application/json'); - $response->setBody(Convert::raw2json(['campaign' => 'update'])); - - // TODO Implement data update and permission checks - - return $response; - } - /** * REST endpoint to delete a campaign. * @@ -460,20 +442,51 @@ JSON; ))->addHeader('Content-Type', 'application/json'); } + /** + * Url handler for edit form + * + * @param SS_HTTPRequest $request + * @return Form + */ + public function DetailEditForm($request) { + // Get ID either from posted back value, or url parameter + $id = $request->param('ID') ?: $request->postVar('ID'); + return $this->getDetailEditForm($id); + } + /** * @todo Use GridFieldDetailForm once it can handle structured data and form schemas * + * @param int $id * @return Form */ public function getDetailEditForm($id) { - return Form::create( + // Get record-specific fields + $record = null; + if($id) { + $record = ChangeSet::get()->byId($id); + } + if(!$record) { + $record = ChangeSet::singleton(); + } + $fields = $record->getCMSFields(); + + // Add standard fields + $fields->push(HiddenField::create('ID')); + $form = Form::create( $this, 'DetailEditForm', - ChangeSet::get()->byId($id)->getCMSFields(), + $fields, FieldList::create( FormAction::create('save', 'Save') ) ); + // Configure form to respond to validation errors with form schema + // if requested via react. + $form->setValidationResponseCallback(function() use ($form) { + return $this->getSchemaResponse($form); + }); + return $form; } /** diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index e5a4b0c36..757938b9c 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -267,6 +267,24 @@ class LeftAndMain extends Controller implements PermissionProvider { return $response; } + /** + * Given a form, generate a response containing the requested form + * schema if X-Formschema-Request header is set. + * + * @param Form $form + * @return SS_HTTPResponse + */ + protected function getSchemaResponse($form) { + $request = $this->getRequest(); + if($request->getHeader('X-Formschema-Request')) { + $data = $this->getSchemaForForm($form); + $response = new SS_HTTPResponse(Convert::raw2json($data)); + $response->addHeader('Content-Type', 'application/json'); + return $response; + } + return null; + } + /** * Returns a representation of the provided {@link Form} as structured data, * based on the request data. @@ -1137,6 +1155,10 @@ class LeftAndMain extends Controller implements PermissionProvider { /** * Save handler + * + * @param array $data + * @param Form $form + * @return SS_HTTPResponse */ public function save($data, $form) { $request = $this->getRequest(); @@ -1159,9 +1181,9 @@ class LeftAndMain extends Controller implements PermissionProvider { $this->extend('onAfterSave', $record); $this->setCurrentPageID($record->ID); - $this->getResponse()->addHeader('X-Status', rawurlencode(_t('LeftAndMain.SAVEDUP', 'Saved.'))); - + $message = _t('LeftAndMain.SAVEDUP', 'Saved.'); if($request->getHeader('X-Formschema-Request')) { + $form->setMessage($message, 'good'); $data = $this->getSchemaForForm($form); $response = new SS_HTTPResponse(Convert::raw2json($data)); $response->addHeader('Content-Type', 'application/json'); @@ -1169,6 +1191,7 @@ class LeftAndMain extends Controller implements PermissionProvider { $response = $this->getResponseNegotiator()->respond($request); } + $response->addHeader('X-Status', rawurlencode($message)); return $response; }