Remove duplicate props for PopoverField, added FormBuilder->handleAction and flattened state data

This commit is contained in:
Christopher Joe 2016-08-12 16:34:51 +12:00 committed by Ingo Schommer
parent 6a4b29d703
commit d7663e850e
12 changed files with 124 additions and 112 deletions

View File

@ -6,6 +6,7 @@ if (typeof(ss) === 'undefined' || typeof(ss.i18n) === 'undefined') {
} }
} else { } else {
ss.i18n.addDictionary('en', { ss.i18n.addDictionary('en', {
"Boolean.ANY": "Any",
"CMSMAIN.BATCH_ARCHIVE_PROMPT": "You have {num} page(s) selected.\n\nAre you sure you want to archive these pages?\n\nThese pages and all of their children pages will be unpublished and sent to the archive.", "CMSMAIN.BATCH_ARCHIVE_PROMPT": "You have {num} page(s) selected.\n\nAre you sure you want to archive these pages?\n\nThese pages and all of their children pages will be unpublished and sent to the archive.",
"CMSMAIN.BATCH_DELETELIVE_PROMPT": "You have {num} page(s) selected.\n\nDo you really want to delete these pages from live?", "CMSMAIN.BATCH_DELETELIVE_PROMPT": "You have {num} page(s) selected.\n\nDo you really want to delete these pages from live?",
"CMSMAIN.BATCH_DELETE_PROMPT": "You have {num} page(s) selected.\n\nDo you really want to delete?", "CMSMAIN.BATCH_DELETE_PROMPT": "You have {num} page(s) selected.\n\nDo you really want to delete?",

View File

@ -13,11 +13,6 @@ class AddToCampaignModal extends SilverStripeComponent {
handleSubmit(event, fieldValues, submitFn) { handleSubmit(event, fieldValues, submitFn) {
if (!fieldValues.Campaign && fieldValues.Campaign !== 0) {
event.preventDefault();
return;
}
if (typeof this.props.handleSubmit === 'function') { if (typeof this.props.handleSubmit === 'function') {
this.props.handleSubmit(event, fieldValues, submitFn); this.props.handleSubmit(event, fieldValues, submitFn);
return; return;

View File

@ -131,17 +131,17 @@ class FormAction extends SilverStripeComponent {
* @return undefined * @return undefined
*/ */
handleClick(event) { handleClick(event) {
if (typeof this.props.handleClick === 'undefined') { if (typeof this.props.handleClick === 'function') {
return; this.props.handleClick(event, this.props.name || this.props.id);
} }
this.props.handleClick(event);
} }
} }
FormAction.propTypes = { FormAction.propTypes = {
id: React.PropTypes.string, id: React.PropTypes.string,
name: React.PropTypes.string,
handleClick: React.PropTypes.func, handleClick: React.PropTypes.func,
title: React.PropTypes.string, title: React.PropTypes.string,
type: React.PropTypes.string, type: React.PropTypes.string,

View File

@ -25,6 +25,7 @@ export class FormBuilderComponent extends SilverStripeComponent {
this.mapFieldsToComponents = this.mapFieldsToComponents.bind(this); this.mapFieldsToComponents = this.mapFieldsToComponents.bind(this);
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.handleAction = this.handleAction.bind(this);
this.removeForm = this.removeForm.bind(this); this.removeForm = this.removeForm.bind(this);
this.getFormId = this.getFormId.bind(this); this.getFormId = this.getFormId.bind(this);
this.getFormSchema = this.getFormSchema.bind(this); this.getFormSchema = this.getFormSchema.bind(this);
@ -171,6 +172,12 @@ export class FormBuilderComponent extends SilverStripeComponent {
} }
} }
handleAction(event, name) {
if (typeof this.props.handleAction === 'function') {
this.props.handleAction(event, name);
}
}
/** /**
* Form submission handler passed to the Form Component as a prop. * Form submission handler passed to the Form Component as a prop.
* Provides a hook for controllers to access for state and provide custom functionality. * Provides a hook for controllers to access for state and provide custom functionality.
@ -228,6 +235,30 @@ export class FormBuilderComponent extends SilverStripeComponent {
submitFn(); submitFn();
} }
buildComponent(field, extraProps = {}) {
const Component = field.component !== null
? injector.getComponentByName(field.component)
: injector.getComponentByDataType(field.type);
if (Component === null) {
return null;
}
// Props which every form field receives.
// Leave it up to the schema and component to determine
// which props are required.
const props = Object.assign({}, field, extraProps);
// Provides container components a place to hook in
// and apply customisations to scaffolded components.
const createFn = this.props.createFn;
if (typeof createFn === 'function') {
return createFn(Component, props);
}
return <Component key={props.id} {...props} />;
}
/** /**
* Maps a list of schema fields to their React Component. * Maps a list of schema fields to their React Component.
* Only top level form fields are handled here, composite fields (TabSets etc), * Only top level form fields are handled here, composite fields (TabSets etc),
@ -237,38 +268,16 @@ export class FormBuilderComponent extends SilverStripeComponent {
* @return {Array} * @return {Array}
*/ */
mapFieldsToComponents(fields) { mapFieldsToComponents(fields) {
const createFn = this.props.createFn;
const handleFieldUpdate = this.handleFieldUpdate;
return fields.map((field) => { return fields.map((field) => {
const Component = field.component !== null
? injector.getComponentByName(field.component)
: injector.getComponentByDataType(field.type);
if (Component === null) {
return null;
}
// Events // Events
const extraProps = { onChange: handleFieldUpdate }; const extraProps = { onChange: this.handleFieldUpdate };
// Build child nodes // Build child nodes
if (field.children) { if (field.children) {
extraProps.children = this.mapFieldsToComponents(field.children); extraProps.children = this.mapFieldsToComponents(field.children);
} }
// Props which every form field receives. return this.buildComponent(field, extraProps);
// Leave it up to the schema and component to determine
// which props are required.
const props = Object.assign({}, field, extraProps);
// Provides container components a place to hook in
// and apply customisations to scaffolded components.
if (typeof createFn === 'function') {
return createFn(Component, props);
}
return <Component key={props.id} {...props} />;
}); });
} }
@ -279,7 +288,17 @@ export class FormBuilderComponent extends SilverStripeComponent {
* @return {Array} * @return {Array}
*/ */
mapActionsToComponents(actions) { mapActionsToComponents(actions) {
return this.mapFieldsToComponents(actions); return actions.map((action) => {
// Events
const extraProps = { handleClick: this.handleAction };
// Build child nodes
if (action.children) {
extraProps.children = this.mapActionsToComponents(action.children);
}
return this.buildComponent(action, extraProps);
});
} }
/** /**
@ -297,7 +316,8 @@ export class FormBuilderComponent extends SilverStripeComponent {
return structure; return structure;
} }
return merge.recursive(true, structure, { return merge.recursive(true, structure, {
data: state.data, data: Object.assign({}, structure.data, state.data),
source: state.source,
messages: state.messages, messages: state.messages,
valid: state.valid, valid: state.valid,
value: state.value, value: state.value,
@ -368,6 +388,7 @@ FormBuilderComponent.propTypes = {
form: React.PropTypes.object.isRequired, form: React.PropTypes.object.isRequired,
formActions: React.PropTypes.object.isRequired, formActions: React.PropTypes.object.isRequired,
handleSubmit: React.PropTypes.func, handleSubmit: React.PropTypes.func,
handleAction: React.PropTypes.func,
schemas: React.PropTypes.object.isRequired, schemas: React.PropTypes.object.isRequired,
schemaActions: React.PropTypes.object.isRequired, schemaActions: React.PropTypes.object.isRequired,
schemaUrl: React.PropTypes.string.isRequired, schemaUrl: React.PropTypes.string.isRequired,

View File

@ -28,7 +28,7 @@ HeaderField.propTypes = {
React.PropTypes.array, React.PropTypes.array,
React.PropTypes.shape({ React.PropTypes.shape({
headingLevel: React.PropTypes.number.isRequired, headingLevel: React.PropTypes.number.isRequired,
title: React.PropTypes.string.isRequired, title: React.PropTypes.string,
}), }),
]).isRequired, ]).isRequired,
}; };

View File

@ -8,7 +8,7 @@ class PopoverField extends SilverStripeComponent {
const placement = this.getPlacement(); const placement = this.getPlacement();
const overlay = ( const overlay = (
<Popover id={`${this.props.id}_Popover`} className={`fade in popover-${placement}`} <Popover id={`${this.props.id}_Popover`} className={`fade in popover-${placement}`}
title={this.getPopoverTitle()} title={this.props.data.popoverTitle}
> >
{this.props.children} {this.props.children}
</Popover> </Popover>
@ -35,52 +35,21 @@ class PopoverField extends SilverStripeComponent {
* @returns {String} * @returns {String}
*/ */
getPlacement() { getPlacement() {
const placement = this.getDataProperty('placement'); const placement = this.props.data.placement;
return placement || 'bottom'; return placement || 'bottom';
} }
/**
* Gets title of popup box
*
* @return {String} Return the string to use.
*/
getPopoverTitle() {
const title = this.getDataProperty('popoverTitle');
return title || '';
}
/**
* Search for a given property either passed in to data or as a direct prop
*
* @param {String} name
* @returns {String}
*/
getDataProperty(name) {
if (typeof this.props[name] !== 'undefined') {
return this.props[name];
}
// In case this is nested in the form schema data prop
if (
typeof this.props.data !== 'undefined'
&& typeof this.props.data[name] !== 'undefined'
) {
return this.props.data[name];
}
return null;
}
} }
PopoverField.propTypes = { PopoverField.propTypes = {
id: React.PropTypes.string, id: React.PropTypes.string,
title: React.PropTypes.string, title: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.bool]),
popoverTitle: React.PropTypes.string, data: React.PropTypes.oneOfType([
placement: React.PropTypes.oneOf(['top', 'right', 'bottom', 'left']), React.PropTypes.array,
data: React.PropTypes.shape({ React.PropTypes.shape({
popoverTitle: React.PropTypes.string, popoverTitle: React.PropTypes.string,
placement: React.PropTypes.oneOf(['top', 'right', 'bottom', 'left']), placement: React.PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
}), }),
]),
}; };
export default PopoverField; export default PopoverField;

View File

@ -60,12 +60,7 @@ class SingleSelectField extends SilverStripeComponent {
* @returns ReactComponent * @returns ReactComponent
*/ */
getSelectField() { getSelectField() {
const options = this.props.source.map((item) => { const options = this.props.source || [];
return Object.assign({},
item,
{disabled: this.props.data.disabled.indexOf(item.value) > -1}
);
});
if (this.props.hasEmptyDefault) { if (this.props.hasEmptyDefault) {
options.unshift({ options.unshift({
@ -124,21 +119,15 @@ SingleSelectField.propTypes = {
source: React.PropTypes.arrayOf(React.PropTypes.shape({ source: React.PropTypes.arrayOf(React.PropTypes.shape({
value: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]), value: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]),
title: React.PropTypes.string, title: React.PropTypes.string,
})).isRequired, disabled: React.PropTypes.bool,
data: React.PropTypes.shape({ })),
disabled: React.PropTypes.arrayOf(
React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number])
),
}),
hasEmptyDefault: React.PropTypes.bool, hasEmptyDefault: React.PropTypes.bool,
emptyString: React.PropTypes.string, emptyString: React.PropTypes.string,
}; };
SingleSelectField.defaultProps = { SingleSelectField.defaultProps = {
data: { source: [],
disabled: [], emptyString: i18n._t('Boolean.ANY', 'Any'),
},
emptyString: '',
}; };
export default SingleSelectField; export default SingleSelectField;

View File

@ -79,6 +79,7 @@ export function submitForm(submitApi, formId, fieldValues) {
type: ACTION_TYPES.SUBMIT_FORM_FAILURE, type: ACTION_TYPES.SUBMIT_FORM_FAILURE,
payload: { formId, error }, payload: { formId, error },
}); });
return error;
}); });
}; };
} }

