Home Reference Source

src/components/controls/InputMoney/InputMoney.jsx

import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import {
  isEqual,
  forOwn,
  isEmpty,
  split,
  replace,
  includes,
  isNaN,
  last,
} from 'lodash';
import InputMask from '../InputMask/InputMask';

const ReplaceableChar = {
  PREFIX: 'prefix',
  SUFFIX: 'suffix',
  THOUSANDS_SYMBOL: 'thousandsSeparatorSymbol',
  DECIMAL_SYMBOL: 'decimalSymbol',
};

/**
 * Компонент ввода денег
 * @reactProps {string} value - значение
 * @reactProps {string} className - класс компонента
 * @reactProps {string} prefix - вывод валюты в начале
 * @reactProps {string}  suffix - вывод валюты в конце
 * @reactProps {boolean}   includeThousandsSeparator - флаг включения разделителя тысяч
 * @reactProps {string}   thousandsSeparatorSymbol - разделитель тысяч
 * @reactProps {boolean}  allowDecimal - флаг разрешения float
 * @reactProps {string}  decimalSymbol - разделитель float
 * @reactProps {number}  decimalLimit - лимит float
 * @reactProps {number}  integerLimit - целочисленный лимит
 * @reactProps {boolean}  allowNegative - флаг включения отрицательных чисел
 * @reactProps {boolean}  allowLeadingZeroes - флаг разрешения нулей вначале
 * @example
 */
class InputMoney extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      value: props.value,
    };

    this.onBlur = this.onBlur.bind(this);
    this.onChange = this.onChange.bind(this);
    this.convertToMoney = this.convertToMoney.bind(this);
    this.convertToFloat = this.convertToFloat.bind(this);
    this.getInputMoneyProps = this.getInputMoneyProps.bind(this);
  }

  componentDidUpdate(prevProps) {
    if (!isEqual(prevProps.value, this.props.value)) {
      this.setState({ value: this.props.value });
    }
  }

  convertToMoney(value) {
    const { allowDecimal } = this.props;
    if (value !== '') {
      value = replace(value, '.', this.props[ReplaceableChar.DECIMAL_SYMBOL]);
    }

    const splitBySymbol = split(
      value,
      this.props[ReplaceableChar.DECIMAL_SYMBOL]
    );

    if (!allowDecimal) {
      value = splitBySymbol[0];
    }
    return value;
  }

  onBlur(value) {
    const { onBlur } = this.props;
    const convertedValue = parseFloat(this.convertToFloat(value));
    onBlur && onBlur(!isNaN(convertedValue) ? convertedValue : null);
    this.setState({ value: convertedValue });
  }

  replaceSpecialSymbol(value, searchValue, replaceValue) {
    return value.replace(searchValue, replaceValue);
  }

  convertToFloat(value) {
    const { allowDecimal } = this.props;
    let convertedValue = value.toString();
    forOwn(ReplaceableChar, char => {
      if (!isEmpty(this.props[char])) {
        const pattern = this.props[char].replace(
          /[-\/\\^$*+?.()|[\]{}]/g,
          '\\$&'
        );
        const regExp = new RegExp(pattern, 'g');
        const replaceableValue =
          char === ReplaceableChar.DECIMAL_SYMBOL ? '.' : '';
        convertedValue = this.replaceSpecialSymbol(
          convertedValue,
          regExp,
          replaceableValue
        );
      }
    });

    if (
      allowDecimal &&
      includes(this.state.value, '.') &&
      !includes(value, this.props[ReplaceableChar.DECIMAL_SYMBOL])
    ) {
      convertedValue = convertedValue.substring(0, convertedValue.length - 3);
    }

    if (
      includes(convertedValue, '.') &&
      last(split(convertedValue, '.')).length === 1
    ) {
      convertedValue += '0';
    }

    this.setState({ value: convertedValue });
    return convertedValue;
  }

  onChange(value) {
    const { onChange } = this.props;
    const convertedValue = parseFloat(this.convertToFloat(value));
    onChange && onChange(!isNaN(convertedValue) ? convertedValue : null);
    this.setState({ value: convertedValue });
  }

  getInputMoneyProps() {
    const {
      value,
      className,
      suffix,
      prefix,
      includeThousandsSeparator,
      thousandsSeparatorSymbol,
      allowDecimal,
      decimalSymbol,
      decimalLimit,
      integerLimit,
      allowNegative,
      allowLeadingZeroes,
    } = this.props;
    return {
      ...this.props,
      preset: 'money',
      value: this.convertToMoney(value || this.state.value),
      onChange: this.onChange,
      onBlur: this.onBlur,
      className: cn('n2o-input-money', className),
      presetConfig: {
        suffix,
        prefix,
        includeThousandsSeparator,
        thousandsSeparatorSymbol,
        allowDecimal,
        decimalSymbol,
        decimalLimit,
        integerLimit,
        requireDecimal: false,
        allowNegative,
        allowLeadingZeroes,
      },
    };
  }

  render() {
    return <InputMask {...this.getInputMoneyProps()} />;
  }
}

InputMoney.propTypes = {
  value: PropTypes.string,
  className: PropTypes.string,
  prefix: PropTypes.string,
  suffix: PropTypes.string,
  includeThousandsSeparator: PropTypes.bool,
  thousandsSeparatorSymbol: PropTypes.string,
  allowDecimal: PropTypes.bool,
  decimalSymbol: PropTypes.string,
  decimalLimit: PropTypes.number,
  integerLimit: PropTypes.any,
  allowNegative: PropTypes.bool,
  allowLeadingZeroes: PropTypes.bool,
};

InputMoney.defaultProps = {
  value: '',
  prefix: '',
  suffix: ' руб.',
  includeThousandsSeparator: true,
  thousandsSeparatorSymbol: ' ',
  allowDecimal: true,
  decimalSymbol: ',',
  decimalLimit: 2,
  integerLimit: 15,
  allowNegative: false,
  allowLeadingZeroes: false,
  guide: false,
};

export default InputMoney;