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