Home Reference Source

src/components/widgets/Form/fields/withFieldContainer.js

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { isBoolean } from 'lodash';
import {
  isInitSelector,
  isVisibleSelector,
  isDisabledSelector,
  messageSelector,
  requiredSelector,
} from '../../../../selectors/formPlugin';
import { registerFieldExtra } from '../../../../actions/formPlugin';
import { compose, pure, withProps, defaultProps } from 'recompose';
import propsResolver from '../../../../utils/propsResolver';
import { getFormValues } from 'redux-form';

/**
 * HOC обертка для полей, в которой содержится мэппинг свойств редакса и регистрация дополнительных свойств полей
 * @param Field
 * @returns {*}
 */
export default Field => {
  class FieldContainer extends React.Component {
    constructor(props) {
      super(props);
      this.onChange = this.onChange.bind(this);
      this.onFocus = this.onFocus.bind(this);
      this.onBlur = this.onBlur.bind(this);
      this.initIfNeeded(props);
    }

    /**
     * Регистрация дополнительных свойств поля
     */
    initIfNeeded(props) {
      const {
        meta: { form },
        input: { name },
        isInit,
        visibleToRegister,
        disabledToRegister,
        dependency,
        requiredToRegister,
        registerFieldExtra,
      } = props;
      !isInit &&
        registerFieldExtra(form, name, {
          visible: visibleToRegister,
          disabled: disabledToRegister,
          dependency,
          required: requiredToRegister,
        });
    }

    /**
     * мэппинг onChange
     * @param e
     */
    onChange(e) {
      const { input, onChange } = this.props;
      input && input.onChange(e);
      onChange && onChange(e);
    }

    /**
     * мэппинг onBlur
     * @param e
     */
    onBlur(e) {
      const {
        meta: { form },
        input: { name },
        value,
        input,
        onBlur,
      } = this.props;
      input && input.onBlur(e);
      onBlur && onBlur(e.target.value);
    }

    /**
     * мэппинг onFocus
     * @param e
     */
    onFocus(e) {
      const { input, onFocus } = this.props;
      input && input.onFocus(e);
      onFocus && onFocus(e.target.value);
    }

    /**
     * мэппинг сообщений
     * @param error
     * @returns {string}
     */
    getValidationState(message) {
      if (!message) return;
      if (message.severity === 'success') {
        return 'is-valid';
      } else if (message.severity === 'warning') {
        return 'has-warning';
      }
      return 'is-invalid';
    }

    /**
     * общий мэппинг
     * @returns {{validationClass: string, value: *, onChange: FieldContainer.onChange, onFocus: FieldContainer.onFocus, onBlur: FieldContainer.onBlur}}
     */
    mapProps() {
      const { input, message, meta, model, ...rest } = this.props;
      return {
        ...propsResolver(rest, model),
        ...meta,
        validationClass: this.getValidationState(message),
        message,
        ...input,
      };
    }

    /**
     * Бозовый рендер
     * @returns {*}
     */
    render() {
      return <Field {...this.mapProps()} />;
    }
  }

  const mapStateToProps = (state, ownProps) => {
    const { form } = ownProps.meta;
    const { name } = ownProps.input;
    return {
      isInit: isInitSelector(form, name)(state),
      visible: isVisibleSelector(form, name)(state),
      disabled: isDisabledSelector(form, name)(state),
      message: messageSelector(form, name)(state),
      required: requiredSelector(form, name)(state),
      model: getFormValues(form)(state),
    };
  };

  const mapDispatchToProps = {
    registerFieldExtra,
  };

  return compose(
    defaultProps({
      disabled: false,
      required: false,
    }),
    withProps(props => ({
      visibleToRegister: props.visible,
      disabledToRegister:
        isBoolean(props.enabled) && !props.disabled
          ? !props.enabled
          : props.disabled,
      requiredToRegister: props.required,
    })),
    connect(
      mapStateToProps,
      mapDispatchToProps
    ),
    withProps(props => ({
      ref: props.setReRenderRef,
    })),
    pure
  )(FieldContainer);
};