Add new code new version

Change-Id: Ic02a76313503b526f17c3df29eb387a29fe6a42a
Signed-off-by: Michael Lando <ml636r@att.com>
diff --git a/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx b/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx
new file mode 100644
index 0000000..3ac3fca
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import FontAwesome from 'react-fontawesome';
+import classnames from 'classnames';
+import Input from 'react-bootstrap/lib/Input';
+
+
+class ExpandableInput extends React.Component {
+	constructor(props){
+		super(props);
+		this.state = {showInput: false, value: ''};
+		this.toggleInput = this.toggleInput.bind(this);
+		this.handleFocus = this.handleFocus.bind(this);
+		this.handleInput = this.handleInput.bind(this);
+		this.handleClose = this.handleClose.bind(this);
+	}
+
+	toggleInput(){
+		if (!this.state.showInput){
+			this.searchInputNode.refs.input.focus();
+		} else {
+			this.setState({showInput: false});
+		}
+	}
+
+	handleInput(e){
+		let {onChange} = this.props;
+
+		this.setState({value: e.target.value});
+		onChange(e);
+	}
+
+	handleClose(){
+		this.handleInput({target: {value: ''}});
+		this.searchInputNode.refs.input.focus();
+	}
+
+	handleFocus(){
+		if (!this.state.showInput){
+			this.setState({showInput: true});
+		}
+	}
+
+	getValue(){
+		return this.state.value;
+	}
+
+	render(){
+		let {iconType} = this.props;
+
+		let inputClasses = classnames({
+			'expandable-active': this.state.showInput,
+			'expandable-not-active': !this.state.showInput
+		});
+
+		let iconClasses = classnames(
+			'expandable-icon',
+			{'expandable-icon-active': this.state.showInput}
+		);
+
+		return (
+			<div className='expandable-input-wrapper'>
+				<Input
+					type='text'
+					value={this.state.value}
+					ref={(input) => this.searchInputNode = input}
+					className={inputClasses}
+					groupClassName='expandable-input-control'
+					onChange={e => this.handleInput(e)}
+					onFocus={this.handleFocus}/>
+				{this.state.showInput && this.state.value && <FontAwesome onClick={this.handleClose} name='close' className='expandable-close-button'/>}
+				{!this.state.value && <FontAwesome onClick={this.toggleInput} name={iconType} className={iconClasses}/>}
+			</div>
+		);
+	}
+}
+
+export default ExpandableInput;
diff --git a/openecomp-ui/src/nfvo-components/input/SelectInput.jsx b/openecomp-ui/src/nfvo-components/input/SelectInput.jsx
new file mode 100644
index 0000000..1036ac4
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/SelectInput.jsx
@@ -0,0 +1,52 @@
+/**
+ * The HTML structure here is aligned with bootstrap HTML structure for form elements.
+ * In this way we have proper styling and it is aligned with other form elements on screen.
+ *
+ * Select and MultiSelect options:
+ *
+ * label - the label to be shown which paired with the input
+ *
+ * all other "react-select" props - as documented on
+ * http://jedwatson.github.io/react-select/
+ * or
+ * https://github.com/JedWatson/react-select
+ */
+import React, {Component} from 'react';
+import Select from 'react-select';
+
+class SelectInput extends Component {
+
+	inputValue = [];
+
+	render() {
+		let {label, value, ...other} = this.props;
+		return (
+			<div className='validation-input-wrapper dropdown-multi-select'>
+				<div className='form-group'>
+					{label && <label className='control-label'>{label}</label>}
+					<Select ref='_myInput' onChange={value => this.onSelectChanged(value)} {...other} value={value} />
+				</div>
+			</div>
+		);
+	}
+
+	getValue() {
+		return this.inputValue && this.inputValue.length ? this.inputValue : '';
+	}
+
+	onSelectChanged(value) {
+		this.props.onMultiSelectChanged(value);
+	}
+
+	componentDidMount() {
+		let {value} = this.props;
+		this.inputValue = value ? value : [];
+	}
+	componentDidUpdate() {
+		if (this.inputValue !== this.props.value) {
+			this.inputValue = this.props.value;
+		}
+	}
+}
+
+export default SelectInput;
diff --git a/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx b/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx
new file mode 100644
index 0000000..873d3de
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx
@@ -0,0 +1,53 @@
+import React from 'react';
+
+export default
+class ToggleInput extends React.Component {
+
+	static propTypes = {
+		label: React.PropTypes.node,
+		value: React.PropTypes.bool,
+		onChange: React.PropTypes.func,
+		disabled: React.PropTypes.bool
+	}
+
+	static defaultProps = {
+		value: false,
+		label: ''
+	}
+
+	state = {
+		value: this.props.value
+	}
+
+	status() {
+		return this.state.value ? 'on' : 'off';
+	}
+
+	render() {
+		let {label, disabled} = this.props;
+		let checked = this.status() === 'on';
+		return (
+			<div className='toggle-input-wrapper form-group' onClick={!disabled && this.click}>
+				<div className='toggle-input-label'>{label}</div>
+				<div className='toggle-switch'>
+					<input className='toggle toggle-round-flat' type='checkbox' checked={checked} readOnly/>
+					<label></label>
+				</div>
+			</div>
+		);
+	}
+
+	click = () => {
+		let value = !this.state.value;
+		this.setState({value});
+
+		let onChange = this.props.onChange;
+		if (onChange) {
+			onChange(value);
+		}
+	}
+
+	getValue() {
+		return this.state.value;
+	}
+}
diff --git a/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx b/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx
new file mode 100644
index 0000000..171bead
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx
@@ -0,0 +1,132 @@
+import React from 'react';
+import FontAwesome from 'react-fontawesome';
+import Input from 'react-bootstrap/lib/Input.js';
+
+class DualListboxView extends React.Component {
+
+	static propTypes = {
+
+		availableList: React.PropTypes.arrayOf(React.PropTypes.shape({
+			id: React.PropTypes.string.isRequired,
+			name: React.PropTypes.string.isRequired
+		})),
+		filterTitle: React.PropTypes.shape({
+			left: React.PropTypes.string,
+			right: React.PropTypes.string
+		}),
+		selectedValuesList: React.PropTypes.arrayOf(React.PropTypes.string),
+
+		onChange: React.PropTypes.func.isRequired
+	};
+
+	static defaultProps = {
+		selectedValuesList: [],
+		availableList: [],
+		filterTitle: {
+			left: '',
+			right: ''
+		}
+	};
+
+	state = {
+		availableListFilter: '',
+		selectedValuesListFilter: ''
+	};
+
+	static contextTypes = {
+		isReadOnlyMode: React.PropTypes.bool
+	};
+
+	render() {
+		let {availableList, selectedValuesList, filterTitle} = this.props;
+		let {availableListFilter, selectedValuesListFilter} = this.state;
+		let isReadOnlyMode = this.context.isReadOnlyMode;
+
+		let unselectedList = availableList.filter(availableItem => !selectedValuesList.find(value => value === availableItem.id));
+		let selectedList = availableList.filter(availableItem => selectedValuesList.find(value => value === availableItem.id));
+		selectedList = selectedList.sort((a, b) => selectedValuesList.indexOf(a.id) - selectedValuesList.indexOf(b.id));
+
+		return (
+			<div className='dual-list-box'>
+				{this.renderListbox(filterTitle.left, unselectedList, {
+					value: availableListFilter,
+					ref: 'availableListFilter',
+					disabled: isReadOnlyMode,
+					onChange: () => this.setState({availableListFilter: this.refs.availableListFilter.getValue()})
+				}, {ref: 'availableValues', disabled: isReadOnlyMode})}
+				{this.renderOperationsBar(isReadOnlyMode)}
+				{this.renderListbox(filterTitle.right, selectedList, {
+					value: selectedValuesListFilter,
+					ref: 'selectedValuesListFilter',
+					disabled: isReadOnlyMode,
+					onChange: () => this.setState({selectedValuesListFilter: this.refs.selectedValuesListFilter.getValue()})
+				}, {ref: 'selectedValues', disabled: isReadOnlyMode})}
+			</div>
+		);
+	}
+
+	renderListbox(filterTitle, list, filterProps, props) {
+		let regExFilter = new RegExp(escape(filterProps.value), 'i');
+		let matchedItems = list.filter(item => item.name.match(regExFilter));
+		let unMatchedItems = list.filter(item => !item.name.match(regExFilter));
+
+
+		return (
+			<div className='dual-search-multi-select-section'>
+				<p>{filterTitle}</p>
+				<div className='dual-text-box-search search-wrapper'>
+					<Input name='search-input-control' type='text' groupClassName='search-input-control' {...filterProps}/>
+					<FontAwesome name='search' className='search-icon'/>
+				</div>
+				<Input
+					multiple
+					groupClassName='dual-list-box-multi-select'
+					type='select'
+					name='dual-list-box-multi-select'
+					{...props}>
+					{matchedItems.map(item => this.renderOption(item.id, item.name))}
+					{matchedItems.length && unMatchedItems.length && <option style={{pointerEvents: 'none'}}>--------------------</option>}
+					{unMatchedItems.map(item => this.renderOption(item.id, item.name))}
+				</Input>
+			</div>
+		);
+	}
+
+	renderOption(value, name) {
+		return (<option className='dual-list-box-multi-select-text' key={value} value={value}>{name}</option>);
+	}
+
+	renderOperationsBar(isReadOnlyMode) {
+		return (
+			<div className={`dual-list-options-bar${isReadOnlyMode ? ' disabled' : ''}`}>
+				{this.renderOperationBarButton(() => this.addToSelectedList(), 'angle-right')}
+				{this.renderOperationBarButton(() => this.removeFromSelectedList(), 'angle-left')}
+				{this.renderOperationBarButton(() => this.addAllToSelectedList(), 'angle-double-right')}
+				{this.renderOperationBarButton(() => this.removeAllFromSelectedList(), 'angle-double-left')}
+			</div>
+		);
+	}
+
+	renderOperationBarButton(onClick, fontAwesomeIconName){
+		return (<div className='dual-list-option' onClick={onClick}><FontAwesome name={fontAwesomeIconName}/></div>);
+	}
+
+	addToSelectedList() {
+		this.props.onChange(this.props.selectedValuesList.concat(this.refs.availableValues.getValue()));
+	}
+
+	removeFromSelectedList() {
+		const selectedValues = this.refs.selectedValues.getValue();
+		this.props.onChange(this.props.selectedValuesList.filter(value => !selectedValues.find(selectedValue => selectedValue === value)));
+	}
+
+	addAllToSelectedList() {
+		this.props.onChange(this.props.availableList.map(item => item.id));
+	}
+
+	removeAllFromSelectedList() {
+		this.props.onChange([]);
+	}
+}
+
+export default DualListboxView;
diff --git a/openecomp-ui/src/nfvo-components/input/inputOptions/InputOptions.jsx b/openecomp-ui/src/nfvo-components/input/inputOptions/InputOptions.jsx
new file mode 100644
index 0000000..5daaffe
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/inputOptions/InputOptions.jsx
@@ -0,0 +1,221 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import classNames from 'classnames';
+import Select from 'nfvo-components/input/SelectInput.jsx';
+
+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,
+		title: React.PropTypes.string,
+		selectedValue: React.PropTypes.string,
+		multiSelectedEnum: React.PropTypes.array,
+		selectedEnum: React.PropTypes.string,
+		otherValue: React.PropTypes.string,
+		onEnumChange: React.PropTypes.func,
+		onOtherChange: React.PropTypes.func,
+		isRequired: React.PropTypes.bool,
+		isMultiSelect: React.PropTypes.bool
+	};
+
+
+	static contextTypes = {
+		isReadOnlyMode: React.PropTypes.bool
+	};
+
+	state = {
+		otherInputDisabled: !this.props.otherValue
+	};
+
+	oldProps = {
+		selectedEnum: '',
+		otherValue: '',
+		multiSelectedEnum: []
+	};
+
+	render() {
+		let {label, isRequired, values, otherValue, onOtherChange, isMultiSelect, onBlur, multiSelectedEnum, selectedEnum, hasError, validations, children} = this.props;
+
+		let currentMultiSelectedEnum = [];
+		let currentSelectedEnum = '';
+		let {otherInputDisabled} = this.state;
+		if (isMultiSelect) {
+			currentMultiSelectedEnum = multiSelectedEnum;
+			if(!otherInputDisabled) {
+				currentSelectedEnum = multiSelectedEnum ? multiSelectedEnum.toString() : undefined;
+			}
+		}
+		else if(selectedEnum){
+			currentSelectedEnum = selectedEnum;
+		}
+
+		let isReadOnlyMode = this.context.isReadOnlyMode;
+
+		return(
+			<div className={classNames('form-group', {'required' : validations.required , 'has-error' : hasError})}>
+				{label && <label className='control-label'>{label}</label>}
+				{isMultiSelect && otherInputDisabled ?
+					<Select
+						ref='_myInput'
+						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' : hasError})}>
+						<select
+							ref={'_myInput'}
+							label={label}
+							className='form-control input-options-select'
+							value={currentSelectedEnum}
+							style={{'width' : otherInputDisabled ? '100%' : '95px'}}
+							onBlur={() => onBlur()}
+							disabled={isReadOnlyMode || Boolean(this.props.disabled)}
+							onChange={ value => this.enumChanged(value)}
+							type='select'>
+							{values && values.length && values.map(val => this.renderOptions(val))}
+							{onOtherChange && <option key='other' value={other.OTHER}>{i18n(other.OTHER)}</option>}
+							{children}
+						</select>
+
+						{!otherInputDisabled && <div className='input-options-separator'/>}
+						<input
+							className='form-control input-options-other'
+							placeholder={i18n('other')}
+							ref='_otherValue'
+							style={{'display' : otherInputDisabled ? 'none' : 'block'}}
+							disabled={isReadOnlyMode || Boolean(this.props.disabled)}
+							value={otherValue || ''}
+							onBlur={() => onBlur()}
+							onChange={() => this.changedOtherInput()}/>
+					</div>
+				}
+			</div>
+		);
+	}
+
+	renderOptions(val){
+		return(
+			<option key={val.enum} 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;
+	}
+
+	getValue() {
+		let res = '';
+		let {isMultiSelect} = this.props;
+		let {otherInputDisabled} = this.state;
+
+		if (otherInputDisabled) {
+			res = isMultiSelect ? this.refs._myInput.getValue() : this.refs._myInput.value;
+		} else {
+			res = this.refs._otherValue.value;
+		}
+		return res;
+	}
+
+	enumChanged() {
+		let enumValue = this.refs._myInput.value;
+		let {onEnumChange, isMultiSelect, onChange} = this.props;
+		this.setState({
+			otherInputDisabled: enumValue !== other.OTHER
+		});
+
+		let value = isMultiSelect ? [enumValue] : enumValue;
+		if (onEnumChange) {
+			onEnumChange(value);
+		}
+		if (onChange) {
+			onChange(value);
+		}
+	}
+
+	multiSelectEnumChanged(enumValue) {
+		let {onEnumChange} = 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: !selectedValues.includes(i18n(other.OTHER))
+		});
+		onEnumChange(selectedValues);
+	}
+
+	changedOtherInput() {
+		let {onOtherChange} = this.props;
+		onOtherChange(this.refs._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/ValidationButtons.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx
new file mode 100644
index 0000000..a87c8d6
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx
@@ -0,0 +1,40 @@
+/**
+ * Holds the buttons for save/reset for forms.
+ * Used by the ValidationForm that changes the state of the buttons according to its own state.
+ *
+ * properties:
+ * labledButtons - whether or not to use labeled buttons or icons only
+ */
+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';
+
+class ValidationButtons extends React.Component {
+
+	static propTypes = {
+		labledButtons: React.PropTypes.bool.isRequired,
+		isReadOnlyMode: React.PropTypes.bool
+	};
+
+	state = {
+		isValid: this.props.formValid
+	};
+
+	render() {
+		var submitBtn = this.props.labledButtons ? i18n('Save') : <FontAwesome className='check' name='check'/>;
+		var closeBtn = this.props.labledButtons ? i18n('Cancel') : <FontAwesome className='close' name='close'/>;
+		return (
+			<div className='validation-buttons'>
+				{!this.props.isReadOnlyMode ?
+					<div>
+						<Button  bsStyle='primary' ref='submitbutton' type='submit' disabled={!this.state.isValid}>{submitBtn}</Button>
+						<Button  type='reset'>{closeBtn}</Button>
+					</div>
+					: <Button  type='reset'>{i18n('Close')}</Button>
+				}
+			</div>
+		);
+	}
+}
+export default ValidationButtons;
diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationForm.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationForm.jsx
new file mode 100644
index 0000000..098ccf1
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationForm.jsx
@@ -0,0 +1,200 @@
+/**
+ * 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 (
+			<form {...formProps} onSubmit={event => this.handleFormSubmit(event)}>
+				<div className='validation-form-content'>{children}</div>
+				{hasButtons && <ValidationButtons labledButtons={labledButtons} ref='buttons' isReadOnlyMode={isReadOnlyMode}/>}
+			</form>
+		);
+	}
+
+	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
new file mode 100644
index 0000000..0f14307
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationInput.jsx
@@ -0,0 +1,509 @@
+/**
+ * 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 (
+			<div className='validation-input-wrapper'>
+				{
+					!isMultiSelect && !onOtherChange && type !== 'select' && type !== 'radiogroup'
+					&& <Input
+						{...props}
+						type={type}
+						groupClassName={groupClasses}
+						ref={'_myInput'}
+						value={value}
+						disabled={isReadOnlyMode || Boolean(this.props.disabled)}
+						bsStyle={style}
+						onChange={() => this.changedInput()}
+						onBlur={() => this.blurInput()}>
+						{this.props.children}
+					</Input>
+				}
+				{
+					type === 'radiogroup'
+					&& <FormGroup>
+						{
+							values.map(val =>
+								<Input disabled={isReadOnlyMode || Boolean(this.props.disabled)}
+									inline={true}
+									ref={'_myInput' + (typeof val.enum === 'string' ? val.enum.replace(/\W/g, '_') : val.enum)}
+									value={val.enum} checked={value === val.enum}
+									type='radio' label={val.title}
+									name={val.groupName}
+									onChange={() => this.changedInput()}/>
+							)
+						}
+					</FormGroup>
+				}
+				{
+					(isMultiSelect || onOtherChange || type === 'select')
+					&& <InputOptions
+						onInputChange={() => this.changedInput()}
+						onBlur={() => this.blurInput()}
+						hasError={!isValid}
+						ref={'_myInput'}
+						isMultiSelect={isMultiSelect}
+						values={values}
+						onEnumChange={onEnumChange}
+						selectedEnum={value}
+						multiSelectedEnum={value}
+						{...props} />
+				}
+				{this.renderOverlay()}
+			</div>
+		);
+	}
+
+	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 (
+			<Overlay
+				show={!this.state.isValid}
+				placement={position}
+				target={() => {
+					let target = ReactDOM.findDOMNode(this.refs._myInput);
+					return target.offsetParent ? target : undefined;
+				}}
+				container={this}>
+				<Tooltip
+					id={`error-${validationMessage.replace(' ', '-')}`}
+					className='validation-error-message'>
+					{validationMessage}
+				</Tooltip>
+			</Overlay>
+		);
+	}
+
+	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
new file mode 100644
index 0000000..6036518
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationTab.jsx
@@ -0,0 +1,107 @@
+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 (
+			<Tab {...tabProps}>{children}</Tab>
+		);
+	}
+}
diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationTabs.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationTabs.jsx
new file mode 100644
index 0000000..6eda4b9
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/input/validation/ValidationTabs.jsx
@@ -0,0 +1,72 @@
+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 (
+			<div>
+				<Tabs {...this.props} ref='tabsList'>
+					{this.props.children.map(element => this.cloneTab(element))}
+				</Tabs>
+				<Overlay
+					animation={false}
+					show={this.showTabsError()}
+					placement='bottom'
+					target={() => {
+						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}>
+					<Tooltip
+						id='error-some-tabs-contain-errors'
+						className='validation-error-message'>
+						{i18n('One or more tabs are invalid')}
+					</Tooltip>
+				</Overlay>
+			</div>
+		);
+	}
+}