| /*! |
| * Copyright (C) 2017 AT&T Intellectual Property. 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. |
| */ |
| import React from 'react'; |
| import PropTypes from 'prop-types'; |
| import ReactDOM from 'react-dom'; |
| import i18n from 'nfvo-utils/i18n/i18n.js'; |
| import classNames from 'classnames'; |
| import Select from 'nfvo-components/input/SelectInput.jsx'; |
| import Overlay from 'react-bootstrap/lib/Overlay.js'; |
| import Tooltip from 'react-bootstrap/lib/Tooltip.js'; |
| |
| export const other = { OTHER: 'Other' }; |
| |
| class InputOptions extends React.Component { |
| static propTypes = { |
| values: PropTypes.arrayOf( |
| PropTypes.shape({ |
| enum: PropTypes.string, |
| title: PropTypes.string |
| }) |
| ), |
| isEnabledOther: PropTypes.bool, |
| label: PropTypes.string, |
| selectedValue: PropTypes.string, |
| multiSelectedEnum: PropTypes.oneOfType([ |
| PropTypes.string, |
| PropTypes.array |
| ]), |
| selectedEnum: PropTypes.string, |
| otherValue: PropTypes.string, |
| overlayPos: PropTypes.string, |
| onEnumChange: PropTypes.func, |
| onOtherChange: PropTypes.func, |
| onBlur: PropTypes.func, |
| isRequired: PropTypes.bool, |
| isMultiSelect: PropTypes.bool, |
| isValid: PropTypes.bool, |
| disabled: PropTypes.bool |
| }; |
| |
| state = { |
| otherInputDisabled: !this.props.otherValue |
| }; |
| |
| oldProps = { |
| selectedEnum: '', |
| otherValue: '', |
| multiSelectedEnum: [] |
| }; |
| |
| render() { |
| let { |
| label, |
| isRequired, |
| values, |
| otherValue, |
| onOtherChange, |
| isMultiSelect, |
| onBlur, |
| multiSelectedEnum, |
| selectedEnum, |
| isValid, |
| children, |
| isReadOnlyMode |
| } = this.props; |
| const dataTestId = this.props['data-test-id'] |
| ? { 'data-test-id': this.props['data-test-id'] } |
| : {}; |
| let currentMultiSelectedEnum = []; |
| let currentSelectedEnum = ''; |
| let otherInputDisabled = |
| (isMultiSelect && |
| (multiSelectedEnum === undefined || |
| multiSelectedEnum.length === 0 || |
| multiSelectedEnum[0] !== other.OTHER)) || |
| (!isMultiSelect && |
| (selectedEnum === undefined || selectedEnum !== other.OTHER)); |
| if (isMultiSelect) { |
| currentMultiSelectedEnum = multiSelectedEnum; |
| if (!otherInputDisabled) { |
| currentSelectedEnum = multiSelectedEnum |
| ? multiSelectedEnum.toString() |
| : undefined; |
| } |
| } else if (selectedEnum) { |
| currentSelectedEnum = selectedEnum; |
| } |
| if (!onBlur) { |
| onBlur = () => {}; |
| } |
| |
| return ( |
| <div className="validation-input-wrapper"> |
| <div |
| className={classNames('form-group', { |
| required: isRequired, |
| 'has-error': !isValid |
| })}> |
| {label && <label className="control-label">{label}</label>} |
| {isMultiSelect && otherInputDisabled ? ( |
| <Select |
| {...dataTestId} |
| ref={input => (this.input = input)} |
| value={currentMultiSelectedEnum} |
| className="options-input" |
| clearable={false} |
| required={isRequired} |
| disabled={ |
| isReadOnlyMode || Boolean(this.props.disabled) |
| } |
| onBlur={() => onBlur()} |
| onMultiSelectChanged={value => |
| this.multiSelectEnumChanged(value) |
| } |
| options={this.renderMultiSelectOptions(values)} |
| multi |
| /> |
| ) : ( |
| <div |
| className={classNames('input-options', { |
| 'has-error': !isValid |
| })}> |
| <select |
| {...dataTestId} |
| ref={input => (this.input = input)} |
| label={label} |
| className="form-control input-options-select" |
| value={currentSelectedEnum} |
| style={{ |
| width: otherInputDisabled ? '100%' : '100px' |
| }} |
| onBlur={() => onBlur()} |
| disabled={ |
| isReadOnlyMode || |
| Boolean(this.props.disabled) |
| } |
| onChange={value => this.enumChanged(value)} |
| type="select"> |
| {children || |
| (values && |
| values.length && |
| values.map((val, index) => |
| this.renderOptions(val, index) |
| ))} |
| {onOtherChange && ( |
| <option key="other" value={other.OTHER}> |
| {i18n(other.OTHER)} |
| </option> |
| )} |
| </select> |
| |
| {!otherInputDisabled && ( |
| <div className="input-options-separator" /> |
| )} |
| <input |
| className="form-control input-options-other" |
| placeholder={i18n('other')} |
| ref={otherValue => |
| (this.otherValue = otherValue) |
| } |
| style={{ |
| display: otherInputDisabled |
| ? 'none' |
| : 'block' |
| }} |
| disabled={ |
| isReadOnlyMode || |
| Boolean(this.props.disabled) |
| } |
| value={otherValue || ''} |
| onBlur={() => onBlur()} |
| onChange={() => this.changedOtherInput()} |
| /> |
| </div> |
| )} |
| </div> |
| {this.renderErrorOverlay()} |
| </div> |
| ); |
| } |
| |
| renderOptions(val, index) { |
| return ( |
| <option key={index} value={val.enum}> |
| {val.title} |
| </option> |
| ); |
| } |
| |
| renderMultiSelectOptions(values) { |
| let { onOtherChange } = this.props; |
| let optionsList = []; |
| if (onOtherChange) { |
| optionsList = values |
| .map(option => { |
| return { |
| label: option.title, |
| value: option.enum |
| }; |
| }) |
| .concat([ |
| { |
| label: i18n(other.OTHER), |
| value: i18n(other.OTHER) |
| } |
| ]); |
| } else { |
| optionsList = values.map(option => { |
| return { |
| label: option.title, |
| value: option.enum |
| }; |
| }); |
| } |
| if (optionsList.length > 0 && optionsList[0].value === '') { |
| optionsList.shift(); |
| } |
| return optionsList; |
| } |
| |
| renderErrorOverlay() { |
| let position = 'right'; |
| const { errorText = '', isValid = true, type, overlayPos } = this.props; |
| |
| if (overlayPos) { |
| position = overlayPos; |
| } else if ( |
| type === 'text' || |
| type === 'email' || |
| type === 'number' || |
| type === 'password' |
| ) { |
| position = 'bottom'; |
| } |
| |
| return ( |
| <Overlay |
| show={!isValid} |
| placement={position} |
| target={() => { |
| let { otherInputDisabled } = this.state; |
| let target = otherInputDisabled |
| ? ReactDOM.findDOMNode(this.input) |
| : ReactDOM.findDOMNode(this.otherValue); |
| return target.offsetParent ? target : undefined; |
| }} |
| container={this}> |
| <Tooltip |
| id={`error-${errorText.replace(' ', '-')}`} |
| className="validation-error-message"> |
| {errorText} |
| </Tooltip> |
| </Overlay> |
| ); |
| } |
| |
| getValue() { |
| let res = ''; |
| let { isMultiSelect } = this.props; |
| let { otherInputDisabled } = this.state; |
| |
| if (otherInputDisabled) { |
| res = isMultiSelect ? this.input.getValue() : this.input.value; |
| } else { |
| res = this.otherValue.value; |
| } |
| return res; |
| } |
| |
| enumChanged() { |
| let enumValue = this.input.value; |
| let { |
| onEnumChange, |
| onOtherChange, |
| isMultiSelect, |
| onChange |
| } = this.props; |
| this.setState({ |
| otherInputDisabled: |
| !Boolean(onOtherChange) || enumValue !== other.OTHER |
| }); |
| |
| let value = isMultiSelect ? [enumValue] : enumValue; |
| if (onEnumChange) { |
| onEnumChange(value); |
| } |
| if (onChange) { |
| onChange(value); |
| } |
| } |
| |
| multiSelectEnumChanged(enumValue) { |
| let { onEnumChange, onOtherChange } = this.props; |
| let selectedValues = enumValue.map(enumVal => { |
| return enumVal.value; |
| }); |
| |
| if (this.state.otherInputDisabled === false) { |
| selectedValues.shift(); |
| } else if (selectedValues.includes(i18n(other.OTHER))) { |
| selectedValues = [i18n(other.OTHER)]; |
| } |
| |
| this.setState({ |
| otherInputDisabled: |
| !Boolean(onOtherChange) || |
| !selectedValues.includes(i18n(other.OTHER)) |
| }); |
| onEnumChange(selectedValues); |
| } |
| |
| changedOtherInput() { |
| let { onOtherChange } = this.props; |
| onOtherChange(this.otherValue.value); |
| } |
| |
| componentDidUpdate() { |
| let { |
| otherValue, |
| selectedEnum, |
| onInputChange, |
| multiSelectedEnum |
| } = this.props; |
| if ( |
| this.oldProps.otherValue !== otherValue || |
| this.oldProps.selectedEnum !== selectedEnum || |
| this.oldProps.multiSelectedEnum !== multiSelectedEnum |
| ) { |
| this.oldProps = { |
| otherValue, |
| selectedEnum, |
| multiSelectedEnum |
| }; |
| onInputChange(); |
| } |
| } |
| |
| static getTitleByName(values, name) { |
| for (let key of Object.keys(values)) { |
| let option = values[key].find(option => option.enum === name); |
| if (option) { |
| return option.title; |
| } |
| } |
| return name; |
| } |
| } |
| |
| export default InputOptions; |