Files
redash/client/app/components/DialogWrapper.jsx
Arik Fraimovich 56d3be2248 Prettier all the Javascript code & GitHub Action (#4433)
* Prettier all the JS files

* Add GitHub Action to autoformat code pushed to master

* Fix eslint violation due to formatting.

* Remove GitHub actions for styling

* Add restyled.io config
2019-12-11 17:05:38 +02:00

210 lines
5.3 KiB
JavaScript

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;