diff --git a/app/javascript/forms/form-buttons-redux.jsx b/app/javascript/forms/form-buttons-redux.jsx
new file mode 100644
index 00000000000..549f2c0db22
--- /dev/null
+++ b/app/javascript/forms/form-buttons-redux.jsx
@@ -0,0 +1,79 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { combineReducers } from 'redux';
+
+import FormButtons from './form-buttons';
+
+function FormButtonsRedux(props) {
+ initReducer();
+
+ return ;
+}
+
+FormButtonsRedux.propTypes = {
+ callbackOverrides: PropTypes.object,
+};
+
+FormButtonsRedux.defaultProps = {
+ callbackOverrides: {},
+};
+
+
+function mapStateToProps(state, ownProps) {
+ let props = {...state.FormButtons};
+
+ if (state.FormButtons && ownProps.callbackOverrides) {
+ // allow overriding click handlers
+ // the original handler is passed as the only argument to the new one
+ Object.keys(ownProps.callbackOverrides).forEach((name) => {
+ props[name] = () => {
+ let origHandler = state.FormButtons[name];
+ ownProps.callbackOverrides[name](origHandler);
+ };
+ });
+ }
+
+ return props;
+}
+
+export default connect(mapStateToProps)(FormButtonsRedux);
+
+function initReducer() {
+ if (! ManageIQ.redux || ! ManageIQ.redux.addReducer) {
+ // login screen
+ return;
+ }
+ if (initReducer.done) {
+ // don't init twice
+ return;
+ }
+ initReducer.done = true;
+
+
+ const initialState = {...FormButtons.defaultProps};
+
+ const actions = {
+ 'FormButtons.init': (_state, payload) => ({ ...initialState, ...payload }),
+ 'FormButtons.customLabel': (state, payload) => ({ ...state, customLabel: payload || ''}),
+ 'FormButtons.newRecord': (state, payload) => ({ ...state, newRecord: !! payload }),
+ 'FormButtons.pristine': (state, payload) => ({ ...state, pristine: !! payload }),
+ 'FormButtons.saveable': (state, payload) => ({ ...state, saveable: !! payload }),
+
+ 'FormButtons.callbacks': (state, payload) => ({ ...state, ...payload }),
+ };
+
+
+ ManageIQ.redux.addReducer(combineReducers({
+ FormButtons: function FormButtonsReducer(state = initialState, action) {
+
+ if (actions[action.type]) {
+ return actions[action.type](state, action.payload);
+ } else if (action.type.match(/^FormButtons\./)) {
+ console.warn(`FormButtonsReducer - unknown action type: ${action.type}`, action);
+ }
+
+ return state;
+ },
+ }));
+}
diff --git a/app/javascript/forms/form-buttons.jsx b/app/javascript/forms/form-buttons.jsx
new file mode 100644
index 00000000000..f2e3c0109a5
--- /dev/null
+++ b/app/javascript/forms/form-buttons.jsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import MiqButton from './miq-button';
+
+function FormButtons(props) {
+ let primaryTitle = props.customLabel || (props.newRecord ? __('Add') : __('Save'));
+ let primaryHandler = (props.newRecord ? props.addClicked : props.saveClicked) || props.addClicked || props.saveClicked;
+
+ let resetTitle = __("Reset");
+ let cancelTitle = __("Cancel");
+
+ return (
+
+
+
+
+ {props.newRecord || }
+
+
+
+
+ );
+}
+
+FormButtons.propTypes = {
+ newRecord: PropTypes.bool,
+ customLabel: PropTypes.string,
+ saveable: PropTypes.bool,
+ pristine: PropTypes.bool,
+ addClicked: PropTypes.func,
+ saveClicked: PropTypes.func,
+ resetClicked: PropTypes.func,
+ cancelClicked: PropTypes.func,
+};
+
+const noop = () => null;
+
+FormButtons.defaultProps = {
+ newRecord: false,
+ customLabel: '',
+ saveable: false,
+ pristine: true,
+ addClicked: noop,
+ saveClicked: noop,
+ resetClicked: noop,
+ cancelClicked: noop,
+}
+
+export default FormButtons;
diff --git a/app/javascript/forms/miq-button.jsx b/app/javascript/forms/miq-button.jsx
new file mode 100644
index 00000000000..521cddba803
--- /dev/null
+++ b/app/javascript/forms/miq-button.jsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ClassNames from 'classnames';
+
+function MiqButton(props) {
+ let title = props.title;
+ if (props.enabled && props.enabledTitle) {
+ title = props.enabledTitle;
+ }
+ if (! props.enabled && props.disabledTitle) {
+ title = props.disabledTitle;
+ }
+
+ let klass = ClassNames({
+ 'btn': true,
+ 'btn-xs': props.xs,
+ 'btn-primary': props.primary,
+ 'btn-default': !props.primary,
+ 'disabled': !props.enabled,
+ });
+
+ let buttonClicked = (event) => {
+ if (props.enabled) {
+ props.onClick();
+ }
+
+ event.preventDefault();
+ event.target.blur();
+ };
+
+ return (
+
+ );
+}
+
+MiqButton.propTypes = {
+ name: PropTypes.string.isRequired,
+ enabled: PropTypes.bool.isRequired,
+ title: PropTypes.string,
+ enabledTitle: PropTypes.string,
+ disabledTitle: PropTypes.string,
+ primary: PropTypes.bool,
+ xs: PropTypes.bool,
+ onClick: PropTypes.func.isRequired,
+};
+
+MiqButton.defaultProps = {
+ title: '',
+ enabledTitle: '',
+ disabledTitle: '',
+ primary: false,
+ xs: false,
+};
+
+export default MiqButton;
diff --git a/app/javascript/packs/component-definitions-common.js b/app/javascript/packs/component-definitions-common.js
index c1a787032f8..a7b816a5b9f 100644
--- a/app/javascript/packs/component-definitions-common.js
+++ b/app/javascript/packs/component-definitions-common.js
@@ -5,6 +5,7 @@ import TableListViewWrapper from '../react/table_list_view_wrapper';
import GenericGroupWrapper from '../react/generic_group_wrapper';
import { addReact } from '../miq-component/helpers';
import VmSnapshotFormComponent from '../components/vm-snapshot-form-component';
+import FormButtonsRedux from '../forms/form-buttons-redux';
/**
* Add component definitions to this file.
@@ -19,3 +20,4 @@ addReact('GenericGroup', GenericGroup);
addReact('GenericGroupWrapper', GenericGroupWrapper);
addReact('TextualSummaryWrapper', TextualSummaryWrapper);
addReact('VmSnapshotFormComponent', VmSnapshotFormComponent);
+addReact('FormButtonsRedux', FormButtonsRedux);
diff --git a/app/javascript/provider-dialogs/modal.js b/app/javascript/provider-dialogs/modal.js
index 570c3dbc1c8..b6a0d42420b 100644
--- a/app/javascript/provider-dialogs/modal.js
+++ b/app/javascript/provider-dialogs/modal.js
@@ -1,6 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Button, Icon, Modal } from 'patternfly-react';
+import { Provider } from 'react-redux';
+import FormButtonsRedux from '../forms/form-buttons-redux';
function closeModal(id) {
// this should have been div.remove();
@@ -25,36 +27,45 @@ export default function renderModal(title = __("Modal"), Inner = () =>
Empt
}
function modal(title, Inner, closed, removeId) {
+ const overrides = {
+ addClicked: function(orig) {
+ Promise.resolve(orig()).then(closed);
+ },
+ saveClicked: function(orig) {
+ Promise.resolve(orig()).then(closed);
+ },
+ cancelClicked: function(orig) {
+ Promise.resolve(orig()).then(closed);
+ },
+ // don't close on reset
+ };
+
return (
-
-
-
- {title}
-
-
-
-
-
-
-
-
-
+
+
+
+
+ {title}
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/package.json b/package.json
index e230110ed12..e9fe278f918 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"angular.validators": "~4.4.2",
"base64-js": "~1.2.3",
"bootstrap-switch": "~3.3.4",
+ "classnames": "^2.2.5",
"graphiql": "^0.11.11",
"graphql": "^0.13.2",
"jquery": "~2.2.4",