Open ggrillone opened 7 years ago
for anyone else wanting to add a min/max date check, moment.js
makes it super easy: http://momentjs.com/docs/#/query/
I ended up coming with a solution, it's a bit hacky as it uses js to change the class names of the table cells in the calendar. but it works for now, hopefully helpful to others:
import React from 'react'
import moment from 'moment'
import InputMoment from 'input-moment'
function _validateDate(minDate, maxDate, dateVal) {
if (!minDate && !maxDate) {
return true
} else if (minDate && maxDate) {
return _validateMinDate(minDate, dateVal) && _validateMaxDate(maxDate, dateVal)
} else if (minDate) {
return _validateMinDate(minDate, dateVal)
} else if (maxDate) {
return _validateMaxDate(maxDate, dateVal)
}
}
function _validateMinDate(minDate, dateVal) {
return !moment(dateVal).isBefore(minDate)
}
function _validateMaxDate(maxDate, dateVal) {
return !moment(dateVal).isAfter(maxDate)
}
class DateTimePicker extends React.Component {
constructor(props) {
super(props)
this.state = { dateTime: null }
}
componentDidMount() {
this.disableOutOfRangeDates()
}
componentDidUpdate() {
this.disableOutOfRangeDates()
}
disableOutOfRangeDates() {
if (this.props.minDate || this.props.maxDate) {
const inputMomentElem = this.refs['date-time-picker'].getElementsByClassName('m-input-moment')[0]
if (inputMomentElem) {
const calendarElem = inputMomentElem.getElementsByClassName('m-calendar tab is-active')[0]
// only do if date selector is active
if (calendarElem) {
const calendarRows = calendarElem.getElementsByTagName('table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')
this.disableCalendarRows(calendarRows)
}
}
}
}
disableCalendarRows(calendarRows) {
for (let i = 0;i < calendarRows.length;i++) {
const rowCells = calendarRows[i].getElementsByTagName('td')
for (let j = 0;j < rowCells.length;j++) {
if (rowCells[j].className.match(/prev\-month/gi) && this.props.minDate) {
this.setDisabledForPrevMonthCells(rowCells[j])
} else if (rowCells[j].className.match(/next\-month/gi) && this.props.maxDate) {
this.setDisabledForNextMonthCells(rowCells[j])
} else if (!rowCells[j].className.match(/prev\-month/gi) && !rowCells[j].className.match(/next\-month/gi)) {
this.setDisabledForCurrentMonthCells(rowCells[j])
}
}
}
}
setDisabledForPrevMonthCells(row) {
let
dateTime = moment(this.state.dateTime),
currentYear = parseInt(dateTime.format('YYYY'), 10),
currentMonth = parseInt(dateTime.format('MM'), 10),
day = row.innerHTML.toString(),
prevMonth = (currentMonth - 1).toString()
// if currently in january, go back to december of previous year
if (currentMonth === 1) {
prevMonth = 12
currentYear = currentYear - 1
}
// for proper ISO format
if (prevMonth.length === 1) {
prevMonth = `0${prevMonth}`
}
// for proper ISO format
if (day.length === 1) {
day = `0${day}`
}
if (!_validateMinDate(this.props.minDate, `${currentYear}-${prevMonth}-${day}`)) {
row.className += ' disabled'
} else {
row.className = row.className.replace(/disabled/gi, '')
}
}
setDisabledForNextMonthCells(row) {
let
dateTime = moment(this.state.dateTime),
currentYear = parseInt(dateTime.format('YYYY'), 10),
currentMonth = parseInt(dateTime.format('MM'), 10),
day = row.innerHTML.toString(),
nextMonth = (currentMonth + 1).toString()
// if currently in december, go to january of next year
if (currentMonth === 12) {
nextMonth = '01'
currentYear = currentYear + 1
}
// for proper ISO format
if (nextMonth.length === 1) {
nextMonth = `0${nextMonth}`
}
// for proper ISO format
if (day.length === 1) {
day = `0${day}`
}
if (!_validateMaxDate(this.props.maxDate, `${currentYear}-${nextMonth}-${day}`)) {
row.className += ' disabled'
} else {
row.className = row.className.replace(/disabled/gi, '')
}
}
setDisabledForCurrentMonthCells(row) {
let
dateTime = moment(this.state.dateTime),
currentYear = dateTime.format('YYYY'),
currentMonth = dateTime.format('MM'),
day = row.innerHTML.toString(),
disabledForMinDate = false
// for proper ISO format
if (day.length === 1) {
day = `0${day}`
}
if (this.props.minDate && !_validateMinDate(this.props.minDate, `${currentYear}-${currentMonth}-${day}`)) {
disabledForMinDate = true
row.className += ' disabled'
} else {
row.className = row.className.replace(/disabled/gi, '')
}
if (this.props.maxDate && !_validateMaxDate(this.props.maxDate, `${currentYear}-${currentMonth}-${day}`)) {
row.className += ' disabled'
} else {
if (!disabledForMinDate) {
row.className = row.className.replace(/disabled/gi, '')
}
}
}
onChangeCallback(newVal) {
const isValidDate = _validateDate(this.props.minDate, this.props.maxDate, newVal)
if (isValidDate) {
this.setState({ dateTime: newVal })
this.props.onChange(newVal)
}
}
render() {
...
}
}
DateTimePicker.propTypes = {
defaultDateTime: oneOfType([string, object]).isRequired,
onSave: func,
onChange: func,
minDate: oneOfType([string, object]),
maxDate: oneOfType([string, object])
}
@ggrillone I've just opened a new pull request trying to solve this issue:
it would be nice to have the ability to set a min/max date on the component. leveraging the
onChange
callback that's already there withmoment.js
wouldn't be too difficult to implement that, but it's difficult to give a visual indicator (applying styles or class names) of what dates are "disabled" from outside the component