Initial coomit for AAI-UI(sparky-fe)
[aai/sparky-fe.git] / src / generic-components / input / validation / ValidationInput.jsx
1 /*
2  * ============LICENSE_START===================================================
3  * SPARKY (AAI UI service)
4  * ============================================================================
5  * Copyright © 2017 AT&T Intellectual Property.
6  * Copyright © 2017 Amdocs
7  * All rights reserved.
8  * ============================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * ============LICENSE_END=====================================================
21  *
22  * ECOMP and OpenECOMP are trademarks
23  * and service marks of AT&T Intellectual Property.
24  */
25
26 /**
27         * Used for inputs on a validation form.
28         * All properties will be passed on to the input element.
29         *
30         * The following properties can be set for OOB validations and callbacks:
31         - required: Boolean:  Should be set to true if the input must have a value
32         - numeric: Boolean : Should be set to true id the input should be an integer
33         - onChange : Function :  Will be called to validate the value if the default validations are not sufficient, should return a boolean value
34         indicating whether the value is valid
35         - didUpdateCallback :Function: Will be called after the state has been updated and the component has rerendered. This can be used if
36         there are dependencies between inputs in a form.
37         *
38         * The following properties of the state can be set to determine
39         * the state of the input from outside components:
40         - isValid : Boolean - whether the value is valid
41         - value : value for the input field,
42         - disabled : Boolean,
43         - required : Boolean - whether the input value must be filled out.
44         */
45 import React from 'react';
46 import ReactDOM from 'react-dom';
47 import Validator from 'validator';
48 import Input from 'react-bootstrap/lib/Input.js';
49 import Overlay from 'react-bootstrap/lib/Overlay.js';
50 import Tooltip from 'react-bootstrap/lib/Tooltip.js';
51 import isEqual from 'lodash/lang/isEqual.js';
52 import i18n from 'utils/i18n/i18n.js';
53
54 import InputOptions  from '../inputOptions/InputOptions.jsx';
55
56 const globalValidationFunctions = {
57                 required: value => value !== '',
58                 maxLength: (value, length) => Validator.isLength(value, {max: length}),
59                 minLength: (value, length) => Validator.isLength(value, {min: length}),
60                 pattern: (value, pattern) => Validator.matches(value, pattern),
61                 numeric: value => Validator.isNumeric(value),
62                 maxValue: (value, maxValue) => value < maxValue,
63                 alphanumeric: value => Validator.isAlphanumeric(value),
64                 alphanumericWithSpaces: value => Validator.isAlphanumeric(
65                                 value.replace(/ /g, '')),
66                 validateName: value => Validator.isAlphanumeric(
67                                 value.replace(/\s|\.|\_|\-/g, ''), 'en-US'),
68                 validateVendorName: value => Validator.isAlphanumeric(
69                                 value.replace(/[\x7F-\xFF]|\s/g, ''), 'en-US'),
70                 freeEnglishText: value => Validator.isAlphanumeric(
71                                 value.replace(/\s|\.|\_|\-|\,|\(|\)|\?/g, ''), 'en-US'),
72                 email: value => Validator.isEmail(value),
73                 ip: value => Validator.isIP(value),
74                 url: value => Validator.isURL(value)
75 };
76
77 const globalValidationMessagingFunctions = {
78                 required: () => i18n('Field is required'),
79                 maxLength: (value, maxLength) => i18n(
80                                 'Field value has exceeded it\'s limit, {maxLength}. current length: {length}',
81                                 {
82                                                 length: value.length,
83                                                 maxLength
84                                 }),
85                 minLength: (value, minLength) => i18n(
86                                 'Field value should contain at least {minLength} characters.', {minLength}),
87                 pattern: (value, pattern) => i18n(
88                                 'Field value should match the pattern: {pattern}.', {pattern}),
89                 numeric: () => i18n('Field value should contain numbers only.'),
90                 maxValue: (value, maxValue) => i18n(
91                                 'Field value should be less then: {maxValue}.', {maxValue}),
92                 alphanumeric: () => i18n(
93                                 'Field value should contain letters or digits only.'),
94                 alphanumericWithSpaces: () => i18n(
95                                 'Field value should contain letters, digits or spaces only.'),
96                 validateName: ()=> i18n(
97                                 'Field value should contain English letters, digits , spaces, underscores, dashes and dots only.'),
98                 validateVendorName: ()=> i18n(
99                                 'Field value should contain English letters digits and spaces only.'),
100                 freeEnglishText: ()=> i18n(
101                                 'Field value should contain  English letters, digits , spaces, underscores, dashes and dots only.'),
102                 email: () => i18n('Field value should be a valid email address.'),
103                 ip: () => i18n('Field value should be a valid ip address.'),
104                 url: () => i18n('Field value should be a valid url address.'),
105                 general: () => i18n('Field value is invalid.')
106 };
107
108 class ValidationInput extends React.Component {
109                 
110                 static contextTypes = {
111                                 validationParent: React.PropTypes.any,
112                                 isReadOnlyMode: React.PropTypes.bool
113                 };
114                 
115                 static defaultProps = {
116                                 onChange: null,
117                                 disabled: null,
118                                 didUpdateCallback: null,
119                                 validations: {},
120                                 value: ''
121                 };
122                 
123                 static propTypes = {
124                                 onChange: React.PropTypes.func,
125                                 disabled: React.PropTypes.bool,
126                                 didUpdateCallback: React.PropTypes.func,
127                                 validations: React.PropTypes.object
128                 };
129                 
130                 
131                 state = {
132                                 isValid: true,
133                                 style: null,
134                                 value: this.props.value,
135                                 error: {},
136                                 previousErrorMessage: '',
137                                 wasInvalid: false
138                 };
139                 
140                 componentWillReceiveProps({value: nextValue, validations: nextValidaions}) {
141                                 if (this.state.wasInvalid) {
142                                                 const {validations, value} = this.props;
143                                                 if (value !== nextValue || !isEqual(validations, nextValidaions)) {
144                                                                 this.validate(nextValue, nextValidaions);
145                                                 }
146                                 } else if (this.props.value !== nextValue) {
147                                                 this.setState({value: nextValue});
148                                 }
149                 }
150                 
151                 render() {
152                                 let {isMultiSelect, onOtherChange, type} = this.props;
153                                 
154                                 let groupClasses = this.props.groupClassName || '';
155                                 if (this.props.validations.required) {
156                                                 groupClasses += ' required';
157                                 }
158                                 let isReadOnlyMode = this.context.isReadOnlyMode;
159                                 
160                                 return (
161                                                 <div className='validation-input-wrapper'>
162                                                                 {
163                                                                                 !isMultiSelect && !onOtherChange && type !== 'select'
164                                                                                 && <Input
165                                                                                                 {...this.props}
166                                                                                                 groupClassName={groupClasses}
167                                                                                                 ref={'_myInput'}
168                                                                                                 value={this.state.value}
169                                                                                                 disabled={isReadOnlyMode || Boolean(this.props.disabled)}
170                                                                                                 bsStyle={this.state.style}
171                                                                                                 onChange={() => this.changedInput()}
172                                                                                                 onBlur={() => this.blurInput()}>
173                                                                                                 {this.props.children}
174                                                                                 </Input>
175                                                                 }
176                                                                 {
177                                                                                 (isMultiSelect || onOtherChange || type === 'select')
178                                                                                 && <InputOptions onInputChange={() => this.changedInput()}
179                                                                                                  onBlur={() => this.blurInput()}
180                                                                                                  hasError={!this.state.isValid}
181                                                                                                  ref={'_myInput'} {...this.props} />
182                                                                 }
183                                                                 {this.renderOverlay()}
184                                                 </div>
185                                 );
186                 }
187                 
188                 renderOverlay() {
189                                 let position = 'right';
190                                 if (this.props.type === 'text'
191                                                 || this.props.type === 'email'
192                                                 || this.props.type === 'number'
193                                                 || this.props.type === 'password'
194                                 
195                                 ) {
196                                                 position = 'bottom';
197                                 }
198                                 
199                                 let validationMessage = this.state.error.message ||
200                                                 this.state.previousErrorMessage;
201                                 return (
202                                                 <Overlay
203                                                                 show={!this.state.isValid}
204                                                                 placement={position}
205                                                                 target={() => {let target = ReactDOM.findDOMNode(this.refs._myInput); return target.offsetParent ? target : undefined;}}
206                                                                 container={this}>
207                                                                 <Tooltip
208                                                                                 id={`error-${validationMessage.replace(' ','-')}`}
209                                                                                 className='validation-error-message'>
210                                                                                 {validationMessage}
211                                                                 </Tooltip>
212                                                 </Overlay>
213                                 );
214                 }
215                 
216                 componentDidMount() {
217                                 if (this.context.validationParent) {
218                                                 this.context.validationParent.register(this);
219                                 }
220                 }
221                 
222                 componentDidUpdate(prevProps, prevState) {
223                                 if (this.context.validationParent) {
224                                                 if (prevState.isValid !== this.state.isValid) {
225                                                                 this.context.validationParent.childValidStateChanged(this,
226                                                                                 this.state.isValid);
227                                                 }
228                                 }
229                                 if (this.props.didUpdateCallback) {
230                                                 this.props.didUpdateCallback();
231                                 }
232                                 
233                 }
234                 
235                 componentWillUnmount() {
236                                 if (this.context.validationParent) {
237                                                 this.context.validationParent.unregister(this);
238                                 }
239                 }
240                 
241                 /***
242                         * Adding same method as the actual input component
243                         * @returns {*}
244                         */
245                 getValue() {
246                                 if (this.props.type === 'checkbox') {
247                                                 return this.refs._myInput.getChecked();
248                                 }
249                                 return this.refs._myInput.getValue();
250                 }
251                 
252                 resetValue() {
253                                 this.setState({value: this.props.value});
254                 }
255                 
256                 
257                 /***
258                         * internal method that validated the value. includes callback to the
259                         * onChange method
260                         * @param value
261                         * @param validations - map containing validation id and the limitation
262                         *   describing the validation.
263                         * @returns {object}
264                         */
265                 validateValue = (value, validations) => {
266                                 let {customValidationFunction} = validations;
267                                 let error = {};
268                                 let isValid = true;
269                                 for (let validation in validations) {
270                                                 if ('customValidationFunction' !== validation) {
271                                                                 if (validations[validation]) {
272                                                                                 if (!globalValidationFunctions[validation](value,
273                                                                                                                 validations[validation])) {
274                                                                                                 error.id = validation;
275                                                                                                 error.message =
276                                                                                                                 globalValidationMessagingFunctions[validation](value,
277                                                                                                                                 validations[validation]);
278                                                                                                 isValid = false;
279                                                                                                 break;
280                                                                                 }
281                                                                 }
282                                                 } else {
283                                                                 let customValidationResult = customValidationFunction(value);
284                                                                 
285                                                                 if (customValidationResult !== true) {
286                                                                                 error.id = 'custom';
287                                                                                 isValid = false;
288                                                                                 if (typeof customValidationResult === 'string') {//custom validation error message supplied.
289                                                                                                 error.message = customValidationResult;
290                                                                                 } else {
291                                                                                                 error.message = globalValidationMessagingFunctions.general();
292                                                                                 }
293                                                                                 break;
294                                                                 }
295                                                                 
296                                                                 
297                                                 }
298                                 }
299                                 
300                                 return {
301                                                 isValid,
302                                                 error
303                                 };
304                 };
305                 
306                 /***
307                         * Internal method that handles the change event of the input. validates and
308                         * updates the state.
309                         */
310                 changedInput() {
311                                 
312                                 let {isValid, error} = this.state.wasInvalid ? this.validate() : this.state;
313                                 let onChange = this.props.onChange;
314                                 if (onChange) {
315                                                 onChange(this.getValue(), isValid, error);
316                                 }
317                 };
318                 
319                 blurInput() {
320                                 if (!this.state.wasInvalid) {
321                                                 this.setState({wasInvalid: true});
322                                 }
323                                 
324                                 let {isValid, error} = !this.state.wasInvalid
325                                                 ? this.validate()
326                                                 : this.state;
327                                 let onBlur = this.props.onBlur;
328                                 if (onBlur) {
329                                                 onBlur(this.getValue(), isValid, error);
330                                 }
331                 };
332                 
333                 validate(value = this.getValue(), validations = this.props.validations) {
334                                 let validationStatus = this.validateValue(value, validations);
335                                 let {isValid, error} = validationStatus;
336                                 let _style = isValid ? null : 'error';
337                                 this.setState({
338                                                 isValid,
339                                                 error,
340                                                 value,
341                                                 previousErrorMessage: this.state.error.message || '',
342                                                 style: _style,
343                                                 wasInvalid: !isValid || this.state.wasInvalid
344                                 });
345                                 
346                                 return validationStatus;
347                 }
348                 
349                 isValid() {
350                                 return this.state.isValid;
351                 }
352                 
353 }
354 export default ValidationInput;