Skip to content

Commit

Permalink
[Codebase Improvement] Refine dialog wrapper and use it for all exist…
Browse files Browse the repository at this point in the history
…ing dialogs (#3407)
  • Loading branch information
kravets-levko authored Feb 8, 2019
1 parent 29326f3 commit 23f5dde
Show file tree
Hide file tree
Showing 14 changed files with 360 additions and 266 deletions.
209 changes: 209 additions & 0 deletions client/app/components/DialogWrapper.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { isFunction } from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';

/**
Wrapper for dialogs based on Ant's <Modal> component.
Using wrapped dialogs
=====================
Wrapped component is an object with two fields:
{
showModal: (dialogProps) => object({
result: Promise,
close: (result) => void,
dismiss: (reason) => void,
}),
Component: React.Component, // wrapped dialog component
}
To open dialog, use `showModal` method; optionally you can pass additional properties that
will be expanded on wrapped component:
const dialog = SomeWrappedDialog.showModal()
const dialog = SomeWrappedDialog.showModal({ greeting: 'Hello' })
To get result of modal, use `result` property:
dialog.result
.then(...) // pressed OK button or used `close` method; resolved value is a result of dialog
.catch(...) // pressed Cancel button or used `dismiss` method; optional argument is a rejection reason.
Also, dialog has `close` and `dismiss` methods that allows to close dialog by caller. Passed arguments
will be used to resolve/reject `dialog.result` promise. `update` methods allows to pass new properties
to dialog.
Creating a dialog
================
1. Add imports:
import { wrap as wrapDialog, DialogPropType } from 'path/to/DialogWrapper';
2. define a `dialog` property on your component:
propTypes = {
dialog: DialogPropType.isRequired,
};
`dialog` property is an object:
{
props: object, // properties for <Modal> component;
close: (result) => void, // method to confirm dialog; `result` will be returned to caller
dismiss: (reason) => void, // method to reject dialog; `reason` will be returned to caller
}
3. expand additional properties on <Modal> component:
render() {
const { dialog } = this.props;
return (
<Modal {...dialog.props}>
);
}
4. wrap your component and export it:
export default wrapDialog(YourComponent).
Your component is ready to use. Wrapper will manage <Modal>'s visibility and events.
If you want to override behavior of `onOk`/`onCancel` - don't forget to close dialog:
customOkHandler() {
this.saveData().then(() => {
this.props.dialog.close({ success: true }); // or dismiss();
});
}
render() {
const { dialog } = this.props;
return (
<Modal {...dialog.props} onOk={() => this.customOkHandler()}>
);
}
Settings
========
You can setup this wrapper to use custom `Promise` library (for example, Bluebird):
import DialogWrapper from 'path/to/DialogWrapper';
import Promise from 'bluebird';
DialogWrapper.Promise = Promise;
It could be useful to avoid `unhandledrejection` exception that would fire with native Promises,
or when some custom Promise library is used in application.
*/

export const DialogPropType = PropTypes.shape({
props: PropTypes.shape({
visible: PropTypes.bool,
onOk: PropTypes.func,
onCancel: PropTypes.func,
afterClose: PropTypes.func,
}).isRequired,
close: PropTypes.func.isRequired,
dismiss: PropTypes.func.isRequired,
});

// default export of module
const DialogWrapper = {
Promise,
DialogPropType,
wrap() {},
};

function openDialog(DialogComponent, props) {
const dialog = {
props: {
visible: true,
onOk: () => {},
onCancel: () => {},
afterClose: () => {},
},
close: () => {},
dismiss: () => {},
};

const dialogResult = {
resolve: () => {},
reject: () => {},
};

const container = document.createElement('div');
document.body.appendChild(container);

function render() {
ReactDOM.render(<DialogComponent {...props} dialog={dialog} />, container);
}

function destroyDialog() {
// Allow calling chain to roll up, and then destroy component
setTimeout(() => {
ReactDOM.unmountComponentAtNode(container);
document.body.removeChild(container);
}, 10);
}

function closeDialog(result) {
dialogResult.resolve(result);
dialog.props.visible = false;
render();
}

function dismissDialog(reason) {
dialogResult.reject(reason);
dialog.props.visible = false;
render();
}

dialog.props.onOk = closeDialog;
dialog.props.onCancel = dismissDialog;
dialog.props.afterClose = destroyDialog;
dialog.close = closeDialog;
dialog.dismiss = dismissDialog;

const result = {
close: closeDialog,
dismiss: dismissDialog,
update: (newProps) => {
props = { ...props, ...newProps };
render();
},
result: new DialogWrapper.Promise((resolve, reject) => {
dialogResult.resolve = resolve;
dialogResult.reject = reject;
}),
};

render(); // show it only when all structures initialized to avoid unnecessary re-rendering

// Some known libraries support
// Bluebird: http://bluebirdjs.com/docs/api/suppressunhandledrejections.html
if (isFunction(result.result.suppressUnhandledRejections)) {
result.result.suppressUnhandledRejections();
}

return result;
}

export function wrap(DialogComponent) {
return {
Component: DialogComponent,
showModal: props => openDialog(DialogComponent, props),
};
}

DialogWrapper.wrap = wrap;

export default DialogWrapper;
Loading

0 comments on commit 23f5dde

Please sign in to comment.