Skip to content

Commit

Permalink
Split UIForm component and base state change on reducers
Browse files Browse the repository at this point in the history
  • Loading branch information
jsomsanith-tlnd committed May 16, 2017
1 parent 7ec9032 commit 0463505
Show file tree
Hide file tree
Showing 15 changed files with 337 additions and 178 deletions.
116 changes: 23 additions & 93 deletions packages/forms/src/UIForm/UIForm.component.js
Original file line number Diff line number Diff line change
@@ -1,138 +1,80 @@
import React, { PropTypes } from 'react';
import { reduxForm } from 'redux-form';
import { merge } from 'talend-json-schema-form-core';

import Widget from './Widget';
import { validate, validateAll } from './utils/validation';
import { mutateValue } from './utils/properties';

const TRIGGER_AFTER = 'after';

class UIForm extends React.Component {
export default class UIForm extends React.Component {
constructor(props) {
super(props);
const { jsonSchema, uiSchema, properties } = props.data;
const { jsonSchema, uiSchema } = props;
this.state = {
mergedSchema: merge(jsonSchema, uiSchema),
properties: { ...properties },
validations: {},
};
console.log(this.state.mergedSchema)

this.consolidate = this.consolidate.bind(this);
this.onChange = this.onChange.bind(this);
this.submit = this.submit.bind(this);
}

/**
* Update the state with the new schema.
* @param jsonSchema
* @param uiSchema
* @param properties
*/
componentWillReceiveProps({ jsonSchema, uiSchema, properties }) {
if (!jsonSchema || !uiSchema || !properties) {
componentWillReceiveProps({ jsonSchema, uiSchema }) {
if (!jsonSchema || !uiSchema) {
return;
}
this.setState({
mergedSchema: merge(jsonSchema, uiSchema),
properties: { ...properties },
});
}

/**
* Consolidate form with the new value.
* - it updates the validation on the modified field.
* - it triggers onChange / onTrigger callbacks
* @param event The change event
* @param schema The schema of the changed field
* @param value The new field value
*/
consolidate(event, schema, value) {
this.setState(
(prevState) => {
const properties = mutateValue(prevState.properties, schema.key, value);
const validations = {
...prevState.validations,
[schema.key]: validate(schema, value, properties, this.props.validation),
};
return { properties, validations };
},
() => this.handleChangesCallbacks(schema, value)
);
}

/**
* Triggers the onTrigger and onChange if needed
* - onChange : at each field change
* - onTrigger : when schema.trigger : ['after']
* @param schema The field schema
* @param value The new value
*/
handleChangesCallbacks(schema, value) {
const { onChange, onTrigger } = this.props;

if (onChange) {
onChange({
jsonSchema: this.props.data.jsonSchema, // original jsonSchema
uiSchema: this.props.data.uiSchema, // original uiSchema
properties: this.state.properties, // current properties values
});
}
onChange(event, schema, value) {
const { onChange, onTrigger, properties } = this.props;
onChange(schema, value, properties);

const { key, triggers } = schema;
if (onTrigger && triggers && triggers.indexOf(TRIGGER_AFTER) !== -1) {
onTrigger(
this.state.properties, // current properties values
this.props.properties, // current properties values
key[key.length - 1], // field name
value // field value
);
}
}

/**
* Triggers a validation and update state.
* @returns {boolean} true if the form is valid, false otherwise
*/
isValid() {
const validations = validateAll(
this.state.mergedSchema,
this.state.properties,
this.props.validation
);

const isValid = Object.keys(validations).every(key => validations[key].valid);
if (!isValid) {
this.setState({ validations });
}
return isValid;
}

/**
* Triggers submit callback if form is valid
* @param event the submit event
*/
submit(event) {
event.preventDefault();
if (this.isValid()) {
this.props.onSubmit(event, this.state.properties);
}
this.props.onSubmit(event, this.state.mergedSchema, this.props.properties);
}

render() {
const { formName } = this.props;
const { properties, validations } = this.state;

const { errors, formName, properties } = this.props;
return (
<form onSubmit={this.submit}>
{
this.state.mergedSchema.map((nextSchema, index) => (
<Widget
key={index}
formName={formName}
onChange={this.consolidate}
onChange={this.onChange}
schema={nextSchema}
properties={properties}
validations={validations}
errors={errors}
/>
))
}
Expand All @@ -144,19 +86,14 @@ class UIForm extends React.Component {

if (process.env.NODE_ENV !== 'production') {
UIForm.propTypes = {
/** Form schema configuration */
data: PropTypes.shape({
/** Json schema that specify the data model */
jsonSchema: PropTypes.object,
/** UI schema that specify how to render the fields */
uiSchema: PropTypes.array,
/** Form fields values. Note that it should contains @definitionName for triggers. */
properties: PropTypes.object,
}),
/** The forms errors { [fieldKey]: errorMessage } */
errors: PropTypes.object, // eslint-disable-line react/forbid-prop-types
/** The form name that will be used to create ids */
formName: PropTypes.string,
/** The change callback. It takes */
onChange: PropTypes.func,
/** Json schema that specify the data model */
jsonSchema: PropTypes.object, // eslint-disable-line react/forbid-prop-types
/** The change callback */
onChange: PropTypes.func.isRequired,
/** Form submit callback */
onSubmit: PropTypes.func.isRequired,
/**
Expand All @@ -165,16 +102,9 @@ if (process.env.NODE_ENV !== 'production') {
* This is executed on changes on fields with uiSchema > triggers : ['after']
*/
onTrigger: PropTypes.func,
/**
* Custom validation function.
* Prototype: function validation(properties, fieldName, value)
* Return format : { valid: true|false, error: { message: 'my validation message' } }
* This is triggered on fields that has their uiSchema > customValidation : true
*/
validation: PropTypes.func,
/** Form fields values. Note that it should contains @definitionName for triggers. */
properties: PropTypes.object, // eslint-disable-line react/forbid-prop-types
/** UI schema that specify how to render the fields */
uiSchema: PropTypes.array, // eslint-disable-line react/forbid-prop-types
};
}

export default reduxForm({
form: 'form', // a unique name for this form
})(UIForm);
135 changes: 135 additions & 0 deletions packages/forms/src/UIForm/UIForm.container.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React, { PropTypes } from 'react';
import UIFormComponent from './UIForm.component';

import { modelReducer, validationReducer } from './reducers';
import { mutateValue, validateAll } from './actions';

export default class UIForm extends React.Component {
constructor(props) {
super(props);
this.state = {
properties: { ...props.data.properties },
errors: {},
};

this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}

/**
* Update the properties.
*/
componentWillReceiveProps({ properties }) {
if (!properties) {
return;
}

this.setState({
properties: { ...properties },
});
}

/**
* Update the model and validation
* If onChange is provided, it is triggered
* @param schema The schema
* @param value The new value
* @param properties The values
*/
onChange(schema, value, properties) {
const action = mutateValue(schema, value, properties, this.props.validation);
this.setState(
{
properties: modelReducer(this.state.properties, action),
errors: validationReducer(this.state.errors, action),
},
() => {
if (this.props.onChange) {
this.props.onChange({
jsonSchema: this.props.data.jsonSchema,
uiSchema: this.props.data.uiSchema,
properties: this.state.properties,
});
}
}
);
}

/**
* Triggers submit callback if form is valid
* @param event the submit event
* @param schema the schema
* @param properties the properties values
*/
onSubmit(event, schema, properties) {
event.preventDefault();
if (this.isValid(schema, properties)) {
this.props.onSubmit(event, properties);
}
}

/**
* Triggers a validation and update state.
* @returns {boolean} true if the form is valid, false otherwise
*/
isValid(schema, properties) {
const action = validateAll(schema, properties, this.props.validation);
const errors = validationReducer(this.state.errors, action);
const isValid = !Object.keys(errors).length;

if (!isValid) {
this.setState({ errors });
}
return isValid;
}

render() {
const { data, ...restProps } = this.props;
const { properties, errors } = this.state;

return (
<UIFormComponent
{...restProps}
jsonSchema={data.jsonSchema}
uiSchema={data.uiSchema}
properties={properties}
errors={errors}
onChange={this.onChange}
onSubmit={this.onSubmit}
/>
);
}
}

if (process.env.NODE_ENV !== 'production') {
UIForm.propTypes = {
/** Form schema configuration */
data: PropTypes.shape({
/** Json schema that specify the data model */
jsonSchema: PropTypes.object,
/** UI schema that specify how to render the fields */
uiSchema: PropTypes.array,
/** Form fields values. Note that it should contains @definitionName for triggers. */
properties: PropTypes.object,
}),
/** The form name that will be used to create ids */
formName: PropTypes.string,
/** The change callback. It takes */
onChange: PropTypes.func,
/** Form submit callback */
onSubmit: PropTypes.func.isRequired,
/**
* Tigger > after callback.
* Prototype: function onTrigger(properties, fieldName, value)
* This is executed on changes on fields with uiSchema > triggers : ['after']
*/
onTrigger: PropTypes.func,
/**
* Custom validation function.
* Prototype: function validation(properties, fieldName, value)
* Return format : { valid: true|false, error: { message: 'my validation message' } }
* This is triggered on fields that has their uiSchema > customValidation : true
*/
validation: PropTypes.func,
};
}
12 changes: 6 additions & 6 deletions packages/forms/src/UIForm/Widget/Widget.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { sfPath } from 'talend-json-schema-form-core';
import widgets from '../utils/widgets';
import { getValue } from '../utils/properties';

export default function Widget({ formName, onChange, properties, schema, validations }) {
export default function Widget({ errors, formName, onChange, properties, schema }) {
const { key, type, validationMessage } = schema;
const id = sfPath.name(key, '-', formName);
const { error, valid } = validations[key] || {};
const errorMessage = validationMessage || (error && error.message);
const error = errors[key];
const errorMessage = validationMessage || error;
const WidgetImpl = widgets[type];
return WidgetImpl ?
(
Expand All @@ -17,18 +17,19 @@ export default function Widget({ formName, onChange, properties, schema, validat
key={id}
errorMessage={errorMessage}
formName={formName}
isValid={valid}
isValid={!error}
onChange={onChange}
properties={properties}
schema={schema}
validations={validations}
errors={errors}
value={getValue(properties, key)}
/>
) : null;
}

if (process.env.NODE_ENV !== 'production') {
Widget.propTypes = {
errors: PropTypes.object, // eslint-disable-line react/forbid-prop-types
formName: PropTypes.string,
onChange: PropTypes.func,
schema: PropTypes.shape({
Expand All @@ -37,6 +38,5 @@ if (process.env.NODE_ENV !== 'production') {
validationMessage: PropTypes.string,
}).isRequired,
properties: PropTypes.object, // eslint-disable-line react/forbid-prop-types
validations: PropTypes.object, // eslint-disable-line react/forbid-prop-types
};
}
2 changes: 2 additions & 0 deletions packages/forms/src/UIForm/actions/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const MUTATE_VALUE = 'MUTATE_VALUE';
export const VALIDATE_ALL = 'VALIDATE_ALL';
3 changes: 3 additions & 0 deletions packages/forms/src/UIForm/actions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { mutateValue } from './model.actions';
export { validateAll } from './validation.actions';
export * from './constants';
11 changes: 11 additions & 0 deletions packages/forms/src/UIForm/actions/model.actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { MUTATE_VALUE } from './constants';

export function mutateValue(schema, value, properties, customValidationFn) {
return {
type: MUTATE_VALUE,
customValidationFn,
properties,
schema,
value,
};
}
Loading

0 comments on commit 0463505

Please sign in to comment.