From a53cc5ee2d01fbf26d9cc5f7dfc4b0659da992c9 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Fri, 2 Mar 2018 12:42:23 +0000 Subject: [PATCH 1/7] Add ClassNames - similar to ng-class but for react --- package.json | 1 + 1 file changed, 1 insertion(+) 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", From 4c0b2b7e0db5385ed41aa0daa218ab31bebb2824 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Fri, 2 Mar 2018 12:43:57 +0000 Subject: [PATCH 2/7] MiqButton - convert angular miqButton to react should be a 1:1 match to the existing miqButton component meant for new react form buttons --- app/javascript/forms/miq-button.jsx | 60 +++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 app/javascript/forms/miq-button.jsx 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; From 8d52cbf18bcadda1e3c697856e4dff0c6c04806c Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Fri, 2 Mar 2018 13:12:28 +0000 Subject: [PATCH 3/7] FormButtons - a replacement for generic_form_buttons in angular this is just a dumb presentation-only component it will be wrapped in another one that listens for messages from the form and updates it accordingly --- app/javascript/forms/form-buttons.jsx | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 app/javascript/forms/form-buttons.jsx 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; From 7613cf93937d509b32d429e4dd2b85d944cd6f4c Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Thu, 19 Jul 2018 12:21:31 +0000 Subject: [PATCH 4/7] FormButtonsRedux - redux wrapper for form buttons reset form buttons to initial state ManageIQ.redux.store.dispatch({ type: 'FormButtons.init', payload: {} }) change the default Add/Save label ManageIQ.redux.store.dispatch({ type: 'FormButtons.customLabel', payload: __('Magic') }) set newRecord - if true, the Add button is shown (and addClicked called), else the Save and Reset buttons are shown (and saveClicked, resetClicked called) ManageIQ.redux.store.dispatch({ type: 'FormButtons.newRecord', payload: true }) set pristine - pristine form can't be reset (and shouldn't be saveable) ManageIQ.redux.store.dispatch({ type: 'FormButtons.pristine', payload: false }) set saveable - means no validation error, the submit button will be enabled based on this ManageIQ.redux.store.dispatch({ type: 'FormButtons.saveable', payload: false }) set callbacks - an object of functions - addClicked, saveClicked, resetClicked and cancelClicked are accepted ManageIQ.redux.store.dispatch({ type: 'FormButtons.callbacks', payload: { addClicked: () => console.log('add'), cancelClicked: () => console.log('cancel'), resetClicked: () => console.log('reset'), saveClicked: () => console.log('save'), }}) --- app/javascript/forms/form-buttons-redux.jsx | 69 +++++++++++++++++++ .../packs/component-definitions-common.js | 2 + 2 files changed, 71 insertions(+) create mode 100644 app/javascript/forms/form-buttons-redux.jsx diff --git a/app/javascript/forms/form-buttons-redux.jsx b/app/javascript/forms/form-buttons-redux.jsx new file mode 100644 index 00000000000..e5f48a6b403 --- /dev/null +++ b/app/javascript/forms/form-buttons-redux.jsx @@ -0,0 +1,69 @@ +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 = { +}; + +FormButtonsRedux.defaultProps = { +}; + + +function mapStateToProps(state) { + return {...state.FormButtons}; +} + +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 = { + customLabel: '', + newRecord: false, + pristine: false, + saveable: true, + }; + + 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/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); From 4ce2158e16b42e1b277a368a32c73a0c9160d923 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Thu, 19 Jul 2018 14:29:35 +0000 Subject: [PATCH 5/7] FormButtonRedux - add callbackOverrides, use in ProviderDialogs modal the idea here is, when using FormButtonRedux from a modal, you want to change the handlers to close the modal after so, you get to replace the handler, and get the original as an argument, to call from the replacement specifically in ProviderDialogs modal, we override save, add and cancel to close the window afterwards if the original handler returns a promise, closing will wait for that promise if the orignal handler returns anything else, closing is immediate --- app/javascript/forms/form-buttons-redux.jsx | 26 ++++++++++++++------- app/javascript/provider-dialogs/modal.js | 22 +++++++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/app/javascript/forms/form-buttons-redux.jsx b/app/javascript/forms/form-buttons-redux.jsx index e5f48a6b403..549f2c0db22 100644 --- a/app/javascript/forms/form-buttons-redux.jsx +++ b/app/javascript/forms/form-buttons-redux.jsx @@ -12,14 +12,29 @@ function FormButtonsRedux(props) { } FormButtonsRedux.propTypes = { + callbackOverrides: PropTypes.object, }; FormButtonsRedux.defaultProps = { + callbackOverrides: {}, }; -function mapStateToProps(state) { - return {...state.FormButtons}; +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); @@ -36,12 +51,7 @@ function initReducer() { initReducer.done = true; - const initialState = { - customLabel: '', - newRecord: false, - pristine: false, - saveable: true, - }; + const initialState = {...FormButtons.defaultProps}; const actions = { 'FormButtons.init': (_state, payload) => ({ ...initialState, ...payload }), diff --git a/app/javascript/provider-dialogs/modal.js b/app/javascript/provider-dialogs/modal.js index 570c3dbc1c8..ea8493b8fb4 100644 --- a/app/javascript/provider-dialogs/modal.js +++ b/app/javascript/provider-dialogs/modal.js @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Button, Icon, Modal } from 'patternfly-react'; +import FormButtonsRedux from '../forms/form-buttons-redux'; function closeModal(id) { // this should have been div.remove(); @@ -25,6 +26,19 @@ export default function renderModal(title = __("Modal"), Inner = () =>
Empt } function modal(title, Inner, closed, removeId) { + const overrides = { + addClicked: function(orig) { + Promise.when(orig()).then(closed); + }, + saveClicked: function(orig) { + Promise.when(orig()).then(closed); + }, + cancelClicked: function(orig) { + Promise.when(orig()).then(closed); + }, + // don't close on reset + }; + return (
- + ); From d65e18877bbc26715297b342007effb8e8200f22 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Thu, 19 Jul 2018 14:56:15 +0000 Subject: [PATCH 6/7] ProviderDialogs - wrap modal in Provider --- app/javascript/provider-dialogs/modal.js | 51 +++++++++++++----------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/app/javascript/provider-dialogs/modal.js b/app/javascript/provider-dialogs/modal.js index ea8493b8fb4..b4723021be4 100644 --- a/app/javascript/provider-dialogs/modal.js +++ b/app/javascript/provider-dialogs/modal.js @@ -1,6 +1,7 @@ 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) { @@ -40,29 +41,31 @@ function modal(title, Inner, closed, removeId) { }; return ( - - - - {title} - - - -
-
- - - -
+ + + + + {title} + + + +
+
+ + + +
+
); } From 034ad78972f10b602ed932deac1f2fe93b7887e8 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Thu, 19 Jul 2018 15:06:34 +0000 Subject: [PATCH 7/7] Fix Promise.when -> Promise.resolve --- app/javascript/provider-dialogs/modal.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/provider-dialogs/modal.js b/app/javascript/provider-dialogs/modal.js index b4723021be4..b6a0d42420b 100644 --- a/app/javascript/provider-dialogs/modal.js +++ b/app/javascript/provider-dialogs/modal.js @@ -29,13 +29,13 @@ export default function renderModal(title = __("Modal"), Inner = () =>
Empt function modal(title, Inner, closed, removeId) { const overrides = { addClicked: function(orig) { - Promise.when(orig()).then(closed); + Promise.resolve(orig()).then(closed); }, saveClicked: function(orig) { - Promise.when(orig()).then(closed); + Promise.resolve(orig()).then(closed); }, cancelClicked: function(orig) { - Promise.when(orig()).then(closed); + Promise.resolve(orig()).then(closed); }, // don't close on reset };