View File

@ -4,6 +4,7 @@ namespace SilverStripe\Forms\Schema;
use Form; use Form;
use FormField; use FormField;
use CompositeField;
/** /**
* Class FormSchema * Class FormSchema
@ -63,11 +64,8 @@ class FormSchema {
'messages' => [] 'messages' => []
]; ];
// @todo - Flatten all nested fields for returning state. At the moment, only top // flattened nested fields are returned, rather than only top level fields.
// level fields are returned. $state['fields'] = $this->getFieldStates($form->Fields());
foreach ($form->Fields() as $field) {
$state['fields'][] = $field->getSchemaState();
}
if($form->Message()) { if($form->Message()) {
$state['messages'][] = [ $state['messages'][] = [
@ -78,4 +76,17 @@ class FormSchema {
return $state; return $state;
} }
protected function getFieldStates($fields) {
$states = [];
foreach ($fields as $field) {
$states[] = $field->getSchemaState();
if ($field instanceof CompositeField) {
$subFields = $field->FieldList();
array_merge($states, $this->getFieldStates($subFields));
}
}
return $states;
}
} }

View File

@ -25,6 +25,13 @@ class PopoverField extends FieldGroup
*/ */
protected $popoverTitle = null; protected $popoverTitle = null;
/**
* Placement of the popup box
*
* @var string
*/
protected $placement = 'bottom';
/** /**
* Get popup title * Get popup title
* *
@ -47,19 +54,33 @@ class PopoverField extends FieldGroup
return $this; return $this;
} }
/**
* Get popup placement
*
* @return string
*/
public function getPlacement()
{
return $this->placement;
}
public function setPlacement($placement)
{
$valid = ['top', 'right', 'bottom', 'left'];
if (in_array($placement, $valid)) {
$this->placement = $placement;
}
return $this;
}
public function getSchemaDataDefaults() public function getSchemaDataDefaults()
{ {
$schema = parent::getSchemaDataDefaults(); $schema = parent::getSchemaDataDefaults();
if($this->getPopoverTitle()) {
$data = [ $schema['data']['popoverTitle'] = $this->getPopoverTitle();
'popoverTitle' => $this->getPopoverTitle() $schema['data']['placement'] = $this->getPlacement();
];
if(isset($schema['data'])) {
$schema['data'] = array_merge($schema['data'], $data);
} else {
$schema['data'] = $data;
}
}
return $schema; return $schema;
} }
} }

