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",