Skip to content

Commit 6282c3e

Browse files
committed
Added the ability to decide whether the modal should be closed when clicking the overlay area.
This is an important ability since in some cases we don't want the modal to be closed when users are clicking outside. Added tests and README instructions.
1 parent 7f631bd commit 6282c3e

File tree

6 files changed

+83
-10
lines changed

6 files changed

+83
-10
lines changed

README.md

+16
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,22 @@ var App = React.createClass({
124124
ReactDOM.render(<App/>, appElement);
125125
```
126126

127+
By default the modal is closed when clicking outside of it (the overlay area). If you want to prevent this behavior you can
128+
pass the 'shouldCloseOnOverlayClick' prop with 'false' value.
129+
```xml
130+
<Modal
131+
isOpen={bool}
132+
onRequestClose={fn}
133+
closeTimeoutMS={n}
134+
shouldCloseOnOverlayClick={false}
135+
style={customStyle}>
136+
137+
<h1>Force Modal</h1>
138+
<p>Modal cannot be closed when clickig the overlay area</p>
139+
<button onClick={handleCloseFunc}>Close Modal...</button>
140+
</Modal>
141+
```
142+
127143
# Demos
128144
* http://reactjs.github.io/react-modal/
129145
* http://reactjs.github.io/react-modal/bootstrap

lib/components/Modal.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@ var Modal = module.exports = React.createClass({
2929
appElement: React.PropTypes.instanceOf(SafeHTMLElement),
3030
onRequestClose: React.PropTypes.func,
3131
closeTimeoutMS: React.PropTypes.number,
32-
ariaHideApp: React.PropTypes.bool
32+
ariaHideApp: React.PropTypes.bool,
33+
shouldCloseOnOverlayClick: React.PropTypes.bool
3334
},
3435

3536
getDefaultProps: function () {
3637
return {
3738
isOpen: false,
3839
ariaHideApp: true,
39-
closeTimeoutMS: 0
40+
closeTimeoutMS: 0,
41+
shouldCloseOnOverlayClick: true
4042
};
4143
},
4244

lib/components/ModalPortal.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,12 @@ var ModalPortal = module.exports = React.createClass({
146146
},
147147

148148
handleOverlayClick: function() {
149-
if (this.ownerHandlesClose())
150-
this.requestClose();
151-
else
152-
this.focusContent();
149+
if (this.props.shouldCloseOnOverlayClick) {
150+
if (this.ownerHandlesClose())
151+
this.requestClose();
152+
else
153+
this.focusContent();
154+
}
153155
},
154156

155157
requestClose: function() {

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@
3434
"karma-mocha": "0.2.0",
3535
"karma-safari-launcher": "^0.1.1",
3636
"mocha": "2.3.3",
37+
"react": "^0.14.0",
3738
"react-addons-test-utils": "^0.14.0",
3839
"react-dom": "^0.14.0",
39-
"react": "^0.14.0",
4040
"reactify": "^1.1.1",
4141
"rf-release": "0.4.0",
42+
"sinon": "^1.17.3",
4243
"uglify-js": "2.4.24",
4344
"webpack-dev-server": "1.11.0"
4445
},
@@ -65,4 +66,4 @@
6566
"react": "global:React",
6667
"react-dom": "global:ReactDOM"
6768
}
68-
}
69+
}

specs/Modal.spec.js

+53-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var Modal = require('../lib/components/Modal');
66
var Simulate = TestUtils.Simulate;
77
var ariaAppHider = require('../lib/helpers/ariaAppHider');
88
var button = ReactDOM.button;
9+
var sinon = require('sinon');
910

1011
describe('Modal', function () {
1112

@@ -74,6 +75,7 @@ describe('Modal', function () {
7475
equal(props.isOpen, false);
7576
equal(props.ariaHideApp, true);
7677
equal(props.closeTimeoutMS, 0);
78+
equal(props.shouldCloseOnOverlayClick, true);
7779
ReactDOM.unmountComponentAtNode(node);
7880
ariaAppHider.resetForTesting();
7981
});
@@ -94,13 +96,13 @@ describe('Modal', function () {
9496

9597
it('supports custom className', function() {
9698
var modal = renderModal({isOpen: true, className: 'myClass'});
97-
equal(modal.portal.refs.content.className.contains('myClass'), true);
99+
notEqual(modal.portal.refs.content.className.indexOf('myClass'), -1);
98100
unmountModal();
99101
});
100102

101103
it('supports overlayClassName', function () {
102104
var modal = renderModal({isOpen: true, overlayClassName: 'myOverlayClass'});
103-
equal(modal.portal.refs.overlay.className.contains('myOverlayClass'), true);
105+
notEqual(modal.portal.refs.overlay.className.indexOf('myOverlayClass'), -1);
104106
unmountModal();
105107
});
106108

@@ -145,6 +147,55 @@ describe('Modal', function () {
145147
unmountModal();
146148
});
147149

150+
describe('should close on overlay click', function() {
151+
afterEach('Unmount modal', function() {
152+
unmountModal();
153+
});
154+
155+
describe('verify props', function() {
156+
it('verify default prop of shouldCloseOnOverlayClick', function () {
157+
var modal = renderModal({isOpen: true});
158+
equal(modal.props.shouldCloseOnOverlayClick, true);
159+
});
160+
161+
it('verify prop of shouldCloseOnOverlayClick', function () {
162+
var modal = renderModal({isOpen: true, shouldCloseOnOverlayClick: false});
163+
equal(modal.props.shouldCloseOnOverlayClick, false);
164+
});
165+
});
166+
167+
describe('verify clicks', function() {
168+
it('verify overlay click when shouldCloseOnOverlayClick sets to false', function () {
169+
var requestCloseCallback = sinon.spy();
170+
var modal = renderModal({
171+
isOpen: true,
172+
shouldCloseOnOverlayClick: false
173+
});
174+
equal(modal.props.isOpen, true);
175+
var overlay = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Overlay');
176+
equal(overlay.length, 1);
177+
Simulate.click(overlay[0]); // click the overlay
178+
ok(!requestCloseCallback.called)
179+
});
180+
181+
it('verify overlay click when shouldCloseOnOverlayClick sets to true', function() {
182+
var requestCloseCallback = sinon.spy();
183+
var modal = renderModal({
184+
isOpen: true,
185+
shouldCloseOnOverlayClick: true,
186+
onRequestClose: function() {
187+
requestCloseCallback();
188+
}
189+
});
190+
equal(modal.props.isOpen, true);
191+
var overlay = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Overlay');
192+
equal(overlay.length, 1);
193+
Simulate.click(overlay[0]); // click the overlay
194+
ok(requestCloseCallback.called)
195+
});
196+
});
197+
});
198+
148199
//it('adds --before-close for animations', function() {
149200
//var node = document.createElement('div');
150201

specs/helper.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var Modal = React.createFactory(require('../lib/components/Modal'));
55

66
ok = assert.ok;
77
equal = assert.equal;
8+
notEqual = assert.notEqual;
89
strictEqual = assert.strictEqual;
910
throws = assert.throws;
1011

0 commit comments

Comments
 (0)