View File

@ -39,20 +39,21 @@ abstract class SelectField extends FormField {
parent::__construct($name, $title, $value); parent::__construct($name, $title, $value);
} }
public function getSchemaDataDefaults() { public function getSchemaStateDefaults() {
$data = parent::getSchemaDataDefaults(); $data = parent::getSchemaStateDefaults();
$disabled = $this->getDisabledItems();
// Add options to 'data' // Add options to 'data'
$source = $this->getSource(); $source = $this->getSource();
$data['source'] = (is_array($source)) $data['source'] = (is_array($source))
? array_map(function ($value, $title) { ? array_map(function ($value, $title) use ($disabled) {
return [ return [
'value' => $value, 'value' => $value,
'title' => $title, 'title' => $title,
'disabled' => in_array($value, $disabled),
]; ];
}, array_keys($source), $source) }, array_keys($source), $source)
: []; : [];
$data['data']['disabled'] = $this->getDisabledItems();
return $data; return $data;
} }

View File

@ -271,7 +271,10 @@ class FormSchemaTest extends SapphireTest {
'disabled' => false, 'disabled' => false,
'customValidationMessage' => '', 'customValidationMessage' => '',
'attributes' => [], 'attributes' => [],
'data' => [], 'data' => [
'popoverTitle' => null,
'placement' => 'bottom',
],
'children' => [ 'children' => [
[ [
'id' => 'Form_TestForm_action_publish', 'id' => 'Form_TestForm_action_publish',