From 9fd86b2aa88a2a28d3bf76dd7795c915bd19991c Mon Sep 17 00:00:00 2001 From: Remon Oldenbeuving Date: Fri, 11 Jul 2014 19:50:47 -0700 Subject: [PATCH 1/3] Abstracted DateInput and added Key handlers Supported keys: - ArrowUp: increase date type (year/month/day) - ArrowDown: decrease date type (year/month/day) - Enter: close date-picker --- react-datepicker.js | 182 ++++++++++++++++++++++++++++++++++---------- src/date_input.js | 121 +++++++++++++++++++++++++++++ src/datepicker.js | 49 ++++-------- 3 files changed, 275 insertions(+), 77 deletions(-) create mode 100644 src/date_input.js diff --git a/react-datepicker.js b/react-datepicker.js index 6f72663147..6a9932e35a 100644 --- a/react-datepicker.js +++ b/react-datepicker.js @@ -101,18 +101,141 @@ var Calendar = React.createClass({displayName: 'Calendar', module.exports = Calendar; -},{"./day":3,"./util/date":5}],2:[function(_dereq_,module,exports){ +},{"./day":4,"./util/date":6}],2:[function(_dereq_,module,exports){ /** @jsx React.DOM */ -var Popover = _dereq_('./popover'); var DateUtil = _dereq_('./util/date'); -var Calendar = _dereq_('./calendar'); + +var DateInput = React.createClass({displayName: 'DateInput', + getInitialState: function() { + return { + value: this.props.date.format("YYYY-MM-DD") + }; + }, + + componentDidMount: function() { + this.toggleFocus(this.props.focus); + }, + + componentWillReceiveProps: function(newProps) { + this.toggleFocus(newProps.focus); + + this.setState({ + value: newProps.date.format("YYYY-MM-DD") + }); + }, + + componentDidUpdate: function() { + var el = this.refs.input.getDOMNode(); + + if (this.props.focus) { + if (typeof this.state.selectionStart == "number") + el.selectionStart = this.state.selectionStart; + + if (typeof this.state.selectionEnd == "number") + el.selectionEnd = this.state.selectionEnd; + } + }, + + toggleFocus: function(focus) { + if (focus) { + this.refs.input.getDOMNode().focus(); + } else { + this.refs.input.getDOMNode().blur(); + } + }, + + handleChange: function(event) { + var date = moment(event.target.value, "YYYY-MM-DD", true); + + this.setState({ + value: event.target.value + }); + + if (this.isValueAValidDate()) { + this.props.setSelected(new DateUtil(date)); + } + }, + + isValueAValidDate: function() { + var date = moment(event.target.value, "YYYY-MM-DD", true); + + return date.isValid(); + }, + + handleKeyDown: function(event) { + switch(event.key) { + case "Enter": + event.preventDefault(); + this.props.handleEnter(); + break; + case "ArrowUp": + case "ArrowDown": + event.preventDefault(); + this.handleArrowUpDown(event.key); + break; + }; + }, + + handleArrowUpDown: function(key) { + if (! this.isValueAValidDate()) + return; + + var selectedDatePart, incrementer; + var clonedDate = this.props.date.clone(); + var el = this.refs.input.getDOMNode(); + + if (key == "ArrowUp") { + incrementer = 1; + } else { + incrementer = -1; + } + + if (el.selectionStart >= 0 && el.selectionEnd <= 4) { + selectedDatePart = "year"; + } else if (el.selectionStart >= 5 && el.selectionEnd <= 7) { + selectedDatePart = "month"; + } else if (el.selectionStart >= 8 && el.selectionEnd <= 10) { + selectedDatePart = "day"; + } + + this.setState({ + selectionStart: el.selectionStart, + selectionEnd: el.selectionEnd + }); + + clonedDate.add(selectedDatePart, incrementer); + + this.props.setSelected(new DateUtil(clonedDate)); + }, + + render: function() { + return React.DOM.input( + {ref:"input", + type:"text", + value:this.state.value, + onBlur:this.props.onBlur, + onKeyDown:this.handleKeyDown, + onFocus:this.props.onFocus, + onChange:this.handleChange, + className:"datepicker-input"} ); + } +}); + +module.exports = DateInput; + +},{"./util/date":6}],3:[function(_dereq_,module,exports){ +/** @jsx React.DOM */ + +var Popover = _dereq_('./popover'); +var DateUtil = _dereq_('./util/date'); +var Calendar = _dereq_('./calendar'); +var DateInput = _dereq_('./date_input'); var DatePicker = React.createClass({displayName: 'DatePicker', getInitialState: function() { return { - focus: false, - value: this.props.selected.format("YYYY-MM-DD") + focus: false }; }, @@ -136,7 +259,9 @@ var DatePicker = React.createClass({displayName: 'DatePicker', if (!! this._shouldBeFocussed) { // Firefox doesn't support immediately focussing inside of blur setTimeout(function() { - this.refs.input.getDOMNode().focus(); + this.setState({ + focus: true + }); }.bind(this), 0); } @@ -157,10 +282,6 @@ var DatePicker = React.createClass({displayName: 'DatePicker', }, setSelected: function(date) { - this.setState({ - value: date.format("YYYY-MM-DD") - }); - this.props.onChange(date.moment()); }, @@ -177,37 +298,16 @@ var DatePicker = React.createClass({displayName: 'DatePicker', } }, - handleInputChange: function(event) { - var date = moment(event.target.value, "YYYY-MM-DD", true); - - this.setState({ - value: event.target.value - }); - - if (date.isValid()) { - this.setSelected(new DateUtil(date)); - } - }, - - componentDidUpdate: function() { - if (this.state.focus) { - this.refs.input.getDOMNode().focus(); - } else { - this.refs.input.getDOMNode().blur(); - } - }, - render: function() { return ( React.DOM.div(null, - React.DOM.input( - {ref:"input", - type:"text", - value:this.state.value, + DateInput( + {date:this.props.selected, + focus:this.state.focus, onBlur:this.handleBlur, onFocus:this.handleFocus, - onChange:this.handleInputChange, - className:"datepicker-input"} ), + handleEnter:this.hideCalendar, + setSelected:this.setSelected} ), this.calendar() ) ); @@ -216,7 +316,7 @@ var DatePicker = React.createClass({displayName: 'DatePicker', module.exports = DatePicker; -},{"./calendar":1,"./popover":4,"./util/date":5}],3:[function(_dereq_,module,exports){ +},{"./calendar":1,"./date_input":2,"./popover":5,"./util/date":6}],4:[function(_dereq_,module,exports){ /** @jsx React.DOM */ var Day = React.createClass({displayName: 'Day', @@ -238,7 +338,7 @@ var Day = React.createClass({displayName: 'Day', module.exports = Day; -},{}],4:[function(_dereq_,module,exports){ +},{}],5:[function(_dereq_,module,exports){ /** @jsx React.DOM */ var Popover = React.createClass({ @@ -316,7 +416,7 @@ var Popover = React.createClass({ module.exports = Popover; -},{}],5:[function(_dereq_,module,exports){ +},{}],6:[function(_dereq_,module,exports){ function DateUtil(date) { this._date = date; } @@ -389,6 +489,6 @@ DateUtil.prototype.moment = function() { module.exports = DateUtil; -},{}]},{},[2]) -(2) +},{}]},{},[3]) +(3) }); \ No newline at end of file diff --git a/src/date_input.js b/src/date_input.js new file mode 100644 index 0000000000..e7318d1afd --- /dev/null +++ b/src/date_input.js @@ -0,0 +1,121 @@ +/** @jsx React.DOM */ + +var DateUtil = require('./util/date'); + +var DateInput = React.createClass({ + getInitialState: function() { + return { + value: this.props.date.format("YYYY-MM-DD") + }; + }, + + componentDidMount: function() { + this.toggleFocus(this.props.focus); + }, + + componentWillReceiveProps: function(newProps) { + this.toggleFocus(newProps.focus); + + this.setState({ + value: newProps.date.format("YYYY-MM-DD") + }); + }, + + componentDidUpdate: function() { + var el = this.refs.input.getDOMNode(); + + if (this.props.focus) { + if (typeof this.state.selectionStart == "number") + el.selectionStart = this.state.selectionStart; + + if (typeof this.state.selectionEnd == "number") + el.selectionEnd = this.state.selectionEnd; + } + }, + + toggleFocus: function(focus) { + if (focus) { + this.refs.input.getDOMNode().focus(); + } else { + this.refs.input.getDOMNode().blur(); + } + }, + + handleChange: function(event) { + var date = moment(event.target.value, "YYYY-MM-DD", true); + + this.setState({ + value: event.target.value + }); + + if (this.isValueAValidDate()) { + this.props.setSelected(new DateUtil(date)); + } + }, + + isValueAValidDate: function() { + var date = moment(event.target.value, "YYYY-MM-DD", true); + + return date.isValid(); + }, + + handleKeyDown: function(event) { + switch(event.key) { + case "Enter": + event.preventDefault(); + this.props.handleEnter(); + break; + case "ArrowUp": + case "ArrowDown": + event.preventDefault(); + this.handleArrowUpDown(event.key); + break; + }; + }, + + handleArrowUpDown: function(key) { + if (! this.isValueAValidDate()) + return; + + var selectedDatePart, incrementer; + var clonedDate = this.props.date.clone(); + var el = this.refs.input.getDOMNode(); + + if (key == "ArrowUp") { + incrementer = 1; + } else { + incrementer = -1; + } + + if (el.selectionStart >= 0 && el.selectionEnd <= 4) { + selectedDatePart = "year"; + } else if (el.selectionStart >= 5 && el.selectionEnd <= 7) { + selectedDatePart = "month"; + } else if (el.selectionStart >= 8 && el.selectionEnd <= 10) { + selectedDatePart = "day"; + } + + this.setState({ + selectionStart: el.selectionStart, + selectionEnd: el.selectionEnd + }); + + clonedDate.add(selectedDatePart, incrementer); + + this.props.setSelected(new DateUtil(clonedDate)); + }, + + render: function() { + return ; + } +}); + +module.exports = DateInput; diff --git a/src/datepicker.js b/src/datepicker.js index 9cc54f79b7..85bf606364 100644 --- a/src/datepicker.js +++ b/src/datepicker.js @@ -1,14 +1,14 @@ /** @jsx React.DOM */ -var Popover = require('./popover'); -var DateUtil = require('./util/date'); -var Calendar = require('./calendar'); +var Popover = require('./popover'); +var DateUtil = require('./util/date'); +var Calendar = require('./calendar'); +var DateInput = require('./date_input'); var DatePicker = React.createClass({ getInitialState: function() { return { - focus: false, - value: this.props.selected.format("YYYY-MM-DD") + focus: false }; }, @@ -32,7 +32,9 @@ var DatePicker = React.createClass({ if (!! this._shouldBeFocussed) { // Firefox doesn't support immediately focussing inside of blur setTimeout(function() { - this.refs.input.getDOMNode().focus(); + this.setState({ + focus: true + }); }.bind(this), 0); } @@ -53,10 +55,6 @@ var DatePicker = React.createClass({ }, setSelected: function(date) { - this.setState({ - value: date.format("YYYY-MM-DD") - }); - this.props.onChange(date.moment()); }, @@ -73,37 +71,16 @@ var DatePicker = React.createClass({ } }, - handleInputChange: function(event) { - var date = moment(event.target.value, "YYYY-MM-DD", true); - - this.setState({ - value: event.target.value - }); - - if (date.isValid()) { - this.setSelected(new DateUtil(date)); - } - }, - - componentDidUpdate: function() { - if (this.state.focus) { - this.refs.input.getDOMNode().focus(); - } else { - this.refs.input.getDOMNode().blur(); - } - }, - render: function() { return (
- + handleEnter={this.hideCalendar} + setSelected={this.setSelected} /> {this.calendar()}
); From 8d12fd7f0ab12eaaf0301e9a928c66fdc70b93e1 Mon Sep 17 00:00:00 2001 From: Remon Oldenbeuving Date: Fri, 18 Jul 2014 13:58:18 +0200 Subject: [PATCH 2/3] Removed unneeded semicolon --- src/date_input.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/date_input.js b/src/date_input.js index e7318d1afd..b46f5438a8 100644 --- a/src/date_input.js +++ b/src/date_input.js @@ -70,7 +70,7 @@ var DateInput = React.createClass({ event.preventDefault(); this.handleArrowUpDown(event.key); break; - }; + } }, handleArrowUpDown: function(key) { From f7aab51258158d0ba0c98a1705e59cae1346eeb5 Mon Sep 17 00:00:00 2001 From: Remon Oldenbeuving Date: Thu, 4 Sep 2014 11:16:48 +0200 Subject: [PATCH 3/3] Processed @markijbema's comments --- react-datepicker.js | 145 ++++++++++++++++++++++---------------------- src/date_input.js | 43 +++++++------ 2 files changed, 97 insertions(+), 91 deletions(-) diff --git a/react-datepicker.js b/react-datepicker.js index 6a9932e35a..8a8652322a 100644 --- a/react-datepicker.js +++ b/react-datepicker.js @@ -46,7 +46,7 @@ var Calendar = React.createClass({displayName: 'Calendar', } return ( - React.DOM.div( {key:key, className:"week"}, + React.DOM.div({key: key, className: "week"}, this.days(weekStart) ) ); @@ -54,12 +54,12 @@ var Calendar = React.createClass({displayName: 'Calendar', renderDay: function(day, key) { return ( - Day( - {key:key, - day:day, - date:this.state.date, - onClick:this.handleDayClick.bind(this, day), - selected:new DateUtil(this.props.selected)} ) + Day({ + key: key, + day: day, + date: this.state.date, + onClick: this.handleDayClick.bind(this, day), + selected: new DateUtil(this.props.selected)}) ); }, @@ -69,29 +69,29 @@ var Calendar = React.createClass({displayName: 'Calendar', render: function() { return ( - React.DOM.div( {className:"datepicker-calendar", onMouseDown:this.props.onMouseDown}, - React.DOM.div( {className:"datepicker-calendar-triangle"}), - React.DOM.div( {className:"datepicker-calendar-header"}, - React.DOM.a( {className:"datepicker-calendar-header-navigation-left", - onClick:this.decreaseMonth} - ), - React.DOM.span( {className:"datepicker-calendar-header-month"}, + React.DOM.div({className: "datepicker-calendar", onMouseDown: this.props.onMouseDown}, + React.DOM.div({className: "datepicker-calendar-triangle"}), + React.DOM.div({className: "datepicker-calendar-header"}, + React.DOM.a({className: "datepicker-calendar-header-navigation-left", + onClick: this.decreaseMonth} + ), + React.DOM.span({className: "datepicker-calendar-header-month"}, this.state.date.format("MMMM YYYY") - ), - React.DOM.a( {className:"datepicker-calendar-header-navigation-right", - onClick:this.increaseMonth} - ), + ), + React.DOM.a({className: "datepicker-calendar-header-navigation-right", + onClick: this.increaseMonth} + ), React.DOM.div(null, - React.DOM.div( {className:"datepicker-calendar-header-day"}, "Mo"), - React.DOM.div( {className:"datepicker-calendar-header-day"}, "Tu"), - React.DOM.div( {className:"datepicker-calendar-header-day"}, "We"), - React.DOM.div( {className:"datepicker-calendar-header-day"}, "Th"), - React.DOM.div( {className:"datepicker-calendar-header-day"}, "Fr"), - React.DOM.div( {className:"datepicker-calendar-header-day"}, "Sa"), - React.DOM.div( {className:"datepicker-calendar-header-day"}, "Su") + React.DOM.div({className: "datepicker-calendar-header-day"}, "Mo"), + React.DOM.div({className: "datepicker-calendar-header-day"}, "Tu"), + React.DOM.div({className: "datepicker-calendar-header-day"}, "We"), + React.DOM.div({className: "datepicker-calendar-header-day"}, "Th"), + React.DOM.div({className: "datepicker-calendar-header-day"}, "Fr"), + React.DOM.div({className: "datepicker-calendar-header-day"}, "Sa"), + React.DOM.div({className: "datepicker-calendar-header-day"}, "Su") ) - ), - React.DOM.div( {className:"datepicker-calendar-month"}, + ), + React.DOM.div({className: "datepicker-calendar-month"}, this.weeks() ) ) @@ -126,9 +126,9 @@ var DateInput = React.createClass({displayName: 'DateInput', }, componentDidUpdate: function() { - var el = this.refs.input.getDOMNode(); - if (this.props.focus) { + var el = this.refs.input.getDOMNode(); + if (typeof this.state.selectionStart == "number") el.selectionStart = this.state.selectionStart; @@ -174,51 +174,54 @@ var DateInput = React.createClass({displayName: 'DateInput', event.preventDefault(); this.handleArrowUpDown(event.key); break; - }; + } }, handleArrowUpDown: function(key) { if (! this.isValueAValidDate()) return; - var selectedDatePart, incrementer; - var clonedDate = this.props.date.clone(); var el = this.refs.input.getDOMNode(); - if (key == "ArrowUp") { - incrementer = 1; - } else { - incrementer = -1; - } - - if (el.selectionStart >= 0 && el.selectionEnd <= 4) { - selectedDatePart = "year"; - } else if (el.selectionStart >= 5 && el.selectionEnd <= 7) { - selectedDatePart = "month"; - } else if (el.selectionStart >= 8 && el.selectionEnd <= 10) { - selectedDatePart = "day"; - } - this.setState({ selectionStart: el.selectionStart, selectionEnd: el.selectionEnd }); - clonedDate.add(selectedDatePart, incrementer); + var step = key === "ArrowUp" ? 1 : -1; + + var selectedDatePart = this.getSelectedDatePart(el.selectionStart, el.selectionEnd); + var newDate = this.stepSelectedDatePart(selectedDatePart, step); - this.props.setSelected(new DateUtil(clonedDate)); + this.props.setSelected(newDate); + }, + + stepSelectedDatePart: function(selectedDatePart, step) { + var clonedDate = this.props.date.clone(); + + return new DateUtil(clonedDate.add(selectedDatePart, step)); + }, + + getSelectedDatePart: function(selectionStart, selectionEnd) { + if (selectionStart >= 0 && selectionEnd <= 4) { + return "year"; + } else if (selectionStart >= 5 && selectionEnd <= 7) { + return "month"; + } else if (selectionStart >= 8 && selectionEnd <= 10) { + return "day"; + } }, render: function() { - return React.DOM.input( - {ref:"input", - type:"text", - value:this.state.value, - onBlur:this.props.onBlur, - onKeyDown:this.handleKeyDown, - onFocus:this.props.onFocus, - onChange:this.handleChange, - className:"datepicker-input"} ); + return React.DOM.input({ + ref: "input", + type: "text", + value: this.state.value, + onBlur: this.props.onBlur, + onKeyDown: this.handleKeyDown, + onFocus: this.props.onFocus, + onChange: this.handleChange, + className: "datepicker-input"}); } }); @@ -289,10 +292,10 @@ var DatePicker = React.createClass({displayName: 'DatePicker', if (this.state.focus) { return ( Popover(null, - Calendar( - {selected:this.props.selected, - onSelect:this.handleSelect, - onMouseDown:this.handleCalendarMouseDown} ) + Calendar({ + selected: this.props.selected, + onSelect: this.handleSelect, + onMouseDown: this.handleCalendarMouseDown}) ) ); } @@ -301,13 +304,13 @@ var DatePicker = React.createClass({displayName: 'DatePicker', render: function() { return ( React.DOM.div(null, - DateInput( - {date:this.props.selected, - focus:this.state.focus, - onBlur:this.handleBlur, - onFocus:this.handleFocus, - handleEnter:this.hideCalendar, - setSelected:this.setSelected} ), + DateInput({ + date: this.props.selected, + focus: this.state.focus, + onBlur: this.handleBlur, + onFocus: this.handleFocus, + handleEnter: this.hideCalendar, + setSelected: this.setSelected}), this.calendar() ) ); @@ -329,7 +332,7 @@ var Day = React.createClass({displayName: 'Day', }); return ( - React.DOM.div( {className:classes, onClick:this.props.onClick}, + React.DOM.div({className: classes, onClick: this.props.onClick}, this.props.day.day() ) ); @@ -364,8 +367,8 @@ var Popover = React.createClass({ _popoverComponent: function() { var className = this.props.className; return ( - React.DOM.div( {className:className}, - React.DOM.div( {className:"datepicker-calendar-popover-content"}, + React.DOM.div({className: className}, + React.DOM.div({className: "datepicker-calendar-popover-content"}, this.props.children ) ) diff --git a/src/date_input.js b/src/date_input.js index b46f5438a8..65e1cd0acd 100644 --- a/src/date_input.js +++ b/src/date_input.js @@ -22,9 +22,9 @@ var DateInput = React.createClass({ }, componentDidUpdate: function() { - var el = this.refs.input.getDOMNode(); - if (this.props.focus) { + var el = this.refs.input.getDOMNode(); + if (typeof this.state.selectionStart == "number") el.selectionStart = this.state.selectionStart; @@ -77,32 +77,35 @@ var DateInput = React.createClass({ if (! this.isValueAValidDate()) return; - var selectedDatePart, incrementer; - var clonedDate = this.props.date.clone(); var el = this.refs.input.getDOMNode(); - if (key == "ArrowUp") { - incrementer = 1; - } else { - incrementer = -1; - } - - if (el.selectionStart >= 0 && el.selectionEnd <= 4) { - selectedDatePart = "year"; - } else if (el.selectionStart >= 5 && el.selectionEnd <= 7) { - selectedDatePart = "month"; - } else if (el.selectionStart >= 8 && el.selectionEnd <= 10) { - selectedDatePart = "day"; - } - this.setState({ selectionStart: el.selectionStart, selectionEnd: el.selectionEnd }); - clonedDate.add(selectedDatePart, incrementer); + var step = key === "ArrowUp" ? 1 : -1; - this.props.setSelected(new DateUtil(clonedDate)); + var selectedDatePart = this.getSelectedDatePart(el.selectionStart, el.selectionEnd); + var newDate = this.stepSelectedDatePart(selectedDatePart, step); + + this.props.setSelected(newDate); + }, + + stepSelectedDatePart: function(selectedDatePart, step) { + var clonedDate = this.props.date.clone(); + + return new DateUtil(clonedDate.add(selectedDatePart, step)); + }, + + getSelectedDatePart: function(selectionStart, selectionEnd) { + if (selectionStart >= 0 && selectionEnd <= 4) { + return "year"; + } else if (selectionStart >= 5 && selectionEnd <= 7) { + return "month"; + } else if (selectionStart >= 8 && selectionEnd <= 10) { + return "day"; + } }, render: function() {