Skip to content

Commit 0463505

Browse files
Split UIForm component and base state change on reducers
1 parent 7ec9032 commit 0463505

File tree

15 files changed

+337
-178
lines changed

15 files changed

+337
-178
lines changed
Lines changed: 23 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,80 @@
11
import React, { PropTypes } from 'react';
2-
import { reduxForm } from 'redux-form';
32
import { merge } from 'talend-json-schema-form-core';
43

54
import Widget from './Widget';
6-
import { validate, validateAll } from './utils/validation';
7-
import { mutateValue } from './utils/properties';
85

96
const TRIGGER_AFTER = 'after';
107

11-
class UIForm extends React.Component {
8+
export default class UIForm extends React.Component {
129
constructor(props) {
1310
super(props);
14-
const { jsonSchema, uiSchema, properties } = props.data;
11+
const { jsonSchema, uiSchema } = props;
1512
this.state = {
1613
mergedSchema: merge(jsonSchema, uiSchema),
17-
properties: { ...properties },
18-
validations: {},
1914
};
2015
console.log(this.state.mergedSchema)
2116

22-
this.consolidate = this.consolidate.bind(this);
17+
this.onChange = this.onChange.bind(this);
2318
this.submit = this.submit.bind(this);
2419
}
2520

2621
/**
2722
* Update the state with the new schema.
2823
* @param jsonSchema
2924
* @param uiSchema
30-
* @param properties
3125
*/
32-
componentWillReceiveProps({ jsonSchema, uiSchema, properties }) {
33-
if (!jsonSchema || !uiSchema || !properties) {
26+
componentWillReceiveProps({ jsonSchema, uiSchema }) {
27+
if (!jsonSchema || !uiSchema) {
3428
return;
3529
}
3630
this.setState({
3731
mergedSchema: merge(jsonSchema, uiSchema),
38-
properties: { ...properties },
3932
});
4033
}
4134

42-
/**
43-
* Consolidate form with the new value.
44-
* - it updates the validation on the modified field.
45-
* - it triggers onChange / onTrigger callbacks
46-
* @param event The change event
47-
* @param schema The schema of the changed field
48-
* @param value The new field value
49-
*/
50-
consolidate(event, schema, value) {
51-
this.setState(
52-
(prevState) => {
53-
const properties = mutateValue(prevState.properties, schema.key, value);
54-
const validations = {
55-
...prevState.validations,
56-
[schema.key]: validate(schema, value, properties, this.props.validation),
57-
};
58-
return { properties, validations };
59-
},
60-
() => this.handleChangesCallbacks(schema, value)
61-
);
62-
}
63-
6435
/**
6536
* Triggers the onTrigger and onChange if needed
6637
* - onChange : at each field change
6738
* - onTrigger : when schema.trigger : ['after']
6839
* @param schema The field schema
6940
* @param value The new value
7041
*/
71-
handleChangesCallbacks(schema, value) {
72-
const { onChange, onTrigger } = this.props;
73-
74-
if (onChange) {
75-
onChange({
76-
jsonSchema: this.props.data.jsonSchema, // original jsonSchema
77-
uiSchema: this.props.data.uiSchema, // original uiSchema
78-
properties: this.state.properties, // current properties values
79-
});
80-
}
42+
onChange(event, schema, value) {
43+
const { onChange, onTrigger, properties } = this.props;
44+
onChange(schema, value, properties);
8145

8246
const { key, triggers } = schema;
8347
if (onTrigger && triggers && triggers.indexOf(TRIGGER_AFTER) !== -1) {
8448
onTrigger(
85-
this.state.properties, // current properties values
49+
this.props.properties, // current properties values
8650
key[key.length - 1], // field name
8751
value // field value
8852
);
8953
}
9054
}
9155

92-
/**
93-
* Triggers a validation and update state.
94-
* @returns {boolean} true if the form is valid, false otherwise
95-
*/
96-
isValid() {
97-
const validations = validateAll(
98-
this.state.mergedSchema,
99-
this.state.properties,
100-
this.props.validation
101-
);
102-
103-
const isValid = Object.keys(validations).every(key => validations[key].valid);
104-
if (!isValid) {
105-
this.setState({ validations });
106-
}
107-
return isValid;
108-
}
109-
11056
/**
11157
* Triggers submit callback if form is valid
11258
* @param event the submit event
11359
*/
11460
submit(event) {
11561
event.preventDefault();
116-
if (this.isValid()) {
117-
this.props.onSubmit(event, this.state.properties);
118-
}
62+
this.props.onSubmit(event, this.state.mergedSchema, this.props.properties);
11963
}
12064

12165
render() {
122-
const { formName } = this.props;
123-
const { properties, validations } = this.state;
124-
66+
const { errors, formName, properties } = this.props;
12567
return (
12668
<form onSubmit={this.submit}>
12769
{
12870
this.state.mergedSchema.map((nextSchema, index) => (
12971
<Widget
13072
key={index}
13173
formName={formName}
132-
onChange={this.consolidate}
74+
onChange={this.onChange}
13375
schema={nextSchema}
13476
properties={properties}
135-
validations={validations}
77+
errors={errors}
13678
/>
13779
))
13880
}
@@ -144,19 +86,14 @@ class UIForm extends React.Component {
14486

14587
if (process.env.NODE_ENV !== 'production') {
14688
UIForm.propTypes = {
147-
/** Form schema configuration */
148-
data: PropTypes.shape({
149-
/** Json schema that specify the data model */
150-
jsonSchema: PropTypes.object,
151-
/** UI schema that specify how to render the fields */
152-
uiSchema: PropTypes.array,
153-
/** Form fields values. Note that it should contains @definitionName for triggers. */
154-
properties: PropTypes.object,
155-
}),
89+
/** The forms errors { [fieldKey]: errorMessage } */
90+
errors: PropTypes.object, // eslint-disable-line react/forbid-prop-types
15691
/** The form name that will be used to create ids */
15792
formName: PropTypes.string,
158-
/** The change callback. It takes */
159-
onChange: PropTypes.func,
93+
/** Json schema that specify the data model */
94+
jsonSchema: PropTypes.object, // eslint-disable-line react/forbid-prop-types
95+
/** The change callback */
96+
onChange: PropTypes.func.isRequired,
16097
/** Form submit callback */
16198
onSubmit: PropTypes.func.isRequired,
16299
/**
@@ -165,16 +102,9 @@ if (process.env.NODE_ENV !== 'production') {
165102
* This is executed on changes on fields with uiSchema > triggers : ['after']
166103
*/
167104
onTrigger: PropTypes.func,
168-
/**
169-
* Custom validation function.
170-
* Prototype: function validation(properties, fieldName, value)
171-
* Return format : { valid: true|false, error: { message: 'my validation message' } }
172-
* This is triggered on fields that has their uiSchema > customValidation : true
173-
*/
174-
validation: PropTypes.func,
105+
/** Form fields values. Note that it should contains @definitionName for triggers. */
106+
properties: PropTypes.object, // eslint-disable-line react/forbid-prop-types
107+
/** UI schema that specify how to render the fields */
108+
uiSchema: PropTypes.array, // eslint-disable-line react/forbid-prop-types
175109
};
176110
}
177-
178-
export default reduxForm({
179-
form: 'form', // a unique name for this form
180-
})(UIForm);
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import React, { PropTypes } from 'react';
2+
import UIFormComponent from './UIForm.component';
3+
4+
import { modelReducer, validationReducer } from './reducers';
5+
import { mutateValue, validateAll } from './actions';
6+
7+
export default class UIForm extends React.Component {
8+
constructor(props) {
9+
super(props);
10+
this.state = {
11+
properties: { ...props.data.properties },
12+
errors: {},
13+
};
14+
15+
this.onChange = this.onChange.bind(this);
16+
this.onSubmit = this.onSubmit.bind(this);
17+
}
18+
19+
/**
20+
* Update the properties.
21+
*/
22+
componentWillReceiveProps({ properties }) {
23+
if (!properties) {
24+
return;
25+
}
26+
27+
this.setState({
28+
properties: { ...properties },
29+
});
30+
}
31+
32+
/**
33+
* Update the model and validation
34+
* If onChange is provided, it is triggered
35+
* @param schema The schema
36+
* @param value The new value
37+
* @param properties The values
38+
*/
39+
onChange(schema, value, properties) {
40+
const action = mutateValue(schema, value, properties, this.props.validation);
41+
this.setState(
42+
{
43+
properties: modelReducer(this.state.properties, action),
44+
errors: validationReducer(this.state.errors, action),
45+
},
46+
() => {
47+
if (this.props.onChange) {
48+
this.props.onChange({
49+
jsonSchema: this.props.data.jsonSchema,
50+
uiSchema: this.props.data.uiSchema,
51+
properties: this.state.properties,
52+
});
53+
}
54+
}
55+
);
56+
}
57+
58+
/**
59+
* Triggers submit callback if form is valid
60+
* @param event the submit event
61+
* @param schema the schema
62+
* @param properties the properties values
63+
*/
64+
onSubmit(event, schema, properties) {
65+
event.preventDefault();
66+
if (this.isValid(schema, properties)) {
67+
this.props.onSubmit(event, properties);
68+
}
69+
}
70+
71+
/**
72+
* Triggers a validation and update state.
73+
* @returns {boolean} true if the form is valid, false otherwise
74+
*/
75+
isValid(schema, properties) {
76+
const action = validateAll(schema, properties, this.props.validation);
77+
const errors = validationReducer(this.state.errors, action);
78+
const isValid = !Object.keys(errors).length;
79+
80+
if (!isValid) {
81+
this.setState({ errors });
82+
}
83+
return isValid;
84+
}
85+
86+
render() {
87+
const { data, ...restProps } = this.props;
88+
const { properties, errors } = this.state;
89+
90+
return (
91+
<UIFormComponent
92+
{...restProps}
93+
jsonSchema={data.jsonSchema}
94+
uiSchema={data.uiSchema}
95+
properties={properties}
96+
errors={errors}
97+
onChange={this.onChange}
98+
onSubmit={this.onSubmit}
99+
/>
100+
);
101+
}
102+
}
103+
104+
if (process.env.NODE_ENV !== 'production') {
105+
UIForm.propTypes = {
106+
/** Form schema configuration */
107+
data: PropTypes.shape({
108+
/** Json schema that specify the data model */
109+
jsonSchema: PropTypes.object,
110+
/** UI schema that specify how to render the fields */
111+
uiSchema: PropTypes.array,
112+
/** Form fields values. Note that it should contains @definitionName for triggers. */
113+
properties: PropTypes.object,
114+
}),
115+
/** The form name that will be used to create ids */
116+
formName: PropTypes.string,
117+
/** The change callback. It takes */
118+
onChange: PropTypes.func,
119+
/** Form submit callback */
120+
onSubmit: PropTypes.func.isRequired,
121+
/**
122+
* Tigger > after callback.
123+
* Prototype: function onTrigger(properties, fieldName, value)
124+
* This is executed on changes on fields with uiSchema > triggers : ['after']
125+
*/
126+
onTrigger: PropTypes.func,
127+
/**
128+
* Custom validation function.
129+
* Prototype: function validation(properties, fieldName, value)
130+
* Return format : { valid: true|false, error: { message: 'my validation message' } }
131+
* This is triggered on fields that has their uiSchema > customValidation : true
132+
*/
133+
validation: PropTypes.func,
134+
};
135+
}

packages/forms/src/UIForm/Widget/Widget.component.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { sfPath } from 'talend-json-schema-form-core';
44
import widgets from '../utils/widgets';
55
import { getValue } from '../utils/properties';
66

7-
export default function Widget({ formName, onChange, properties, schema, validations }) {
7+
export default function Widget({ errors, formName, onChange, properties, schema }) {
88
const { key, type, validationMessage } = schema;
99
const id = sfPath.name(key, '-', formName);
10-
const { error, valid } = validations[key] || {};
11-
const errorMessage = validationMessage || (error && error.message);
10+
const error = errors[key];
11+
const errorMessage = validationMessage || error;
1212
const WidgetImpl = widgets[type];
1313
return WidgetImpl ?
1414
(
@@ -17,18 +17,19 @@ export default function Widget({ formName, onChange, properties, schema, validat
1717
key={id}
1818
errorMessage={errorMessage}
1919
formName={formName}
20-
isValid={valid}
20+
isValid={!error}
2121
onChange={onChange}
2222
properties={properties}
2323
schema={schema}
24-
validations={validations}
24+
errors={errors}
2525
value={getValue(properties, key)}
2626
/>
2727
) : null;
2828
}
2929

3030
if (process.env.NODE_ENV !== 'production') {
3131
Widget.propTypes = {
32+
errors: PropTypes.object, // eslint-disable-line react/forbid-prop-types
3233
formName: PropTypes.string,
3334
onChange: PropTypes.func,
3435
schema: PropTypes.shape({
@@ -37,6 +38,5 @@ if (process.env.NODE_ENV !== 'production') {
3738
validationMessage: PropTypes.string,
3839
}).isRequired,
3940
properties: PropTypes.object, // eslint-disable-line react/forbid-prop-types
40-
validations: PropTypes.object, // eslint-disable-line react/forbid-prop-types
4141
};
4242
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const MUTATE_VALUE = 'MUTATE_VALUE';
2+
export const VALIDATE_ALL = 'VALIDATE_ALL';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { mutateValue } from './model.actions';
2+
export { validateAll } from './validation.actions';
3+
export * from './constants';
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { MUTATE_VALUE } from './constants';
2+
3+
export function mutateValue(schema, value, properties, customValidationFn) {
4+
return {
5+
type: MUTATE_VALUE,
6+
customValidationFn,
7+
properties,
8+
schema,
9+
value,
10+
};
11+
}

0 commit comments

Comments
 (0)