You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AbstractDialog.js 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // @flow
  2. import PropTypes from 'prop-types';
  3. import { Component } from 'react';
  4. import { hideDialog } from '../actions';
  5. import { DIALOG_PROP_TYPES } from '../constants';
  6. /**
  7. * An abstract implementation of a dialog on Web/React and mobile/react-native.
  8. */
  9. export default class AbstractDialog extends Component<*, *> {
  10. /**
  11. * {@code AbstractDialog} React {@code Component}'s prop types.
  12. *
  13. * @static
  14. */
  15. static propTypes = {
  16. ...DIALOG_PROP_TYPES,
  17. /**
  18. * The React {@code Component} children of {@code AbstractDialog}
  19. * which represents the dialog's body.
  20. */
  21. children: PropTypes.node,
  22. /**
  23. * Used to show/hide the dialog on cancel.
  24. */
  25. dispatch: PropTypes.func
  26. };
  27. _mounted: boolean;
  28. state = {
  29. };
  30. /**
  31. * Initializes a new {@code AbstractDialog} instance.
  32. *
  33. * @param {Object} props - The read-only React {@code Component} props with
  34. * which the new instance is to be initialized.
  35. */
  36. constructor(props: Object) {
  37. super(props);
  38. // Bind event handlers so they are only bound once per instance.
  39. this._onCancel = this._onCancel.bind(this);
  40. this._onSubmit = this._onSubmit.bind(this);
  41. this._onSubmitFulfilled = this._onSubmitFulfilled.bind(this);
  42. this._onSubmitRejected = this._onSubmitRejected.bind(this);
  43. }
  44. /**
  45. * Implements React's {@link Component#componentWillMount()}. Invoked
  46. * immediately before mounting occurs.
  47. *
  48. * @inheritdoc
  49. */
  50. componentWillMount() {
  51. this._mounted = true;
  52. }
  53. /**
  54. * Implements React's {@link Component#componentWillUnmount()}. Invoked
  55. * immediately before this component is unmounted and destroyed.
  56. *
  57. * @inheritdoc
  58. */
  59. componentWillUnmount() {
  60. this._mounted = false;
  61. }
  62. /**
  63. * Dispatches a redux action to hide this dialog.
  64. *
  65. * @returns {*} The return value of {@link hideDialog}.
  66. */
  67. _hide() {
  68. return this.props.dispatch(hideDialog());
  69. }
  70. _onCancel: () => void;
  71. /**
  72. * Dispatches a redux action to hide this dialog when it's canceled.
  73. *
  74. * @protected
  75. * @returns {void}
  76. */
  77. _onCancel() {
  78. const { cancelDisabled, onCancel } = this.props;
  79. if ((typeof cancelDisabled === 'undefined' || !cancelDisabled)
  80. && (!onCancel || onCancel())) {
  81. this._hide();
  82. }
  83. }
  84. _onSubmit: (?string) => void;
  85. /**
  86. * Submits this {@code Dialog}. If the React {@code Component} prop
  87. * {@code onSubmit} is defined, the function that is the value of the prop
  88. * is invoked. If the function returns a {@code thenable}, then the
  89. * resolution of the {@code thenable} is awaited. If the submission
  90. * completes successfully, a redux action will be dispatched to hide this
  91. * dialog.
  92. *
  93. * @protected
  94. * @param {string} [value] - The submitted value if any.
  95. * @returns {void}
  96. */
  97. _onSubmit(value: ?string) {
  98. const { okDisabled, onSubmit } = this.props;
  99. if (typeof okDisabled === 'undefined' || !okDisabled) {
  100. this.setState({ submitting: true });
  101. // Invoke the React Compnent prop onSubmit if any.
  102. const r = !onSubmit || onSubmit(value);
  103. // If the invocation returns a thenable, await its resolution;
  104. // otherwise, treat the return value as a boolean indicating whether
  105. // the submission has completed successfully.
  106. let then;
  107. if (r) {
  108. switch (typeof r) {
  109. case 'function':
  110. case 'object':
  111. then = r.then;
  112. break;
  113. }
  114. }
  115. if (typeof then === 'function' && then.length === 2) {
  116. then.call(r, this._onSubmitFulfilled, this._onSubmitRejected);
  117. } else if (r) {
  118. this._onSubmitFulfilled();
  119. } else {
  120. this._onSubmitRejected();
  121. }
  122. }
  123. }
  124. _onSubmitFulfilled: () => void;
  125. /**
  126. * Notifies this {@code AbstractDialog} that it has been submitted
  127. * successfully. Dispatches a redux action to hide this dialog after it has
  128. * been submitted.
  129. *
  130. * @private
  131. * @returns {void}
  132. */
  133. _onSubmitFulfilled() {
  134. this._mounted && this.setState({ submitting: false });
  135. this._hide();
  136. }
  137. _onSubmitRejected: () => void;
  138. /**
  139. * Notifies this {@code AbstractDialog} that its submission has failed.
  140. *
  141. * @private
  142. * @returns {void}
  143. */
  144. _onSubmitRejected() {
  145. this._mounted && this.setState({ submitting: false });
  146. }
  147. }