input-moment icon indicating copy to clipboard operation
input-moment copied to clipboard

min and max dates

Open ggrillone opened this issue 7 years ago • 3 comments

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 with moment.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

ggrillone avatar Apr 28 '17 15:04 ggrillone

for anyone else wanting to add a min/max date check, moment.js makes it super easy: http://momentjs.com/docs/#/query/

ggrillone avatar Apr 28 '17 15:04 ggrillone

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 avatar Apr 28 '17 18:04 ggrillone

@ggrillone I've just opened a new pull request trying to solve this issue:

https://github.com/wangzuo/input-moment/pull/85

anderconal avatar Nov 03 '17 17:11 anderconal