Skip to content

Commit 82bc961

Browse files
committed
Use react-onclickoutside to close calendar, remove dependency on click and blur events
1 parent b784de2 commit 82bc961

File tree

4 files changed

+159
-110
lines changed

4 files changed

+159
-110
lines changed

react-datepicker.js

Lines changed: 144 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,97 @@
11
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.DatePicker=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2+
/**
3+
* A mixin for handling (effectively) onClickOutside for React components.
4+
* Note that we're not intercepting any events in this approach, and we're
5+
* not using double events for capturing and discarding in layers or wrappers.
6+
*
7+
* The idea is that components define function
8+
*
9+
* onClickOutside: function() { ... }
10+
*
11+
* If no such function is defined, an error will be thrown, as this means
12+
* either it still needs to be written, or the component should not be using
13+
* this mixing since it will not exhibit onClickOutside behaviour.
14+
*
15+
*/
16+
(function (root, factory) {
17+
if (typeof define === 'function' && define.amd) {
18+
// AMD. Register as an anonymous module.
19+
define([], factory);
20+
} else if (typeof exports === 'object') {
21+
// Node. Note that this does not work with strict
22+
// CommonJS, but only CommonJS-like environments
23+
// that support module.exports
24+
module.exports = factory();
25+
} else {
26+
// Browser globals (root is window)
27+
root.OnClickOutside = factory();
28+
}
29+
}(this, function () {
30+
"use strict";
31+
32+
// Use a parallel array because we can't use
33+
// objects as keys, they get toString-coerced
34+
var registeredComponents = [];
35+
var handlers = [];
36+
37+
return {
38+
componentDidMount: function() {
39+
if(!this.handleClickOutside)
40+
throw new Error("Component lacks a handleClickOutside(event) function for processing outside click events.");
41+
42+
var fn = (function(localNode, eventHandler) {
43+
return function(evt) {
44+
var source = evt.target;
45+
var found = false;
46+
// If source=local then this event came from "somewhere"
47+
// inside and should be ignored. We could handle this with
48+
// a layered approach, too, but that requires going back to
49+
// thinking in terms of Dom node nesting, running counter
50+
// to React's "you shouldn't care about the DOM" philosophy.
51+
while(source.parentNode) {
52+
found = (source === localNode);
53+
if(found) return;
54+
source = source.parentNode;
55+
}
56+
eventHandler(evt);
57+
}
58+
}(this.getDOMNode(), this.handleClickOutside));
59+
60+
document.addEventListener("mousedown", fn);
61+
document.addEventListener("touchstart", fn);
62+
63+
var pos = registeredComponents.length;
64+
registeredComponents.push(this);
65+
handlers[pos] = fn;
66+
},
67+
68+
componentWillUnmount: function() {
69+
var pos = registeredComponents.indexOf(this);
70+
if( pos>-1) {
71+
var fn = handlers[pos];
72+
if (fn) {
73+
document.removeEventListener("mousedown", fn);
74+
document.removeEventListener("touchstart", fn);
75+
}
76+
}
77+
}
78+
};
79+
80+
}));
81+
82+
},{}],2:[function(require,module,exports){
283
/** @jsx React.DOM */
384

485
var Day = require('./day');
586
var DateUtil = require('./util/date');
687

788
var Calendar = React.createClass({displayName: 'Calendar',
89+
mixins: [require('react-onclickoutside')],
90+
91+
handleClickOutside: function() {
92+
this.props.hideCalendar();
93+
},
94+
895
getInitialState: function() {
996
return {
1097
date: new DateUtil(this.props.selected).clone()
@@ -46,7 +133,7 @@ var Calendar = React.createClass({displayName: 'Calendar',
46133
}
47134

48135
return (
49-
React.DOM.div({key: key},
136+
React.DOM.div({key: key},
50137
this.days(weekStart)
51138
)
52139
);
@@ -55,10 +142,10 @@ var Calendar = React.createClass({displayName: 'Calendar',
55142
renderDay: function(day, key) {
56143
return (
57144
Day({
58-
key: key,
59-
day: day,
60-
date: this.state.date,
61-
onClick: this.handleDayClick.bind(this, day),
145+
key: key,
146+
day: day,
147+
date: this.state.date,
148+
onClick: this.handleDayClick.bind(this, day),
62149
selected: new DateUtil(this.props.selected)})
63150
);
64151
},
@@ -69,29 +156,29 @@ var Calendar = React.createClass({displayName: 'Calendar',
69156

70157
render: function() {
71158
return (
72-
React.DOM.div({className: "datepicker", onMouseDown: this.props.onMouseDown},
73-
React.DOM.div({className: "datepicker__triangle"}),
74-
React.DOM.div({className: "datepicker__header"},
75-
React.DOM.a({className: "datepicker__navigation datepicker__navigation--previous",
159+
React.DOM.div({className: "datepicker"},
160+
React.DOM.div({className: "datepicker__triangle"}),
161+
React.DOM.div({className: "datepicker__header"},
162+
React.DOM.a({className: "datepicker__navigation datepicker__navigation--previous",
76163
onClick: this.decreaseMonth}
77-
),
78-
React.DOM.span({className: "datepicker__current-month"},
164+
),
165+
React.DOM.span({className: "datepicker__current-month"},
79166
this.state.date.format("MMMM YYYY")
80-
),
81-
React.DOM.a({className: "datepicker__navigation datepicker__navigation--next",
167+
),
168+
React.DOM.a({className: "datepicker__navigation datepicker__navigation--next",
82169
onClick: this.increaseMonth}
83-
),
84-
React.DOM.div(null,
85-
React.DOM.div({className: "datepicker__day"}, "Mo"),
86-
React.DOM.div({className: "datepicker__day"}, "Tu"),
87-
React.DOM.div({className: "datepicker__day"}, "We"),
88-
React.DOM.div({className: "datepicker__day"}, "Th"),
89-
React.DOM.div({className: "datepicker__day"}, "Fr"),
90-
React.DOM.div({className: "datepicker__day"}, "Sa"),
170+
),
171+
React.DOM.div(null,
172+
React.DOM.div({className: "datepicker__day"}, "Mo"),
173+
React.DOM.div({className: "datepicker__day"}, "Tu"),
174+
React.DOM.div({className: "datepicker__day"}, "We"),
175+
React.DOM.div({className: "datepicker__day"}, "Th"),
176+
React.DOM.div({className: "datepicker__day"}, "Fr"),
177+
React.DOM.div({className: "datepicker__day"}, "Sa"),
91178
React.DOM.div({className: "datepicker__day"}, "Su")
92179
)
93-
),
94-
React.DOM.div({className: "datepicker__month"},
180+
),
181+
React.DOM.div({className: "datepicker__month"},
95182
this.weeks()
96183
)
97184
)
@@ -101,7 +188,7 @@ var Calendar = React.createClass({displayName: 'Calendar',
101188

102189
module.exports = Calendar;
103190

104-
},{"./day":4,"./util/date":6}],2:[function(require,module,exports){
191+
},{"./day":5,"./util/date":7,"react-onclickoutside":1}],3:[function(require,module,exports){
105192
/** @jsx React.DOM */
106193

107194
var DateUtil = require('./util/date');
@@ -173,21 +260,20 @@ var DateInput = React.createClass({displayName: 'DateInput',
173260

174261
render: function() {
175262
return React.DOM.input({
176-
ref: "input",
177-
type: "text",
178-
value: this.state.value,
179-
onBlur: this.props.onBlur,
180-
onClick: this.handleClick,
181-
onKeyDown: this.handleKeyDown,
182-
onFocus: this.props.onFocus,
183-
onChange: this.handleChange,
263+
ref: "input",
264+
type: "text",
265+
value: this.state.value,
266+
onClick: this.handleClick,
267+
onKeyDown: this.handleKeyDown,
268+
onFocus: this.props.onFocus,
269+
onChange: this.handleChange,
184270
className: "datepicker__input"});
185271
}
186272
});
187273

188274
module.exports = DateInput;
189275

190-
},{"./util/date":6}],3:[function(require,module,exports){
276+
},{"./util/date":7}],4:[function(require,module,exports){
191277
/** @jsx React.DOM */
192278

193279
var Popover = require('./popover');
@@ -209,31 +295,11 @@ var DatePicker = React.createClass({displayName: 'DatePicker',
209295
},
210296

211297
hideCalendar: function() {
212-
this.setState({
213-
focus: false
214-
});
215-
},
216-
217-
handleBlur: function() {
218-
this.setState({
219-
focus: !! this._shouldBeFocussed
220-
});
221-
222-
if (!! this._shouldBeFocussed) {
223-
// Firefox doesn't support immediately focussing inside of blur
224-
setTimeout(function() {
225-
this.setState({
226-
focus: true
227-
});
228-
}.bind(this), 0);
229-
}
230-
231-
// Reset the value of this._shouldBeFocussed to it's default
232-
this._shouldBeFocussed = false;
233-
},
234-
235-
handleCalendarMouseDown: function() {
236-
this._shouldBeFocussed = true;
298+
setTimeout(function() {
299+
this.setState({
300+
focus: false
301+
});
302+
}.bind(this), 0);
237303
},
238304

239305
handleSelect: function(date) {
@@ -257,28 +323,27 @@ var DatePicker = React.createClass({displayName: 'DatePicker',
257323
calendar: function() {
258324
if (this.state.focus) {
259325
return (
260-
Popover(null,
326+
Popover(null,
261327
Calendar({
262-
selected: this.props.selected,
263-
onSelect: this.handleSelect,
264-
onMouseDown: this.handleCalendarMouseDown})
328+
selected: this.props.selected,
329+
onSelect: this.handleSelect,
330+
hideCalendar: this.hideCalendar})
265331
)
266332
);
267333
}
268334
},
269335

270336
render: function() {
271337
return (
272-
React.DOM.div(null,
338+
React.DOM.div(null,
273339
DateInput({
274-
date: this.props.selected,
275-
dateFormat: this.props.dateFormat,
276-
focus: this.state.focus,
277-
onBlur: this.handleBlur,
278-
onFocus: this.handleFocus,
279-
handleClick: this.onInputClick,
280-
handleEnter: this.hideCalendar,
281-
setSelected: this.setSelected}),
340+
date: this.props.selected,
341+
dateFormat: this.props.dateFormat,
342+
focus: this.state.focus,
343+
onFocus: this.handleFocus,
344+
handleClick: this.onInputClick,
345+
handleEnter: this.hideCalendar,
346+
setSelected: this.setSelected}),
282347
this.calendar()
283348
)
284349
);
@@ -287,7 +352,7 @@ var DatePicker = React.createClass({displayName: 'DatePicker',
287352

288353
module.exports = DatePicker;
289354

290-
},{"./calendar":1,"./date_input":2,"./popover":5,"./util/date":6}],4:[function(require,module,exports){
355+
},{"./calendar":2,"./date_input":3,"./popover":6,"./util/date":7}],5:[function(require,module,exports){
291356
/** @jsx React.DOM */
292357

293358
var Day = React.createClass({displayName: 'Day',
@@ -300,7 +365,7 @@ var Day = React.createClass({displayName: 'Day',
300365
});
301366

302367
return (
303-
React.DOM.div({className: classes, onClick: this.props.onClick},
368+
React.DOM.div({className: classes, onClick: this.props.onClick},
304369
this.props.day.day()
305370
)
306371
);
@@ -309,7 +374,7 @@ var Day = React.createClass({displayName: 'Day',
309374

310375
module.exports = Day;
311376

312-
},{}],5:[function(require,module,exports){
377+
},{}],6:[function(require,module,exports){
313378
/** @jsx React.DOM */
314379

315380
var Popover = React.createClass({
@@ -335,8 +400,8 @@ var Popover = React.createClass({
335400
_popoverComponent: function() {
336401
var className = this.props.className;
337402
return (
338-
React.DOM.div({className: className},
339-
React.DOM.div({className: "datepicker-popover-content"},
403+
React.DOM.div({className: className},
404+
React.DOM.div({className: "datepicker-popover-content"},
340405
this.props.children
341406
)
342407
)
@@ -387,7 +452,7 @@ var Popover = React.createClass({
387452

388453
module.exports = Popover;
389454

390-
},{}],6:[function(require,module,exports){
455+
},{}],7:[function(require,module,exports){
391456
function DateUtil(date) {
392457
this._date = date;
393458
}
@@ -460,5 +525,5 @@ DateUtil.prototype.moment = function() {
460525

461526
module.exports = DateUtil;
462527

463-
},{}]},{},[3])(3)
464-
});
528+
},{}]},{},[4])(4)
529+
});

src/calendar.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ var Day = require('./day');
44
var DateUtil = require('./util/date');
55

66
var Calendar = React.createClass({
7+
mixins: [require('react-onclickoutside')],
8+
9+
handleClickOutside: function() {
10+
this.props.hideCalendar();
11+
},
12+
713
getInitialState: function() {
814
return {
915
date: new DateUtil(this.props.selected).clone()
@@ -68,7 +74,7 @@ var Calendar = React.createClass({
6874

6975
render: function() {
7076
return (
71-
<div className="datepicker" onMouseDown={this.props.onMouseDown}>
77+
<div className="datepicker">
7278
<div className="datepicker__triangle"></div>
7379
<div className="datepicker__header">
7480
<a className="datepicker__navigation datepicker__navigation--previous"

src/date_input.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ var DateInput = React.createClass({
7272
ref="input"
7373
type="text"
7474
value={this.state.value}
75-
onBlur={this.props.onBlur}
7675
onClick={this.handleClick}
7776
onKeyDown={this.handleKeyDown}
7877
onFocus={this.props.onFocus}

0 commit comments

Comments
 (0)