BUG Fix form schema to use correct ID values

API Implement creation of new changesets endpoint
This commit is contained in:
Damian Mooyman 2016-04-21 14:38:02 +12:00 committed by Ingo Schommer
parent 2b8ef99d5e
commit b2e8fd96ec
8 changed files with 115 additions and 68 deletions

View File

@ -75,6 +75,30 @@ export class FormBuilderComponent extends SilverStripeComponent {
this.handleFieldUpdate = this.handleFieldUpdate.bind(this); this.handleFieldUpdate = this.handleFieldUpdate.bind(this);
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
this.removeForm = this.removeForm.bind(this); this.removeForm = this.removeForm.bind(this);
this.getFormId = this.getFormId.bind(this);
this.getFormSchema = this.getFormSchema.bind(this);
}
/**
* Get the schema for this form
*
* @returns {array}
*/
getFormSchema() {
return this.props.schemas[this.props.schemaUrl];
}
/**
* Gets the ID for this form
*
* @returns {string}
*/
getFormId() {
const schema = this.getFormSchema();
if (schema) {
return schema.id;
}
return null;
} }
componentDidMount() { componentDidMount() {
@ -184,9 +208,9 @@ export class FormBuilderComponent extends SilverStripeComponent {
*/ */
handleFieldUpdate(event, updates, fn) { handleFieldUpdate(event, updates, fn) {
if (typeof fn !== 'undefined') { if (typeof fn !== 'undefined') {
fn(this.props.formId, this.props.formsActions.updateField); fn(this.getFormId(), this.props.formsActions.updateField);
} else { } else {
this.props.formsActions.updateField(this.props.formId, updates); this.props.formsActions.updateField(this.getFormId(), updates);
} }
} }
@ -223,7 +247,7 @@ export class FormBuilderComponent extends SilverStripeComponent {
*/ */
handleSubmit(event) { handleSubmit(event) {
const schemaFields = this.props.schemas[this.props.schemaUrl].schema.fields; const schemaFields = this.props.schemas[this.props.schemaUrl].schema.fields;
const fieldValues = this.props.forms[this.props.formId].fields const fieldValues = this.props.forms[this.getFormId()].fields
.reduce((prev, curr) => Object.assign({}, prev, { .reduce((prev, curr) => Object.assign({}, prev, {
[schemaFields.find(schemaField => schemaField.id === curr.id).name]: curr.value, [schemaFields.find(schemaField => schemaField.id === curr.id).name]: curr.value,
}), {}); }), {});
@ -231,7 +255,7 @@ export class FormBuilderComponent extends SilverStripeComponent {
const submitFn = () => { const submitFn = () => {
this.props.formsActions.submitForm( this.props.formsActions.submitForm(
this.submitApi, this.submitApi,
this.props.formId, this.getFormId(),
fieldValues fieldValues
); );
}; };
@ -352,8 +376,12 @@ export class FormBuilderComponent extends SilverStripeComponent {
} }
render() { render() {
const formSchema = this.props.schemas[this.props.schemaUrl]; const formId = this.getFormId();
const formState = this.props.forms[this.props.formId]; if (!formId) {
return null;
}
const formSchema = this.getFormSchema();
const formState = this.props.forms[formId];
// If the response from fetching the initial data // If the response from fetching the initial data
// hasn't come back yet, don't render anything. // hasn't come back yet, don't render anything.
@ -382,7 +410,7 @@ export class FormBuilderComponent extends SilverStripeComponent {
componentWillUnmount: this.removeForm, componentWillUnmount: this.removeForm,
data: formSchema.schema.data, data: formSchema.schema.data,
fields: fieldData, fields: fieldData,
formId: formSchema.id, formId,
handleSubmit: this.handleSubmit, handleSubmit: this.handleSubmit,
mapActionsToComponents: this.mapActionsToComponents, mapActionsToComponents: this.mapActionsToComponents,
mapFieldsToComponents: this.mapFieldsToComponents, mapFieldsToComponents: this.mapFieldsToComponents,
@ -397,7 +425,6 @@ FormBuilderComponent.propTypes = {
createFn: React.PropTypes.func, createFn: React.PropTypes.func,
forms: React.PropTypes.object.isRequired, forms: React.PropTypes.object.isRequired,
formsActions: React.PropTypes.object.isRequired, formsActions: React.PropTypes.object.isRequired,
formId: React.PropTypes.string.isRequired,
handleSubmit: React.PropTypes.func, handleSubmit: React.PropTypes.func,
schemas: React.PropTypes.object.isRequired, schemas: React.PropTypes.object.isRequired,
schemaActions: React.PropTypes.object.isRequired, schemaActions: React.PropTypes.object.isRequired,

View File

@ -14,6 +14,13 @@ class TextField extends SilverStripeComponent {
? this.props.leftTitle ? this.props.leftTitle
: this.props.title; : this.props.title;
let field = null;
if (this.props.readOnly) {
field = <div><i>{this.props.value}</i></div>;
} else {
field = <input {...this.getInputProps()} />;
}
return ( return (
<div className="field text"> <div className="field text">
{labelText && {labelText &&
@ -22,7 +29,7 @@ class TextField extends SilverStripeComponent {
</label> </label>
} }
<div className="middleColumn"> <div className="middleColumn">
<input {...this.getInputProps()} /> {field}
</div> </div>
</div> </div>
); );
@ -59,6 +66,7 @@ TextField.propTypes = {
name: React.PropTypes.string.isRequired, name: React.PropTypes.string.isRequired,
handleFieldUpdate: React.PropTypes.func, handleFieldUpdate: React.PropTypes.func,
value: React.PropTypes.string, value: React.PropTypes.string,
readOnly: React.PropTypes.bool,
}; };
export default TextField; export default TextField;

View File

@ -92,7 +92,6 @@ class CampaignAdmin extends SilverStripeComponent {
}; };
const formBuilderProps = { const formBuilderProps = {
createFn: this.campaignListCreateFn, createFn: this.campaignListCreateFn,
formId: 'EditForm',
schemaUrl, schemaUrl,
}; };
@ -140,10 +139,7 @@ class CampaignAdmin extends SilverStripeComponent {
*/ */
renderDetailEditView() { renderDetailEditView() {
const baseSchemaUrl = this.props.sectionConfig.forms.DetailEditForm.schemaUrl; const baseSchemaUrl = this.props.sectionConfig.forms.DetailEditForm.schemaUrl;
const formBuilderProps = { const schemaUrl = `${baseSchemaUrl}/ChangeSet/${this.props.campaignId}`;
formId: 'DetailEditForm',
schemaUrl: `${baseSchemaUrl}/ChangeSet/${this.props.campaignId}`,
};
return ( return (
<div className="cms-middle no-preview"> <div className="cms-middle no-preview">
@ -153,7 +149,7 @@ class CampaignAdmin extends SilverStripeComponent {
<h2 className="text-truncate toolbar__heading">Campaigns</h2> <h2 className="text-truncate toolbar__heading">Campaigns</h2>
</div> </div>
</NorthHeader> </NorthHeader>
<FormBuilder {...formBuilderProps} /> <FormBuilder schemaUrl={schemaUrl} />
</div> </div>
</div> </div>
); );
@ -163,10 +159,9 @@ class CampaignAdmin extends SilverStripeComponent {
* Render the view for creating a new Campaign. * Render the view for creating a new Campaign.
*/ */
renderCreateView() { renderCreateView() {
const baseSchemaUrl = this.props.sectionConfig.forms.CreateEditForm.schemaUrl; const baseSchemaUrl = this.props.sectionConfig.forms.DetailEditForm.schemaUrl;
const formBuilderProps = { const formBuilderProps = {
createFn: this.campaignCreationView, createFn: this.campaignCreationView,
formId: 'CreateEditForm',
schemaUrl: `${baseSchemaUrl}/ChangeSet`, schemaUrl: `${baseSchemaUrl}/ChangeSet`,
}; };

View File

@ -12,12 +12,9 @@ class CampaignAdmin extends LeftAndMain implements PermissionProvider {
'set', 'set',
'sets', 'sets',
'schema', 'schema',
'CreateEditForm',
'DetailEditForm', 'DetailEditForm',
'readCampaigns', 'readCampaigns',
'createCampaign',
'readCampaign', 'readCampaign',
'updateCampaign',
'deleteCampaign', 'deleteCampaign',
'publishCampaign', 'publishCampaign',
]; ];
@ -63,9 +60,6 @@ class CampaignAdmin extends LeftAndMain implements PermissionProvider {
'DetailEditForm' => [ 'DetailEditForm' => [
'schemaUrl' => $this->Link('schema/DetailEditForm') 'schemaUrl' => $this->Link('schema/DetailEditForm')
], ],
'CreateEditForm' => [
'schemaUrl' => $this->Link('schema/CreateEditForm')
],
], ],
'campaignViewRoute' => $this->Link() . ':type?/:id?/:view?', 'campaignViewRoute' => $this->Link() . ':type?/:id?/:view?',
'itemListViewEndpoint' => $this->Link() . 'set/:id/show', 'itemListViewEndpoint' => $this->Link() . 'set/:id/show',
@ -80,10 +74,10 @@ class CampaignAdmin extends LeftAndMain implements PermissionProvider {
// TODO Hardcoding schema until we can get GridField to generate a schema dynamically // TODO Hardcoding schema until we can get GridField to generate a schema dynamically
$json = <<<JSON $json = <<<JSON
{ {
"id": "EditForm", "id": "Form_EditForm",
"schema": { "schema": {
"name": "EditForm", "name": "EditForm",
"id": "EditForm", "id": "Form_EditForm",
"action": "schema", "action": "schema",
"method": "GET", "method": "GET",
"schema_url": "admin\/campaigns\/schema\/EditForm", "schema_url": "admin\/campaigns\/schema\/EditForm",
@ -464,7 +458,8 @@ JSON;
'DetailEditForm', 'DetailEditForm',
$fields, $fields,
FieldList::create( FieldList::create(
FormAction::create('save', 'Save') FormAction::create('save', 'Save'),
FormAction::create('cancel', 'Cancel')
) )
); );
// Configure form to respond to validation errors with form schema // Configure form to respond to validation errors with form schema
@ -475,23 +470,6 @@ JSON;
return $form; return $form;
} }
/**
* @todo Use GridFieldDetailForm once it can handle structured data and form schemas
*
* @return Form
*/
public function getCreateEditForm() {
return Form::create(
$this,
'CreateEditForm',
ChangeSet::singleton()->getCMSFields(),
FieldList::create(
FormAction::create('save', 'Save'),
FormAction::create('cancel', 'Cancel')
)
);
}
/** /**
* Gets user-visible url to edit a specific {@see ChangeSet} * Gets user-visible url to edit a specific {@see ChangeSet}
* *
@ -518,11 +496,4 @@ JSON;
); );
} }
/**
*
*/
public function FindReferencedChanges() {
}
} }

View File

@ -294,7 +294,6 @@ class LeftAndMain extends Controller implements PermissionProvider {
*/ */
protected function getSchemaForForm(Form $form) { protected function getSchemaForForm(Form $form) {
$request = $this->getRequest(); $request = $this->getRequest();
$schemaParts = [];
$return = null; $return = null;
// Valid values for the "X-Formschema-Request" header are "schema" and "state". // Valid values for the "X-Formschema-Request" header are "schema" and "state".
@ -309,7 +308,7 @@ class LeftAndMain extends Controller implements PermissionProvider {
$schemaParts = ['schema']; $schemaParts = ['schema'];
} }
$return = ['id' => $form->getName()]; $return = ['id' => $form->FormName()];
if (in_array('schema', $schemaParts)) { if (in_array('schema', $schemaParts)) {
$return['schema'] = $this->schema->getSchema($form); $return['schema'] = $this->schema->getSchema($form);
@ -1166,12 +1165,18 @@ class LeftAndMain extends Controller implements PermissionProvider {
// Existing or new record? // Existing or new record?
$id = $data['ID']; $id = $data['ID'];
if(substr($id,0,3) != 'new') { if(is_numeric($id) && $id > 0) {
$record = DataObject::get_by_id($className, $id); $record = DataObject::get_by_id($className, $id);
if($record && !$record->canEdit()) return Security::permissionFailure($this); if($record && !$record->canEdit()) {
if(!$record || !$record->ID) $this->httpError(404, "Bad record ID #" . (int)$id); return Security::permissionFailure($this);
}
if(!$record || !$record->ID) {
$this->httpError(404, "Bad record ID #" . (int)$id);
}
} else { } else {
if(!singleton($this->stat('tree_class'))->canCreate()) return Security::permissionFailure($this); if(!singleton($this->stat('tree_class'))->canCreate()) {
return Security::permissionFailure($this);
}
$record = $this->getNewItem($id, false); $record = $this->getNewItem($id, false);
} }
@ -1195,6 +1200,22 @@ class LeftAndMain extends Controller implements PermissionProvider {
return $response; return $response;
} }
/**
* Create new item.
*
* @param string|int $id
* @param bool $setID
* @return DataObject
*/
public function getNewItem($id, $setID = true) {
$class = $this->stat('tree_class');
$object = Injector::inst()->create($class);
if($setID) {
$object->ID = $id;
}
return $object;
}
public function delete($data, $form) { public function delete($data, $form) {
$className = $this->stat('tree_class'); $className = $this->stat('tree_class');

View File

@ -23,13 +23,13 @@ class FormSchema {
*/ */
public function getSchema(Form $form) { public function getSchema(Form $form) {
$request = $form->controller()->getRequest(); $request = $form->controller()->getRequest();
$params = $request->AllParams();
$schema = [ $schema = [
'name' => $form->getName(), 'name' => $form->getName(),
'id' => isset($params['ID']) ? $params['ID'] : null, 'id' => $form->FormName(),
'action' => isset($params['Action']) ? $params['Action'] : null, 'action' => $form->FormAction(),
'method' => $form->controller()->getRequest()->HttpMethod(), 'method' => $form->FormMethod(),
// @todo Not really reliable. Refactor into action on $this->Link('schema')
'schema_url' => $request->getUrl(), 'schema_url' => $request->getUrl(),
'attributes' => $form->getAttributes(), 'attributes' => $form->getAttributes(),
'data' => [], 'data' => [],
@ -57,7 +57,7 @@ class FormSchema {
*/ */
public function getState(Form $form) { public function getState(Form $form) {
$state = [ $state = [
'id' => $form->getName(), 'id' => $form->FormName(),
'fields' => [], 'fields' => [],
'messages' => [] 'messages' => []
]; ];

View File

@ -17,6 +17,8 @@ class ReadonlyField extends FormField {
*/ */
protected $includeHiddenField = false; protected $includeHiddenField = false;
protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_TEXT;
/** /**
* If true, a hidden field will be included in the HTML for the readonly field. * If true, a hidden field will be included in the HTML for the readonly field.
* *
@ -52,9 +54,30 @@ class ReadonlyField extends FormField {
} }
} }
/**
* If $dontEscape is true the returned value will be plain text
* and should be escaped in templates via .XML
*
* If $dontEscape is false the returned value will be safely encoded,
* but should not be escaped by the frontend.
*
* @return mixed|string
*/
public function Value() { public function Value() {
if($this->value) return $this->dontEscape ? $this->value : Convert::raw2xml($this->value); if($this->value) {
else return '<i>(' . _t('FormField.NONE', 'none') . ')</i>'; if($this->dontEscape) {
return $this->value;
} else {
Convert::raw2xml($this->value);
}
} else {
$value = '(' . _t('FormField.NONE', 'none') . ')';
if($this->dontEscape) {
return $value;
} else {
return '<i>'.Convert::raw2xml($value).'</i>';
}
}
} }
public function getAttributes() { public function getAttributes() {

View File

@ -346,10 +346,12 @@ class ChangeSet extends DataObject {
public function getCMSFields() { public function getCMSFields() {
$fields = new FieldList(); $fields = new FieldList();
$fields->merge([ $fields->push(TextField::create('Name'));
TextField::create('Name'), if($this->isInDB()) {
ReadonlyField::create('State') $state = ReadonlyField::create('State')
]); ->setDontEscape(true);
$fields->push($state); // Escape is done in react
}
$this->extend('updateCMSFields', $fields); $this->extend('updateCMSFields', $fields);
return $fields; return $fields;
} }