diff --git a/README.md b/README.md index 7ec366f..f300efb 100644 --- a/README.md +++ b/README.md @@ -56,3 +56,8 @@ export default connectToDomains(MyChildComponentWhoNeedsInformationsFromTheDomai ## Explainations Provider(informationsToPassToTheComponentsTree) => Tree => connectToInformations(Child) => The child gets this information in its props. + + +// todo: + +- [] Check the Provider chain presence (form needs metadata) diff --git a/index.js b/index.js index ea9d306..ba3cd4e 100644 --- a/index.js +++ b/index.js @@ -9,4 +9,4 @@ exports.default = function () { }; module.exports = exports['default']; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJ1aWxkZXIuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O2tCQUFlLFlBQU07QUFDbkIsVUFBUSxHQUFSLGlDQURtQjtDQUFOIiwiZmlsZSI6ImJ1aWxkZXIuanMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCAoKSA9PiB7XG4gIGNvbnNvbGUubG9nKGBGb2N1cyByZWR1eC4uLi4gc28gZ3JlYXQuLi4uYClcbn1cbiJdfQ== \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJ1aWxkZXIuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O2tCQUFlLFlBQU07QUFDbkIsVUFBUSxHQUFSO0FBQ0QsQyIsImZpbGUiOiJidWlsZGVyLmpzIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgKCkgPT4ge1xuICBjb25zb2xlLmxvZyhgRm9jdXMgcmVkdXguLi4uIHNvIGdyZWF0Li4uLmApXG59XG4iXX0= \ No newline at end of file diff --git a/package.json b/package.json index 7400164..770f903 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "focus-redux", - "version": "0.1.1", + "version": "0.2.0", "description": "Form, data behaviours", "main": "src/index.js", "files": [ @@ -12,7 +12,7 @@ "start": "better-npm-run dev-server", "build:node": "better-npm-run babelify", "build": "npm run build:node", - "publish": "npm run build" + "prepublish": "npm run build" }, "betterScripts": { diff --git a/src/behaviours/field.js b/src/behaviours/field.js index 15a719f..2ac3e20 100644 --- a/src/behaviours/field.js +++ b/src/behaviours/field.js @@ -79,9 +79,9 @@ export function connect() { function FieldConnectedComponent({_behaviours, ...otherProps}, {fieldHelpers}) { const fieldFor = fieldHelpers.fieldForBuilder(otherProps); const selectFor = fieldHelpers.fieldForBuilder(otherProps, true); - const list = fieldHelpers.fieldForBuilder( otherProps, false, true, fieldHelpers.fieldForListBuilder); + const listFor = fieldHelpers.fieldForBuilder( otherProps, false, true, fieldHelpers.fieldForListBuilder); const behaviours = {connectedToFieldHelpers: true, ..._behaviours}; - return ; + return ; } FieldConnectedComponent.displayName = `${ComponentToConnect.displayName}FieldConnected`; FieldConnectedComponent.contextTypes = FIELD_CONTEXT_TYPE; diff --git a/src/components/line.js b/src/components/line.js index d6ccc93..47f1f30 100644 --- a/src/components/line.js +++ b/src/components/line.js @@ -1,10 +1,9 @@ import React, {PropTypes} from 'react'; - -function Line({onClick, children, fieldForLine, options,index, ...otherProps}) { +const Line = ({onClick, children, fieldForLine, options,index, ...otherProps}) => { return (
-
{fieldForLine('firstName', {entityPath: 'child'}, index)}
-
{fieldForLine('lastName', {entityPath: 'child'}, index)}
+
{fieldForLine('firstName', {entityPath: 'child'})}
+
{fieldForLine('lastName', {entityPath: 'child'})}
); } @@ -12,7 +11,7 @@ function Line({onClick, children, fieldForLine, options,index, ...otherProps}) Line.displayName = 'Line'; Line.propTypes = { - onClick: PropTypes.func.isRequired, + onClick: PropTypes.func, options: PropTypes.arrayOf(PropTypes.string) }; Line.defaultProps = { diff --git a/src/components/list.js b/src/components/list.js index 533debb..808e562 100644 --- a/src/components/list.js +++ b/src/components/list.js @@ -1,11 +1,12 @@ import React, {PropTypes} from 'react'; -import DefaultLineComponent from './line' -function List({onClick, fieldForLine, LineComponent = DefaultLineComponent, children, options, error, values, ...otherProps}) { +function List({onClick, fieldForLine, LineComponent, children, options, error, values, ...otherProps}) { const renderLine = () => { return (values ? values.map((element, index) => { - return + // fieldFor which wrapp the index. + const lineFieldFor = (linePropertyName, lineOptions) => fieldForLine(linePropertyName, lineOptions, index) + return }):
) // todo: null ? } return ( @@ -16,7 +17,7 @@ function List({onClick, fieldForLine, LineComponent = DefaultLineComponent, chil List.displayName = 'List'; List.propTypes = { - LineComponent: PropTypes.element.isRequired, + LineComponent: PropTypes.func.isRequired, fieldForLine: PropTypes.func.isRequired, onClick: PropTypes.func, options: PropTypes.arrayOf(PropTypes.string) diff --git a/src/example/components/user-and-address-form.js b/src/example/components/user-and-address-form.js index 98b1780..6a27b8a 100644 --- a/src/example/components/user-and-address-form.js +++ b/src/example/components/user-and-address-form.js @@ -7,7 +7,7 @@ import {loadMixedAction, saveMixedAction} from '../actions/mixed-actions'; import Panel from '../../components/panel'; import compose from 'lodash/flowRight'; - +import LineComponent from '../../components/line' class UserAddressForm extends Component { componentWillMount() { const {id, load, loadMasterData} = this.props; @@ -16,11 +16,11 @@ class UserAddressForm extends Component { } render() { - const {editing, fields, fieldFor, list, selectFor} = this.props; + const {editing, fields, fieldFor, listFor, selectFor} = this.props; return ( {fieldFor('uuid', {onChange: () => {console.log(fields)}, entityPath: 'user'})} - {list('childs', {entityPath : 'user', redirectEntityPath: 'child'})} + {listFor('childs', {LineComponent, entityPath : 'user', redirectEntityPath: 'child'})} ); } diff --git a/src/example/config.js b/src/example/config.js index 648617e..e1ba91f 100644 --- a/src/example/config.js +++ b/src/example/config.js @@ -9,7 +9,8 @@ export const definitions = { lastName: { domain: 'DO_DON_DIEGO', isRequired: true}, date: { domain: 'DO_DATE', isRequired: false}, civility: { domain: 'DO_CIVILITE', isRequired: true}, - childs : {redirect: 'child'} + // TODO: ['childs'] ? + childs : {redirect: ['child']} }, child : { firstName : { domain: 'DO_RODRIGO', isRequired: false}, @@ -25,7 +26,7 @@ export const definitions = { export const domains = { DO_RODRIGO: { type: 'text', - validator: [{ + validators: [{ type: 'string', options: { maxLength: 50 @@ -35,7 +36,7 @@ export const domains = { }, DO_DON_DIEGO: { type: 'text', - validator: [{ + validators: [{ type: 'string', options: { maxLength: 200 @@ -48,7 +49,7 @@ export const domains = { }, DO_CIVILITE: { type: 'text', - validator: [{ + validators: [{ type: 'string', options: { maxLength: 200 diff --git a/src/middlewares/__tests__/field-middleware-test.js b/src/middlewares/__tests__/field-middleware-test.js new file mode 100644 index 0000000..23f856e --- /dev/null +++ b/src/middlewares/__tests__/field-middleware-test.js @@ -0,0 +1,62 @@ +import fieldMiddleware from '../field'; +import { + INPUT_CHANGE, + INPUT_BLUR, + INPUT_ERROR, + INPUT_BLUR_LIST, + INPUT_ERROR_LIST +} from '../../actions/input' +import {definitions, domains} from '../../example/config'; +describe('The field middleware', () => { + const getStateSpy = sinon.spy(); + const nextSpy = sinon.spy(); + const dispatchSpy = sinon.spy(); + const store = { + getState: () => { + getStateSpy(); + return { + dataset: { + user: { + data: { + firstName: 'Joe', + lastName: 'Lopez' + }, + loading: true, + saving: false + } + }, + definitions, + domains, + forms: [{ + formKey: 'formKey', + entityPathArray: ['user'], + fields: [{ + name: 'firstName', + dataSetValue: 'Joe', + entityPath: 'user', + loading: false, + saving: false + }] + }] + } + }, + dispatch: dispatchSpy + }; + beforeEach(() => { + getStateSpy.reset(); + nextSpy.reset(); + dispatchSpy.reset(); + }); + describe('when no store is given tho the middleware ', ()=>{ + it('should throw an error', () =>{ + expect(() => fieldMiddleware()(nextSpy)({type: 'lol'})) + .to.throw('FIELD_MIDDLEWARE: Your middleware needs a redux store.'); + }); + it('when an INPUT_BLUR action is passed', () => { + fieldMiddleware(store)(nextSpy)({type: INPUT_BLUR, entityPath: 'user', fieldName: 'firstName'}) + }); + it('when an INPUT_BLUR_LIST action is passed', () => {}); + it('when an INPUT_CHANGE action is passed', () => {}); + it('when an SYNC_FORM_ENTITIES action is passed', () => {}); + }); +}); diff --git a/src/middlewares/__tests__/form-middleware-test.js b/src/middlewares/__tests__/form-middleware-test.js index 13bb104..c75b721 100644 --- a/src/middlewares/__tests__/form-middleware-test.js +++ b/src/middlewares/__tests__/form-middleware-test.js @@ -40,12 +40,18 @@ describe('The form middleware', () => { nextSpy.reset(); dispatchSpy.reset(); }) + describe('when no store is given tho the middleware ', ()=>{ + it('should throw an error', () =>{ + expect(() => formMiddleware()(nextSpy)({type: 'lol'})) + .to.throw('FORM_MIDDLEWARE: You middleware needs a redux store.'); + }) + }); describe('when a random action is passed', () => { const randomAction = { type: 'LOL' }; it('should just pass the action to next', () => { - formMiddleware(null)(nextSpy)(randomAction); + formMiddleware(store)(nextSpy)(randomAction); expect(nextSpy).to.have.been.callCount(1); expect(nextSpy).to.have.been.calledWith(randomAction); }); diff --git a/src/middlewares/__tests__/form-middleware-validation.js b/src/middlewares/__tests__/form-middleware-validation.js index e62466b..87af436 100644 --- a/src/middlewares/__tests__/form-middleware-validation.js +++ b/src/middlewares/__tests__/form-middleware-validation.js @@ -1,10 +1,73 @@ -import {filterNonValidatedFields} from '../validations'; - +import {filterNonValidatedFields, validateField} from '../validations'; +import {definitions, domains} from '../../example/config'; +import {INPUT_ERROR} from '../../actions/input'; const FIELDS_CONFIG = JSON.parse('{"fields":[{"valid":true,"error":false,"active":true,"dirty":false,"loading":false,"saving":false,"name":"city","entityPath":"address","dataSetValue":"Kubland","rawInputValue":"Kubland","formattedInputValue":"Kubland - formaté"},{"valid":true,"error":false,"active":true,"dirty":false,"loading":false,"saving":false,"name":"uuid","entityPath":"user","dataSetValue":"1234","rawInputValue":"1234","formattedInputValue":"1234"},{"valid":true,"error":false,"active":true,"dirty":false,"loading":false,"saving":false,"name":"lastName","entityPath":"user","dataSetValue":"De Libercourt","rawInputValue":"De Libercourt","formattedInputValue":"De Libercourt - formaté"},{"valid":true,"error":false,"active":true,"dirty":false,"loading":false,"saving":false,"name":"childs","entityPath":"user","dataSetValue":[{"firstName":"FirstChildOne","lastName":"LastChildOne"},{"firstName":"FirstChildTwo","lastName":"LastChildTwo"}],"rawInputValue":[{"firstName":"FirstChildOne","lastName":"LastChildOne"},{"firstName":"FirstChildTwo","lastName":"LastChildTwo"}],"formattedInputValue":[{"firstName":"FirstChildOne","lastName":"LastChildOne"},{"firstName":"FirstChildTwo","lastName":"LastChildTwo"}],"redirectEntityPath":"child"},{"valid":true,"error":false,"active":true,"dirty":false,"loading":false,"saving":false,"name":"uuid","entityPath":"address","dataSetValue":"1234","rawInputValue":"1234","formattedInputValue":"1234"}],"nonValidatedFields":["user.firstName",{"user.childs":["firstName"]}]}') -describe.only('Form: validation', () => { +describe('Form: validation', () => { + describe('when the validateField is called', ()=> { + const dispatchSpy = sinon.spy(); + beforeEach(() => { + dispatchSpy.reset(); + }) + describe('with a simple field', ()=> { + it('should validate the name given correct defintion and value' , ()=>{ + const validationResult = validateField(definitions, domains, 'formBalec', 'user', 'firstName', 'NewName', dispatchSpy); + expect(validationResult).to.be.true; + expect(dispatchSpy).to.have.callCount(0); + }); + it('should dispatch an invalid the name given correct defintion and value' , ()=>{ + const validationResult = validateField(definitions, domains, 'formBalec', 'user', 'firstName', 12, dispatchSpy); + expect(validationResult).to.be.false; + expect(dispatchSpy).to.have.callCount(1); + const dispatchArgs = dispatchSpy.lastCall.args[0]; + expect(dispatchArgs.type = INPUT_ERROR); + expect(dispatchArgs.formKey === 'formBalec'); + expect(dispatchArgs.entityPath === 'user'); + expect(dispatchArgs.fieldName === 'firstName'); + expect(dispatchArgs.error.length > 0 ); + }); + }); + describe('with a list field', ()=> { + it('should validate the name given correct defintion and value' , ()=>{ + const newListValue = [ + {firstName:'FirstChildOne',lastName:'LastChildOne'}, + {firstName:'FirstChildTwo',lastName:'LastChildTwo'} + ]; + const validationResult = validateField(definitions, domains, 'formBalec', 'user', 'childs', newListValue, dispatchSpy); + expect(validationResult).to.be.true; + expect(dispatchSpy).to.have.callCount(0); + }); + it('should throw an error when the data type is not an array in case of a redirect', () => { + const WRONG_DEFINITION_ERROR = `MIDDLEWARES_FIELD_VALIDATION: Your field childs in the entity user don't have a domain, you may have an array field which have a **redirect** property in it`; + const wrongDefinition = () => validateField(definitions, domains, 'formBalec', 'user', 'childs', 12, dispatchSpy); + expect(wrongDefinition).to.throw(WRONG_DEFINITION_ERROR) + }); + it('should throw an error when the redirect is incorrect', () => { + const WRONG_REDIRECT_ERROR = `MIDDLEWARES_FIELD_VALIDATION: Your field childs in the entity user don't have a domain, you may have an array field which have a **redirect** property in it.` + const def = {...definitions, user: {...definitions.user, redirect: 12}}; + const wrongDefinition = () => validateField(definitions, domains, 'formBalec', 'user', 'childs', 12, dispatchSpy); + expect(wrongDefinition).to.throw(WRONG_REDIRECT_ERROR) + }); + it('should dispatch an invalid the name given correct defintion and an incorrect value' , ()=>{ + const newListValue = [ + {firstName:1,lastName:'LastChildOne'}, + {firstName:'FirstChildTwo',lastName: 'test'} + ]; + const validationResult = validateField(definitions, domains, 'formBalec', 'user', 'childs', newListValue, dispatchSpy); + expect(validationResult).to.be.false; + expect(dispatchSpy).to.have.callCount(2); + const dispatchArgs = dispatchSpy.lastCall.args[0]; + expect(dispatchArgs.type = INPUT_ERROR); + expect(dispatchArgs.formKey === 'formBalec'); + expect(dispatchArgs.entityPath === 'user'); + expect(dispatchArgs.fieldName === 'firstName'); + // expect(dispatchArgs.error.length > 0 ); + }); + }); + }) + it('filterNonValidatedInListField', () => { const FIELD_TO_VALIDATE = [ {"valid":true,"error":false,"active":true,"dirty":false,"loading":false,"saving":false,"name":"city","entityPath":"address","dataSetValue":"Kubland","rawInputValue":"Kubland","formattedInputValue":"Kubland - formaté"}, @@ -17,14 +80,7 @@ describe.only('Form: validation', () => { {"valid":true,"error":false,"active":true,"dirty":false,"loading":false,"saving":false,"name":"uuid","entityPath":"address","dataSetValue":"1234","rawInputValue":"1234","formattedInputValue":"1234"} ]; - - - - /*expect( - filterNonValidatedInListField(FIELDS_CONFIG.fields,FIELDS_CONFIG.nonValidatedFields ) - ).to.deep.equal(FIELD_TO_VALIDATE)*/ const CANDIDATE_FIELD_TO_VALIDATE = filterNonValidatedFields(FIELDS_CONFIG.fields,FIELDS_CONFIG.nonValidatedFields ); - console.log(CANDIDATE_FIELD_TO_VALIDATE); expect( CANDIDATE_FIELD_TO_VALIDATE ).to.deep.equal(FIELD_TO_VALIDATE) diff --git a/src/middlewares/_fake-validate.js b/src/middlewares/_fake-validate.js new file mode 100644 index 0000000..29330dd --- /dev/null +++ b/src/middlewares/_fake-validate.js @@ -0,0 +1,107 @@ +import isNull from 'lodash/isNull'; +import isUndefined from 'lodash/isUndefined'; +//Dependency + +const translate = (str, params) => `${str} ${JSON.stringify(params)}`; + +//Focus validators +const emailValidation = v => typeof v === 'string'; +const numberValidation = v => typeof v === 'number'; +const stringLength = v => typeof v === 'string'; +const dateValidation = v => typeof v === 'string'; + +/** +* Validae a property given validators. +* @param {object} property - Property to validate which should be as follows: `{name: "field_name",value: "field_value", validators: [{...}] }`. +* @param {array} validators - The validators to apply on the property. +* @return {object} - The validation status. +*/ +function validate(property, validators) { + //console.log("validate", property, validators); + let errors = [], res, validator; + if (validators) { + for (let i = 0, _len = validators.length; i < _len; i++) { + validator = validators[i]; + res = validateProperty(property, validator); + if (!isNull(res) && !isUndefined(res)) { + errors.push(res); + } + } + } + //Check what's the good type to return. + return { + name: property.name, + value: property.value, + isValid: 0 === errors.length, + errors: errors + }; +} + +/** +* Validate a property. +* @param {object} property - The property to validate. +* @param {function} validator - The validator to apply. +* @return {object} - The property validation status. +*/ +function validateProperty(property, validator) { + let isValid; + if (!validator) { + return void 0; + } + if (!property) { + return void 0; + } + const {value} = property; + const {options} = validator; + const isValueNullOrUndefined = isNull(value) || isUndefined(value ); + isValid = (() => { + switch (validator.type) { + case 'required': + const prevalidString = '' === property.value ? false : true; + const prevalidDate = true; + return true === validator.value ? (!isNull(value) && !isUndefined(value) && prevalidString && prevalidDate) : true; + case 'regex': + if (isValueNullOrUndefined) { + return true; + } + return validator.value.test(value); + case 'email': + if (isValueNullOrUndefined) { + return true; + } + return emailValidation(value, options); + case 'number': + return numberValidation(value, options); + case 'string': + const stringToValidate = value || ''; + return stringLength(stringToValidate, options); + case 'date': + return dateValidation(value, options); + case 'function': + return validator.value(value, options); + default: + return void 0; + } + })(); + if (isUndefined(isValid) || isNull(isValid)) { + console.warn(`The validator of type: ${validator.tye} is not defined`); + } else if (false === isValid) { + //Add the name of the property. + return getErrorLabel(validator.type, property.modelName + '.' + property.name, options); //"The property " + property.name + " is invalid."; + } +} +/** + * Get the error label from a type and a field name. + * @param {string} type - The type name. + * @param {string} fieldName - The field name. + * @param {object} options - The options to put such as the translationKey which could be defined in the domain. + * @return {string} The formatted error. + */ +function getErrorLabel(type, fieldName, options = {}) { + options = options || {}; + const translationKey = options.translationKey ? options.translationKey : `domain.validation.${type}`; + const opts = {fieldName: translate(fieldName), ...options}; + return translate(translationKey, opts); +} + +export default validate; diff --git a/src/middlewares/field.js b/src/middlewares/field.js index 073053b..da36b55 100644 --- a/src/middlewares/field.js +++ b/src/middlewares/field.js @@ -10,10 +10,29 @@ import isNull from 'lodash/isNull'; import isEmpty from 'lodash/isEmpty'; import mapKeys from 'lodash/mapKeys'; import isArray from 'lodash/lang'; +const FIELD_MIDDLEWARE = 'FIELD_MIDDLEWARE'; -const fieldMiddleware = store => next => action => { +// Check the definitions givent the data. +export const _checkFieldDefinition = (fieldName: string, entityPath: string, definitions: object)=> { + if(!definitions){ + return console.warn(`${FIELD_MIDDLEWARE}: You need to provide definitions.`) + } + if(!entityPath || !fieldName){ + return console.warn(`${FIELD_MIDDLEWARE}: You need an entityPath and a fieldName.`) + } + if(!definitions[entityPath]){ + return console.warn(`${FIELD_MIDDLEWARE}: your entityPath ${entityPath} is not in your definitions`, definitions); + } + if(!definitions[entityPath][fieldName]){ + return console.warn(`${FIELD_MIDDLEWARE}: your field ${fieldName} is not in the definitions of ${entityPath}, please check the data in your store. Maybe your server response is not what you think it is.`, definitions[entityPath]); + } +} + +const fieldMiddleware = store => next => (action) => { + if(!store || !store.getState){ throw new Error(`${FIELD_MIDDLEWARE}: Your middleware needs a redux store.`)} const {forms, definitions, domains} = store.getState(); switch(action.type) { + // Middleware post state processing case INPUT_BLUR: // On input blur action, validate the provided field validateField(definitions, domains, action.formKey, action.entityPath, action.fieldName, action.rawValue, store.dispatch); @@ -21,24 +40,28 @@ const fieldMiddleware = store => next => action => { case INPUT_BLUR_LIST: validateFieldArray(definitions, domains, action.formKey, action.entityPath, action.fieldName, action.rawValue, action.propertyNameLine, action.index,store.dispatch); break; - + // Middleware pre state processing case INPUT_CHANGE: next({ ...action, - formattedValue: formatValue(action.rawValue, action.entityPath, action.fieldName, definitions, domains), - redirectEntityPath : 'child' + formattedValue: formatValue(action.rawValue, action.entityPath, action.fieldName, definitions, domains) }); break; + case CREATE_FORM: case SYNC_FORM_ENTITIES: case SYNC_FORMS_ENTITY: - case CREATE_FORM: next({ ...action, - fields: action.fields.map(field => ({ + fields: action.fields.map(field => { + _checkFieldDefinition(field.name, field.entityPath, definitions); + const redirectEntityPath = getRedirectEntityPath(field.dataSetValue, field.entityPath, field.name, definitions, domains); + const _redirectEntityPath = redirectEntityPath ? {redirectEntityPath} : {}; + return { ...field, formattedInputValue: formatValue(field.dataSetValue, field.entityPath, field.name, definitions, domains), - redirectEntityPath : getRedirectEntityPath(field.dataSetValue, field.entityPath, field.name, definitions, domains) - })) + ..._redirectEntityPath + } + }) }); break; default: diff --git a/src/middlewares/form.js b/src/middlewares/form.js index cbc1088..288ada4 100644 --- a/src/middlewares/form.js +++ b/src/middlewares/form.js @@ -1,13 +1,17 @@ import {CREATE_FORM, SYNC_FORM_ENTITIES, VALIDATE_FORM} from '../actions/form'; import {SUCCESS} from '../actions/entity-actions-builder'; -import {__fake_focus_core_validation_function__, filterNonValidatedFields, validateField, validateFieldArray, formatValue,getRedirectEntityPath} from './validations' +import {__fake_focus_core_validation_function__, filterNonValidatedFields, validateField, validateFieldArray, formatValue} from './validations' import {syncFormsEntity, toggleFormEditing, setFormToSaving} from '../actions/form'; import get from 'lodash/get'; import map from 'lodash/map'; import find from 'lodash/find'; - +const FORM_MIDDLEWARE = 'FORM_MIDDLEWARE'; const formMiddleware = store => next => action => { + + if(!store || !store.getState){ throw new Error(`${FORM_MIDDLEWARE}: You middleware needs a redux store.`)} + if(action.syncForm) { + // The action requires the forms to sync themselves with the dataset, let's do it // Grab the new state, to have the updates on the dataset const newState = next(action); @@ -45,6 +49,9 @@ const formMiddleware = store => next => action => { // Continue with the rest of the redux flow return newState; }else { + if(store === null){ + throw new Error('Store not defined') + } const {dataset, forms, definitions, domains} = store.getState(); const {formKey, nonValidatedFields,entityPathArray } = action; switch(action.type) { diff --git a/src/middlewares/validations.js b/src/middlewares/validations.js index 6f00c42..ebd46fd 100644 --- a/src/middlewares/validations.js +++ b/src/middlewares/validations.js @@ -1,8 +1,6 @@ import identity from 'lodash/identity'; -import {INPUT_CHANGE, INPUT_BLUR, INPUT_BLUR_LIST} from '../actions/input'; -import {inputError, inputErrorList} from '../actions/input'; -import {CREATE_FORM, VALIDATE_FORM, SYNC_FORMS_ENTITY, SYNC_FORM_ENTITIES} from '../actions/form'; -import {setFormToSaving} from '../actions/form'; +import {INPUT_CHANGE, INPUT_BLUR, INPUT_BLUR_LIST,inputError, inputErrorList} from '../actions/input'; +import {CREATE_FORM, VALIDATE_FORM, SYNC_FORMS_ENTITY, SYNC_FORM_ENTITIES, setFormToSaving} from '../actions/form'; import {PENDING} from '../actions/entity-actions-builder'; import find from 'lodash/find'; import isUndefined from 'lodash/isUndefined'; @@ -13,7 +11,7 @@ import isArray from 'lodash/isArray'; import isString from 'lodash/isString'; import omit from 'lodash/omit'; import map from 'lodash/map'; - +import _fakeValidate from './_fake-validate'; /** * Default field formatter. Defaults to the identity function * @type {function} @@ -27,9 +25,13 @@ const MIDDLEWARES_FORM_VALIDATION = 'MIDDLEWARES_FORM_VALIDATION'; // THIS IS A MOCK FUNCTION THAT MUST BE REPLACED BY THE FOCUS CORE VALIDATION // TODO : replace this with the focus core function export const __fake_focus_core_validation_function__ = (isRequired = false, validators = [], name, rawValue) => { - const rand = Math.random(); - const isValid = rand > 0.5; - const error = isRequired && (isUndefined(rawValue) || isNull(rawValue) || isEmpty(rawValue)) ? `${name} is required` : isValid ? false : 'Random error set by a fake function'; + + const validationResult = _fakeValidate({name, value: rawValue}, validators); + const isValid = validationResult.isValid; + + //const rand = Math.random(); + //const isValid = rand > 0.5; + const error = isRequired && (isUndefined(rawValue) || isNull(rawValue) || isEmpty(rawValue)) ? `${name} is required` : isValid ? false : validationResult.errors.join(' '); return { name, value: rawValue, @@ -66,7 +68,16 @@ export const filterNonValidatedFields = (fields, nonValidatedFields) => { } - +const _getRedirectDefinition = (redirect: Array, definitions: Object) => { + if(!isArray(redirect)){ + throw new Error(`${MIDDLEWARES_FIELD_VALIDATION}: The redirect property must be an array`); + } + if(redirect.length > 1){ + console.warn(`${MIDDLEWARES_FIELD_VALIDATION}: This feature is not yet supported. It will be done soon.`) + } + const FAKE_REDIRECT_INDEX = 0; + return definitions[redirect.length === 1 ? redirect[0]: redirect[FAKE_REDIRECT_INDEX]]; +} /** @@ -82,26 +93,39 @@ export const filterNonValidatedFields = (fields, nonValidatedFields) => { * @param {function} dispatch redux dispatch function * @return {boolean} the field validation status */ -export const validateField = (definitions, domains, formKey, entityPath, fieldName, value, dispatch) => { +export const validateField = (definitions, domains , formKey, entityPath, fieldName, value, dispatch) => { let {isRequired, domain: domainName, redirect} = definitions[entityPath][fieldName]; let validationResult= {}; // Redirect use to have the information of a list field if(isArray(value)){ + // If it is a list field it should redirect to a line entity definition. if(redirect){ - domainName = definitions[redirect] - value.map((element, index)=>{ + if(!isArray(redirect)){ + throw new Error(`${MIDDLEWARES_FIELD_VALIDATION}: your redirect property should be an array in the definition of ${entityPath}.${fieldName}.`) + } + //TODO: feature redirect array + const redirectDefinition = _getRedirectDefinition(redirect, definitions); + // The value is an array and we iterate over it. + validationResult = {isValid : true}; + value.map((element, index) => { mapKeys(element, (value, propertyNameLine) => { - const domain = domains[domainName[propertyNameLine].domain]; + const domain = domains[redirectDefinition[propertyNameLine].domain]; const fieldValid = validateFieldForList(definitions, domain , propertyNameLine, formKey, value, dispatch,index, entityPath, fieldName ); + if(fieldValid === false){ + validationResult.isValid = false; + } }) }) }else { - throw new Error(`${MIDDLEWARES_FIELD_VALIDATION} : You must provide a "redirect" defintions to your list field : ${fieldName}`) + throw new Error(`${MIDDLEWARES_FIELD_VALIDATION} : You must provide a "redirect" defintions to your list field : ${entityPath}.${fieldName}`) } - validationResult = {isValid : true} }else { + //TODO: Maybe it should be entityName + fieldName. const domain = domains[domainName]; + if(!domain){ + throw new Error(`${MIDDLEWARES_FIELD_VALIDATION}: Your field ${fieldName} in the entity ${entityPath} don't have a domain, you may have an array field which have a **redirect** property in it.`) + } validationResult = __fake_focus_core_validation_function__(isRequired, domain.validators, fieldName, value); } @@ -127,9 +151,11 @@ export const validateField = (definitions, domains, formKey, entityPath, fieldNa * @return {boolean} the field validation status */ export const validateFieldForList = (definitions, domain, propertyNameLine, formKey, value, dispatch,index, entityPath, fieldNameList ) => { +// if(value === 1) throw new Error(JSON.stringify({ domain, propertyNameLine, formKey, value, index, entityPath, fieldNameList})) let validationResult= {}; - let {isRequired} = definitions[entityPath][fieldNameList]; + const {isRequired} = definitions[entityPath][fieldNameList]; validationResult = __fake_focus_core_validation_function__(isRequired, domain.validators, fieldNameList, value); + //if(value === 1) throw new Error(JSON.stringify(validationResult)); if (!validationResult.isValid ){ dispatch(inputErrorList(formKey, fieldNameList , entityPath, validationResult.error, propertyNameLine , index)); return false; @@ -187,11 +213,11 @@ export const formatValue = (value, entityPath, fieldName, definitions, domains) const entityDefinition = definitions[entityPath] || {}; const {domain: domainName = {} , redirect} = entityDefinition[fieldName] || {}; if(redirect){ - const domainName = definitions[redirect] + const redirectDefinition = _getRedirectDefinition(redirect, definitions); value = value.map((element, index)=>{ const newElement ={}; Object.keys(element).map((propertyNameLine)=> { - const domain = domains[domainName[propertyNameLine].domain]; + const domain = domains[redirectDefinition[propertyNameLine].domain]; const {formatter = defaultFormatter} = domain || {}; newElement[propertyNameLine] = formatter(element[propertyNameLine]); }) @@ -207,8 +233,7 @@ export const formatValue = (value, entityPath, fieldName, definitions, domains) export const getRedirectEntityPath = (value, entityPath, fieldName, definitions, domains) => { - if(definitions[entityPath][fieldName].redirect){ + if(definitions && definitions[entityPath] && definitions[entityPath][fieldName] && definitions[entityPath][fieldName].redirect){ return definitions[entityPath][fieldName].redirect; - }else return; - + } return; }