Add collaboration feature
[sdc.git] / openecomp-ui / src / nfvo-components / input / validation / InputOptions.jsx
1 /*!
2  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13  * or implied. See the License for the specific language governing
14  * permissions and limitations under the License.
15  */
16 import React from 'react';
17 import PropTypes from 'prop-types';
18 import ReactDOM from 'react-dom';
19 import i18n from 'nfvo-utils/i18n/i18n.js';
20 import classNames from 'classnames';
21 import Select from 'nfvo-components/input/SelectInput.jsx';
22 import Overlay from 'react-bootstrap/lib/Overlay.js';
23 import Tooltip from 'react-bootstrap/lib/Tooltip.js';
24
25 export const other = {OTHER: 'Other'};
26
27 class InputOptions extends React.Component {
28
29         static propTypes = {
30                 values: PropTypes.arrayOf(PropTypes.shape({
31                         enum: PropTypes.string,
32                         title: PropTypes.string
33                 })),
34                 isEnabledOther: PropTypes.bool,
35                 label: PropTypes.string,
36                 selectedValue: PropTypes.string,
37                 multiSelectedEnum: PropTypes.oneOfType([
38                         PropTypes.string,
39                         PropTypes.array
40                 ]),
41                 selectedEnum: PropTypes.string,
42                 otherValue: PropTypes.string,
43                 overlayPos: PropTypes.string,
44                 onEnumChange: PropTypes.func,
45                 onOtherChange: PropTypes.func,
46                 onBlur: PropTypes.func,
47                 isRequired: PropTypes.bool,
48                 isMultiSelect: PropTypes.bool,
49                 isValid: PropTypes.bool,
50                 disabled: PropTypes.bool
51         };
52
53         state = {
54                 otherInputDisabled: !this.props.otherValue
55         };
56
57         oldProps = {
58                 selectedEnum: '',
59                 otherValue: '',
60                 multiSelectedEnum: []
61         };
62
63         render() {
64                 let {label, isRequired, values, otherValue, onOtherChange, isMultiSelect, onBlur, multiSelectedEnum, selectedEnum, isValid, children, isReadOnlyMode} = this.props;
65                 const dataTestId = this.props['data-test-id'] ? {'data-test-id': this.props['data-test-id']} : {};
66                 let currentMultiSelectedEnum = [];
67                 let currentSelectedEnum = '';
68                 let otherInputDisabled = (isMultiSelect && (multiSelectedEnum === undefined || multiSelectedEnum.length === 0 || multiSelectedEnum[0] !== other.OTHER))
69                         || (!isMultiSelect && (selectedEnum === undefined || selectedEnum !== other.OTHER));
70                 if (isMultiSelect) {
71                         currentMultiSelectedEnum = multiSelectedEnum;
72                         if(!otherInputDisabled) {
73                                 currentSelectedEnum = multiSelectedEnum ? multiSelectedEnum.toString() : undefined;
74                         }
75                 }
76                 else if(selectedEnum){
77                         currentSelectedEnum = selectedEnum;
78                 }
79                 if (!onBlur) {
80                         onBlur = () => {};
81                 }
82
83                 return(
84                         <div className='validation-input-wrapper' >
85                                 <div className={classNames('form-group', {'required' : isRequired, 'has-error' : !isValid})} >
86                                         {label && <label className='control-label'>{label}</label>}
87                                         {isMultiSelect && otherInputDisabled ?
88                                                 <Select
89                                                         {...dataTestId}
90                                                         ref={(input) => this.input = input}
91                                                         value={currentMultiSelectedEnum}
92                                                         className='options-input'
93                                                         clearable={false}
94                                                         required={isRequired}
95                                                         disabled={isReadOnlyMode || Boolean(this.props.disabled)}
96                                                         onBlur={() => onBlur()}
97                                                         onMultiSelectChanged={value => this.multiSelectEnumChanged(value)}
98                                                         options={this.renderMultiSelectOptions(values)}
99                                                         multi/> :
100                                                 <div className={classNames('input-options',{'has-error' : !isValid})} >
101                                                         <select
102                                                                 {...dataTestId}
103                                                                 ref={(input) => this.input = input}
104                                                                 label={label}
105                                                                 className='form-control input-options-select'
106                                                                 value={currentSelectedEnum}
107                                                                 style={{'width' : otherInputDisabled ? '100%' : '100px'}}
108                                                                 onBlur={() => onBlur()}
109                                                                 disabled={isReadOnlyMode || Boolean(this.props.disabled)}
110                                                                 onChange={ value => this.enumChanged(value)}
111                                                                 type='select'>
112                                                                 {children || (values && values.length && values.map((val, index) => this.renderOptions(val, index)))}
113                                                                 {onOtherChange && <option key='other' value={other.OTHER}>{i18n(other.OTHER)}</option>}
114                                                         </select>
115
116                                                         {!otherInputDisabled && <div className='input-options-separator'/>}
117                                                         <input
118                                                                 className='form-control input-options-other'
119                                                                 placeholder={i18n('other')}
120                                                                 ref={(otherValue) => this.otherValue = otherValue}
121                                                                 style={{'display' : otherInputDisabled ? 'none' : 'block'}}
122                                                                 disabled={isReadOnlyMode || Boolean(this.props.disabled)}
123                                                                 value={otherValue || ''}
124                                                                 onBlur={() => onBlur()}
125                                                                 onChange={() => this.changedOtherInput()}/>
126                                                 </div>
127                                         }
128                                         </div>
129                                 { this.renderErrorOverlay() }
130                         </div>
131                 );
132         }
133
134         renderOptions(val, index){
135                 return (
136                         <option key={index} value={val.enum}>{val.title}</option>
137                 );
138         }
139
140
141         renderMultiSelectOptions(values) {
142                 let {onOtherChange} = this.props;
143                 let optionsList = [];
144                 if (onOtherChange) {
145                         optionsList = values.map(option => {
146                                 return {
147                                         label: option.title,
148                                         value: option.enum,
149                                 };
150                         }).concat([{
151                                 label: i18n(other.OTHER),
152                                 value: i18n(other.OTHER),
153                         }]);
154                 }
155                 else {
156                         optionsList = values.map(option => {
157                                 return {
158                                         label: option.title,
159                                         value: option.enum,
160                                 };
161                         });
162                 }
163                 if (optionsList.length > 0 && optionsList[0].value === '') {
164                         optionsList.shift();
165                 }
166                 return optionsList;
167         }
168
169         renderErrorOverlay() {
170                 let position = 'right';
171                 const {errorText = '', isValid = true, type, overlayPos} = this.props;
172
173                 if (overlayPos) {
174                         position = overlayPos;
175                 }
176                 else if (type === 'text'
177                         || type === 'email'
178                         || type === 'number'
179                         || type === 'password') {
180                         position = 'bottom';
181                 }
182
183                 return (
184                         <Overlay
185                                 show={!isValid}
186                                 placement={position}
187                                 target={() => {
188                                         let {otherInputDisabled} = this.state;
189                                         let target = otherInputDisabled ? ReactDOM.findDOMNode(this.input) :  ReactDOM.findDOMNode(this.otherValue);
190                                         return target.offsetParent ? target : undefined;
191                                 }}
192                                 container={this}>
193                                 <Tooltip
194                                         id={`error-${errorText.replace(' ', '-')}`}
195                                         className='validation-error-message'>
196                                         {errorText}
197                                 </Tooltip>
198                         </Overlay>
199                 );
200         }
201
202         getValue() {
203                 let res = '';
204                 let {isMultiSelect} = this.props;
205                 let {otherInputDisabled} = this.state;
206
207                 if (otherInputDisabled) {
208                         res = isMultiSelect ? this.input.getValue() : this.input.value;
209                 } else {
210                         res = this.otherValue.value;
211                 }
212                 return res;
213         }
214
215         enumChanged() {
216                 let enumValue = this.input.value;
217                 let {onEnumChange, onOtherChange, isMultiSelect, onChange} = this.props;
218                 this.setState({
219                         otherInputDisabled: !Boolean(onOtherChange) || enumValue !== other.OTHER
220                 });
221
222                 let value = isMultiSelect ? [enumValue] : enumValue;
223                 if (onEnumChange) {
224                         onEnumChange(value);
225                 }
226                 if (onChange) {
227                         onChange(value);
228                 }
229         }
230
231         multiSelectEnumChanged(enumValue) {
232                 let {onEnumChange, onOtherChange} = this.props;
233                 let selectedValues = enumValue.map(enumVal => {
234                         return enumVal.value;
235                 });
236
237                 if (this.state.otherInputDisabled === false) {
238                         selectedValues.shift();
239                 }
240                 else if (selectedValues.includes(i18n(other.OTHER))) {
241                         selectedValues = [i18n(other.OTHER)];
242                 }
243
244                 this.setState({
245                         otherInputDisabled: !Boolean(onOtherChange) || !selectedValues.includes(i18n(other.OTHER))
246                 });
247                 onEnumChange(selectedValues);
248         }
249
250         changedOtherInput() {
251                 let {onOtherChange} = this.props;
252                 onOtherChange(this.otherValue.value);
253         }
254
255         componentDidUpdate() {
256                 let {otherValue, selectedEnum, onInputChange, multiSelectedEnum} = this.props;
257                 if (this.oldProps.otherValue !== otherValue
258                         || this.oldProps.selectedEnum !== selectedEnum
259                         || this.oldProps.multiSelectedEnum !== multiSelectedEnum) {
260                         this.oldProps = {
261                                 otherValue,
262                                 selectedEnum,
263                                 multiSelectedEnum
264                         };
265                         onInputChange();
266                 }
267         }
268
269         static getTitleByName(values, name) {
270                 for (let key of Object.keys(values)) {
271                         let option = values[key].find(option => option.enum === name);
272                         if (option) {
273                                 return option.title;
274                         }
275                 }
276                 return name;
277         }
278
279 }
280
281 export default InputOptions;