-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Codebase Improvement] Refine dialog wrapper and use it for all exist…
…ing dialogs (#3407)
- Loading branch information
1 parent
29326f3
commit 23f5dde
Showing
14 changed files
with
360 additions
and
266 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.