diff --git a/packages/forms/src/UIForm/UIForm.component.js b/packages/forms/src/UIForm/UIForm.component.js index 26609a13278..23676ed8b54 100644 --- a/packages/forms/src/UIForm/UIForm.component.js +++ b/packages/forms/src/UIForm/UIForm.component.js @@ -1,9 +1,9 @@ import React, { PropTypes } from 'react'; import { reduxForm } from 'redux-form'; -import { merge, validate } from 'talend-json-schema-form-core'; +import { merge } from 'talend-json-schema-form-core'; import Widget from './Widget'; -import { validateAll } from './utils/validation'; +import { validate, validateAll } from './utils/validation'; import { mutateValue } from './utils/properties'; const TRIGGER_AFTER = 'after'; @@ -56,46 +56,57 @@ class UIForm extends React.Component { properties: mutateValue(prevState.properties, schema.key, value), validations: { ...prevState.validations, - [schema.key]: validate(schema, value), + [schema.key]: validate(schema, value, prevState.properties, this.props.validation), }, }), - () => { - 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 - }); - } - - const { key, triggers } = schema; - if (onTrigger && triggers && triggers.indexOf(TRIGGER_AFTER) !== -1) { - onTrigger( - this.state.properties, // current properties values - key[key.length - 1], // field name - value // field value - ); - } - } + () => 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 + }); + } + + const { key, triggers } = schema; + if (onTrigger && triggers && triggers.indexOf(TRIGGER_AFTER) !== -1) { + onTrigger( + this.state.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); - const keys = Object.keys(validations); - for (const key of keys) { - if (!validations[key].valid) { - this.setState({ validations }); - return false; - } + 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 true; + return isValid; } /** @@ -152,9 +163,17 @@ if (process.env.NODE_ENV !== 'production') { 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, }; } diff --git a/packages/forms/src/UIForm/utils/validation.js b/packages/forms/src/UIForm/utils/validation.js index 6959ccbedc0..443d075463f 100644 --- a/packages/forms/src/UIForm/utils/validation.js +++ b/packages/forms/src/UIForm/utils/validation.js @@ -1,22 +1,39 @@ -import { validate } from 'talend-json-schema-form-core'; +import { validate as staticValidate } from 'talend-json-schema-form-core'; import { getValue } from './properties'; /** * Validate values. - * @param mergedSchema The merged schema. + * @param schema The merged schema. + * @param value The value. * @param properties The values. + * @param customValidationFn A custom validation function + * that is applied on schema.customValidation = true + * @returns {object} The validation result. + */ +export function validate(schema, value, properties, customValidationFn) { + const staticResult = staticValidate(schema, value); + if (staticResult.valid && schema.customValidation && customValidationFn) { + return customValidationFn(properties, schema, value); + } + return staticResult; +} + +/** + * Validate values. + * @param mergedSchema The merged schema array. + * @param properties The values. + * @param customValidationFn A custom validation function + * that is applied on schema.customValidation = true * @returns {object} The validation result by field. */ -export function validateAll(mergedSchema, properties) { +export function validateAll(mergedSchema, properties, customValidationFn) { const validations = {}; mergedSchema.forEach((schema) => { const { key, items } = schema; if (key) { - validations[key] = validate( - schema, - getValue(properties, key) - ); + const value = getValue(properties, key); + validations[key] = validate(schema, value, properties, customValidationFn); } if (items) { const subValidations = validateAll(items, properties); @@ -27,7 +44,7 @@ export function validateAll(mergedSchema, properties) { } /** - * Check if a schema value is invalid. + * Check if a schema value is valid. * It is invalid if : * - the schema is an invalid field (validations[key] = { valid: false }) * - the schema has items (ex: fieldset, tabs, ...), and at least one of them is invalid diff --git a/packages/forms/stories/index.js b/packages/forms/stories/index.js index 8c72fe88252..d1e2a456509 100644 --- a/packages/forms/stories/index.js +++ b/packages/forms/stories/index.js @@ -65,6 +65,15 @@ sampleFilenames onTrigger={action('Trigger')} onBlur={action('Blur')} onSubmit={action('Submit')} + validation={(properties, schema, value) => { + action('customValidation')(properties, schema, value); + return { + valid: value.length < 5, + error: { + message: 'Custom validation : The value should be less than 5 chars', + }, + }; + }} /> )); diff --git a/packages/forms/stories/json/core-custom-validation.json b/packages/forms/stories/json/core-custom-validation.json new file mode 100644 index 00000000000..4559c937793 --- /dev/null +++ b/packages/forms/stories/json/core-custom-validation.json @@ -0,0 +1,32 @@ +{ + "jsonSchema": { + "type": "object", + "title": "Comment", + "properties": { + "lastname": { + "type": "string" + }, + "firstname": { + "type": "string" + } + }, + "required": [ + "lastname", + "firstname" + ] + }, + "uiSchema": [ + { + "key": "lastname", + "title": "Last Name", + "description": "This field has custom validation (less than 5 chars)", + "customValidation": true + }, + { + "key": "firstname", + "title": "First Name", + "description": "This field has no custom validation" + } + ], + "properties": {} +}