[SDC-29] Amdocs OnBoard 1707 initial commit.
Change-Id: Ie4d12a3f574008b792899b368a0902a8b46b5370
Signed-off-by: AviZi <avi.ziv@amdocs.com>
diff --git a/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx b/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx
index f2ec158..0759f2c 100644
--- a/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx
+++ b/openecomp-ui/src/nfvo-components/SubmitErrorResponse.jsx
@@ -1,9 +1,24 @@
+/*!
+ * 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, {Component} from 'react';
import ListGroupItem from 'react-bootstrap/lib/ListGroupItem.js';
-import ListGroup from 'react-bootstrap/lib/ListGroup.js';
-import Panel from 'react-bootstrap/lib/Panel.js';
import i18n from 'nfvo-utils/i18n/i18n.js';
-
+import SVGIcon from 'nfvo-components/icon/SVGIcon.jsx';
+import Icon from 'nfvo-components/icon/Icon.jsx';
+import {Collapse} from 'react-bootstrap';
/**
* parsing and showing the following Java Response object
*
@@ -31,103 +46,117 @@
render() {
- let {validationResponse} = this.props;
+ let {validationResponse : {vspErrors, licensingDataErrors, questionnaireValidationResult, uploadDataErrors}} = this.props;
return (
<div className='submit-error-response-view'>
- {validationResponse.vspErrors && this.renderVspErrors(validationResponse.vspErrors)}
- {validationResponse.licensingDataErrors && this.renderVspErrors(validationResponse.licensingDataErrors)}
- {validationResponse.compilationErrors && this.renderCompilationErrors(validationResponse.compilationErrors)}
- {validationResponse.uploadDataErrors && this.renderUploadDataErrors(validationResponse.uploadDataErrors)}
- {validationResponse.questionnaireValidationResult && this.renderQuestionnaireValidationResult(validationResponse.questionnaireValidationResult)}
+ {vspErrors && this.renderVspErrors(vspErrors)}
+ {licensingDataErrors && this.renderVspErrors(licensingDataErrors)}
+ {questionnaireValidationResult && this.renderComponentsErrors(questionnaireValidationResult)}
+ {uploadDataErrors && this.renderUploadDataErrors(uploadDataErrors)}
</div>
);
}
- renderVspErrors(vspErrors) {
+ renderVspErrors(errors) {
return (
- <Panel header={i18n('VSP Errors')} collapsible>{this.parseErrorCodeCollection(vspErrors)}</Panel>
+ <ErrorBlock errorType={i18n('VSP Errors')}>
+ <div>
+ {errors.length && errors.map(error=>{return (<ErrorMessage error={error.message}/>);})}
+ </div>
+ </ErrorBlock>
);
}
- renderLicensingDataErrors(licensingDataErrors) {
+
+ renderComponentsErrors(errors) {
return (
- <Panel
- header={i18n('Licensing Data Errors')}
- collapsible>{this.parseErrorCodeCollection(licensingDataErrors)}
- </Panel>
+ <ErrorBlock errorType={i18n('Components Errors')}>
+ <div>
+ {errors.validationData.length && errors.validationData.map(item =>{ return (<ComponentError item={item}/>);})}
+ </div>
+ </ErrorBlock>
);
}
renderUploadDataErrors(uploadDataErrors) {
return (
- <Panel
- header={i18n('Upload Data Errors')}
- collapsible>{this.parseMapOfErrorMessagesList(uploadDataErrors)}
- </Panel>
+ <ErrorBlock errorType={i18n('Upload Data Errors')}>
+ <div>
+ <UploadErrorList items={uploadDataErrors}/>
+ </div>
+ </ErrorBlock>
);
}
-
- renderCompilationErrors(compilationErrors) {
- return (
- <Panel
- header={i18n('Compilation Errors')}
- collapsible>{this.parseMapOfErrorMessagesList(compilationErrors)}
- </Panel>
- );
- }
-
- parseErrorCodeCollection(errors) {
- return (
- <ListGroup>{errors.map(error =>
- <ListGroupItem className='error-code-list-item'>
- <div><span>{i18n('Category: ')}</span>{error.category}</div>
- <div><span>{i18n('Message: ')}</span>{error.message}</div>
- </ListGroupItem>
- )}</ListGroup>
- );
- }
-
- parseMapOfErrorMessagesList(errorMap) {
- return (
- <ListGroup>
- {Object.keys(errorMap).map(errorStringKey =>
- <Panel header={errorStringKey} collapsible>
- <ListGroup>{errorMap[errorStringKey].map(error =>
- <ListGroupItem className='error-code-list-item'>
- <div><span>{i18n('Level: ')}</span>{error.level}</div>
- <div><span>{i18n('Message: ')}</span>{error.message}</div>
- </ListGroupItem>
- )}</ListGroup>
- </Panel>
- )}
- </ListGroup>
- );
- }
-
-
- renderQuestionnaireValidationResult(questionnaireValidationResult) {
- if (!questionnaireValidationResult.valid) {
- return this.parseAndRenderCompositionEntityValidationData(questionnaireValidationResult.validationData);
- }
- }
-
- parseAndRenderCompositionEntityValidationData(validationData) {
- let {entityType, entityId, errors = [], subEntitiesValidationData = []} = validationData;
- return (
- <ListGroup>
- <Panel header={`${entityType}: ${entityId}`} collapsible>
- <ListGroup>{errors.map(error =>
- <ListGroupItem className='error-code-list-item'>
- <div>{error}</div>
- </ListGroupItem>
- )}</ListGroup>
- {subEntitiesValidationData.map(subValidationData => this.parseAndRenderCompositionEntityValidationData(subValidationData))}
- </Panel>
- </ListGroup>
- );
- }
-
-
}
+
+const ComponentError = ({item}) => {
+ let i = 0;
+ return (
+ <div>
+ <div className='component-name-header'>{item.entityName}</div>
+ {item.errors.map(error => {return(<ErrorMessage key={i++} error={error}/>);})}
+ </div>
+ );
+};
+
+function* entries(obj) {
+ for (let key of Object.keys(obj)) {
+ yield {header: key, list: obj[key]};
+ }
+}
+
+const UploadErrorList = ({items}) => {
+ let generator = entries(items);
+
+ let errors = [];
+ let i = 0;
+ for (let item of generator) {errors.push(
+ <div>
+ <div className='component-name-header'>{item.header}</div>
+ {item.list.map(error => <ErrorMessage key={i++} warning={error.level === 'WARNING'} error={error.message}/> )}
+ </div>
+ );}
+ return (
+ <div>
+ {errors}
+ </div>
+ );
+};
+
+class ErrorBlock extends React.Component {
+ state = {
+ collapsed: false
+ };
+
+ render() {
+ let {errorType, children} = this.props;
+ return (
+ <div className='error-block'>
+ <ErrorHeader collapsed={this.state.collapsed} onClick={()=>{this.setState({collapsed: !this.state.collapsed});}} errorType={errorType}/>
+ <Collapse in={this.state.collapsed}>
+ {children}
+ </Collapse>
+ </div>
+ );
+ }
+}
+
+const ErrorHeader = ({errorType, collapsed, onClick}) => {
+ return(
+ <div onClick={onClick} className='error-block-header'>
+ <SVGIcon iconClassName={collapsed ? '' : 'right' } name='chevron-down'/>
+ {errorType}
+ </div>
+ );
+};
+
+const ErrorMessage = ({error, warning}) => {
+ return (
+ <ListGroupItem className='error-code-list-item'>
+ <Icon image={warning ? 'warning' : 'error'} label={error}/>
+ </ListGroupItem>
+ );
+};
+
export default SubmitErrorResponse;
diff --git a/openecomp-ui/src/nfvo-components/activity-log/ActivityLog.js b/openecomp-ui/src/nfvo-components/activity-log/ActivityLog.js
new file mode 100644
index 0000000..f7354f9
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/activity-log/ActivityLog.js
@@ -0,0 +1,27 @@
+/*!
+ * 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 {connect} from 'react-redux';
+import ActivityLogView from './ActivityLogView.jsx';
+
+export const mapStateToProps = ({licenseModel: {activityLog}}) => {
+
+ let activities = activityLog;
+ return {
+ activities
+ };
+};
+
+export default connect(mapStateToProps, undefined, null, {withRef: true})(ActivityLogView);
diff --git a/openecomp-ui/src/nfvo-components/activity-log/ActivityLogActionHelper.js b/openecomp-ui/src/nfvo-components/activity-log/ActivityLogActionHelper.js
new file mode 100644
index 0000000..01a27ab
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/activity-log/ActivityLogActionHelper.js
@@ -0,0 +1,31 @@
+/*!
+ * 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 RestAPIUtil from 'nfvo-utils/RestAPIUtil.js';
+import Configuration from 'sdc-app/config/Configuration.js';
+import ActivityLogConstants from './ActivityLogConstants.js';
+
+
+function baseUrl(itemId, versionId) {
+ const restPrefix = Configuration.get('restPrefix');
+ return `${restPrefix}/v1.0/activity-logs/${itemId}/versions/${versionId}`;
+}
+
+export default {
+
+ fetchActivityLog(dispatch, {itemId, versionId}){
+ return RestAPIUtil.fetch(baseUrl(itemId, versionId)).then(response => dispatch({type: ActivityLogConstants.ACTIVITY_LOG_UPDATED, response}));
+ }
+};
diff --git a/openecomp-ui/src/nfvo-components/activity-log/ActivityLogConstants.js b/openecomp-ui/src/nfvo-components/activity-log/ActivityLogConstants.js
new file mode 100644
index 0000000..69faf7c
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/activity-log/ActivityLogConstants.js
@@ -0,0 +1,23 @@
+/*!
+ * 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 keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export default keyMirror({
+
+ ACTIVITY_LOG_UPDATED: null
+
+});
+
diff --git a/openecomp-ui/src/nfvo-components/activity-log/ActivityLogReducer.js b/openecomp-ui/src/nfvo-components/activity-log/ActivityLogReducer.js
new file mode 100644
index 0000000..fc3dfa1
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/activity-log/ActivityLogReducer.js
@@ -0,0 +1,25 @@
+/*!
+ * 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 ActivityLogConstants from './ActivityLogConstants.js';
+
+export default (state = [], action) => {
+ switch (action.type) {
+ case ActivityLogConstants.ACTIVITY_LOG_UPDATED:
+ return [...action.response.results];
+ }
+
+ return state;
+};
diff --git a/openecomp-ui/src/nfvo-components/activity-log/ActivityLogView.jsx b/openecomp-ui/src/nfvo-components/activity-log/ActivityLogView.jsx
new file mode 100644
index 0000000..6ff3c80
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/activity-log/ActivityLogView.jsx
@@ -0,0 +1,124 @@
+/*!
+ * 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, {Component} from 'react';
+import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger.js';
+import Tooltip from 'react-bootstrap/lib/Tooltip.js';
+import ListEditorView from 'nfvo-components/listEditor/ListEditorView.jsx';
+import SVGIcon from 'nfvo-components/icon/SVGIcon.jsx';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+
+function ActivityLogSortableCellHeader({isHeader, data, isDes, onSort}) {
+ if (isHeader) {
+ return (
+ <span className='date-header' onClick={onSort}>
+ <span>{data}</span>
+ <span className={`header-sort-arrow ${isDes ? 'up' : 'down'}`}></span>
+ </span>
+ );
+ }
+ return (
+ <span className='date-cell'>
+ <span>{i18n.dateNormal(data, {
+ year: 'numeric', month: 'numeric', day: 'numeric'
+ })}</span>
+ <span>{i18n.dateNormal(data, {
+ hour: 'numeric', minute: 'numeric',
+ hour12: true
+ })}</span>
+ </span>
+ );
+}
+
+function ActivityLogStatus({status, isHeader}) {
+ if (isHeader) {
+ return <span>{status}</span>;
+ }
+ let {message, success} = status;
+ return (
+ <span>
+ <span className={`status-icon ${success}`}>{`${success ? i18n('Success') : i18n('Failure')}`}</span>
+ {success && <SVGIcon name='check-circle'/>}
+ {!success && <OverlayTrigger placement='bottom' overlay={<Tooltip className='activity-log-message-tooltip' id={'activity-log-message-tooltip'}>
+ <div className='message-block'>{message}</div>
+ </Tooltip>}>
+ <span className='message-further-info-icon'>{'?'}</span>
+ </OverlayTrigger>}
+ </span>
+ );
+}
+
+export function ActivityListItem({activity, isHeader, isDes, onSort}) {
+ let {type, timestamp, comment, user, status} = activity;
+ return (
+ <li className={`activity-list-item ${isHeader ? 'header' : ''}`} data-test-id='activity-list-item'>
+ <div className='table-cell activity-date' data-test-id='activity-date'><ActivityLogSortableCellHeader isHeader={isHeader} data={timestamp} isDes={isDes} onSort={onSort}/></div>
+ <div className='table-cell activity-action' data-test-id='activity-action'>{type}</div>
+ <div className='table-cell activity-comment' title={comment} data-test-id='activity-comment'><span>{comment}</span></div>
+ <div className='table-cell activity-username' data-test-id='activity-username'>{user}</div>
+ <div className='table-cell activity-status' data-test-id='activity-status'><ActivityLogStatus isHeader={isHeader} status={status}/></div>
+ </li>
+ );
+}
+
+class ActivityLogView extends Component {
+
+ state = {
+ localFilter: '',
+ sortDescending: true
+ };
+
+ render() {
+ return (
+ <div className='activity-log-view'>
+ <ListEditorView
+ title={i18n('Activity Log')}
+ filterValue={this.state.localFilter}
+ onFilter={filter => this.setState({localFilter: filter})}>
+ <ActivityListItem
+ isHeader={true}
+ activity={{timestamp: 'Date', type: 'Action', comment: 'Comment', user: 'Username', status: 'Status'}}
+ isDes={this.state.sortDescending}
+ onSort={() => this.setState({sortDescending: !this.state.sortDescending})}/>
+ {this.sortActivities(this.filterActivities(), this.state.sortDescending).map(activity => <ActivityListItem key={activity.id} activity={activity}/>)}
+ </ListEditorView>
+ </div>
+ );
+ }
+
+ filterActivities() {
+ let {activities} = this.props;
+ let {localFilter} = this.state;
+ if (localFilter.trim()) {
+ const filter = new RegExp(escape(localFilter), 'i');
+ return activities.filter(({user = '', comment = '', type = ''}) => escape(user).match(filter) || escape(comment).match(filter) || escape(type).match(filter));
+ }
+ else {
+ return activities;
+ }
+ }
+
+ sortActivities(activities) {
+ if (this.state.sortDescending) {
+ return activities.sort((a, b) => a.timestamp - b.timestamp);
+ }
+ else {
+ return activities.reverse();
+ }
+ }
+
+}
+
+export default ActivityLogView;
diff --git a/openecomp-ui/src/nfvo-components/confirmations/ConfirmationModalView.jsx b/openecomp-ui/src/nfvo-components/confirmations/ConfirmationModalView.jsx
deleted file mode 100644
index cc971c6..0000000
--- a/openecomp-ui/src/nfvo-components/confirmations/ConfirmationModalView.jsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from 'react';
-import Button from 'react-bootstrap/lib/Button.js';
-
-import i18n from 'nfvo-utils/i18n/i18n.js';
-import Modal from 'nfvo-components/modal/Modal.jsx';
-
-let typeClass = {
- 'default': 'primary',
- error: 'danger',
- warning: 'warning',
- success: 'success'
-};
-
-
-class ConfirmationModalView extends React.Component {
-
- static propTypes = {
- show: React.PropTypes.bool,
- type: React.PropTypes.oneOf(['default', 'error', 'warning', 'success']),
- msg: React.PropTypes.node,
- title: React.PropTypes.string,
- confirmationDetails: React.PropTypes.object,
- confirmationButtonText: React.PropTypes.string,
-
- };
-
- static defaultProps = {
- show: false,
- type: 'warning',
- title: 'Warning',
- msg: '',
- confirmationButtonText: i18n('Delete')
- };
-
- render() {
- let {title, type, msg, show, confirmationButtonText} = this.props;
-
- return(
- <Modal show={show} className={`notification-modal ${typeClass[type]}`}>
- <Modal.Header>
- <Modal.Title>{title}</Modal.Title>
- </Modal.Header>
- <Modal.Body>{msg}</Modal.Body>
- <Modal.Footer>
- <Button bsStyle={typeClass[type]} onClick={() => this.props.onDeclined(this.props.confirmationDetails)}>{i18n('Cancel')}</Button>
- <Button bsStyle={typeClass[type]} onClick={() => this.props.onConfirmed(this.props.confirmationDetails)}>{confirmationButtonText}</Button>
- </Modal.Footer>
- </Modal>
- );
- };
-}
-
-export default ConfirmationModalView;
diff --git a/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.jsx b/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.jsx
index 4a106b5..2a0b7d4 100644
--- a/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.jsx
+++ b/openecomp-ui/src/nfvo-components/editor/TabulatedEditor.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.
+ */
import React from 'react';
import classnames from 'classnames';
@@ -7,10 +22,17 @@
export default class TabulatedEditor extends React.Component {
render() {
- const {versionControllerProps, navigationBarProps, onToggle, onVersionSwitching, onCreate, onSave, onClose, onVersionControllerAction, onNavigate, children} = this.props;
+ const {navigationBarProps, onToggle, onVersionSwitching, onCreate, onSave, onClose, onVersionControllerAction, onNavigate, children, meta} = this.props;
+ let {versionControllerProps} = this.props;
const {className = ''} = React.Children.only(children).props;
const child = this.prepareChild();
+ if(onClose) {
+ versionControllerProps = {
+ ...versionControllerProps,
+ onClose: () => onClose(versionControllerProps)
+ };
+ }
return (
<div className='software-product-view'>
<div className='software-product-navigation-side-bar'>
@@ -19,11 +41,10 @@
<div className='software-product-landing-view-right-side flex-column'>
<VersionController
{...versionControllerProps}
- onVersionSwitching={version => onVersionSwitching(version)}
- callVCAction={onVersionControllerAction}
+ onVersionSwitching={version => onVersionSwitching(version, meta)}
+ callVCAction={(action, version) => onVersionControllerAction(action, version, meta)}
onCreate={onCreate && this.handleCreate}
- onSave={onSave && this.handleSave}
- onClose={() => onClose(versionControllerProps)}/>
+ onSave={onSave && this.handleSave}/>
<div className={classnames('content-area', `${className}`)}>
{
child
diff --git a/openecomp-ui/src/nfvo-components/grid/GridItem.jsx b/openecomp-ui/src/nfvo-components/grid/GridItem.jsx
new file mode 100644
index 0000000..8819ab7
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/grid/GridItem.jsx
@@ -0,0 +1,26 @@
+/*!
+ * 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';
+
+const GridItem = ({colSpan = 1, children, stretch = false}) => (
+ <div className={`grid-col-${colSpan}`}>
+ <div className={`grid-item${stretch ? '-stretch' : ''}`}>
+ {children}
+ </div>
+ </div>
+);
+
+export default GridItem;
diff --git a/openecomp-ui/src/nfvo-components/grid/GridSection.jsx b/openecomp-ui/src/nfvo-components/grid/GridSection.jsx
new file mode 100644
index 0000000..175b3ee
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/grid/GridSection.jsx
@@ -0,0 +1,33 @@
+/*!
+ * 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';
+
+const GridSection = ({title, children, titleClassName}) => {
+ return (
+ <div className='grid-section'>
+ {title && <div className={`section-title ${titleClassName || ''}`}>{title}</div>}
+ <div className='grid-items'>
+ {children}
+ </div>
+ </div>
+ );
+};
+
+GridSection.propTypes = {
+ title: React.PropTypes.string,
+};
+
+export default GridSection;
diff --git a/openecomp-ui/src/nfvo-components/icon/Icon.jsx b/openecomp-ui/src/nfvo-components/icon/Icon.jsx
new file mode 100644
index 0000000..1255776
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/icon/Icon.jsx
@@ -0,0 +1,45 @@
+/*!
+ * 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, { Component, PropTypes } from 'react';
+
+
+export default class Icon extends Component {
+
+ static propTypes = {
+ image: PropTypes.string.isRequired,
+ onClick: PropTypes.func,
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
+ className: PropTypes.string,
+ iconClassName: PropTypes.string
+ };
+
+ static defaultProps = {
+ label: '',
+ className: '',
+ iconClassName: ''
+ };
+
+ render() {
+ let {image, onClick, label, className, iconClassName, ...other} = this.props;
+ let classes = `icon-component ${className} ${onClick ? 'clickable' : ''}`;
+ return (
+ <div {...other} onClick={onClick} className={classes}>
+ <span className={`icon ${image} ${iconClassName}`}></span>
+ <span className='icon-label'>{label}</span>
+ </div>
+ );
+ }
+}
diff --git a/openecomp-ui/src/nfvo-components/icon/SVGIcon.jsx b/openecomp-ui/src/nfvo-components/icon/SVGIcon.jsx
new file mode 100644
index 0000000..dd165fb
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/icon/SVGIcon.jsx
@@ -0,0 +1,54 @@
+/*!
+ * 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, {PropTypes} from 'react';
+import Configuration from 'sdc-app/config/Configuration.js';
+
+export default class SVGIcon extends React.Component {
+
+ static propTypes = {
+ name: PropTypes.string.isRequired,
+ onClick: PropTypes.func,
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
+ labelPosition: PropTypes.string,
+ className: PropTypes.string,
+ iconClassName: PropTypes.string,
+ labelClassName: PropTypes.string
+ };
+
+ static defaultProps = {
+ name: '',
+ label: '',
+ className: '',
+ iconClassName: '',
+ labelClassName: '',
+ labelPosition: 'bottom'
+ };
+
+ render() {
+ let {name, onClick, label, className, iconClassName, labelClassName, labelPosition, ...other} = this.props;
+ let classes = `svg-icon-wrapper ${className} ${onClick ? 'clickable' : ''} ${labelPosition}`;
+
+ return (
+ <div {...other} onClick={onClick} className={classes}>
+ <svg className={`svg-icon ${name} ${iconClassName}`} >
+ <use href={Configuration.get('appContextPath') + '/resources/images/svg/' + this.props.name + '.svg#' + this.props.name + '_icon' }
+ xlinkHref={Configuration.get('appContextPath') + '/resources/images/svg/' + this.props.name + '.svg#' + this.props.name + '_icon' } />
+ </svg>
+ {label && <span className={`svg-icon-label ${labelClassName}`}>{label}</span>}
+ </div>
+ );
+ }
+}
diff --git a/openecomp-ui/src/nfvo-components/icon/SVGIcon.stories.js b/openecomp-ui/src/nfvo-components/icon/SVGIcon.stories.js
new file mode 100644
index 0000000..6675670
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/icon/SVGIcon.stories.js
@@ -0,0 +1,50 @@
+import React from 'react';
+import {storiesOf, action} from '@kadira/storybook';
+import {select, text, withKnobs} from '@kadira/storybook-addon-knobs';
+import SVGIcon from './SVGIcon.jsx';
+
+const stories = storiesOf('SVGIcon', module);
+
+const iconNames = ['locked',
+ 'pencil',
+ 'plus-circle',
+ 'plus',
+ 'search',
+ 'sliders',
+ 'trash-o',
+ 'unlocked',
+ 'vendor',
+ 'version-controller-lock-closed',
+ 'version-controller-lock-open',
+ 'version-controller-revert',
+ 'version-controller-save',
+ 'version-controller-submit',
+ 'vlm',
+ 'vsp' ];
+
+function colorChanger() {
+ return {fill: text('Color', '')};
+}
+
+function iconName() {
+ return select('Icon name' , iconNames, iconNames[0]);
+}
+
+stories.addDecorator(withKnobs);
+
+stories
+ .add('icon', () => {
+ return (
+ <SVGIcon name={iconName()} style={colorChanger()}/>
+ );
+ })
+ .add('icon with label', () => {
+ return (
+ <SVGIcon name={iconName()} label={iconName()} style={colorChanger()}/>
+ );
+ })
+ .add('locked clickable', () => {
+ return (
+ <SVGIcon name={iconName()} onClick={action('clicked')} style={colorChanger()}/>
+ );
+ });
\ No newline at end of file
diff --git a/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx b/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx
index 3ac3fca..e2ee40f 100644
--- a/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx
+++ b/openecomp-ui/src/nfvo-components/input/ExpandableInput.jsx
@@ -1,77 +1,115 @@
+/*!
+ * 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 FontAwesome from 'react-fontawesome';
-import classnames from 'classnames';
-import Input from 'react-bootstrap/lib/Input';
+import ReactDOM from 'react-dom';
+import SVGIcon from 'nfvo-components/icon/SVGIcon.jsx';
+import Input from 'nfvo-components/input/validation/InputWrapper.jsx';
+const ExpandableInputClosed = ({iconType, onClick}) => (
+ <SVGIcon className='expandable-input-wrapper closed' name={iconType} onClick={onClick} />
+);
-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);
+class ExpandableInputOpened extends React.Component {
+ componentDidMount(){
+ this.rawDomNode = ReactDOM.findDOMNode(this.searchInputNode.inputWrapper);
+ this.rawDomNode.focus();
}
- toggleInput(){
- if (!this.state.showInput){
- this.searchInputNode.refs.input.focus();
- } else {
+ componentWillReceiveProps(newProps){
+ if (!newProps.value){
+ if (!(document.activeElement === this.rawDomNode)){
+ this.props.handleBlur();
+ }
+ }
+ }
+
+ handleClose(){
+ this.props.onChange('');
+ this.rawDomNode.focus();
+ }
+
+ handleKeyDown(e){
+ if (e.key === 'Escape'){
+ e.preventDefault();
+ if (this.props.value) {
+ this.handleClose();
+ } else {
+ this.rawDomNode.blur();
+ }
+ };
+ }
+
+ render() {
+ let {iconType, value, onChange, handleBlur} = this.props;
+ return (
+ <div className='expandable-input-wrapper opened' key='expandable'>
+ <Input
+ type='text'
+ value={value}
+ ref={(input) => this.searchInputNode = input}
+ className='expandable-active'
+ groupClassName='expandable-input-control'
+ onChange={e => onChange(e)}
+ onKeyDown={e => this.handleKeyDown(e)}
+ onBlur={handleBlur}/>
+ {value && <SVGIcon onClick={() => this.handleClose()} name='close' />}
+ {!value && <SVGIcon name={iconType} onClick={handleBlur}/>}
+ </div>
+ );
+ }
+}
+
+class ExpandableInput extends React.Component {
+
+ static propTypes = {
+ iconType: React.PropTypes.string,
+ onChange: React.PropTypes.func,
+ value: React.PropTypes.string
+ };
+
+ state = {showInput: false};
+
+ closeInput(){
+ if (!this.props.value) {
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;
+ return this.props.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}
- );
-
+ let {iconType, value, onChange = false} = this.props;
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 className='expandable-input-top'>
+ {this.state.showInput &&
+ <ExpandableInputOpened
+ key='open'
+ iconType={iconType}
+ onChange={onChange}
+ value={value}
+ handleKeyDown={(e) => this.handleKeyDown(e)}
+ handleBlur={() => this.closeInput()}/>
+ }
+ {!this.state.showInput && <ExpandableInputClosed key='closed' iconType={iconType} onClick={() => this.setState({showInput: true})} />}
</div>
- );
+ );
}
}
+
export default ExpandableInput;
diff --git a/openecomp-ui/src/nfvo-components/input/SelectInput.jsx b/openecomp-ui/src/nfvo-components/input/SelectInput.jsx
index 1036ac4..03c7273 100644
--- a/openecomp-ui/src/nfvo-components/input/SelectInput.jsx
+++ b/openecomp-ui/src/nfvo-components/input/SelectInput.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.
+ */
/**
* 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.
@@ -20,8 +35,9 @@
render() {
let {label, value, ...other} = this.props;
+ const dataTestId = this.props['data-test-id'] ? {'data-test-id': this.props['data-test-id']} : {};
return (
- <div className='validation-input-wrapper dropdown-multi-select'>
+ <div {...dataTestId} 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} />
diff --git a/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx b/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx
index 873d3de..7bbafa3 100644
--- a/openecomp-ui/src/nfvo-components/input/ToggleInput.jsx
+++ b/openecomp-ui/src/nfvo-components/input/ToggleInput.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.
+ */
import React from 'react';
export default
diff --git a/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx b/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx
index 171bead..c60d6f7 100644
--- a/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx
+++ b/openecomp-ui/src/nfvo-components/input/dualListbox/DualListboxView.jsx
@@ -1,6 +1,21 @@
+/*!
+ * 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 FontAwesome from 'react-fontawesome';
-import Input from 'react-bootstrap/lib/Input.js';
+import SVGIcon from 'nfvo-components/icon/SVGIcon.jsx';
+import Input from 'nfvo-components/input/validation/InputWrapper.jsx';
class DualListboxView extends React.Component {
@@ -30,37 +45,32 @@
state = {
availableListFilter: '',
- selectedValuesListFilter: ''
- };
-
- static contextTypes = {
- isReadOnlyMode: React.PropTypes.bool
+ selectedValuesListFilter: '',
+ selectedValues: []
};
render() {
- let {availableList, selectedValuesList, filterTitle} = this.props;
+ let {availableList, selectedValuesList, filterTitle, isReadOnlyMode} = 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})}
+ onChange: (value) => this.setState({availableListFilter: value})
+ }, {ref: 'availableValues', disabled: isReadOnlyMode, testId: 'available',})}
{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})}
+ onChange: (value) => this.setState({selectedValuesListFilter: value})
+ }, {ref: 'selectedValues', disabled: isReadOnlyMode, testId: 'selected'})}
</div>
);
}
@@ -69,21 +79,25 @@
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'/>
+ <Input data-test-id={`${props.testId}-search-input`}
+ name='search-input-control' type='text'
+ groupClassName='search-input-control'
+ {...filterProps}/>
+ <SVGIcon name='search' className='search-icon'/>
</div>
<Input
multiple
+ onChange={(event) => this.onSelectItems(event.target.selectedOptions)}
groupClassName='dual-list-box-multi-select'
type='select'
name='dual-list-box-multi-select'
- {...props}>
+ data-test-id={`${props.testId}-select-input`}
+ disabled={props.disabled}
+ ref={props.ref}>
{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))}
@@ -92,6 +106,11 @@
);
}
+ onSelectItems(selectedOptions) {
+ let selectedValues = Object.keys(selectedOptions).map((k) => selectedOptions[k].value);
+ this.setState({selectedValues});
+ }
+
renderOption(value, name) {
return (<option className='dual-list-box-multi-select-text' key={value} value={value}>{name}</option>);
}
@@ -107,17 +126,19 @@
);
}
- renderOperationBarButton(onClick, fontAwesomeIconName){
- return (<div className='dual-list-option' onClick={onClick}><FontAwesome name={fontAwesomeIconName}/></div>);
+ renderOperationBarButton(onClick, iconName){
+ return (<div className='dual-list-option' data-test-id={`operation-icon-${iconName}`} onClick={onClick}><SVGIcon name={iconName}/></div>);
}
addToSelectedList() {
- this.props.onChange(this.props.selectedValuesList.concat(this.refs.availableValues.getValue()));
+ this.props.onChange(this.props.selectedValuesList.concat(this.state.selectedValues));
+ this.setState({selectedValues: []});
}
removeFromSelectedList() {
- const selectedValues = this.refs.selectedValues.getValue();
+ const selectedValues = this.state.selectedValues;
this.props.onChange(this.props.selectedValuesList.filter(value => !selectedValues.find(selectedValue => selectedValue === value)));
+ this.setState({selectedValues: []});
}
addAllToSelectedList() {
diff --git a/openecomp-ui/src/nfvo-components/input/inputOptions/InputOptions.jsx b/openecomp-ui/src/nfvo-components/input/inputOptions/InputOptions.jsx
index 5daaffe..e8aadc4 100644
--- a/openecomp-ui/src/nfvo-components/input/inputOptions/InputOptions.jsx
+++ b/openecomp-ui/src/nfvo-components/input/inputOptions/InputOptions.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.
+ */
import React from 'react';
import i18n from 'nfvo-utils/i18n/i18n.js';
import classNames from 'classnames';
@@ -13,15 +28,21 @@
title: React.PropTypes.string
})),
isEnabledOther: React.PropTypes.bool,
- title: React.PropTypes.string,
+ label: React.PropTypes.string,
selectedValue: React.PropTypes.string,
- multiSelectedEnum: React.PropTypes.array,
+ multiSelectedEnum: React.PropTypes.oneOfType([
+ React.PropTypes.string,
+ React.PropTypes.array
+ ]),
selectedEnum: React.PropTypes.string,
otherValue: React.PropTypes.string,
onEnumChange: React.PropTypes.func,
onOtherChange: React.PropTypes.func,
+ onBlur: React.PropTypes.func,
isRequired: React.PropTypes.bool,
- isMultiSelect: React.PropTypes.bool
+ isMultiSelect: React.PropTypes.bool,
+ hasError: React.PropTypes.bool,
+ disabled: React.PropTypes.bool
};
@@ -41,7 +62,7 @@
render() {
let {label, isRequired, values, otherValue, onOtherChange, isMultiSelect, onBlur, multiSelectedEnum, selectedEnum, hasError, validations, children} = 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;
@@ -54,14 +75,18 @@
else if(selectedEnum){
currentSelectedEnum = selectedEnum;
}
+ if (!onBlur) {
+ onBlur = () => {};
+ }
let isReadOnlyMode = this.context.isReadOnlyMode;
return(
- <div className={classNames('form-group', {'required' : validations.required , 'has-error' : hasError})}>
+ <div className={classNames('form-group', {'required' : (validations && validations.required) || isRequired, 'has-error' : hasError})}>
{label && <label className='control-label'>{label}</label>}
{isMultiSelect && otherInputDisabled ?
<Select
+ {...dataTestId}
ref='_myInput'
value={currentMultiSelectedEnum}
className='options-input'
@@ -74,18 +99,18 @@
multi/> :
<div className={classNames('input-options',{'has-error' : hasError})}>
<select
+ {...dataTestId}
ref={'_myInput'}
label={label}
className='form-control input-options-select'
value={currentSelectedEnum}
- style={{'width' : otherInputDisabled ? '100%' : '95px'}}
+ style={{'width' : otherInputDisabled ? '100%' : '100px'}}
onBlur={() => onBlur()}
disabled={isReadOnlyMode || Boolean(this.props.disabled)}
onChange={ value => this.enumChanged(value)}
type='select'>
- {values && values.length && values.map(val => this.renderOptions(val))}
+ {children || (values && values.length && values.map((val, index) => this.renderOptions(val, index)))}
{onOtherChange && <option key='other' value={other.OTHER}>{i18n(other.OTHER)}</option>}
- {children}
</select>
{!otherInputDisabled && <div className='input-options-separator'/>}
@@ -104,9 +129,9 @@
);
}
- renderOptions(val){
- return(
- <option key={val.enum} value={val.enum}>{val.title}</option>
+ renderOptions(val, index){
+ return (
+ <option key={index} value={val.enum}>{val.title}</option>
);
}
@@ -154,9 +179,9 @@
enumChanged() {
let enumValue = this.refs._myInput.value;
- let {onEnumChange, isMultiSelect, onChange} = this.props;
+ let {onEnumChange, onOtherChange, isMultiSelect, onChange} = this.props;
this.setState({
- otherInputDisabled: enumValue !== other.OTHER
+ otherInputDisabled: !Boolean(onOtherChange) || enumValue !== other.OTHER
});
let value = isMultiSelect ? [enumValue] : enumValue;
@@ -169,7 +194,7 @@
}
multiSelectEnumChanged(enumValue) {
- let {onEnumChange} = this.props;
+ let {onEnumChange, onOtherChange} = this.props;
let selectedValues = enumValue.map(enumVal => {
return enumVal.value;
});
@@ -182,7 +207,7 @@
}
this.setState({
- otherInputDisabled: !selectedValues.includes(i18n(other.OTHER))
+ otherInputDisabled: !Boolean(onOtherChange) || !selectedValues.includes(i18n(other.OTHER))
});
onEnumChange(selectedValues);
}
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 0000000..47922f86
--- /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 (
+ <form {...formProps} ref={(form) => this.form = form} onSubmit={event => this.handleFormValidation(event)}>
+ <div className='validation-form-content'>
+ <fieldset disabled={isReadOnlyMode}>
+ {children}
+ </fieldset>
+ </div>
+ {hasButtons && <ValidationButtons labledButtons={labledButtons} ref={(buttons) => this.buttons = buttons} isReadOnlyMode={isReadOnlyMode}/>}
+ </form>
+ );
+ }
+
+ 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 0000000..59c35d7
--- /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(
+ <div className={wrapperClassName}>
+ <FormGroup className={classNames('form-group', [groupClassName], {'required' : isRequired , 'has-error' : !isValid})} >
+ {(label && (type !== 'checkbox' && type !== 'radio')) && <label className='control-label'>{label}</label>}
+ {(type === 'text' || type === 'number') &&
+ <FormControl
+ bsClass={'form-control input-options-other'}
+ onChange={(e) => 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' &&
+ <FormControl
+ className='form-control input-options-other'
+ disabled={isReadOnlyMode || Boolean(disabled)}
+ value={value || ''}
+ onBlur={onBlur}
+ onKeyDown={onKeyDown}
+ componentClass={type}
+ onChange={(e) => this.onChange(e)}
+ inputRef={(input) => this.input = input}
+ data-test-id={this.props['data-test-id']}/>}
+
+ {type === 'checkbox' &&
+ <Checkbox
+ className={classNames({'required' : isRequired , 'has-error' : !isValid})}
+ onChange={(e)=>this.onChangeCheckBox(e)}
+ disabled={isReadOnlyMode || Boolean(disabled)}
+ checked={value}
+ data-test-id={this.props['data-test-id']}>{label}</Checkbox>}
+
+ {type === 'radio' &&
+ <Radio name={name}
+ checked={checked}
+ disabled={isReadOnlyMode || Boolean(disabled)}
+ value={value}
+ onChange={(e)=>this.onChangeRadio(e)}
+ data-test-id={this.props['data-test-id']}>{label}</Radio>}
+ {type === 'select' &&
+ <FormControl onClick={ (e) => this.optionSelect(e) }
+ componentClass={type}
+ inputRef={(input) => this.input = input}
+ name={name} {...inputProps}
+ data-test-id={this.props['data-test-id']}/>}
+ </FormGroup>
+ { this.renderErrorOverlay() }
+ </div>
+ );
+ }
+
+ 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 (
+ <Overlay
+ show={!isValid}
+ placement={position}
+ target={() => {
+ let target = ReactDOM.findDOMNode(this.input);
+ return target.offsetParent ? target : undefined;
+ }}
+ container={this}>
+ <Tooltip
+ id={`error-${errorText.replace(' ', '-')}`}
+ className='validation-error-message'>
+ {errorText}
+ </Tooltip>
+ </Overlay>
+ );
+ }
+
+}
+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 0000000..6e54254
--- /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(
+ <div className='validation-input-wrapper' >
+ <div className={classNames('form-group', {'required' : isRequired, 'has-error' : !isValid})} >
+ {label && <label className='control-label'>{label}</label>}
+ {isMultiSelect && otherInputDisabled ?
+ <Select
+ {...dataTestId}
+ ref={(input) => this.input = input}
+ value={currentMultiSelectedEnum}
+ className='options-input'
+ clearable={false}
+ required={isRequired}
+ disabled={isReadOnlyMode || Boolean(this.props.disabled)}
+ onBlur={() => onBlur()}
+ onMultiSelectChanged={value => this.multiSelectEnumChanged(value)}
+ options={this.renderMultiSelectOptions(values)}
+ multi/> :
+ <div className={classNames('input-options',{'has-error' : !isValid})} >
+ <select
+ {...dataTestId}
+ ref={(input) => this.input = input}
+ label={label}
+ className='form-control input-options-select'
+ value={currentSelectedEnum}
+ style={{'width' : otherInputDisabled ? '100%' : '100px'}}
+ onBlur={() => onBlur()}
+ disabled={isReadOnlyMode || Boolean(this.props.disabled)}
+ onChange={ value => this.enumChanged(value)}
+ type='select'>
+ {children || (values && values.length && values.map((val, index) => this.renderOptions(val, index)))}
+ {onOtherChange && <option key='other' value={other.OTHER}>{i18n(other.OTHER)}</option>}
+ </select>
+
+ {!otherInputDisabled && <div className='input-options-separator'/>}
+ <input
+ className='form-control input-options-other'
+ placeholder={i18n('other')}
+ ref={(otherValue) => this.otherValue = otherValue}
+ style={{'display' : otherInputDisabled ? 'none' : 'block'}}
+ disabled={isReadOnlyMode || Boolean(this.props.disabled)}
+ value={otherValue || ''}
+ onBlur={() => onBlur()}
+ onChange={() => this.changedOtherInput()}/>
+ </div>
+ }
+ </div>
+ { this.renderErrorOverlay() }
+ </div>
+ );
+ }
+
+ renderOptions(val, index){
+ return (
+ <option key={index} value={val.enum}>{val.title}</option>
+ );
+ }
+
+
+ renderMultiSelectOptions(values) {
+ let {onOtherChange} = this.props;
+ let optionsList = [];
+ if (onOtherChange) {
+ optionsList = values.map(option => {
+ return {
+ label: option.title,
+ value: option.enum,
+ };
+ }).concat([{
+ label: i18n(other.OTHER),
+ value: i18n(other.OTHER),
+ }]);
+ }
+ else {
+ optionsList = values.map(option => {
+ return {
+ label: option.title,
+ value: option.enum,
+ };
+ });
+ }
+ if (optionsList.length > 0 && optionsList[0].value === '') {
+ optionsList.shift();
+ }
+ return optionsList;
+ }
+
+ renderErrorOverlay() {
+ let position = 'right';
+ const {errorText = '', isValid = true, type, overlayPos} = this.props;
+
+ if (overlayPos) {
+ position = overlayPos;
+ }
+ else if (type === 'text'
+ || type === 'email'
+ || type === 'number'
+ || type === 'password') {
+ position = 'bottom';
+ }
+
+ return (
+ <Overlay
+ show={!isValid}
+ placement={position}
+ target={() => {
+ let {otherInputDisabled} = this.state;
+ let target = otherInputDisabled ? ReactDOM.findDOMNode(this.input) : ReactDOM.findDOMNode(this.otherValue);
+ return target.offsetParent ? target : undefined;
+ }}
+ container={this}>
+ <Tooltip
+ id={`error-${errorText.replace(' ', '-')}`}
+ className='validation-error-message'>
+ {errorText}
+ </Tooltip>
+ </Overlay>
+ );
+ }
+
+ getValue() {
+ let res = '';
+ let {isMultiSelect} = this.props;
+ let {otherInputDisabled} = this.state;
+
+ if (otherInputDisabled) {
+ res = isMultiSelect ? this.input.getValue() : this.input.value;
+ } else {
+ res = this.otherValue.value;
+ }
+ return res;
+ }
+
+ enumChanged() {
+ let enumValue = this.input.value;
+ let {onEnumChange, onOtherChange, isMultiSelect, onChange} = this.props;
+ this.setState({
+ otherInputDisabled: !Boolean(onOtherChange) || enumValue !== other.OTHER
+ });
+
+ let value = isMultiSelect ? [enumValue] : enumValue;
+ if (onEnumChange) {
+ onEnumChange(value);
+ }
+ if (onChange) {
+ onChange(value);
+ }
+ }
+
+ multiSelectEnumChanged(enumValue) {
+ let {onEnumChange, onOtherChange} = this.props;
+ let selectedValues = enumValue.map(enumVal => {
+ return enumVal.value;
+ });
+
+ if (this.state.otherInputDisabled === false) {
+ selectedValues.shift();
+ }
+ else if (selectedValues.includes(i18n(other.OTHER))) {
+ selectedValues = [i18n(other.OTHER)];
+ }
+
+ this.setState({
+ otherInputDisabled: !Boolean(onOtherChange) || !selectedValues.includes(i18n(other.OTHER))
+ });
+ onEnumChange(selectedValues);
+ }
+
+ changedOtherInput() {
+ let {onOtherChange} = this.props;
+ onOtherChange(this.otherValue.value);
+ }
+
+ componentDidUpdate() {
+ let {otherValue, selectedEnum, onInputChange, multiSelectedEnum} = this.props;
+ if (this.oldProps.otherValue !== otherValue
+ || this.oldProps.selectedEnum !== selectedEnum
+ || this.oldProps.multiSelectedEnum !== multiSelectedEnum) {
+ this.oldProps = {
+ otherValue,
+ selectedEnum,
+ multiSelectedEnum
+ };
+ onInputChange();
+ }
+ }
+
+ static getTitleByName(values, name) {
+ for (let key of Object.keys(values)) {
+ let option = values[key].find(option => option.enum === name);
+ if (option) {
+ return option.title;
+ }
+ }
+ return name;
+ }
+
+}
+
+export default InputOptions;
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 0000000..5ca716c
--- /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(
+ <FormGroup className={classNames('form-group', [groupClassName], {'required' : validations.required , 'has-error' : hasError})} >
+ {(label && (type !== 'checkbox' && type !== 'radio')) && <label className='control-label'>{label}</label>}
+ {(type === 'text' || type === 'number') &&
+ <FormControl
+ bsClass={'form-control input-options-other'}
+ onChange={(e) => 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' &&
+ <FormControl
+ className='form-control input-options-other'
+ disabled={isReadOnlyMode || Boolean(disabled)}
+ value={value || ''}
+ onBlur={onBlur}
+ onKeyDown={onKeyDown}
+ componentClass={type}
+ onChange={(e) => this.onChange(e)}
+ data-test-id={this.props['data-test-id']}/>}
+
+ {type === 'checkbox' &&
+ <Checkbox
+ className={classNames({'required' : validations.required , 'has-error' : hasError})}
+ onChange={(e)=>this.onChangeCheckBox(e)}
+ disabled={isReadOnlyMode || Boolean(disabled)}
+ checked={value}
+ data-test-id={this.props['data-test-id']}>{label}</Checkbox>}
+
+ {type === 'radio' &&
+ <Radio name={name}
+ checked={checked}
+ disabled={isReadOnlyMode || Boolean(disabled)}
+ value={value}
+ onChange={(e)=>this.onChangeRadio(e)}
+ data-test-id={this.props['data-test-id']}>{label}</Radio>}
+ {type === 'select' &&
+ <FormControl onClick={ (e) => this.optionSelect(e) }
+ componentClass={type}
+ name={name} {...inputProps}
+ data-test-id={this.props['data-test-id']}/>}
+
+ </FormGroup>
+
+ );
+ }
+
+ 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 0000000..95144b1
--- /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 (
+ <div>
+ <BTabs {...tabProps} ref='tabsList' id='tabsList' >
+ {this.props.children.map(element => this.cloneTab(element))}
+ </BTabs>
+ <Overlay
+ animation={false}
+ show={this.showTabsError()}
+ placement='bottom'
+ containerPadding={50}
+ 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={() => {
+ 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;
+ }}>
+ <Tooltip
+ id='error-some-tabs-contain-errors'
+ className='validation-error-message'>
+ {i18n('One or more tabs are invalid')}
+ </Tooltip>
+ </Overlay>
+ </div>
+ );
+ }
+}
diff --git a/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx b/openecomp-ui/src/nfvo-components/input/validation/ValidationButtons.jsx
index a87c8d6..ebb1473 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 @@
};
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'/>;
+ var submitBtn = this.props.labledButtons ? i18n('Save') : <SVGIcon className='check' name='check'/>;
+ var closeBtn = this.props.labledButtons ? i18n('Cancel') : <SVGIcon className='close' name='close'/>;
return (
<div className='validation-buttons'>
{!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 098ccf1..0000000
--- 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 (
- <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
deleted file mode 100644
index 0f14307..0000000
--- 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 (
- <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
deleted file mode 100644
index 6036518..0000000
--- 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 (
- <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
deleted file mode 100644
index 6eda4b9..0000000
--- 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 (
- <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>
- );
- }
-}
diff --git a/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx b/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx
index e8d0fc2..f6c906b 100644
--- a/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx
+++ b/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemView.jsx
@@ -1,7 +1,24 @@
+/*!
+ * 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 FontAwesome from 'react-fontawesome';
+import classnames from 'classnames';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import SVGIcon from 'nfvo-components/icon/SVGIcon.jsx';
import store from 'sdc-app/AppStore.js';
-import NotificationConstants from 'nfvo-components/notifications/NotificationConstants.js';
+import {actionTypes as modalActionTypes} from 'nfvo-components/modal/GlobalModalConstants.js';
class ListEditorItem extends React.Component {
static propTypes = {
@@ -16,14 +33,14 @@
let {onDelete, onSelect, onEdit, children, isReadOnlyMode} = this.props;
let isAbilityToDelete = isReadOnlyMode === undefined ? true : !isReadOnlyMode;
return (
- <div className='list-editor-item-view'>
+ <div className={classnames('list-editor-item-view', {'selectable': Boolean(onSelect)})} data-test-id='list-editor-item'>
<div className='list-editor-item-view-content' onClick={onSelect}>
{children}
</div>
- <div className='list-editor-item-view-controller'>
- {onEdit && <FontAwesome name='sliders' onClick={() => this.onClickedItem(onEdit)}/>}
- {onDelete && isAbilityToDelete && <FontAwesome name='trash-o' onClick={() => this.onClickedItem(onDelete)}/>}
- </div>
+ {(onEdit || onDelete) && <div className='list-editor-item-view-controller'>
+ {onEdit && <SVGIcon name='sliders' onClick={() => this.onClickedItem(onEdit)}/>}
+ {onDelete && isAbilityToDelete && <SVGIcon name='trash-o' onClick={() => this.onClickedItem(onDelete)}/>}
+ </div>}
</div>
);
}
@@ -33,8 +50,11 @@
let {isCheckedOut} = this.props;
if (isCheckedOut === false) {
store.dispatch({
- type: NotificationConstants.NOTIFY_ERROR,
- data: {title: 'Error', msg: 'This item is checkedin/submitted, Click Check Out to continue'}
+ type: modalActionTypes.GLOBAL_MODAL_WARNING,
+ data: {
+ title: i18n('Error'),
+ msg: i18n('This item is checkedin/submitted, Click Check Out to continue')
+ }
});
}
else {
diff --git a/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemViewField.jsx b/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemViewField.jsx
new file mode 100644
index 0000000..839f9a5
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/listEditor/ListEditorItemViewField.jsx
@@ -0,0 +1,24 @@
+/*!
+ * 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';
+
+export const ListEditorItemViewField = ({children}) => (
+ <div className='list-editor-item-view-field'>
+ {children}
+ </div>
+);
+
+export default ListEditorItemViewField;
diff --git a/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx b/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx
index 1ee91f3..cc805e9 100644
--- a/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx
+++ b/openecomp-ui/src/nfvo-components/listEditor/ListEditorView.jsx
@@ -1,12 +1,63 @@
+/*!
+ * 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 FontAwesome from 'react-fontawesome';
-import Input from 'react-bootstrap/lib/Input.js';
+import classnames from 'classnames';
+import ExpandableInput from 'nfvo-components/input/ExpandableInput.jsx';
+const ListEditorHeader = ({onAdd, isReadOnlyMode, title, plusButtonTitle}) => {
+ return (
+ <div className='list-editor-view-header'>
+ {title && <div className='list-editor-view-title'>{title}</div>}
+ <div className={`list-editor-view-add-controller${isReadOnlyMode ? ' disabled' : ''}`}>
+ { onAdd &&
+ <div className='list-editor-view-add-title' data-test-id='add-button' onClick={onAdd}>
+ <span>{`+ ${plusButtonTitle}`}</span>
+ </div>
+ }
+ </div>
+ </div>
+ );
+};
+
+const ListEditorScroller = ({children, twoColumns}) => {
+ return (
+ <div className='list-editor-view-list-scroller'>
+ <div className={classnames('list-editor-view-list', {'two-columns': twoColumns})}>
+ {children}
+ </div>
+ </div>
+ );
+};
+
+const FilterWrapper = ({onFilter, filterValue}) => {
+ return (
+ <div className='expandble-search-wrapper'>
+ <ExpandableInput
+ onChange={onFilter}
+ iconType='search'
+ value={filterValue}/>
+ </div>
+ );
+};
class ListEditorView extends React.Component {
static defaultProps = {
- className: ''
+ className: '',
+ twoColumns: false
};
static propTypes = {
@@ -17,45 +68,17 @@
onFilter: React.PropTypes.func,
className: React.PropTypes.string,
isReadOnlyMode: React.PropTypes.bool,
- placeholder: React.PropTypes.string
+ placeholder: React.PropTypes.string,
+ twoColumns: React.PropTypes.bool
};
render() {
- let {title, plusButtonTitle, onAdd, children, filterValue, onFilter, className, placeholder, isReadOnlyMode} = this.props;
+ let {title, plusButtonTitle, onAdd, children, onFilter, className, isReadOnlyMode, twoColumns, filterValue} = this.props;
return (
- <div className={`list-editor-view ${className}`}>
- {title && onAdd && <div className='list-editor-view-title'>{title}</div>}
- <div className='list-editor-view-actions'>
- {title && !onAdd && <div className='list-editor-view-title-inline'>{title}</div>}
- <div className={`list-editor-view-add-controller${isReadOnlyMode ? ' disabled' : ''}`} >
- { onAdd &&
- <div onClick={onAdd}>
- <span className='plus-icon-button pull-left'/>
- <span>{plusButtonTitle}</span>
- </div>
- }
- </div>
-
- {
- onFilter &&
- <div className='list-editor-view-search search-wrapper'>
- <Input
- ref='filter'
- type='text'
- value={filterValue}
- name='list-editor-view-search'
- placeholder={placeholder}
- groupClassName='search-input-control'
- onChange={() => onFilter(this.refs.filter.getValue())}/>
- <FontAwesome name='filter' className='filter-icon'/>
- </div>
- }
- </div>
- <div className='list-editor-view-list-scroller'>
- <div className='list-editor-view-list'>
- {children}
- </div>
- </div>
+ <div className={classnames('list-editor-view', className)}>
+ <ListEditorHeader onAdd={onAdd} isReadOnlyMode={isReadOnlyMode} plusButtonTitle={plusButtonTitle} title={title}/>
+ {onFilter && (children.length || filterValue) && <FilterWrapper onFilter={onFilter} filterValue={filterValue}/>}
+ <ListEditorScroller children={children} twoColumns={twoColumns}/>
</div>
);
}
diff --git a/openecomp-ui/src/nfvo-components/listEditor/listEditor.stories.js b/openecomp-ui/src/nfvo-components/listEditor/listEditor.stories.js
new file mode 100644
index 0000000..276b05e
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/listEditor/listEditor.stories.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import {storiesOf, action} from '@kadira/storybook';
+import ListEditorView from './ListEditorView.jsx';
+import ListEditorItemView from './ListEditorItemView.jsx';
+import ListEditorItemViewField from './ListEditorItemViewField.jsx';
+import {text, number} from '@kadira/storybook-addon-knobs';
+import {withKnobs} from '@kadira/storybook-addon-knobs';
+
+function makeChildren({onEdit = false, onDelete = false} = {}) {
+ return (
+ [...Array(number('Items', 2)).keys()].map(index => (
+ <ListEditorItemView
+ key={index}
+ onEdit={onEdit ? onEdit : undefined}
+ onDelete={onDelete ? onDelete : undefined}>
+ <ListEditorItemViewField>
+ <div>{text('field 1', 'Lorum Ipsum')}</div>
+ </ListEditorItemViewField>
+ <ListEditorItemViewField>
+ <div>{text('field 2', 'Lorum Ipsum')}</div>
+ </ListEditorItemViewField>
+ </ListEditorItemView>)
+ )
+ );
+}
+
+const stories = storiesOf('ListEditor', module);
+stories.addDecorator(withKnobs);
+
+stories
+ .add('regular', () => (
+ <ListEditorView title='List Editor'>
+ {makeChildren()}
+ </ListEditorView>
+ ))
+ .add('two columns', () => (
+ <ListEditorView title='List Editor' twoColumns>
+ {makeChildren()}
+ </ListEditorView>
+ ))
+ .add('with add', () => (
+ <ListEditorView title='List Editor' onAdd={action('onAdd')} plusButtonTitle='Add' twoColumns>
+ {makeChildren()}
+ </ListEditorView>
+ ))
+ .add('with delete', () => (
+ <ListEditorView title='List Editor' onAdd={action('onAdd')} plusButtonTitle='Add' twoColumns>
+ {makeChildren({onDelete: action('onDelete')})}
+ </ListEditorView>
+ ))
+ .add('with edit', () => (
+ <ListEditorView title='List Editor' onAdd={action('onAdd')} plusButtonTitle='Add' twoColumns>
+ {makeChildren({onEdit: action('onEdit')})}
+ </ListEditorView>
+ ))
+ .add('with edit and delete', () => (
+ <ListEditorView title='List Editor' onAdd={action('onAdd')} plusButtonTitle='Add' twoColumns>
+ {makeChildren({onDelete: action('onDelete'), onEdit: action('onEdit')})}
+ </ListEditorView>
+ ));
diff --git a/openecomp-ui/src/nfvo-components/loader/Loader.jsx b/openecomp-ui/src/nfvo-components/loader/Loader.jsx
index cc1ffdb..675b04c 100644
--- a/openecomp-ui/src/nfvo-components/loader/Loader.jsx
+++ b/openecomp-ui/src/nfvo-components/loader/Loader.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.
+ */
import React from 'react';
import {connect} from 'react-redux';
diff --git a/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js b/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js
index e8e4953..7c0c0e2 100644
--- a/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js
+++ b/openecomp-ui/src/nfvo-components/loader/LoaderConstants.js
@@ -1,23 +1,18 @@
-/*-
- * ============LICENSE_START=======================================================
- * SDC
- * ================================================================================
+/*!
* 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
- *
+ *
+ * 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.
- * ============LICENSE_END=========================================================
+ * 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 keyMirror from 'nfvo-utils/KeyMirror.js';
export const actionTypes = keyMirror({
diff --git a/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js b/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js
index 582eff3..2eff70a 100644
--- a/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js
+++ b/openecomp-ui/src/nfvo-components/loader/LoaderReducer.js
@@ -1,23 +1,18 @@
-/*-
- * ============LICENSE_START=======================================================
- * SDC
- * ================================================================================
+/*!
* 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
- *
+ *
+ * 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.
- * ============LICENSE_END=========================================================
+ * 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 {actionTypes} from './LoaderConstants.js';
export default (state = {}, action) => {
diff --git a/openecomp-ui/src/nfvo-components/modal/GlobalModal.js b/openecomp-ui/src/nfvo-components/modal/GlobalModal.js
new file mode 100644
index 0000000..65a1ad6
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/modal/GlobalModal.js
@@ -0,0 +1,120 @@
+/*!
+ * 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 {connect} from 'react-redux';
+
+import Modal from 'nfvo-components/modal/Modal.jsx';
+import Button from 'react-bootstrap/lib/Button.js';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import {modalContentComponents} from 'sdc-app/common/modal/ModalContentMapper.js';
+import {actionTypes, typeEnum} from './GlobalModalConstants.js';
+
+
+const typeClass = {
+ 'default': 'primary',
+ error: 'danger',
+ warning: 'warning',
+ success: 'success'
+};
+
+
+const ModalFooter = ({type, onConfirmed, onDeclined, onClose, confirmationButtonText, cancelButtonText}) =>
+ <Modal.Footer>
+ <Button bsStyle={typeClass[type]} onClick={onDeclined ? () => {
+ onDeclined();
+ onClose();} : () => onClose()}>
+ {cancelButtonText}
+ </Button>
+ {onConfirmed && <Button bsStyle={typeClass[type]} onClick={() => {
+ onConfirmed();
+ onClose();
+ }}>{confirmationButtonText}</Button>}
+ </Modal.Footer>;
+
+ModalFooter.defaultProps = {
+ type: 'default',
+ confirmationButtonText: i18n('OK'),
+ cancelButtonText: i18n('Cancel')
+};
+
+export const mapStateToProps = ({modal}) => {
+ const show = !!modal;
+ return {
+ show,
+ ...modal
+ };
+};
+
+export const mapActionToProps = (dispatch) => {
+ return {
+ onClose: () => dispatch({type: actionTypes.GLOBAL_MODAL_CLOSE})
+ };
+};
+
+
+export class GlobalModalView extends React.Component {
+
+ static propTypes = {
+ show: React.PropTypes.bool,
+ type: React.PropTypes.oneOf(['default', 'error', 'warning', 'success']),
+ title: React.PropTypes.string,
+ modalComponentProps: React.PropTypes.object,
+ modalComponentName: React.PropTypes.string,
+ onConfirmed: React.PropTypes.func,
+ onDeclined: React.PropTypes.func,
+ confirmationButtonText: React.PropTypes.string,
+ cancelButtonText: React.PropTypes.string
+ };
+
+ static defaultProps = {
+ show: false,
+ type: 'default',
+ title: ''
+ };
+
+ render() {
+ let {title, type, show, modalComponentName, modalComponentProps,
+ modalClassName, msg, onConfirmed, onDeclined, confirmationButtonText, cancelButtonText, onClose} = this.props;
+ const ComponentToRender = modalContentComponents[modalComponentName];
+ return (
+ <Modal show={show} bsSize={modalComponentProps && modalComponentProps.size} className={`onborading-modal ${modalClassName || ''} ${typeClass[type]}`}>
+ <Modal.Header>
+ <Modal.Title>{title}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ {ComponentToRender ? <ComponentToRender {...modalComponentProps}/> : msg}
+ </Modal.Body>
+ {(onConfirmed || onDeclined || type !== typeEnum.DEFAULT) &&
+ <ModalFooter
+ type={type}
+ onConfirmed={onConfirmed}
+ onDeclined={onDeclined}
+ onClose={onClose}
+ confirmationButtonText={confirmationButtonText}
+ cancelButtonText={cancelButtonText}/>}
+ </Modal>
+ );
+ }
+
+ componentDidUpdate() {
+ if (this.props.timeout) {
+ setTimeout(this.props.onClose, this.props.timeout);
+ }
+ }
+};
+
+export default connect(mapStateToProps, mapActionToProps, null, {withRef: true})(GlobalModalView);
diff --git a/openecomp-ui/src/nfvo-components/modal/GlobalModalConstants.js b/openecomp-ui/src/nfvo-components/modal/GlobalModalConstants.js
new file mode 100644
index 0000000..0a0ed1f
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/modal/GlobalModalConstants.js
@@ -0,0 +1,33 @@
+/*!
+ * 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 keyMirror from 'nfvo-utils/KeyMirror.js';
+
+export const actionTypes = keyMirror({
+ GLOBAL_MODAL_SHOW: null,
+ GLOBAL_MODAL_CLOSE: null,
+ GLOBAL_MODAL_ERROR: null,
+ GLOBAL_MODAL_WARNING: null,
+ GLOBAL_MODAL_SUCCESS: null,
+
+});
+
+
+export const typeEnum = {
+ DEFAULT: 'default',
+ ERROR: 'error',
+ WARNING: 'warning',
+ SUCCESS: 'success'
+};
diff --git a/openecomp-ui/src/nfvo-components/modal/GlobalModalReducer.js b/openecomp-ui/src/nfvo-components/modal/GlobalModalReducer.js
new file mode 100644
index 0000000..28674ea
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/modal/GlobalModalReducer.js
@@ -0,0 +1,50 @@
+/*!
+ * 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 {actionTypes, typeEnum} from './GlobalModalConstants.js';
+
+export default (state = null, action) => {
+ switch (action.type) {
+ case actionTypes.GLOBAL_MODAL_SHOW:
+ return {
+ ...action.data
+ };
+ case actionTypes.GLOBAL_MODAL_ERROR:
+ return {
+ type: typeEnum.ERROR,
+ modalClassName: 'notification-modal',
+ ...action.data
+ };
+ case actionTypes.GLOBAL_MODAL_WARNING:
+ return {
+ type: typeEnum.WARNING,
+ modalClassName: 'notification-modal',
+ ...action.data
+ };
+
+ case actionTypes.GLOBAL_MODAL_SUCCESS:
+ return {
+ type: typeEnum.SUCCESS,
+ modalClassName: 'notification-modal',
+ ...action.data
+ };
+
+ case actionTypes.GLOBAL_MODAL_CLOSE:
+ return null;
+ default:
+ return state;
+ }
+};
diff --git a/openecomp-ui/src/nfvo-components/modal/Modal.jsx b/openecomp-ui/src/nfvo-components/modal/Modal.jsx
index be4963e..b0f704d 100644
--- a/openecomp-ui/src/nfvo-components/modal/Modal.jsx
+++ b/openecomp-ui/src/nfvo-components/modal/Modal.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.
+ */
import React from 'react';
import ReactDOM from 'react-dom';
import BootstrapModal from 'react-bootstrap/lib/Modal.js';
diff --git a/openecomp-ui/src/nfvo-components/notifications/NotificationConstants.js b/openecomp-ui/src/nfvo-components/notifications/NotificationConstants.js
deleted file mode 100644
index 1a53f4c..0000000
--- a/openecomp-ui/src/nfvo-components/notifications/NotificationConstants.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * SDC
- * ================================================================================
- * 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.
- * ============LICENSE_END=========================================================
- */
-
-import keyMirror from 'nfvo-utils/KeyMirror.js';
-
-export default keyMirror({
- NOTIFY_ERROR: null,
- NOTIFY_SUCCESS: null,
- NOTIFY_WARNING: null,
- NOTIFY_INFO: null,
- NOTIFY_CLOSE: null
-});
diff --git a/openecomp-ui/src/nfvo-components/notifications/NotificationModal.jsx b/openecomp-ui/src/nfvo-components/notifications/NotificationModal.jsx
deleted file mode 100644
index 7179309..0000000
--- a/openecomp-ui/src/nfvo-components/notifications/NotificationModal.jsx
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- * NotificationModal options:
- *
- * show: whether to show notification or not,
- * type: the type of the notification. valid values are: 'default', 'error', 'warning', 'success'
- * msg: the notification content. could be a string or node (React component)
- * title: the notification title
- * timeout: timeout for the notification to fade out. if timeout == 0 then the notification is rendered until the user closes it
- *
- */
-import React, {Component, PropTypes} from 'react';
-import {connect} from 'react-redux';
-import Button from 'react-bootstrap/lib/Button.js';
-
-import i18n from 'nfvo-utils/i18n/i18n.js';
-import Modal from 'nfvo-components/modal/Modal.jsx';
-import SubmitErrorResponse from 'nfvo-components/SubmitErrorResponse.jsx';
-import NotificationConstants from './NotificationConstants.js';
-
-let typeClass = {
- 'default': 'primary',
- error: 'danger',
- warning: 'warning',
- success: 'success'
-};
-
-const mapActionsToProps = (dispatch) => {
- return {onCloseClick: () => dispatch({type: NotificationConstants.NOTIFY_CLOSE})};
-};
-
-const mapStateToProps = ({notification}) => {
-
- let show = notification !== null && notification.title !== 'Conflict';
- let mapResult = {show};
- if (show) {
- mapResult = {show, ...notification};
- }
-
- return mapResult;
-};
-
-export class NotificationModal extends Component {
-
- static propTypes = {
- show: PropTypes.bool,
- type: PropTypes.oneOf(['default', 'error', 'warning', 'success']),
- title: PropTypes.string,
- msg: PropTypes.node,
- validationResponse: PropTypes.object,
- timeout: PropTypes.number
- };
-
- static defaultProps = {
- show: false,
- type: 'default',
- title: '',
- msg: '',
- timeout: 0
- };
-
- state = {type: undefined};
-
- componentWillReceiveProps(nextProps) {
- if (this.props.show !== nextProps.show && nextProps.show === false) {
- this.setState({type: this.props.type});
- }
- else {
- this.setState({type: undefined});
- }
- }
-
- componentDidUpdate() {
- if (this.props.timeout) {
- setTimeout(this.props.onCloseClick, this.props.timeout);
- }
- }
-
- render() {
- let {title, type, msg, show, validationResponse, onCloseClick} = this.props;
- if (!show) {
- type = this.state.type;
- }
- if (validationResponse) {
- msg = (<SubmitErrorResponse validationResponse={validationResponse}/>);
- }
- return (
- <Modal show={show} className={`notification-modal ${typeClass[type]}`}>
- <Modal.Header>
- <Modal.Title>{title}</Modal.Title>
- </Modal.Header>
- <Modal.Body>{msg}</Modal.Body>
- <Modal.Footer>
- <Button bsStyle={typeClass[type]} onClick={onCloseClick}>{i18n('OK')}</Button>
- </Modal.Footer>
- </Modal>
- );
- }
-}
-
-export default connect(mapStateToProps, mapActionsToProps)(NotificationModal);
diff --git a/openecomp-ui/src/nfvo-components/notifications/NotificationReducer.js b/openecomp-ui/src/nfvo-components/notifications/NotificationReducer.js
deleted file mode 100644
index c8b30d6..0000000
--- a/openecomp-ui/src/nfvo-components/notifications/NotificationReducer.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * SDC
- * ================================================================================
- * 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.
- * ============LICENSE_END=========================================================
- */
-
-import NotificationConstants from './NotificationConstants.js';
-
-export default (state = null, action) => {
- switch (action.type) {
- case NotificationConstants.NOTIFY_INFO:
- return {type: 'default', title: action.data.title, msg: action.data.msg, timeout: action.data.timeout};
-
- case NotificationConstants.NOTIFY_ERROR:
- return {
- type: 'error',
- title: action.data.title,
- msg: action.data.msg,
- validationResponse: action.data.validationResponse,
- timeout: action.data.timeout
- };
-
- case NotificationConstants.NOTIFY_WARNING:
- return {type: 'warning', title: action.data.title, msg: action.data.msg, timeout: action.data.timeout};
-
- case NotificationConstants.NOTIFY_SUCCESS:
- return {
- type: 'success', title: action.data.title, msg: action.data.msg, timeout: action.data.timeout
- };
- case NotificationConstants.NOTIFY_CLOSE:
- return null;
-
- default:
- return state;
- }
-
-};
diff --git a/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx b/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx
index feb0f81..3b89137 100644
--- a/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx
+++ b/openecomp-ui/src/nfvo-components/panel/NavigationSideBar.jsx
@@ -1,9 +1,23 @@
+/*!
+ * 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 classnames from 'classnames';
import Collapse from 'react-bootstrap/lib/Collapse.js';
class NavigationSideBar extends React.Component {
-
static PropTypes = {
activeItemId: React.PropTypes.string.isRequired,
onSelect: React.PropTypes.func,
@@ -11,51 +25,26 @@
groups: React.PropTypes.array
};
+ constructor(props) {
+ super(props);
+ this.state = {
+ activeItemId: null
+ };
+ this.handleItemClicked = this.handleItemClicked.bind(this);
+ }
+
render() {
let {groups, activeItemId} = this.props;
return (
<div className='navigation-side-content'>
{groups.map(group => (
- <div className='navigation-group' key={group.id}>
- <div className='group-name'>{group.name}</div>
- <div className='navigation-group-items'>
- {
- group.items && group.items.map(item => this.renderGroupItem(item, activeItemId))
- }
- </div>
- </div>
+ <NavigationMenu menu={group} activeItemId={activeItemId} onNavigationItemClick={this.handleItemClicked} key={'menu_' + group.id} />
))}
</div>
);
}
- renderGroupItem(item, activeItemId) {
- let isGroup = item.items && item.items.length > 0;
- return (
- <div className={classnames('navigation-group-item', {'selected-item': item.id === activeItemId})}>
- <div
- key={item.id}
- className={classnames('navigation-group-item-name', {
- 'selected': item.id === activeItemId,
- 'disabled': item.disabled,
- 'bold-name': item.expanded,
- 'hidden': item.hidden
- })}
- onClick={(event) => this.handleItemClicked(event, item)}>
- {item.name}
- </div>
- {isGroup &&
- <Collapse in={item.expanded}>
- <div>
- {item.items.map(item => this.renderGroupItem(item, activeItemId))}
- </div>
- </Collapse>
- }
- </div>
- );
- }
-
handleItemClicked(event, item) {
event.stopPropagation();
if(this.props.onToggle) {
@@ -70,4 +59,70 @@
}
}
+class NavigationMenu extends React.Component {
+ static PropTypes = {
+ activeItemId: React.PropTypes.string.isRequired,
+ onNavigationItemClick: React.PropTypes.func,
+ menu: React.PropTypes.array
+ };
+
+ render() {
+ const {menu, activeItemId, onNavigationItemClick} = this.props;
+ return (
+ <div className='navigation-group' key={menu.id}>
+ <NavigationMenuHeader title={menu.name} />
+ <NavigationMenuItems items={menu.items} activeItemId={activeItemId} onNavigationItemClick={onNavigationItemClick} />
+ </div>);
+ }
+}
+
+function NavigationMenuHeader(props) {
+ return <div className='group-name' data-test-id='navbar-group-name'>{props.title}</div>;
+}
+
+function NavigationMenuItems(props) {
+ const {items, activeItemId, onNavigationItemClick} = props;
+ return (
+ <div className='navigation-group-items'>
+ {
+ items && items.map(item => (<NavigationMenuItem key={'menuItem_' + item.id} item={item} activeItemId={activeItemId} onNavigationItemClick={onNavigationItemClick} />))
+ }
+ </div>
+ );
+}
+
+function NavigationMenuItem(props) {
+ const {onNavigationItemClick, item, activeItemId} = props;
+ const isGroup = item.items && item.items.length > 0;
+ return (
+ <div className={classnames('navigation-group-item', {'selected-item': item.id === activeItemId})} key={'item_' + item.id}>
+ <NavigationLink item={item} activeItemId={activeItemId} onClick={onNavigationItemClick} />
+ {isGroup && <Collapse in={item.expanded} data-test-id={'navigation-group-' + item.id}>
+ <div>
+ {item.items.map(subItem => (<NavigationMenuItem key={'menuItem_' + subItem.id} item={subItem} onNavigationItemClick={onNavigationItemClick} activeItemId={activeItemId} />)) }
+ </div>
+ </Collapse>
+ }
+ </div>
+ );
+}
+
+function NavigationLink(props) {
+ const {item, activeItemId, onClick} = props;
+ return (
+ <div
+ key={'navAction_' + item.id}
+ className={classnames('navigation-group-item-name', {
+ 'selected': item.id === activeItemId,
+ 'disabled': item.disabled,
+ 'bold-name': item.expanded,
+ 'hidden': item.hidden
+ })}
+ onClick={(event) => onClick(event, item)}
+ data-test-id={'navbar-group-item-' + item.id}>
+ {item.name}
+ </div>
+ );
+}
+
export default NavigationSideBar;
diff --git a/openecomp-ui/src/nfvo-components/panel/SlidePanel.jsx b/openecomp-ui/src/nfvo-components/panel/SlidePanel.jsx
deleted file mode 100644
index 10c5326..0000000
--- a/openecomp-ui/src/nfvo-components/panel/SlidePanel.jsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import React from 'react';
-import FontAwesome from 'react-fontawesome';
-import ReactDOM from 'react-dom';
-
-class SlidePanel extends React.Component {
-
- static PropTypes = {
- direction: React.PropTypes.string.isRequired,
- className: React.PropTypes.string,
- title: React.PropTypes.string,
- isOpen: React.PropTypes.bool
- };
-
- static defaultProps = {
- title: '',
- className: '',
- isOpen: true
- };
-
- state = {
- isOpen: this.props.isOpen,
- direction: this.props.direction,
- width: 0,
- arrowWidth: 0
- };
-
- componentDidMount() {
- this.setSliderPosition();
- }
-
- componentDidUpdate() {
- this.setSliderPosition();
- }
-
- render() {
-
- let {children, className} = this.props;
- let {isOpen} = this.state;
-
- return (
- <div className={ `slide-panel ${className}`}>
- {this.renderHeader(isOpen)}
- <div className={'slide-panel-content ' + (isOpen ? 'opened' : 'closed')}>{children}</div>
- </div>
- );
- }
-
- renderHeader(isOpen) {
- let {direction: initialDirection, title} = this.props;
- let {direction: currentDirection} = this.state;
-
- let iconName = currentDirection === 'right' ? 'angle-double-right collapse-double-icon' : 'angle-double-left collapse-double-icon';
-
- let awestyle = {padding: '5px'};
-
- if (!isOpen && initialDirection === 'right') {
- awestyle.marginLeft = '-1px';
- }
- return (
- <div className='slide-panel-header'>
- { initialDirection === 'left' && <span className='slide-panel-header-title'>{title}</span>}
- <FontAwesome
- ref='arrowIcon'
- style={awestyle}
- onClick={this.handleClick}
- className='pull-right'
- name={iconName}
- size='2x'/>
- { initialDirection === 'right' && <span className='slide-panel-header-title'>{title}</span>}
- </div>
- );
- }
-
- handleClick = () => {
- this.setState({
- isOpen: !this.state.isOpen,
- direction: this.state.direction === 'left' ? 'right' : 'left'
- });
- }
-
- setSliderPosition = () => {
-
- let el = ReactDOM.findDOMNode(this);
- let {style} = el;
-
- let {direction: initialDirection} = this.props;
- let arrowIconSize = Math.floor(ReactDOM.findDOMNode(this.refs.arrowIcon).getBoundingClientRect().width) * 2;
- if (!this.state.isOpen) {
- if (this.props.direction === 'left') {
- style.left = arrowIconSize - el.getBoundingClientRect().width + 'px';
- }
- if (initialDirection === 'right') {
- style.right = arrowIconSize - el.getBoundingClientRect().width + 'px';
- }
- }
- else {
- if (initialDirection === 'left') {
- style.left = '0px';
- }
-
- if (this.props.direction === 'right') {
- style.right = '0px';
- }
- }
- }
-
-}
-
-export default SlidePanel;
\ No newline at end of file
diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx b/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx
index 78525f8..6d900dd 100644
--- a/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx
+++ b/openecomp-ui/src/nfvo-components/panel/versionController/VersionController.jsx
@@ -1,17 +1,31 @@
+/*!
+ * 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 classnames from 'classnames';
import i18n from 'nfvo-utils/i18n/i18n.js';
-import Navbar from 'react-bootstrap/lib/Navbar.js';
-import Nav from 'react-bootstrap/lib/Nav.js';
-import ValidationInput from 'nfvo-components/input/validation/ValidationInput.jsx';
-import {actionsEnum, statusEnum} from './VersionControllerConstants.js';
+import {actionsEnum, statusEnum, statusBarTextMap } from './VersionControllerConstants.js';
+import SVGIcon from 'nfvo-components/icon/SVGIcon.jsx';
+import Tooltip from 'react-bootstrap/lib/Tooltip.js';
+import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger.js';
class VersionController extends React.Component {
static propTypes = {
- version: React.PropTypes.string,
+ version: React.PropTypes.object,
viewableVersions: React.PropTypes.array,
onVersionSwitching: React.PropTypes.func,
isCheckedOut: React.PropTypes.bool.isRequired,
@@ -23,143 +37,146 @@
};
render() {
- let {status, isCheckedOut, version = '', viewableVersions = [], onVersionSwitching, callVCAction, onSave, isFormDataValid, onClose} = this.props;
+ let {status, isCheckedOut, version = {}, viewableVersions = [], onVersionSwitching, callVCAction, onSave, isFormDataValid, onClose} = this.props;
let isCheckedIn = Boolean(status === statusEnum.CHECK_IN_STATUS);
- let isLatestVersion = Boolean(version === viewableVersions[viewableVersions.length - 1]);
+ let isLatestVersion = Boolean(version.id === viewableVersions[viewableVersions.length - 1].id);
if (!isLatestVersion) {
status = statusEnum.PREVIOUS_VERSION;
}
-
return (
<div className='version-controller-bar'>
- <Navbar inverse className='navbar'>
- <Navbar.Collapse>
- <Nav className='items-in-left'>
- <div className='version-section'>
- <ValidationInput
- type='select'
- selectedEnum={version}
- onEnumChange={value => onVersionSwitching && onVersionSwitching(value)}>
- {viewableVersions && viewableVersions.map(viewVersion => {
- return (
- <option key={viewVersion} value={viewVersion}>{`V ${viewVersion}`}</option>
- );
- })
- }
- {!viewableVersions.includes(version) &&
- <option key={version} value={version}>{`V ${version}`}</option>}
- </ValidationInput>
- </div>
- <div className='vc-status'>
- <div className='onboarding-status-icon'></div>
- <div className='status-text'> {i18n('ONBOARDING')}
- <div className='status-text-dash'> -</div>
- </div>
- {this.renderStatus(status)}
- </div>
- </Nav>
- <Nav pullRight>
- <div className='items-in-right'>
- <div className='action-buttons'>
- {callVCAction &&
- <div className='version-control-buttons'>
- <div
- className={classnames('vc-nav-item-button button-submit', {'disabled': !isCheckedIn || !isLatestVersion})}
- onClick={() => this.submit(callVCAction)}>
- {i18n('Submit')}
- </div>
- <div
- className={classnames('vc-nav-item-button button-checkin-checkout', {'disabled': status === statusEnum.LOCK_STATUS || !isLatestVersion})}
- onClick={() => this.checkinCheckoutVersion(callVCAction)}>
- {`${isCheckedOut ? i18n('Check In') : i18n('Check Out')}`}
- </div>
- <div
- className={classnames('sprite-new revert-btn ng-scope ng-isolate-scope', {'disabled': !isCheckedOut || version === '0.1' || !isLatestVersion})}
- onClick={() => this.revertCheckout(callVCAction)}>
- </div>
- </div>
- }
- {onSave &&
- <div
- className={classnames('sprite-new save-btn ng-scope ng-isolate-scope', {'disabled': !isCheckedOut || !isFormDataValid || !isLatestVersion})}
- onClick={() => onSave()}>
- </div>
- }
- </div>
- <div className='vc-nav-item-close' onClick={() => onClose && onClose()}> X</div>
- </div>
- </Nav>
- </Navbar.Collapse>
- </Navbar>
+ <div className='vc-container'>
+ <div className='version-status-container'>
+ <VersionSelector viewableVersions={viewableVersions} version={version} onVersionSwitching={onVersionSwitching} />
+ <StatusBarUpdates status={status}/>
+ </div>
+ <div className='save-submit-cancel-container'>
+ <ActionButtons onSubmit={callVCAction ? () => this.submit(callVCAction, version) : undefined}
+ onRevert={callVCAction ? () => this.revertCheckout(callVCAction, version) : undefined}
+ status={status}
+ onCheckinCheckout={callVCAction ? () => this.checkinCheckoutVersion(callVCAction, version) : undefined}
+ onSave={onSave ? () => onSave() : undefined}
+ isLatestVersion={isLatestVersion}
+ isCheckedOut={isCheckedOut}
+ isCheckedIn={isCheckedIn} isFormDataValid={isFormDataValid} version={version}/>
+ {onClose && <div className='vc-nav-item-close' onClick={() => onClose()} data-test-id='vc-cancel-btn'> X</div>}
+ </div>
+ </div>
</div>
);
}
- renderStatus(status) {
- switch (status) {
- case statusEnum.CHECK_OUT_STATUS:
- return (
- <div className='checkout-status-icon'>
- <div className='catalog-tile-check-in-status sprite-new checkout-editable-status-icon'></div>
- <div className='status-text'> {i18n('CHECKED OUT')} </div>
- </div>
- );
- case statusEnum.LOCK_STATUS:
- return (
- <div className='status-text'> {i18n('LOCKED')} </div>
- );
- case statusEnum.CHECK_IN_STATUS:
- return (
- <div className='status-text'> {i18n('CHECKED IN')} </div>
- );
- case statusEnum.SUBMIT_STATUS:
- return (
- <div className='status-text'> {i18n('SUBMITTED')} </div>
- );
- default:
- return (
- <div className='status-text'> {i18n(status)} </div>
- );
- }
+ submit(callVCAction, version) {
+ const action = actionsEnum.SUBMIT;
+ callVCAction(action, version);
}
- checkinCheckoutVersion(callVCAction) {
+ revertCheckout(callVCAction, version) {
+ const action = actionsEnum.UNDO_CHECK_OUT;
+ callVCAction(action, version);
+ }
+
+ checkinCheckoutVersion(callVCAction, version) {
if (this.props.isCheckedOut) {
- this.checkin(callVCAction);
+ this.checkin(callVCAction, version);
}
else {
- this.checkout(callVCAction);
+ this.checkout(callVCAction, version);
}
}
-
- checkin(callVCAction) {
-
+ checkin(callVCAction, version) {
const action = actionsEnum.CHECK_IN;
-
if (this.props.onSave) {
this.props.onSave().then(()=>{
- callVCAction(action);
- });
+ callVCAction(action, version);
+ });
}else{
- callVCAction(action);
+ callVCAction(action, version);
}
}
-
- checkout(callVCAction) {
+ checkout(callVCAction, version) {
const action = actionsEnum.CHECK_OUT;
- callVCAction(action);
+ callVCAction(action, version);
}
+}
- submit(callVCAction) {
- const action = actionsEnum.SUBMIT;
- callVCAction(action);
+class ActionButtons extends React.Component {
+ static propTypes = {
+ version: React.PropTypes.object,
+ onSubmit: React.PropTypes.func,
+ onRevert: React.PropTypes.func,
+ onSave: React.PropTypes.func,
+ isLatestVersion: React.PropTypes.bool,
+ isCheckedIn: React.PropTypes.bool,
+ isCheckedOut: React.PropTypes.bool,
+ isFormDataValid: React.PropTypes.bool
+ };
+ render() {
+ const {onSubmit, onRevert, onSave, isLatestVersion, isCheckedIn, isCheckedOut, isFormDataValid, version, status, onCheckinCheckout} = this.props;
+ const [checkinBtnIconSvg, checkinCheckoutBtnTitle] = status === statusEnum.CHECK_OUT_STATUS ?
+ ['version-controller-lock-open', i18n('Check In')] :
+ ['version-controller-lock-closed', i18n('Check Out')];
+ const disabled = (isLatestVersion && onCheckinCheckout && status !== statusEnum.LOCK_STATUS) ? false : true;
+ return (
+ <div className='action-buttons'>
+ <VCButton dataTestId='vc-checkout-btn' onClick={onCheckinCheckout} isDisabled={disabled}
+ name={checkinBtnIconSvg} tooltipText={checkinCheckoutBtnTitle}/>
+ {onSubmit && onRevert &&
+ <div className='version-control-buttons'>
+ <VCButton dataTestId='vc-submit-btn' onClick={onSubmit} isDisabled={!isCheckedIn || !isLatestVersion}
+ name='version-controller-submit' tooltipText={i18n('Submit')}/>
+ <VCButton dataTestId='vc-revert-btn' onClick={onRevert} isDisabled={!isCheckedOut || version.label === '0.1' || !isLatestVersion}
+ name='version-controller-revert' tooltipText={i18n('Revert')}/>
+ </div>
+ }
+ {onSave &&
+ <VCButton dataTestId='vc-save-btn' onClick={() => onSave()} isDisabled={!isCheckedOut || !isFormDataValid || !isLatestVersion}
+ name='version-controller-save' tooltipText={i18n('Save')}/>
+ }
+ </div>
+ );
}
+}
- revertCheckout(callVCAction) {
- const action = actionsEnum.UNDO_CHECK_OUT;
- callVCAction(action);
- }
+function StatusBarUpdates({status}) {
+ return (
+ <div className='vc-status'>
+ <span className='status-text'>{i18n(statusBarTextMap[status])}</span>
+ </div>
+ );
+}
+
+function VCButton({name, tooltipText, isDisabled, onClick, dataTestId}) {
+ let onClickAction = isDisabled ? ()=>{} : onClick;
+ let disabled = isDisabled ? 'disabled' : '';
+
+ return (
+ <OverlayTrigger placement='top' overlay={<Tooltip id='vc-tooltip'>{tooltipText}</Tooltip>}>
+ <div disabled={disabled} className='action-buttons-svg'>
+ <SVGIcon data-test-id={dataTestId} iconClassName={disabled} onClick={onClickAction ? onClickAction : undefined} name={name}/>
+ </div>
+ </OverlayTrigger>
+ );
+}
+
+function VersionSelector(props) {
+ let {version = {}, viewableVersions = [], onVersionSwitching} = props;
+ const includedVersions = viewableVersions.filter(ver => {return ver.id === version.id;});
+ return (<div className='version-section-wrapper'>
+ <select className='version-selector'
+ onChange={ev => onVersionSwitching && onVersionSwitching({id: ev.target.value, label: ev.target.value})}
+ value={version.label}>
+ {viewableVersions && viewableVersions.map(viewVersion => {
+ return (
+ <option key={viewVersion.id} value={viewVersion.id} data-test-id='vc-version-option'>{`V ${viewVersion.label}`}</option>
+ );
+ })
+ }
+ {!includedVersions.length &&
+ <option key={version.id} value={version.id}>{`V ${version.label}`}</option>}
+ </select>
+ </div>);
}
export default VersionController;
diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js
index 9251fd1..9af1424 100644
--- a/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js
+++ b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerConstants.js
@@ -1,23 +1,18 @@
-/*-
- * ============LICENSE_START=======================================================
- * SDC
- * ================================================================================
+/*!
* 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
- *
+ *
+ * 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.
- * ============LICENSE_END=========================================================
+ * 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 keyMirror from 'nfvo-utils/KeyMirror.js';
export const actionsEnum = keyMirror({
@@ -36,3 +31,11 @@
PREVIOUS_VERSION: 'READ ONLY'
});
+export const statusBarTextMap = keyMirror({
+ 'Locked': 'Checked Out',
+ 'LockedByUser': '',
+ 'Available': 'Checked In',
+ 'Final': 'Submitted',
+ 'READ ONLY': 'Locked'
+});
+
diff --git a/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerUtils.js b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerUtils.js
index de99144..e8c12ab 100644
--- a/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerUtils.js
+++ b/openecomp-ui/src/nfvo-components/panel/versionController/VersionControllerUtils.js
@@ -1,23 +1,18 @@
-/*-
- * ============LICENSE_START=======================================================
- * SDC
- * ================================================================================
+/*!
* 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
- *
+ *
+ * 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.
- * ============LICENSE_END=========================================================
+ * 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 Configuration from 'sdc-app/config/Configuration.js';
import {statusEnum} from './VersionControllerConstants.js';
@@ -25,24 +20,32 @@
const VersionControllerUtils = {
getCheckOutStatusKindByUserID(status, lockingUser) {
- let currentLoginUserID = Configuration.get('ATTUserID');
- let isCheckedOut = currentLoginUserID === lockingUser;
+ let returnStatus;
+ let isCheckedOut;
+ let currentLoginUserID = Configuration.get('UserID');
+ if (lockingUser) {
+ isCheckedOut = currentLoginUserID === lockingUser;
+ returnStatus = isCheckedOut ? status : statusEnum.LOCK_STATUS;
+ } else {
+ isCheckedOut = false;
+ returnStatus = status;
+ }
return {
- status: isCheckedOut ? status : statusEnum.LOCK_STATUS,
+ status: returnStatus,
isCheckedOut
};
},
isCheckedOutByCurrentUser(resource) {
- let currentLoginUserID = Configuration.get('ATTUserID');
+ let currentLoginUserID = Configuration.get('UserID');
return resource.lockingUser !== undefined && resource.lockingUser === currentLoginUserID;
},
isReadOnly(resource) {
const {version, viewableVersions = []} = resource;
const latestVersion = viewableVersions[viewableVersions.length - 1];
- return version !== latestVersion || !VersionControllerUtils.isCheckedOutByCurrentUser(resource);
+ return version.id !== latestVersion.id || !VersionControllerUtils.isCheckedOutByCurrentUser(resource);
}
};
diff --git a/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.jsx b/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.jsx
index d786aee..40720c3 100644
--- a/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.jsx
+++ b/openecomp-ui/src/nfvo-components/progressBar/ProgressBar.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.
+ */
import React from 'react';
class ProgressBar extends React.Component {
diff --git a/openecomp-ui/src/nfvo-components/table/SelectActionTable.jsx b/openecomp-ui/src/nfvo-components/table/SelectActionTable.jsx
new file mode 100644
index 0000000..06cb98b
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/table/SelectActionTable.jsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import i18n from 'nfvo-utils/i18n/i18n.js';
+import SVGIcon from 'nfvo-components/icon/SVGIcon.jsx';
+import uuid from 'uuid-js';
+
+export default class SelectActionTable extends React.Component {
+
+ render() {
+ let {columns, onAdd, isReadOnlyMode, children, onAddItem} = this.props;
+ return (
+ <div className={`select-action-table-view ${isReadOnlyMode ? 'disabled' : ''}`}>
+ <div className='select-action-table-controllers'>
+ {onAdd && onAddItem && <div data-test-id='select-action-table-add' onClick={onAdd}>{onAddItem}</div>}
+ <SVGIcon name='trash-o' className='dummy-icon' />
+ </div>
+ <div className='select-action-table'>
+ <div className='select-action-table-headers'>
+ {columns.map(column => <div key={uuid.create()} className='select-action-table-header'>{i18n(column)}</div>)}
+ <SVGIcon name='trash-o' className='dummy-icon' />
+ <SVGIcon name='trash-o' className='dummy-icon' />
+ </div>
+ <div className='select-action-table-body'>
+ {children}
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/openecomp-ui/src/nfvo-components/table/SelectActionTableCell.jsx b/openecomp-ui/src/nfvo-components/table/SelectActionTableCell.jsx
new file mode 100644
index 0000000..2664c8e
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/table/SelectActionTableCell.jsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import SelectInput from 'nfvo-components/input/SelectInput.jsx';
+
+const SelectActionTableCell = ({options, selected, disabled, onChange, clearable = true, placeholder}) => {
+ return (
+ <div className='select-action-table-cell'>
+ <SelectInput
+ placeholder={placeholder}
+ type='select'
+ value={selected}
+ data-test-id='select-action-table-dropdown'
+ disabled={disabled}
+ onChange={option => onChange(option ? option.value : null)}
+ clearable={clearable}
+ options={options} />
+ </div>
+ );
+};
+
+export default SelectActionTableCell;
diff --git a/openecomp-ui/src/nfvo-components/table/SelectActionTableRow.jsx b/openecomp-ui/src/nfvo-components/table/SelectActionTableRow.jsx
new file mode 100644
index 0000000..17d8a17
--- /dev/null
+++ b/openecomp-ui/src/nfvo-components/table/SelectActionTableRow.jsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import SVGIcon from '../icon/SVGIcon.jsx';
+import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger.js';
+import Tooltip from 'react-bootstrap/lib/Tooltip.js';
+
+function tooltip (msg) {
+ return (
+ <Tooltip className='select-action-table-error-tooltip' id='error-tooltip'>{msg}</Tooltip>
+ );
+};
+
+const IconWithOverlay = ({overlayMsg}) => (
+ <OverlayTrigger placement='bottom' overlay={tooltip(overlayMsg)}>
+ <SVGIcon name='error-circle'/>
+ </OverlayTrigger>
+);
+
+const SelectActionTableRow = ({children, onDelete, hasError, overlayMsg}) => (
+ <div className='select-action-table-row-wrapper'>
+ <div className={`select-action-table-row ${hasError ? 'has-error' : ''}`}>
+ {children}
+ </div>
+ {onDelete ? <SVGIcon name='trash-o' data-test-id='select-action-table-delete' onClick={onDelete} /> : <SVGIcon name='angle-left' className='dummy-icon' />}
+ {hasError ? overlayMsg ? <IconWithOverlay overlayMsg={overlayMsg}/> : <SVGIcon name='error-circle'/>
+ : hasError === undefined ? <SVGIcon name='angle-left' className='dummy-icon'/> : <SVGIcon name='check-circle'/>}
+
+ </div>
+);
+
+export default SelectActionTableRow;