Home Reference Source

src/components/controls/DatePicker/DateInput.jsx

import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import cx from 'classnames';
import { isNaN, isNumber, isObject, get } from 'lodash';

import DateTimeControl from './DateTimeControl';
import MaskedInput from 'react-text-mask';
import { formatToMask, hasInsideMixMax } from './utils';

const inputStyle = { flexGrow: 1 };
const dashStyle = { alignSelf: 'center' };

/**
 * Компонент DateInput
 * @reactProps {string} dateFormat
 * @reactProps {string} defaultTime
 * @reactProps {string} placeholder
 * @reactProps {boolean} disabled
 * @reactProps {moment} value
 * @reactProps {function} inputOnClick
 * @reactProps {string} inputClassName
 * @reactProps {string} name
 * @reactProps {function} onClick
 */
class DateInput extends React.Component {
  constructor(props) {
    super(props);
    const { value, dateFormat } = props;
    this.state = {
      value: value && value.format(dateFormat),
    };
    this.onChange = this.onChange.bind(this);
    this.onFocus = this.onFocus.bind(this);
    this.onBlur = this.onBlur.bind(this);
    this.onButtonClick = this.onButtonClick.bind(this);
    this.onInputClick = this.onInputClick.bind(this);
    this.onKeyDown = this.onKeyDown.bind(this);
    this.setInputRef = this.setInputRef.bind(this);
    this.getDeletedSymbol = this.getDeletedSymbol.bind(this);
  }

  setInputRef(input) {
    this._input = input;
  }

  onChange(e, callback) {
    const value = isObject(e) ? get(e, 'target.value', '') : e;
    const { dateFormat, name } = this.props;
    if (value === '') {
      this.props.onInputChange(null, name);
    } else if (
      moment(value, dateFormat).format(dateFormat) === value &&
      hasInsideMixMax(value, this.props)
    ) {
      this.props.onInputChange(moment(value, dateFormat), name);
    } else {
      this.setState({ value }, () => {
        if (callback) callback();
      });
    }
  }

  onFocus(e) {
    const { setVisibility, onFocus, openOnFocus } = this.props;
    onFocus && onFocus(e);
    if (openOnFocus) {
      setVisibility(true);
    }
  }

  onBlur(e) {
    const { value } = e.target;
    const { dateFormat, name } = this.props;
    if (value === '') {
      this.props.onBlur(null, name);
    } else if (moment(value, dateFormat).format(dateFormat) === value) {
      this.props.onBlur(moment(value, dateFormat), name);
    }
  }

  /**
   * Показывается попап при нажатии на кнопку с иконкой календаря
   */
  onButtonClick() {
    this.props.setVisibility(true);
  }

  onInputClick(event) {
    const { setVisibility, onClick } = this.props;
    setVisibility(true);
    onClick && onClick(event);
  }

  replaceAt(string, index, replacement) {
    return (
      string.substring(0, index - 1) +
      replacement +
      string.substring(index, string.length)
    );
  }

  setCursorPosition(cursorPosition) {
    this._input.inputElement.setSelectionRange(cursorPosition, cursorPosition);
  }

  getDeletedSymbol(index) {
    return this.state.value.substring(index - 1, index);
  }

  onKeyDown(e) {
    const cursorPos = Number(e.target.selectionStart);
    const keyCode = Number(e.keyCode);
    const deletedChar = +this.getDeletedSymbol(cursorPos);

    if (keyCode === 8 && cursorPos !== 0 && !isNaN(deletedChar)) {
      e.preventDefault();

      const value = this.replaceAt(this.state.value, cursorPos, '_');

      this.onChange(value, () => this.setCursorPosition(cursorPos - 1));
    }
  }

  /**
   * Базовый рендер
   */
  render() {
    const {
      disabled,
      placeholder,
      name,
      autoFocus,
      dateFormat,
      inputClassName,
    } = this.props;

    return (
      <div
        className={cx('n2o-date-input', {
          'n2o-date-input-first': name === DateTimeControl.beginInputName,
          'n2o-date-input-last': name === DateTimeControl.endInputName,
        })}
      >
        {name === DateTimeControl.endInputName && (
          <span style={dashStyle}>-</span>
        )}
        <MaskedInput
          ref={this.setInputRef}
          onKeyDown={this.onKeyDown}
          value={this.state.value}
          type="text"
          mask={formatToMask(dateFormat)}
          className={cx('form-control', inputClassName)}
          placeholder={placeholder}
          disabled={disabled}
          onChange={this.onChange}
          onClick={this.onInputClick}
          keepCharPositions={true}
          style={inputStyle}
          onFocus={this.onFocus}
          onBlur={this.onBlur}
          autoFocus={autoFocus}
          render={(ref, props) => {
            return <input ref={ref} {...props} />;
          }}
        />
        {(name === DateTimeControl.defaultInputName ||
          name === DateTimeControl.endInputName) && (
          <button
            disabled={disabled}
            onClick={this.onButtonClick}
            className="btn n2o-calendar-button"
            tabIndex={-1}
          >
            <i className="fa fa-calendar" aria-hidden="true" />
          </button>
        )}
      </div>
    );
  }

  componentWillReceiveProps(props) {
    const { value, dateFormat } = props;
    if (props.value) {
      this.setState({ value: value.format(dateFormat) });
    } else {
      this.setState({ value: '' });
    }
  }
}

DateInput.defaultProps = {
  value: moment(),
  dateFormat: 'DD/MM/YYYY',
  autoFocus: false,
  onFocus: () => {},
  onBlur: () => {},
  openOnFocus: false,
};

DateInput.propTypes = {
  onFocus: PropTypes.func,
  onBlur: PropTypes.func,
  dateFormat: PropTypes.string,
  defaultTime: PropTypes.string,
  placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
  disabled: PropTypes.bool,
  value: PropTypes.instanceOf(moment),
  inputOnClick: PropTypes.func,
  inputClassName: PropTypes.string,
  name: PropTypes.string,
  onClick: PropTypes.func,
  autoFocus: PropTypes.bool,
  openOnFocus: PropTypes.bool,
  min: PropTypes.oneOfType([
    PropTypes.instanceOf(moment),
    PropTypes.instanceOf(Date),
    PropTypes.string,
  ]),
  max: PropTypes.oneOfType([
    PropTypes.instanceOf(moment),
    PropTypes.instanceOf(Date),
    PropTypes.string,
  ]),
};
export default DateInput;