Initial coomit for AAI-UI(sparky-fe)
[aai/sparky-fe.git] / src / generic-components / input / validation / ValidationInput.jsx
diff --git a/src/generic-components/input/validation/ValidationInput.jsx b/src/generic-components/input/validation/ValidationInput.jsx
new file mode 100644 (file)
index 0000000..f030cd6
--- /dev/null
@@ -0,0 +1,354 @@
+/*
+ * ============LICENSE_START===================================================
+ * SPARKY (AAI UI service)
+ * ============================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ============================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+
+/**
+       * 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 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/lang/isEqual.js';
+import i18n from 'utils/i18n/i18n.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 => Validator.isNumeric(value),
+               maxValue: (value, maxValue) => value < maxValue,
+               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 then: {maxValue}.', {maxValue}),
+               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
+               };
+               
+               static defaultProps = {
+                               onChange: null,
+                               disabled: null,
+                               didUpdateCallback: null,
+                               validations: {},
+                               value: ''
+               };
+               
+               static propTypes = {
+                               onChange: React.PropTypes.func,
+                               disabled: React.PropTypes.bool,
+                               didUpdateCallback: React.PropTypes.func,
+                               validations: React.PropTypes.object
+               };
+               
+               
+               state = {
+                               isValid: true,
+                               style: null,
+                               value: this.props.value,
+                               error: {},
+                               previousErrorMessage: '',
+                               wasInvalid: false
+               };
+               
+               componentWillReceiveProps({value: nextValue, validations: nextValidaions}) {
+                               if (this.state.wasInvalid) {
+                                               const {validations, value} = this.props;
+                                               if (value !== nextValue || !isEqual(validations, nextValidaions)) {
+                                                               this.validate(nextValue, nextValidaions);
+                                               }
+                               } else if (this.props.value !== nextValue) {
+                                               this.setState({value: nextValue});
+                               }
+               }
+               
+               render() {
+                               let {isMultiSelect, onOtherChange, type} = this.props;
+                               
+                               let groupClasses = this.props.groupClassName || '';
+                               if (this.props.validations.required) {
+                                               groupClasses += ' required';
+                               }
+                               let isReadOnlyMode = this.context.isReadOnlyMode;
+                               
+                               return (
+                                               <div className='validation-input-wrapper'>
+                                                               {
+                                                                               !isMultiSelect && !onOtherChange && type !== 'select'
+                                                                               && <Input
+                                                                                               {...this.props}
+                                                                                               groupClassName={groupClasses}
+                                                                                               ref={'_myInput'}
+                                                                                               value={this.state.value}
+                                                                                               disabled={isReadOnlyMode || Boolean(this.props.disabled)}
+                                                                                               bsStyle={this.state.style}
+                                                                                               onChange={() => this.changedInput()}
+                                                                                               onBlur={() => this.blurInput()}>
+                                                                                               {this.props.children}
+                                                                               </Input>
+                                                               }
+                                                               {
+                                                                               (isMultiSelect || onOtherChange || type === 'select')
+                                                                               && <InputOptions onInputChange={() => this.changedInput()}
+                                                                                                onBlur={() => this.blurInput()}
+                                                                                                hasError={!this.state.isValid}
+                                                                                                ref={'_myInput'} {...this.props} />
+                                                               }
+                                                               {this.renderOverlay()}
+                                               </div>
+                               );
+               }
+               
+               renderOverlay() {
+                               let position = 'right';
+                               if (this.props.type === 'text'
+                                               || this.props.type === 'email'
+                                               || this.props.type === 'number'
+                                               || this.props.type === 'password'
+                               
+                               ) {
+                                               position = 'bottom';
+                               }
+                               
+                               let validationMessage = this.state.error.message ||
+                                               this.state.previousErrorMessage;
+                               return (
+                                               <Overlay
+                                                               show={!this.state.isValid}
+                                                               placement={position}
+                                                               target={() => {let target = ReactDOM.findDOMNode(this.refs._myInput); return target.offsetParent ? target : undefined;}}
+                                                               container={this}>
+                                                               <Tooltip
+                                                                               id={`error-${validationMessage.replace(' ','-')}`}
+                                                                               className='validation-error-message'>
+                                                                               {validationMessage}
+                                                               </Tooltip>
+                                               </Overlay>
+                               );
+               }
+               
+               componentDidMount() {
+                               if (this.context.validationParent) {
+                                               this.context.validationParent.register(this);
+                               }
+               }
+               
+               componentDidUpdate(prevProps, prevState) {
+                               if (this.context.validationParent) {
+                                               if (prevState.isValid !== this.state.isValid) {
+                                                               this.context.validationParent.childValidStateChanged(this,
+                                                                               this.state.isValid);
+                                               }
+                               }
+                               if (this.props.didUpdateCallback) {
+                                               this.props.didUpdateCallback();
+                               }
+                               
+               }
+               
+               componentWillUnmount() {
+                               if (this.context.validationParent) {
+                                               this.context.validationParent.unregister(this);
+                               }
+               }
+               
+               /***
+                       * Adding same method as the actual input component
+                       * @returns {*}
+                       */
+               getValue() {
+                               if (this.props.type === 'checkbox') {
+                                               return this.refs._myInput.getChecked();
+                               }
+                               return this.refs._myInput.getValue();
+               }
+               
+               resetValue() {
+                               this.setState({value: this.props.value});
+               }
+               
+               
+               /***
+                       * internal method that validated the value. includes callback to the
+                       * onChange method
+                       * @param value
+                       * @param validations - map containing validation id and the limitation
+                       *   describing the validation.
+                       * @returns {object}
+                       */
+               validateValue = (value, validations) => {
+                               let {customValidationFunction} = validations;
+                               let error = {};
+                               let isValid = true;
+                               for (let validation in validations) {
+                                               if ('customValidationFunction' !== validation) {
+                                                               if (validations[validation]) {
+                                                                               if (!globalValidationFunctions[validation](value,
+                                                                                                               validations[validation])) {
+                                                                                               error.id = validation;
+                                                                                               error.message =
+                                                                                                               globalValidationMessagingFunctions[validation](value,
+                                                                                                                               validations[validation]);
+                                                                                               isValid = false;
+                                                                                               break;
+                                                                               }
+                                                               }
+                                               } else {
+                                                               let customValidationResult = customValidationFunction(value);
+                                                               
+                                                               if (customValidationResult !== true) {
+                                                                               error.id = 'custom';
+                                                                               isValid = false;
+                                                                               if (typeof customValidationResult === 'string') {//custom validation error message supplied.
+                                                                                               error.message = customValidationResult;
+                                                                               } else {
+                                                                                               error.message = globalValidationMessagingFunctions.general();
+                                                                               }
+                                                                               break;
+                                                               }
+                                                               
+                                                               
+                                               }
+                               }
+                               
+                               return {
+                                               isValid,
+                                               error
+                               };
+               };
+               
+               /***
+                       * Internal method that handles the change event of the input. validates and
+                       * updates the state.
+                       */
+               changedInput() {
+                               
+                               let {isValid, error} = this.state.wasInvalid ? this.validate() : this.state;
+                               let onChange = this.props.onChange;
+                               if (onChange) {
+                                               onChange(this.getValue(), isValid, error);
+                               }
+               };
+               
+               blurInput() {
+                               if (!this.state.wasInvalid) {
+                                               this.setState({wasInvalid: true});
+                               }
+                               
+                               let {isValid, error} = !this.state.wasInvalid
+                                               ? this.validate()
+                                               : this.state;
+                               let onBlur = this.props.onBlur;
+                               if (onBlur) {
+                                               onBlur(this.getValue(), isValid, error);
+                               }
+               };
+               
+               validate(value = this.getValue(), validations = this.props.validations) {
+                               let validationStatus = this.validateValue(value, validations);
+                               let {isValid, error} = validationStatus;
+                               let _style = isValid ? null : 'error';
+                               this.setState({
+                                               isValid,
+                                               error,
+                                               value,
+                                               previousErrorMessage: this.state.error.message || '',
+                                               style: _style,
+                                               wasInvalid: !isValid || this.state.wasInvalid
+                               });
+                               
+                               return validationStatus;
+               }
+               
+               isValid() {
+                               return this.state.isValid;
+               }
+               
+}
+export default ValidationInput;