From 280f8015d06af1f41a3ef12e8300801c7a5e0d54 Mon Sep 17 00:00:00 2001 From: AviZi Date: Fri, 9 Jun 2017 02:39:56 +0300 Subject: [SDC-29] Amdocs OnBoard 1707 initial commit. Change-Id: Ie4d12a3f574008b792899b368a0902a8b46b5370 Signed-off-by: AviZi --- .../src/nfvo-components/input/validation/Form.jsx | 114 +++++ .../src/nfvo-components/input/validation/Input.jsx | 180 ++++++++ .../input/validation/InputOptions.jsx | 279 +++++++++++ .../input/validation/InputWrapper.jsx | 134 ++++++ .../src/nfvo-components/input/validation/Tabs.jsx | 79 ++++ .../input/validation/ValidationButtons.jsx | 21 +- .../input/validation/ValidationForm.jsx | 200 -------- .../input/validation/ValidationInput.jsx | 509 --------------------- .../input/validation/ValidationTab.jsx | 107 ----- .../input/validation/ValidationTabs.jsx | 72 --- 10 files changed, 804 insertions(+), 891 deletions(-) create mode 100644 openecomp-ui/src/nfvo-components/input/validation/Form.jsx create mode 100644 openecomp-ui/src/nfvo-components/input/validation/Input.jsx create mode 100644 openecomp-ui/src/nfvo-components/input/validation/InputOptions.jsx create mode 100644 openecomp-ui/src/nfvo-components/input/validation/InputWrapper.jsx create mode 100644 openecomp-ui/src/nfvo-components/input/validation/Tabs.jsx delete mode 100644 openecomp-ui/src/nfvo-components/input/validation/ValidationForm.jsx delete mode 100644 openecomp-ui/src/nfvo-components/input/validation/ValidationInput.jsx delete mode 100644 openecomp-ui/src/nfvo-components/input/validation/ValidationTab.jsx delete mode 100644 openecomp-ui/src/nfvo-components/input/validation/ValidationTabs.jsx (limited to 'openecomp-ui/src/nfvo-components/input/validation') diff --git a/openecomp-ui/src/nfvo-components/input/validation/Form.jsx b/openecomp-ui/src/nfvo-components/input/validation/Form.jsx new file mode 100644 index 0000000000..47922f86a0 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/validation/Form.jsx @@ -0,0 +1,114 @@ +/*! + * 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 ValidationButtons from './ValidationButtons.jsx'; + +class Form extends React.Component { + + static defaultProps = { + hasButtons : true, + onSubmit : null, + onReset : null, + labledButtons: true, + onValidChange : null, + isValid: true + }; + + static propTypes = { + isValid : React.PropTypes.bool, + formReady : React.PropTypes.bool, + isReadOnlyMode : React.PropTypes.bool, + hasButtons : React.PropTypes.bool, + onSubmit : React.PropTypes.func, + onReset : React.PropTypes.func, + labledButtons: React.PropTypes.bool, + onValidChange : React.PropTypes.func, + onValidityChanged: React.PropTypes.func, + onValidateForm: React.PropTypes.func + }; + + constructor(props) { + super(props); + } + + + render() { + // eslint-disable-next-line no-unused-vars + let {isValid, formReady, onValidateForm, isReadOnlyMode, hasButtons, onSubmit, labledButtons, onValidChange, onValidityChanged, onDataChanged, children, ...formProps} = this.props; + return ( +
this.form = form} onSubmit={event => this.handleFormValidation(event)}> +
+
+ {children} +
+
+ {hasButtons && this.buttons = buttons} isReadOnlyMode={isReadOnlyMode}/>} + + ); + } + + handleFormValidation(event) { + event.preventDefault(); + if (this.props.onValidateForm && !this.props.formReady){ + return this.props.onValidateForm(); + } else { + return this.handleFormSubmit(event); + } + } + handleFormSubmit(event) { + if (event) { + event.preventDefault(); + } + if(this.props.onSubmit) { + return this.props.onSubmit(event); + } + } + + componentDidMount() { + if (this.props.hasButtons) { + this.buttons.setState({isValid: this.props.isValid}); + } + } + + + + componentDidUpdate(prevProps) { + // only handling this programatically if the validation of the form is done outside of the view + // (example with a form that is dependent on the state of other forms) + if (prevProps.isValid !== this.props.isValid) { + if (this.props.hasButtons) { + this.buttons.setState({isValid: this.props.isValid}); + } + // callback in case form is part of bigger picture in view + if (this.props.onValidChange) { + this.props.onValidChange(this.props.isValid); + } + + // TODO - maybe this has to be part of componentWillUpdate + if(this.props.onValidityChanged) { + this.props.onValidityChanged(this.props.isValid); + } + } + if (this.props.formReady) { // if form validation succeeded -> continue with submit + this.handleFormSubmit(); + } + } + +} + + +export default Form; diff --git a/openecomp-ui/src/nfvo-components/input/validation/Input.jsx b/openecomp-ui/src/nfvo-components/input/validation/Input.jsx new file mode 100644 index 0000000000..59c35d7993 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/validation/Input.jsx @@ -0,0 +1,180 @@ +/*! + * 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 ReactDOM from 'react-dom'; +import classNames from 'classnames'; +import Checkbox from 'react-bootstrap/lib/Checkbox.js'; +import Radio from 'react-bootstrap/lib/Radio.js'; +import FormGroup from 'react-bootstrap/lib/FormGroup.js'; +import FormControl from 'react-bootstrap/lib/FormControl.js'; +import Overlay from 'react-bootstrap/lib/Overlay.js'; +import Tooltip from 'react-bootstrap/lib/Tooltip.js'; + +class Input extends React.Component { + + state = { + value: this.props.value, + checked: this.props.checked, + selectedValues: [] + } + + render() { + const {label, isReadOnlyMode, value, onBlur, onKeyDown, type, disabled, checked, name} = this.props; + // eslint-disable-next-line no-unused-vars + const {groupClassName, isValid = true, errorText, isRequired, ...inputProps} = this.props; + let wrapperClassName = (type !== 'radio') ? 'validation-input-wrapper' : 'form-group'; + if (disabled) { + wrapperClassName += ' disabled'; + } + return( +
+ + {(label && (type !== 'checkbox' && type !== 'radio')) && } + {(type === 'text' || type === 'number') && + this.onChange(e)} + disabled={isReadOnlyMode || Boolean(disabled)} + onBlur={onBlur} + onKeyDown={onKeyDown} + value={value || ''} + inputRef={(input) => this.input = input} + type={type} + data-test-id={this.props['data-test-id']}/>} + + {type === 'textarea' && + this.onChange(e)} + inputRef={(input) => this.input = input} + data-test-id={this.props['data-test-id']}/>} + + {type === 'checkbox' && + this.onChangeCheckBox(e)} + disabled={isReadOnlyMode || Boolean(disabled)} + checked={value} + data-test-id={this.props['data-test-id']}>{label}} + + {type === 'radio' && + this.onChangeRadio(e)} + data-test-id={this.props['data-test-id']}>{label}} + {type === 'select' && + this.optionSelect(e) } + componentClass={type} + inputRef={(input) => this.input = input} + name={name} {...inputProps} + data-test-id={this.props['data-test-id']}/>} + + { this.renderErrorOverlay() } +
+ ); + } + + getValue() { + return this.props.type !== 'select' ? this.state.value : this.state.selectedValues; + } + + getChecked() { + return this.state.checked; + } + + optionSelect(e) { + let selectedValues = []; + if (e.target.value) { + selectedValues.push(e.target.value); + } + this.setState({ + selectedValues + }); + } + + onChange(e) { + const {onChange, type} = this.props; + let value = e.target.value; + if (type === 'number') { + value = Number(value); + } + this.setState({ + value + }); + onChange(value); + } + + onChangeCheckBox(e) { + let {onChange} = this.props; + this.setState({ + checked: e.target.checked + }); + onChange(e.target.checked); + } + + onChangeRadio(e) { + let {onChange} = this.props; + this.setState({ + checked: e.target.checked + }); + onChange(this.state.value); + } + + focus() { + ReactDOM.findDOMNode(this.input).focus(); + } + + 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 ( + { + let target = ReactDOM.findDOMNode(this.input); + return target.offsetParent ? target : undefined; + }} + container={this}> + + {errorText} + + + ); + } + +} +export default Input; diff --git a/openecomp-ui/src/nfvo-components/input/validation/InputOptions.jsx b/openecomp-ui/src/nfvo-components/input/validation/InputOptions.jsx new file mode 100644 index 0000000000..6e54254eb0 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/validation/InputOptions.jsx @@ -0,0 +1,279 @@ +/*! + * 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 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: React.PropTypes.arrayOf(React.PropTypes.shape({ + enum: React.PropTypes.string, + title: React.PropTypes.string + })), + isEnabledOther: React.PropTypes.bool, + label: React.PropTypes.string, + selectedValue: React.PropTypes.string, + multiSelectedEnum: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.array + ]), + selectedEnum: React.PropTypes.string, + otherValue: React.PropTypes.string, + overlayPos: React.PropTypes.string, + onEnumChange: React.PropTypes.func, + onOtherChange: React.PropTypes.func, + onBlur: React.PropTypes.func, + isRequired: React.PropTypes.bool, + isMultiSelect: React.PropTypes.bool, + isValid: React.PropTypes.bool, + disabled: React.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} = this.state; + if (isMultiSelect) { + currentMultiSelectedEnum = multiSelectedEnum; + if(!otherInputDisabled) { + currentSelectedEnum = multiSelectedEnum ? multiSelectedEnum.toString() : undefined; + } + } + else if(selectedEnum){ + currentSelectedEnum = selectedEnum; + } + if (!onBlur) { + onBlur = () => {}; + } + + return( +
+
+ {label && } + {isMultiSelect && otherInputDisabled ? + 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 && } + + + {!otherInputDisabled &&
} + this.otherValue = otherValue} + style={{'display' : otherInputDisabled ? 'none' : 'block'}} + disabled={isReadOnlyMode || Boolean(this.props.disabled)} + value={otherValue || ''} + onBlur={() => onBlur()} + onChange={() => this.changedOtherInput()}/> +
+ } +
+ { this.renderErrorOverlay() } +
+ ); + } + + renderOptions(val, index){ + return ( + + ); + } + + + 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 ( + { + let {otherInputDisabled} = this.state; + let target = otherInputDisabled ? ReactDOM.findDOMNode(this.input) : ReactDOM.findDOMNode(this.otherValue); + return target.offsetParent ? target : undefined; + }} + container={this}> + + {errorText} + + + ); + } + + 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; diff --git a/openecomp-ui/src/nfvo-components/input/validation/InputWrapper.jsx b/openecomp-ui/src/nfvo-components/input/validation/InputWrapper.jsx new file mode 100644 index 0000000000..5ca716cc20 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/validation/InputWrapper.jsx @@ -0,0 +1,134 @@ +/*! + * 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 ReactDOM from 'react-dom'; +import classNames from 'classnames'; +import Checkbox from 'react-bootstrap/lib/Checkbox.js'; +import Radio from 'react-bootstrap/lib/Radio.js'; +import FormGroup from 'react-bootstrap/lib/FormGroup.js'; +import FormControl from 'react-bootstrap/lib/FormControl.js'; + +class InputWrapper extends React.Component { + + state = { + value: this.props.value, + checked: this.props.checked, + selectedValues: [] + } + + render() { + const {label, hasError, validations = {}, isReadOnlyMode, value, onBlur, onKeyDown, type, disabled, checked, name} = this.props; + const {groupClassName, ...inputProps} = this.props; + return( + + {(label && (type !== 'checkbox' && type !== 'radio')) && } + {(type === 'text' || type === 'number') && + this.onChange(e)} + disabled={isReadOnlyMode || Boolean(disabled)} + onBlur={onBlur} + onKeyDown={onKeyDown} + value={value || ''} + ref={(input) => this.inputWrapper = input} + type={type} + data-test-id={this.props['data-test-id']}/>} + + {type === 'textarea' && + this.onChange(e)} + data-test-id={this.props['data-test-id']}/>} + + {type === 'checkbox' && + this.onChangeCheckBox(e)} + disabled={isReadOnlyMode || Boolean(disabled)} + checked={value} + data-test-id={this.props['data-test-id']}>{label}} + + {type === 'radio' && + this.onChangeRadio(e)} + data-test-id={this.props['data-test-id']}>{label}} + {type === 'select' && + this.optionSelect(e) } + componentClass={type} + name={name} {...inputProps} + data-test-id={this.props['data-test-id']}/>} + + + + ); + } + + getValue() { + return this.props.type !== 'select' ? this.state.value : this.state.selectedValues; + } + + getChecked() { + return this.state.checked; + } + + optionSelect(e) { + let selectedValues = []; + if (e.target.value) { + selectedValues.push(e.target.value); + } + this.setState({ + selectedValues + }); + } + + onChange(e) { + let {onChange} = this.props; + this.setState({ + value: e.target.value + }); + onChange(e.target.value); + } + + onChangeCheckBox(e) { + let {onChange} = this.props; + this.setState({ + checked: e.target.checked + }); + onChange(e.target.checked); + } + + onChangeRadio(e) { + let {onChange} = this.props; + this.setState({ + checked: e.target.checked + }); + onChange(this.state.value); + } + + focus() { + ReactDOM.findDOMNode(this.inputWrapper).focus(); + } + +} +export default InputWrapper; diff --git a/openecomp-ui/src/nfvo-components/input/validation/Tabs.jsx b/openecomp-ui/src/nfvo-components/input/validation/Tabs.jsx new file mode 100644 index 0000000000..95144b1468 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/input/validation/Tabs.jsx @@ -0,0 +1,79 @@ +/*! + * 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 ReactDOM from 'react-dom'; +import {default as BTabs} from 'react-bootstrap/lib/Tabs.js'; +import Overlay from 'react-bootstrap/lib/Overlay.js'; +import Tooltip from 'react-bootstrap/lib/Tooltip.js'; + +import i18n from 'nfvo-utils/i18n/i18n.js'; + +export default +class Tabs extends React.Component { + + static propTypes = { + children: React.PropTypes.node + }; + + cloneTab(element) { + const {invalidTabs} = this.props; + return React.cloneElement( + element, + { + key: element.props.eventKey, + tabClassName: invalidTabs.indexOf(element.props.eventKey) > -1 ? 'invalid-tab' : 'valid-tab' + } + ); + } + + showTabsError() { + const {invalidTabs} = this.props; + const showError = ((invalidTabs.length === 1 && invalidTabs[0] !== this.props.activeKey) || (invalidTabs.length > 1)); + return showError; + } + + render() { + // eslint-disable-next-line no-unused-vars + let {invalidTabs, ...tabProps} = this.props; + return ( +
+ + {this.props.children.map(element => this.cloneTab(element))} + + { + let target = ReactDOM.findDOMNode(this.refs.tabsList).querySelector('ul > li.invalid-tab:not(.active):nth-of-type(n)'); + return target && target.offsetParent ? target : undefined; + } + } + container={() => { + let target = ReactDOM.findDOMNode(this.refs.tabsList).querySelector('ul > li.invalid-tab:not(.active):nth-of-type(n)'); + return target && target.offsetParent ? target.offsetParent : this; + }}> + + {i18n('One or more tabs are invalid')} + + +
+ ); + } +} diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx index a87c8d6f40..ebb1473c04 100644 --- a/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx +++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx @@ -1,3 +1,18 @@ +/*! + * 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. + */ /** * Holds the buttons for save/reset for forms. * Used by the ValidationForm that changes the state of the buttons according to its own state. @@ -8,7 +23,7 @@ import React from 'react'; import i18n from 'nfvo-utils/i18n/i18n.js'; import Button from 'react-bootstrap/lib/Button.js'; -import FontAwesome from 'react-fontawesome'; +import SVGIcon from 'nfvo-components/icon/SVGIcon.jsx'; class ValidationButtons extends React.Component { @@ -22,8 +37,8 @@ class ValidationButtons extends React.Component { }; render() { - var submitBtn = this.props.labledButtons ? i18n('Save') : ; - var closeBtn = this.props.labledButtons ? i18n('Cancel') : ; + var submitBtn = this.props.labledButtons ? i18n('Save') : ; + var closeBtn = this.props.labledButtons ? i18n('Cancel') : ; return (
{!this.props.isReadOnlyMode ? diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationForm.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationForm.jsx deleted file mode 100644 index 098ccf1fd4..0000000000 --- a/openecomp-ui/src/nfvo-components/input/validation/ValidationForm.jsx +++ /dev/null @@ -1,200 +0,0 @@ -/** - * ValidationForm should be used in order to have a form that handles it's internal validation state. - * All ValidationInputs inside the form are checked for validity and the styling and submit buttons - * are updated accordingly. - * - * The properties that ahould be given to the form: - * labledButtons - whether or not use icons only as the form default buttons or use buttons with labels - * onSubmit - function for click on the submit button - * onReset - function for click on the reset button - */ -import React from 'react'; -import JSONSchema from 'nfvo-utils/json/JSONSchema.js'; -import JSONPointer from 'nfvo-utils/json/JSONPointer.js'; -import ValidationButtons from './ValidationButtons.jsx'; - -class ValidationForm extends React.Component { - - static childContextTypes = { - validationParent: React.PropTypes.any, - isReadOnlyMode: React.PropTypes.bool, - validationSchema: React.PropTypes.instanceOf(JSONSchema), - validationData: React.PropTypes.object - }; - - static defaultProps = { - hasButtons : true, - onSubmit : null, - onReset : null, - labledButtons: true, - onValidChange : null, - isValid: true - }; - - static propTypes = { - isValid : React.PropTypes.bool, - isReadOnlyMode : React.PropTypes.bool, - hasButtons : React.PropTypes.bool, - onSubmit : React.PropTypes.func, - onReset : React.PropTypes.func, - labledButtons: React.PropTypes.bool, - onValidChange : React.PropTypes.func, - onValidityChanged: React.PropTypes.func, - schema: React.PropTypes.object, - data: React.PropTypes.object - }; - - state = { - isValid: this.props.isValid - }; - - constructor(props) { - super(props); - this.validationComponents = []; - } - - componentWillMount() { - let {schema, data} = this.props; - if (schema) { - this.processSchema(schema, data); - } - } - - componentWillReceiveProps(nextProps) { - let {schema, data} = this.props; - let {schema: nextSchema, data: nextData} = nextProps; - - if (schema !== nextSchema || data !== nextData) { - if (!schema || !nextSchema) { - throw new Error('ValidationForm: dynamically adding/removing schema is not supported'); - } - - if (schema !== nextSchema) { - this.processSchema(nextSchema, nextData); - } else { - this.setState({data: nextData}); - } - } - } - - processSchema(rawSchema, rawData) { - let schema = new JSONSchema(); - schema.setSchema(rawSchema); - let data = schema.processData(rawData); - this.setState({ - schema, - data - }); - } - - render() { - // eslint-disable-next-line no-unused-vars - let {isValid, isReadOnlyMode, hasButtons, onSubmit, labledButtons, onValidChange, onValidityChanged, schema, data, children, ...formProps} = this.props; - return ( -
this.handleFormSubmit(event)}> -
{children}
- {hasButtons && } - - ); - } - - handleFormSubmit(event) { - event.preventDefault(); - let isFormValid = true; - this.validationComponents.forEach(validationComponent => { - const isInputValid = validationComponent.validate().isValid; - isFormValid = isInputValid && isFormValid; - }); - if(isFormValid && this.props.onSubmit) { - return this.props.onSubmit(event); - } else if(!isFormValid) { - this.setState({isValid: false}); - } - } - - componentWillUpdate(nextProps, nextState) { - if(this.state.isValid !== nextState.isValid && this.props.onValidityChanged) { - this.props.onValidityChanged(nextState.isValid); - } - } - - componentDidUpdate(prevProps, prevState) { - // only handling this programatically if the validation of the form is done outside of the view - // (example with a form that is dependent on the state of other forms) - if (prevProps.isValid !== this.props.isValid) { - if (this.props.hasButtons) { - this.refs.buttons.setState({isValid: this.state.isValid}); - } - } else if(this.state.isValid !== prevState.isValid) { - if (this.props.hasButtons) { - this.refs.buttons.setState({isValid: this.state.isValid}); - } - // callback in case form is part of bigger picture in view - if (this.props.onValidChange) { - this.props.onValidChange(this.state.isValid); - } - } - } - - componentDidMount() { - if (this.props.hasButtons) { - this.refs.buttons.setState({isValid: this.state.isValid}); - } - } - - - getChildContext() { - return { - validationParent: this, - isReadOnlyMode: this.props.isReadOnlyMode, - validationSchema: this.state.schema, - validationData: this.state.data - }; - } - - - /*** - * Used by ValidationInput in order to let the (parent) form know - * the valid state. If there is a change in the state of the form, - * the buttons will be updated. - * - * @param validationComponent - * @param isValid - */ - childValidStateChanged(validationComponent, isValid) { - if (isValid !== this.state.isValid) { - let oldState = this.state.isValid; - let newState = isValid && this.validationComponents.filter(otherValidationComponent => validationComponent !== otherValidationComponent).every(otherValidationComponent => { - return otherValidationComponent.isValid(); - }); - - if (oldState !== newState) { - this.setState({isValid: newState}); - } - } - } - - register(validationComponent) { - if (this.state.schema) { - // TODO: register - } else { - this.validationComponents.push(validationComponent); - } - } - - unregister(validationComponent) { - this.childValidStateChanged(validationComponent, true); - this.validationComponents = this.validationComponents.filter(otherValidationComponent => validationComponent !== otherValidationComponent); - } - - onValueChanged(pointer, value, isValid, error) { - this.props.onDataChanged({ - data: JSONPointer.setValue(this.props.data, pointer, value), - isValid, - error - }); - } -} - - -export default ValidationForm; diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationInput.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationInput.jsx deleted file mode 100644 index 0f14307645..0000000000 --- a/openecomp-ui/src/nfvo-components/input/validation/ValidationInput.jsx +++ /dev/null @@ -1,509 +0,0 @@ -/** - * 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 FormGroup from 'react-bootstrap/lib/FormGroup.js'; -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/isEqual.js'; -import i18n from 'nfvo-utils/i18n/i18n.js'; -import JSONSchema from 'nfvo-utils/json/JSONSchema.js'; -import JSONPointer from 'nfvo-utils/json/JSONPointer.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 => { - if (value === '') { - // to allow empty value which is not zero - return true; - } - return Validator.isNumeric(value); - }, - maxValue: (value, maxValue) => value < maxValue, - minValue: (value, minValue) => value >= minValue, - 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 than: {maxValue}.', {maxValue}), - minValue: (value, minValue) => i18n('Field value should be at least: {minValue}.', {minValue}), - 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, - validationSchema: React.PropTypes.instanceOf(JSONSchema), - validationData: React.PropTypes.object - }; - - static defaultProps = { - onChange: null, - disabled: null, - didUpdateCallback: null, - validations: {}, - value: '' - }; - - static propTypes = { - type: React.PropTypes.string.isRequired, - onChange: React.PropTypes.func, - disabled: React.PropTypes.bool, - didUpdateCallback: React.PropTypes.func, - validations: React.PropTypes.object, - isMultiSelect: React.PropTypes.bool, - onOtherChange: React.PropTypes.func, - pointer: React.PropTypes.string - }; - - - state = { - isValid: true, - style: null, - value: this.props.value, - error: {}, - previousErrorMessage: '', - wasInvalid: false, - validations: this.props.validations, - isMultiSelect: this.props.isMultiSelect - }; - - componentWillMount() { - if (this.context.validationSchema) { - let {validationSchema: schema, validationData: data} = this.context, - {pointer} = this.props; - - if (!schema.exists(pointer)) { - console.error(`Field doesn't exists in the schema ${pointer}`); - } - - let value = JSONPointer.getValue(data, pointer); - if (value === undefined) { - value = schema.getDefault(pointer); - if (value === undefined) { - value = ''; - } - } - this.setState({value}); - - let enums = schema.getEnum(pointer); - if (enums) { - let values = enums.map(value => ({enum: value, title: value, groupName: pointer})), - isMultiSelect = schema.isArray(pointer); - - if (!isMultiSelect && this.props.type !== 'radiogroup') { - values = [{enum: '', title: i18n('Select...')}, ...values]; - } - if (isMultiSelect && Array.isArray(value) && value.length === 0) { - value = ''; - } - - this.setState({ - isMultiSelect, - values, - onEnumChange: value => this.changedInputOptions(value), - value - }); - } - - this.setState({validations: this.extractValidationsFromSchema(schema, pointer, this.props)}); - } - } - - extractValidationsFromSchema(schema, pointer, props) { - /* props are here to get precedence over the scheme definitions */ - let validations = {}; - - if (schema.isRequired(pointer)) { - validations.required = true; - } - - if (schema.isNumber(pointer)) { - validations.numeric = true; - - const maxValue = props.validations.maxValue || schema.getMaxValue(pointer); - if (maxValue !== undefined) { - validations.maxValue = maxValue; - } - - const minValue = props.validations.minValue || schema.getMinValue(pointer); - if (minValue !== undefined) { - validations.minValue = minValue; - } - } - - - if (schema.isString(pointer)) { - - const pattern = schema.getPattern(pointer); - if (pattern) { - validations.pattern = pattern; - } - - const maxLength = schema.getMaxLength(pointer); - if (maxLength !== undefined) { - validations.maxLength = maxLength; - } - - const minLength = schema.getMinLength(pointer); - if (minLength !== undefined) { - validations.minLength = minLength; - } - } - - return validations; - } - - componentWillReceiveProps({value: nextValue, validations: nextValidations, pointer: nextPointer}, nextContext) { - const {validations, value} = this.props; - const validationsChanged = !isEqual(validations, nextValidations); - if (nextContext.validationSchema) { - if (this.props.pointer !== nextPointer || - this.context.validationData !== nextContext.validationData) { - let currentValue = JSONPointer.getValue(this.context.validationData, this.props.pointer), - nextValue = JSONPointer.getValue(nextContext.validationData, nextPointer); - if(nextValue === undefined) { - nextValue = ''; - } - if (this.state.isMultiSelect && Array.isArray(nextValue) && nextValue.length === 0) { - nextValue = ''; - } - if (currentValue !== nextValue) { - this.setState({value: nextValue}); - } - if (validationsChanged) { - this.setState({ - validations: this.extractValidationsFromSchema(nextContext.validationSchema, nextPointer, {validations: nextValidations}) - }); - } - } - } else { - if (validationsChanged) { - this.setState({validations: nextValidations}); - } - if (this.state.wasInvalid && (value !== nextValue || validationsChanged)) { - this.validate(nextValue, nextValidations); - } else if (value !== nextValue) { - this.setState({value: nextValue}); - } - } - } - - shouldTypeBeNumberBySchemeDefinition(pointer) { - return this.context.validationSchema && - this.context.validationSchema.isNumber(pointer); - } - - hasEnum(pointer) { - return this.context.validationSchema && - this.context.validationSchema.getEnum(pointer); - } - - render() { - let {value, isMultiSelect, values, onEnumChange, style, isValid, validations} = this.state; - let {onOtherChange, type, pointer} = this.props; - if (this.shouldTypeBeNumberBySchemeDefinition(pointer) && !this.hasEnum(pointer)) { - type = 'number'; - } - let props = {...this.props}; - - let groupClasses = this.props.groupClassName || ''; - if (validations.required) { - groupClasses += ' required'; - } - let isReadOnlyMode = this.context.isReadOnlyMode; - - if (value === true && (type === 'checkbox' || type === 'radio')) { - props.checked = true; - } - return ( -
- { - !isMultiSelect && !onOtherChange && type !== 'select' && type !== 'radiogroup' - && this.changedInput()} - onBlur={() => this.blurInput()}> - {this.props.children} - - } - { - type === 'radiogroup' - && - { - values.map(val => - this.changedInput()}/> - ) - } - - } - { - (isMultiSelect || onOtherChange || type === 'select') - && this.changedInput()} - onBlur={() => this.blurInput()} - hasError={!isValid} - ref={'_myInput'} - isMultiSelect={isMultiSelect} - values={values} - onEnumChange={onEnumChange} - selectedEnum={value} - multiSelectedEnum={value} - {...props} /> - } - {this.renderOverlay()} -
- ); - } - - 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 ( - { - let target = ReactDOM.findDOMNode(this.refs._myInput); - return target.offsetParent ? target : undefined; - }} - container={this}> - - {validationMessage} - - - ); - } - - 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); - } - } - - isNumberInputElement() { - return this.props.type === 'number' || this.refs._myInput.props.type === 'number'; - } - - /*** - * Adding same method as the actual input component - * @returns {*} - */ - getValue() { - if (this.props.type === 'checkbox') { - return this.refs._myInput.getChecked(); - } - if (this.props.type === 'radiogroup') { - for (let key in this.refs) { // finding the value of the radio button that was checked - if (this.refs[key].getChecked()) { - return this.refs[key].getValue(); - } - } - } - if (this.isNumberInputElement()) { - return Number(this.refs._myInput.getValue()); - } - - 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); - } - if (this.context.validationSchema) { - let value = this.getValue(); - if (this.state.isMultiSelect && value === '') { - value = []; - } - if (this.shouldTypeBeNumberBySchemeDefinition(this.props.pointer)) { - value = Number(value); - } - this.context.validationParent.onValueChanged(this.props.pointer, value, isValid, error); - } - } - - changedInputOptions(value) { - this.context.validationParent.onValueChanged(this.props.pointer, value, true); - } - - 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.state.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; diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationTab.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationTab.jsx deleted file mode 100644 index 6036518288..0000000000 --- a/openecomp-ui/src/nfvo-components/input/validation/ValidationTab.jsx +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react'; -import Tab from 'react-bootstrap/lib/Tab.js'; - -export default -class ValidationTab extends React.Component { - - static propTypes = { - children: React.PropTypes.node, - eventKey: React.PropTypes.any.isRequired, - onValidationStateChange: React.PropTypes.func //This property is assigned dynamically via React.cloneElement. lookup ValidationTabs.jsx. therefore it cannot be stated as required! - }; - - constructor(props) { - super(props); - this.validationComponents = []; - } - - static childContextTypes = { - validationParent: React.PropTypes.any - }; - - static contextTypes = { - validationParent: React.PropTypes.any - }; - - getChildContext() { - return {validationParent: this}; - } - - state = { - isValid: true, - notifyParent: false - }; - - componentDidMount() { - let validationParent = this.context.validationParent; - if (validationParent) { - validationParent.register(this); - } - } - - componentWillUnmount() { - let validationParent = this.context.validationParent; - if (validationParent) { - validationParent.unregister(this); - } - } - - register(validationComponent) { - this.validationComponents.push(validationComponent); - } - - unregister(validationComponent) { - this.childValidStateChanged(validationComponent, true); - this.validationComponents = this.validationComponents.filter(otherValidationComponent => validationComponent !== otherValidationComponent); - } - - notifyValidStateChangedToParent(isValid) { - - let validationParent = this.context.validationParent; - if (validationParent) { - validationParent.childValidStateChanged(this, isValid); - } - } - - childValidStateChanged(validationComponent, isValid) { - - const currentValidState = this.state.isValid; - if (isValid !== currentValidState) { - let filteredValidationComponents = this.validationComponents.filter(otherValidationComponent => validationComponent !== otherValidationComponent); - let newValidState = isValid && filteredValidationComponents.every(otherValidationComponent => { - return otherValidationComponent.isValid(); - }); - this.setState({isValid: newValidState, notifyParent: true}); - } - } - - validate() { - let isValid = true; - this.validationComponents.forEach(validationComponent => { - const isValidationComponentValid = validationComponent.validate().isValid; - isValid = isValidationComponentValid && isValid; - }); - this.setState({isValid, notifyParent: false}); - return {isValid}; - } - - componentDidUpdate(prevProps, prevState) { - if(prevState.isValid !== this.state.isValid) { - if(this.state.notifyParent) { - this.notifyValidStateChangedToParent(this.state.isValid); - } - this.props.onValidationStateChange(this.props.eventKey, this.state.isValid); - } - } - - isValid() { - return this.state.isValid; - } - - render() { - let {children, ...tabProps} = this.props; - return ( - {children} - ); - } -} diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationTabs.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationTabs.jsx deleted file mode 100644 index 6eda4b9827..0000000000 --- a/openecomp-ui/src/nfvo-components/input/validation/ValidationTabs.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import Tabs from 'react-bootstrap/lib/Tabs.js'; -import Overlay from 'react-bootstrap/lib/Overlay.js'; -import Tooltip from 'react-bootstrap/lib/Tooltip.js'; - -import i18n from 'nfvo-utils/i18n/i18n.js'; - -export default -class ValidationTab extends React.Component { - - static propTypes = { - children: React.PropTypes.node - }; - - state = { - invalidTabs: [] - }; - - cloneTab(element) { - const {invalidTabs} = this.state; - return React.cloneElement( - element, - { - key: element.props.eventKey, - tabClassName: invalidTabs.indexOf(element.props.eventKey) > -1 ? 'invalid-tab' : 'valid-tab', - onValidationStateChange: (eventKey, isValid) => this.validTabStateChanged(eventKey, isValid) - } - ); - } - - validTabStateChanged(eventKey, isValid) { - let {invalidTabs} = this.state; - let invalidTabIndex = invalidTabs.indexOf(eventKey); - if (isValid && invalidTabIndex > -1) { - this.setState({invalidTabs: invalidTabs.filter(otherEventKey => eventKey !== otherEventKey)}); - } else if (!isValid && invalidTabIndex === -1) { - this.setState({invalidTabs: [...invalidTabs, eventKey]}); - } - } - - showTabsError() { - const {invalidTabs} = this.state; - return invalidTabs.length > 0 && (invalidTabs.length > 1 || invalidTabs[0] !== this.props.activeKey); - } - - render() { - return ( -
- - {this.props.children.map(element => this.cloneTab(element))} - - { - let target = ReactDOM.findDOMNode(this.refs.tabsList).querySelector('ul > li.invalid-tab:not(.active):nth-of-type(n)'); - return target && target.offsetParent ? target : undefined; - } - } - container={this}> - - {i18n('One or more tabs are invalid')} - - -
- ); - } -} -- cgit 1.2.3-korg