diff --git a/admin/code/CampaignAdmin.php b/admin/code/CampaignAdmin.php index 37b7e0897..572b3d7ad 100644 --- a/admin/code/CampaignAdmin.php +++ b/admin/code/CampaignAdmin.php @@ -39,6 +39,17 @@ class CampaignAdmin extends LeftAndMain implements PermissionProvider { } public function getClientConfig() { + return array_merge(parent::getClientConfig(), [ + 'forms' => [ + // TODO Use schemaUrl instead + 'editForm' => [ + 'schemaUrl' => $this->Link('schema/EditForm') + ] + ] + ]); + } + + public function schema($request) { // TODO Hardcoding schema until we can get GridField to generate a schema dynamically $json = << [ - 'editForm' => [ - // TODO Use schemaUrl instead - 'schema' => json_decode($json) - ] - ] - ]); - } - - public function getEditForm($id = null, $fields = null) { - return ''; + $formName = $request->param('ID'); + if($formName == 'EditForm') { + $response = $this->getResponse(); + $response->addHeader('Content-Type', 'application/json'); + $response->setBody($json); + return $response; + } else { + return parent::schema($request); + } } /** diff --git a/admin/javascript/src/boot/index.js b/admin/javascript/src/boot/index.js index 49afbb79e..529088079 100644 --- a/admin/javascript/src/boot/index.js +++ b/admin/javascript/src/boot/index.js @@ -6,9 +6,11 @@ import reducerRegister from 'reducer-register'; import * as configActions from '../state/config/actions'; import ConfigReducer from '../state/config/reducer'; +import SchemaReducer from '../state/schema/reducer'; function appBoot() { reducerRegister.add('config', ConfigReducer); + reducerRegister.add('schemas', SchemaReducer); const initialState = {}; const rootReducer = combineReducers(reducerRegister.getAll()); diff --git a/admin/javascript/src/components/form-builder/README.md b/admin/javascript/src/components/form-builder/README.md index 0d793d617..f91c2237e 100644 --- a/admin/javascript/src/components/form-builder/README.md +++ b/admin/javascript/src/components/form-builder/README.md @@ -14,7 +14,7 @@ Actions the component can dispatch. This should include but is not limited to: An action to call when the response from fetching schema data is returned. This would normally be a simple action to set the store's `schema` key to the returned data. -### formSchemaUrl +### schemaUrl The schema URL where the form will be scaffolded from e.g. '/admin/pages/schema/1'. diff --git a/admin/javascript/src/components/form-builder/index.js b/admin/javascript/src/components/form-builder/index.js index 1e15a5bfb..980c4fbde 100644 --- a/admin/javascript/src/components/form-builder/index.js +++ b/admin/javascript/src/components/form-builder/index.js @@ -6,6 +6,8 @@ import * as schemaActions from '../../state/schema/actions'; import SilverStripeComponent from '../../SilverStripeComponent'; import FormComponent from '../form'; import TextField from '../text-field'; +import HiddenField from '../hidden-field'; +import GridField from '../../sections/grid-field'; // Using this to map field types to components until we implement dependency injection. var fakeInjector = { @@ -14,7 +16,9 @@ var fakeInjector = { * Components registered with the fake DI container. */ components: { - 'TextField': TextField + 'TextField': TextField, + 'GridField': GridField, + 'HiddenField': HiddenField }, /** @@ -42,7 +46,7 @@ var fakeInjector = { case 'String': return this.components.TextField; case 'Hidden': - return this.components.TextField; + return this.components.HiddenField; case 'Text': // Textarea field (not implemented) return null; @@ -73,6 +77,8 @@ var fakeInjector = { case 'Boolean': // Checkbox field (not implemented) return null; + case 'Custom': + return this.components.GridField; default: return null; } @@ -117,7 +123,7 @@ export class FormBuilderComponent extends SilverStripeComponent { this.formSchemaPromise = $.ajax({ method: 'GET', headers: { 'X-FormSchema-Request': headerValues.join() }, - url: this.props.formSchemaUrl + url: this.props.schemaUrl }).done((data, status, xhr) => { this.isFetching = false; this.props.actions.setSchema(data); @@ -128,17 +134,6 @@ export class FormBuilderComponent extends SilverStripeComponent { return this.formSchemaPromise; } - /** - * Gets form schema for the FormBuilder. - * - * @return object|undefined - */ - getFormSchema() { - return this.props.schema.forms.find(function (form) { - return form.schema.schema_url === this.props.formSchemaUrl; - }.bind(this)); - } - /** * Maps a list of schema fields to their React Component. * Only top level form fields are handled here, composite fields (TabSets etc), @@ -189,19 +184,19 @@ export class FormBuilderComponent extends SilverStripeComponent { } render() { + const schema = this.props.schemas[this.props.schemaUrl]; + // If the response from fetching the initial data // hasn't come back yet, don't render anything. - if (this.props.schema.forms.length === 0) { + if (!schema) { return null; } - const schema = this.getFormSchema().schema; - const formProps = { - actions: schema.actions, - attributes: schema.attributes, - data: schema.data, - fields: schema.fields, + actions: schema.schema.actions, + attributes: schema.schema.attributes, + data: schema.schema.data, + fields: schema.schema.fields, mapFieldsToComponents: this.mapFieldsToComponents }; @@ -211,13 +206,13 @@ export class FormBuilderComponent extends SilverStripeComponent { FormBuilderComponent.propTypes = { actions: React.PropTypes.object.isRequired, - formSchemaUrl: React.PropTypes.string.isRequired, - schema: React.PropTypes.object.isRequired + schemaUrl: React.PropTypes.string.isRequired, + schemas: React.PropTypes.object.isRequired }; function mapStateToProps(state) { return { - schema: state.schema + schemas: state.schemas } } diff --git a/admin/javascript/src/components/form-builder/tests/form-builder-test.js b/admin/javascript/src/components/form-builder/tests/form-builder-test.js index 908a3155f..782c5e9a0 100644 --- a/admin/javascript/src/components/form-builder/tests/form-builder-test.js +++ b/admin/javascript/src/components/form-builder/tests/form-builder-test.js @@ -15,7 +15,7 @@ describe('FormBuilderComponent', () => { getState: () => {} }, actions: {}, - formSchemaUrl: 'admin/assets/schema/1', + schemaUrl: 'admin/assets/schema/1', schema: { forms: [{ schema: { id: '1', schema_url: 'admin/assets/schema/1' } }] } }; diff --git a/admin/javascript/src/components/form/index.js b/admin/javascript/src/components/form/index.js index 36c5a15bc..5903d7d21 100644 --- a/admin/javascript/src/components/form/index.js +++ b/admin/javascript/src/components/form/index.js @@ -43,10 +43,10 @@ class FormComponent extends SilverStripeComponent { } FormComponent.propTypes = { - actions: React.PropTypes.array.isRequired, + actions: React.PropTypes.array, attributes: React.PropTypes.shape({ action: React.PropTypes.string.isRequired, - className: React.PropTypes.string.isRequired, + 'class': React.PropTypes.string.isRequired, enctype: React.PropTypes.string.isRequired, id: React.PropTypes.string.isRequired, method: React.PropTypes.string.isRequired diff --git a/admin/javascript/src/sections/campaign-admin/controller.js b/admin/javascript/src/sections/campaign-admin/controller.js index 284fcf542..5c827c233 100644 --- a/admin/javascript/src/sections/campaign-admin/controller.js +++ b/admin/javascript/src/sections/campaign-admin/controller.js @@ -5,6 +5,7 @@ import ActionButton from 'action-button'; import i18n from 'i18n'; import NorthHeader from 'north-header'; import GridField from 'grid-field'; +import FormBuilder from '../../components/form-builder'; class CampaignAdminContainer extends SilverStripeComponent { @@ -15,17 +16,17 @@ class CampaignAdminContainer extends SilverStripeComponent { } render() { + const schemaUrl = this.props.config.forms.editForm.schemaUrl; + return (
- - - +
); } diff --git a/admin/javascript/src/state/schema/actions.js b/admin/javascript/src/state/schema/actions.js index 011fb7135..3f8cc2058 100644 --- a/admin/javascript/src/state/schema/actions.js +++ b/admin/javascript/src/state/schema/actions.js @@ -1,7 +1,7 @@ import ACTION_TYPES from './action-types'; /** - * Sets the schema being used to generate the curent layout. + * Sets the schema being used to generate the current form layout. * * @param string schema - JSON schema for the layout. */ diff --git a/admin/javascript/src/state/schema/reducer.js b/admin/javascript/src/state/schema/reducer.js index 6e846ded5..29e25faa0 100644 --- a/admin/javascript/src/state/schema/reducer.js +++ b/admin/javascript/src/state/schema/reducer.js @@ -1,30 +1,15 @@ import deepFreeze from 'deep-freeze'; import ACTION_TYPES from './action-types'; -const initialState = deepFreeze({ - forms: [] -}); +const initialState = deepFreeze({}); export default function schemaReducer(state = initialState, action = null) { switch (action.type) { case ACTION_TYPES.SET_SCHEMA: - if (state.forms.length === 0) { - return deepFreeze(Object.assign({}, state, { forms: [action.payload] })); - } - - // Replace the form which has a matching `schema.id` property. - return deepFreeze(Object.assign({}, state, { - forms: state.forms.map((form) => { - if (form.schema.id === action.payload.schema.id) { - // Only replace the `schema` key incase other actions have updated other keys. - return Object.assign({}, form, action.payload); - } - - return form; - }) - })); + const id = action.payload.schema.schema_url; + return deepFreeze(Object.assign({}, state, {[id]: action.payload})); default: return state; diff --git a/admin/templates/Includes/CampaignAdmin_Content.ss b/admin/templates/Includes/CampaignAdmin_Content.ss new file mode 100644 index 000000000..a6b8bf9ed --- /dev/null +++ b/admin/templates/Includes/CampaignAdmin_Content.ss @@ -0,0 +1,2 @@ +
+