From 8050f496fa8768a80f92adc4431e3b8fdfab29fd Mon Sep 17 00:00:00 2001 From: Jorgen Evens Date: Tue, 27 Oct 2015 22:48:45 +0100 Subject: [PATCH 1/2] [Dialog] Switch from imperative control to props. - Remove .dismiss() and .show() - Remove onShow and onDismiss callback - Added onRequestClose callback - Removed modal property - Updated docs - Add default values for defaultIsOpen and isOpen. --- .../components/pages/components/dialog.jsx | 85 ++++++++++++------- .../app/components/raw-code/dialog-code.txt | 21 +++-- src/dialog.jsx | 72 ++++++++-------- 3 files changed, 108 insertions(+), 70 deletions(-) diff --git a/docs/src/app/components/pages/components/dialog.jsx b/docs/src/app/components/pages/components/dialog.jsx index 2ef62cc95b6f15..c083255c48359e 100644 --- a/docs/src/app/components/pages/components/dialog.jsx +++ b/docs/src/app/components/pages/components/dialog.jsx @@ -11,6 +11,9 @@ export default class DialogPage extends React.Component { super(); this.state = { modal: false, + showDialogStandardActions: false, + showDialogCustomActions: false, + showDialogScrollable: false, }; this._handleCustomDialogCancel = this._handleCustomDialogCancel.bind(this); this._handleCustomDialogSubmit = this._handleCustomDialogSubmit.bind(this); @@ -19,6 +22,7 @@ export default class DialogPage extends React.Component { this._handleCustomDialogTouchTap = this._handleCustomDialogTouchTap.bind(this); this._handleStandardDialogTouchTap = this._handleStandardDialogTouchTap.bind(this); this._handleScrollableDialogTouchTap = this._handleScrollableDialogTouchTap.bind(this); + this._handleRequestClose = this._handleRequestClose.bind(this); this._handleToggleChange = this._handleToggleChange.bind(this); } @@ -69,8 +73,20 @@ export default class DialogPage extends React.Component { name: 'openImmediately', type: 'bool', header: 'default: false', + desc: 'Deprecated: Set to true to have the dialog automatically open on mount.', + }, + { + name: 'defaultIsOpen', + type: 'bool', + header: 'default: false', desc: 'Set to true to have the dialog automatically open on mount.', }, + { + name: 'isOpen', + type: 'bool', + header: 'default: null', + desc: 'Controls whether the Dialog is opened or not.', + }, { name: 'title', type: 'node', @@ -95,16 +111,6 @@ export default class DialogPage extends React.Component { { name: 'Methods', infoArray: [ - { - name: 'dismiss', - header: 'Dialog.dismiss()', - desc: 'Hides the dialog.', - }, - { - name: 'show', - header: 'Dialog.show()', - desc: 'Shows the dialog.', - }, { name: 'isOpen', header: 'Dialog.isOpen()', @@ -116,14 +122,9 @@ export default class DialogPage extends React.Component { name: 'Events', infoArray: [ { - name: 'onDismiss', - header: 'function()', - desc: 'Fired when the dialog is dismissed.', - }, - { - name: 'onShow', - header: 'function()', - desc: 'Fired when the dialog is shown.', + name: 'onRequestClose', + header: 'function(buttonClicked)', + desc: 'Fired when the dialog is requested to be closed by a click outside the dialog or on the buttons.', }, ], }, @@ -175,7 +176,8 @@ export default class DialogPage extends React.Component { title="Dialog With Standard Actions" actions={standardActions} actionFocus="submit" - modal={this.state.modal}> + isOpen={this.state.showDialogStandardActions} + onRequestClose={this._handleRequestClose}> The actions in this window are created from the json that's passed in. @@ -183,7 +185,8 @@ export default class DialogPage extends React.Component { ref="customDialog" title="Dialog With Custom Actions" actions={customActions} - modal={this.state.modal}> + isOpen={this.state.showDialogCustomActions} + onRequestClose={this._handleRequestClose}> The actions in this window were passed in as an array of react objects.
@@ -196,9 +199,10 @@ export default class DialogPage extends React.Component { ref="scrollableContentDialog" title="Dialog With Scrollable Content" actions={scrollableCustomActions} - modal={this.state.modal} autoDetectWindowHeight={true} - autoScrollBodyContent={true}> + autoScrollBodyContent={true} + isOpen={this.state.showDialogScrollable} + onRequestClose={this._handleRequestClose}>
Really long content
@@ -214,11 +218,15 @@ export default class DialogPage extends React.Component { } _handleCustomDialogCancel() { - this.refs.customDialog.dismiss(); + this.setState({ + showDialogCustomActions: true, + }); } _handleCustomDialogSubmit() { - this.refs.customDialog.dismiss(); + this.setState({ + showDialogCustomActions: true, + }); } _handleToggleChange(e, toggled) { @@ -226,23 +234,42 @@ export default class DialogPage extends React.Component { } _handleScrollableDialogCancel() { - this.refs.scrollableContentDialog.dismiss(); + this.setState({ + showDialogScrollable: false, + }); } _handleScrollableDialogSubmit() { - this.refs.scrollableContentDialog.dismiss(); + this.setState({ + showDialogScrollable: false, + }); } _handleCustomDialogTouchTap() { - this.refs.customDialog.show(); + this.setState({ + showDialogScrollable: true, + }); } _handleStandardDialogTouchTap() { - this.refs.standardDialog.show(); + this.setState({ + showDialogStandardActions: true, + }); } _handleScrollableDialogTouchTap() { - this.refs.scrollableContentDialog.show(); + this.setState({ + showDialogScrollable: true, + }); + } + + _handleRequestClose(buttonClicked) { + if (!buttonClicked && this.state.modal) return; + this.setState({ + showDialogStandardActions: false, + showDialogCustomActions: false, + showDialogScrollable: false, + }); } } diff --git a/docs/src/app/components/raw-code/dialog-code.txt b/docs/src/app/components/raw-code/dialog-code.txt index ee89651d477e41..f55befb1450e41 100644 --- a/docs/src/app/components/raw-code/dialog-code.txt +++ b/docs/src/app/components/raw-code/dialog-code.txt @@ -8,7 +8,8 @@ let standardActions = [ title="Dialog With Standard Actions" actions={standardActions} actionFocus="submit" - modal={this.state.modal}> + isOpen={this.state.showDialogStandardActions} + onRequestClose={this._handleRequestClose}> The actions in this window are created from the json that's passed in. @@ -27,11 +28,19 @@ let customActions = [ + isOpen={this.state.showDialogCustomActions} + onRequestClose={this._handleRequestClose}> The actions in this window were passed in as an array of react objects. - -
Really long content
-
+ +
+ Really long content +
+
\ No newline at end of file diff --git a/src/dialog.jsx b/src/dialog.jsx index 77ee8650c49c27..e0823f6b2dcdfa 100644 --- a/src/dialog.jsx +++ b/src/dialog.jsx @@ -109,13 +109,12 @@ let Dialog = React.createClass({ bodyStyle: React.PropTypes.object, contentClassName: React.PropTypes.string, contentStyle: React.PropTypes.object, - modal: React.PropTypes.bool, openImmediately: React.PropTypes.bool, onClickAway: React.PropTypes.func, - onDismiss: React.PropTypes.func, - onShow: React.PropTypes.func, repositionOnUpdate: React.PropTypes.bool, title: React.PropTypes.node, + defaultIsOpen: React.PropTypes.bool, + isOpen: React.PropTypes.bool, }, windowListeners: { @@ -128,14 +127,20 @@ let Dialog = React.createClass({ autoDetectWindowHeight: false, autoScrollBodyContent: false, actions: [], - modal: false, repositionOnUpdate: true, + defaultIsOpen: false, + isOpen: null, }; }, getInitialState() { + let open = this.props.isOpen; + + if (open === null) + open = (this.props.openImmediately || this.props.defaultIsOpen); + return { - open: this.props.openImmediately || false, + open: open, muiTheme: this.context.muiTheme ? this.context.muiTheme : ThemeManager.getMuiTheme(DefaultRawTheme), }; }, @@ -145,13 +150,19 @@ let Dialog = React.createClass({ componentWillReceiveProps (nextProps, nextContext) { let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; this.setState({muiTheme: newMuiTheme}); + + if (nextProps.isOpen !== this.props.isOpen) { + if (nextProps.isOpen && !this.state.open) + this._show(); + else if (!nextProps.isOpen && this.state.open) + this._dismiss(); + } }, componentDidMount() { this._positionDialog(); - if (this.props.openImmediately) { + if (this.isOpen()) { this.refs.dialogOverlay.preventScrolling(); - this._onShow(); } }, @@ -265,20 +276,6 @@ let Dialog = React.createClass({ return this.state.open; }, - dismiss() { - CssEvent.onTransitionEnd(ReactDOM.findDOMNode(this), () => { - this.refs.dialogOverlay.allowScrolling(); - }); - - this.setState({ open: false }); - this._onDismiss(); - }, - - show() { - this.refs.dialogOverlay.preventScrolling(); - this.setState({ open: true }, this._onShow); - }, - _getAction(actionJSON, key) { let styles = {marginRight: 8}; let props = { @@ -290,7 +287,7 @@ let Dialog = React.createClass({ actionJSON.onTouchTap.call(undefined); } if (!(actionJSON.onClick || actionJSON.onTouchTap)) { - this.dismiss(); + this._requestClose(true); } }, label: actionJSON.text, @@ -379,27 +376,32 @@ let Dialog = React.createClass({ } }, - _onShow() { - if (this.props.onShow) this.props.onShow(); + _show() { + this.refs.dialogOverlay.preventScrolling(); + this.setState({ open: true }); + }, + + _dismiss() { + CssEvent.onTransitionEnd(ReactDOM.findDOMNode(this), () => { + this.refs.dialogOverlay.allowScrolling(); + }); + + this.setState({ open: false }); }, - _onDismiss() { - if (this.props.onDismiss) this.props.onDismiss(); + _requestClose(buttonClicked) { + // Close the dialog if the isOpen state is not explicitly set. + if (this.props.isOpen === null) this._dismiss(); + if (this.props.onRequestClose) this.props.onRequestClose(!!buttonClicked); }, _handleOverlayTouchTap(e) { - if (this.props.modal) { - e.stopPropagation(); - } - else { - this.dismiss(); - if (this.props.onClickAway) this.props.onClickAway(); - } + this._requestClose(false); }, _handleWindowKeyUp(e) { - if (e.keyCode === KeyCode.ESC && !this.props.modal) { - this.dismiss(); + if (e.keyCode === KeyCode.ESC) { + this._requestClose(false); } }, From 801e05ac6724cd711b16bad8fe14a69d7bc76dd7 Mon Sep 17 00:00:00 2001 From: Jorgen Evens Date: Thu, 29 Oct 2015 20:26:37 +0100 Subject: [PATCH 2/2] [Dialog] Re-enable legacy API with deprecation warnings. --- docs/webpack-production.config.js | 5 +++ package.json | 3 +- src/dialog.jsx | 53 +++++++++++++++++++++++++++++-- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/docs/webpack-production.config.js b/docs/webpack-production.config.js index 27987231b2d7fe..e742dec2f3c6de 100644 --- a/docs/webpack-production.config.js +++ b/docs/webpack-production.config.js @@ -45,6 +45,11 @@ var config = { warnings: false } }), + new webpack.DefinePlugin({ + "process.env": { + NODE_ENV: JSON.stringify("production") + } + }), new HtmlWebpackPlugin({ inject: false, template: path.join(__dirname, '/src/www/index.html') diff --git a/package.json b/package.json index b248478ed5004e..a276ef2ababd73 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "homepage": "http://material-ui.com/", "dependencies": { "lodash.throttle": "~3.0.4", - "lodash.debounce": "~3.1.1" + "lodash.debounce": "~3.1.1", + "warning": "^2.1.0" }, "peerDependencies": { "inline-style-prefixer": "^0.3.3", diff --git a/src/dialog.jsx b/src/dialog.jsx index e0823f6b2dcdfa..e457ac29fc846f 100644 --- a/src/dialog.jsx +++ b/src/dialog.jsx @@ -10,6 +10,7 @@ const Overlay = require('./overlay'); const Paper = require('./paper'); const DefaultRawTheme = require('./styles/raw-themes/light-raw-theme'); const ThemeManager = require('./styles/theme-manager'); +const warning = (process.env.NODE_ENV !== 'production') ? require('warning') : function(){}; const ReactTransitionGroup = require('react-addons-transition-group'); @@ -134,6 +135,8 @@ let Dialog = React.createClass({ }, getInitialState() { + if (process.env.NODE_ENV !== 'production') this._testDeprecations(); + let open = this.props.isOpen; if (open === null) @@ -151,6 +154,8 @@ let Dialog = React.createClass({ let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; this.setState({muiTheme: newMuiTheme}); + if (process.env.NODE_ENV !== 'production') this._testDeprecations(); + if (nextProps.isOpen !== this.props.isOpen) { if (nextProps.isOpen && !this.state.open) this._show(); @@ -276,6 +281,22 @@ let Dialog = React.createClass({ return this.state.open; }, + _testDeprecations() { + if (process.env.NODE_ENV !== 'production') { + warning(!this.props.hasOwnProperty('openImmediately'), + 'openImmediately has been deprecated in favor of defaultIsOpen'); + + warning(!this.props.hasOwnProperty('onShow'), + 'onShow will be removed in favor of explicitly setting isOpen'); + + warning(!this.props.hasOwnProperty('onDismiss'), + 'onDismiss will be removed in favor of explicitly setting isOpen and can be replaced by onRequestClose'); + + warning(!this.props.hasOwnProperty('modal'), + 'modal will be removed in favor of explicitly setting isOpen and onRequestClose'); + } + }, + _getAction(actionJSON, key) { let styles = {marginRight: 8}; let props = { @@ -376,9 +397,31 @@ let Dialog = React.createClass({ } }, + show() { + if (process.env.NODE_ENV !== 'production') + warning(false, 'show has been deprecated in favor of explicitly setting the isOpen property.'); + + this._show(); + }, + + _onShow() { + if (this.props.onShow) this.props.onShow(); + }, + _show() { this.refs.dialogOverlay.preventScrolling(); - this.setState({ open: true }); + this.setState({ open: true }, this._onShow); + }, + + dismiss() { + if (process.env.NODE_ENV !== 'production') + warning(false, 'dismiss has been deprecated in favor of explicitly setting the isOpen property.'); + + this._dismiss(); + }, + + _onDismiss() { + if (this.props.onDismiss) this.props.onDismiss(); }, _dismiss() { @@ -386,10 +429,16 @@ let Dialog = React.createClass({ this.refs.dialogOverlay.allowScrolling(); }); - this.setState({ open: false }); + this.setState({ open: false }, this._onDismiss); }, _requestClose(buttonClicked) { + if (process.env.NODE_ENV !== 'production') + warning(!this.props.hasOwnProperty('modal'), + 'modal will be removed in favor of explicitly setting isOpen and onRequestClose'); + + if (!buttonClicked && this.props.modal) return; + // Close the dialog if the isOpen state is not explicitly set. if (this.props.isOpen === null) this._dismiss(); if (this.props.onRequestClose) this.props.onRequestClose(!!buttonClicked);