/** * Used for inputs on a validation form. * All properties will be passed on to the input element. * * The following properties can be set for OOB validations and callbacks: - required: Boolean: Should be set to true if the input must have a value - numeric: Boolean : Should be set to true id the input should be an integer - onChange : Function : Will be called to validate the value if the default validations are not sufficient, should return a boolean value indicating whether the value is valid - didUpdateCallback :Function: Will be called after the state has been updated and the component has rerendered. This can be used if there are dependencies between inputs in a form. * * The following properties of the state can be set to determine * the state of the input from outside components: - isValid : Boolean - whether the value is valid - value : value for the input field, - disabled : Boolean, - required : Boolean - whether the input value must be filled out. */ import React from 'react'; import ReactDOM from 'react-dom'; import Validator from 'validator'; import FormGroup from 'react-bootstrap/lib/FormGroup.js'; import Input from 'react-bootstrap/lib/Input.js'; import Overlay from 'react-bootstrap/lib/Overlay.js'; import Tooltip from 'react-bootstrap/lib/Tooltip.js'; import isEqual from 'lodash/isEqual.js'; import i18n from 'nfvo-utils/i18n/i18n.js'; import JSONSchema from 'nfvo-utils/json/JSONSchema.js'; import JSONPointer from 'nfvo-utils/json/JSONPointer.js'; import InputOptions from '../inputOptions/InputOptions.jsx'; const globalValidationFunctions = { required: value => value !== '', maxLength: (value, length) => Validator.isLength(value, {max: length}), minLength: (value, length) => Validator.isLength(value, {min: length}), pattern: (value, pattern) => Validator.matches(value, pattern), numeric: value => { if (value === '') { // to allow empty value which is not zero return true; } return Validator.isNumeric(value); }, maxValue: (value, maxValue) => value < maxValue, minValue: (value, minValue) => value >= minValue, alphanumeric: value => Validator.isAlphanumeric(value), alphanumericWithSpaces: value => Validator.isAlphanumeric(value.replace(/ /g, '')), validateName: value => Validator.isAlphanumeric(value.replace(/\s|\.|\_|\-/g, ''), 'en-US'), validateVendorName: value => Validator.isAlphanumeric(value.replace(/[\x7F-\xFF]|\s/g, ''), 'en-US'), freeEnglishText: value => Validator.isAlphanumeric(value.replace(/\s|\.|\_|\-|\,|\(|\)|\?/g, ''), 'en-US'), email: value => Validator.isEmail(value), ip: value => Validator.isIP(value), url: value => Validator.isURL(value) }; const globalValidationMessagingFunctions = { required: () => i18n('Field is required'), maxLength: (value, maxLength) => i18n('Field value has exceeded it\'s limit, {maxLength}. current length: {length}', { length: value.length, maxLength }), minLength: (value, minLength) => i18n('Field value should contain at least {minLength} characters.', {minLength}), pattern: (value, pattern) => i18n('Field value should match the pattern: {pattern}.', {pattern}), numeric: () => i18n('Field value should contain numbers only.'), maxValue: (value, maxValue) => i18n('Field value should be less than: {maxValue}.', {maxValue}), minValue: (value, minValue) => i18n('Field value should be at least: {minValue}.', {minValue}), alphanumeric: () => i18n('Field value should contain letters or digits only.'), alphanumericWithSpaces: () => i18n('Field value should contain letters, digits or spaces only.'), validateName: ()=> i18n('Field value should contain English letters, digits , spaces, underscores, dashes and dots only.'), validateVendorName: ()=> i18n('Field value should contain English letters digits and spaces only.'), freeEnglishText: ()=> i18n('Field value should contain English letters, digits , spaces, underscores, dashes and dots only.'), email: () => i18n('Field value should be a valid email address.'), ip: () => i18n('Field value should be a valid ip address.'), url: () => i18n('Field value should be a valid url address.'), general: () => i18n('Field value is invalid.') }; class ValidationInput extends React.Component { static contextTypes = { validationParent: React.PropTypes.any, isReadOnlyMode: React.PropTypes.bool, validationSchema: React.PropTypes.instanceOf(JSONSchema), validationData: React.PropTypes.object }; static defaultProps = { onChange: null, disabled: null, didUpdateCallback: null, validations: {}, value: '' }; static propTypes = { type: React.PropTypes.string.isRequired, onChange: React.PropTypes.func, disabled: React.PropTypes.bool, didUpdateCallback: React.PropTypes.func, validations: React.PropTypes.object, isMultiSelect: React.PropTypes.bool, onOtherChange: React.PropTypes.func, pointer: React.PropTypes.string }; state = { isValid: true, style: null, value: this.props.value, error: {}, previousErrorMessage: '', wasInvalid: false, validations: this.props.validations, isMultiSelect: this.props.isMultiSelect }; componentWillMount() { if (this.context.validationSchema) { let {validationSchema: schema, validationData: data} = this.context, {pointer} = this.props; if (!schema.exists(pointer)) { console.error(`Field doesn't exists in the schema ${pointer}`); } let value = JSONPointer.getValue(data, pointer); if (value === undefined) { value = schema.getDefault(pointer); if (value === undefined) { value = ''; } } this.setState({value}); let enums = schema.getEnum(pointer); if (enums) { let values = enums.map(value => ({enum: value, title: value, groupName: pointer})), isMultiSelect = schema.isArray(pointer); if (!isMultiSelect && this.props.type !== 'radiogroup') { values = [{enum: '', title: i18n('Select...')}, ...values]; } if (isMultiSelect && Array.isArray(value) && value.length === 0) { value = ''; } this.setState({ isMultiSelect, values, onEnumChange: value => this.changedInputOptions(value), value }); } this.setState({validations: this.extractValidationsFromSchema(schema, pointer, this.props)}); } } extractValidationsFromSchema(schema, pointer, props) { /* props are here to get precedence over the scheme definitions */ let validations = {}; if (schema.isRequired(pointer)) { validations.required = true; } if (schema.isNumber(pointer)) { validations.numeric = true; const maxValue = props.validations.maxValue || schema.getMaxValue(pointer); if (maxValue !== undefined) { validations.maxValue = maxValue; } const minValue = props.validations.minValue || schema.getMinValue(pointer); if (minValue !== undefined) { validations.minValue = minValue; } } if (schema.isString(pointer)) { const pattern = schema.getPattern(pointer); if (pattern) { validations.pattern = pattern; } const maxLength = schema.getMaxLength(pointer); if (maxLength !== undefined) { validations.maxLength = maxLength; } const minLength = schema.getMinLength(pointer); if (minLength !== undefined) { validations.minLength = minLength; } } return validations; } componentWillReceiveProps({value: nextValue, validations: nextValidations, pointer: nextPointer}, nextContext) { const {validations, value} = this.props; const validationsChanged = !isEqual(validations, nextValidations); if (nextContext.validationSchema) { if (this.props.pointer !== nextPointer || this.context.validationData !== nextContext.validationData) { let currentValue = JSONPointer.getValue(this.context.validationData, this.props.pointer), nextValue = JSONPointer.getValue(nextContext.validationData, nextPointer); if(nextValue === undefined) { nextValue = ''; } if (this.state.isMultiSelect && Array.isArray(nextValue) && nextValue.length === 0) { nextValue = ''; } if (currentValue !== nextValue) { this.setState({value: nextValue}); } if (validationsChanged) { this.setState({ validations: this.extractValidationsFromSchema(nextContext.validationSchema, nextPointer, {validations: nextValidations}) }); } } } else { if (validationsChanged) { this.setState({validations: nextValidations}); } if (this.state.wasInvalid && (value !== nextValue || validationsChanged)) { this.validate(nextValue, nextValidations); } else if (value !== nextValue) { this.setState({value: nextValue}); } } } shouldTypeBeNumberBySchemeDefinition(pointer) { return this.context.validationSchema && this.context.validationSchema.isNumber(pointer); } hasEnum(pointer) { return this.context.validationSchema && this.context.validationSchema.getEnum(pointer); } render() { let {value, isMultiSelect, values, onEnumChange, style, isValid, validations} = this.state; let {onOtherChange, type, pointer} = this.props; if (this.shouldTypeBeNumberBySchemeDefinition(pointer) && !this.hasEnum(pointer)) { type = 'number'; } let props = {...this.props}; let groupClasses = this.props.groupClassName || ''; if (validations.required) { groupClasses += ' required'; } let isReadOnlyMode = this.context.isReadOnlyMode; if (value === true && (type === 'checkbox' || type === 'radio')) { props.checked = true; } return (