如何onFocus和onBlur连接到React Date Picker的React / Redux表单字段?

时间:2016-10-15 21:12:08

标签: reactjs datepicker redux redux-form react-day-picker

我有一个简单的v6 redux-form,其输入呈现一个自定义组件,使用react-day-picker填充其值。

https://github.com/gpbl/react-day-picker

我选择了反应日选择器而不是其他因为它不依赖于moment.js并且可以使用我当前的设置。

当我关注该字段时,我想要弹出日期选择器,但如果我点击不是日期选择器的任何地方,我希望它消失。

基本上,我希望我的React datepicker像jQueryUI一样工作:

https://jqueryui.com/datepicker/

我最终得出的三个场景是:

  1. 我单击该字段,弹出日期选择器,但不会消失,除非我选择日期或再次单击该字段(这对我们的需求来说过于严格)。

  2. 我单击该字段,弹出日期选择器,但如果我点击任意位置,将很快消失TOO,因为输入字段的onBlur在处理datepicker的click事件之前被调用以填充具有所选日期的字段

  3. 我单击该字段,弹出日期选择器,自动聚焦,正确模糊,除非我点击任何不是<体>或者是datepicker。

  4. 我首先尝试使用包含整个页面的兄弟空div,所以当我单击空div时,它会正确切换datepicker。这对z-indexes和position有效:修复直到我更改了datepicker的月份,这似乎重新渲染了datepicker并且与点击的顺序相混淆,这又导致了情况2)。

    我最近的尝试是在弹出时自动对焦datepicker div,所以当我模糊不是datepicker的任何东西时,它会切换datepicker。这在理论上起作用,除了datepicker是一个具有许多嵌套&lt;的组件。 div&gt;在里面控制日,周,月,禁用日...所以当我点击'day'时,它会记录模糊,因为我点击'day'<div>,而不是root 'datepicker'<div>,这是最初关注的内容。

    上述解决方案是调整'datepicker'<div>的onBlur,这样只有在document.activeElement为&lt;时才会切换日期选择器。 body&gt;,但只有在我不点击其他表单字段时才有效。

    WizardFormPageOne.js:

    function WizardFormPageOne({ handleSubmit }) {
      return (
        <form onSubmit={handleSubmit} className="col-xs-6">
          <h1>WizardFormPageOne</h1>
    
          <div className="card">
            <div className="card-block">
              <div className="form-group">
                <label htmlFor="first">Label 1</label>
                <Field type="text" name="first" component={DateInput} className="form-control" />
              </div>
              ...
    
    export default reduxForm({
      form: 'wizardForm',
      destroyOnUnmount: false,
    })(WizardFormPageOne);
    

    DateInput.js:

    import React from 'react';
    
    import styles from './styles.css';
    import DatePicker from '../DatePicker';
    
    class DateInput extends React.Component { // eslint-disable-line react/prefer-stateless-function
      constructor(props) {
        super(props);
    
        this.state = {
          dateValue: new Date(),
          activeDateWidget: false,
        };
      }
    
      changeDate = (date) => {
        this.setState({
          dateValue: date,
        });
      }
    
      changeActiveDateWidget = (e) => {
        e.stopPropagation();
        this.setState({
          activeDateWidget: !this.state.activeDateWidget,
        });
      }
    
      render() {
        const { input, meta } = this.props;
        const { dateValue, activeDateWidget } = this.state;
        return (
          <div className={styles.dateInput}>
            <input
              {...input}
              className="form-control"
              type="text"
              value={dateValue}
              onClick={this.changeActiveDateWidget}
              // onBlur={this.changeActiveDateWidget}
            />
    
            {activeDateWidget ? (
              <div>
                <DatePicker
                  changeActiveDateWidget={this.changeActiveDateWidget}
                  changeDate={this.changeDate}
                  dateValue={dateValue}
                />
              </div>
            ) : (
              <div></div>
            )}
          </div>
        );
      }
    }
    
    export default DateInput;
    

    DatePicker.js:

    import React from 'react';
    import 'react-day-picker/lib/style.css';
    import DayPicker, { DateUtils } from 'react-day-picker';
    
    import styles from './styles.css';
    import disabledDays from './disabledDays';
    
    
    class DatePicker extends React.Component { // eslint-disable-line react/prefer-stateless-function
      constructor(props) {
        super(props);
        this.state = {
          selectedDay: new Date(),
        };
      }
    
      componentDidMount() {
        if (this._input) {
          this._input.focus();
        }
      }
    
      handleDayClick = (e, day, { disabled }) => {
        e.stopPropagation();
        if (disabled) {
          return;
        }
        this.setState({ selectedDay: day }, () => {
          this.props.changeDate(day);
          this.props.changeActiveDateWidget();
        });
      }
    
      focusThisComponent = (e) => {
        if (e) {
          this._input = e;
        }
      }
    
      focusIt = () => {
        console.log('focusing');
      }
    
      blurIt = () => {
        console.log('blurring');
        setTimeout(() => {
          if (document.activeElement == document.body) {
            this.props.changeActiveDateWidget();
          }
        }, 1);
      }
    
      render() {
        const { changeActiveDateWidget } = this.props;
        const { selectedDay } = this.state;
        return (
          <div
            className={styles.datePicker}
            ref={this.focusThisComponent}
            tabIndex="1"
            onFocus={this.focusIt}
            onBlur={this.blurIt}
          >
            <DayPicker
              id="THISTHING"
              initialMonth={selectedDay}
              disabledDays={disabledDays}
              selectedDays={(day) => DateUtils.isSameDay(selectedDay, day)}
              onDayClick={this.handleDayClick}
            />
          </div>
        );
      }
    }
    
    export default DatePicker;
    

    以下是我现在遇到的问题的截屏视频:

    http://screencast.com/t/kZuIwUzl

    日期选择器正确切换,除非单击另一个字段,此时它会停止模糊/正确切换。我所有的修补工作都让我进入了上面列出的三个场景之一。

3 个答案:

答案 0 :(得分:1)

您可以使用此示例ShaneC并进行一些小修改,使其与redux-form v6兼容。您应该使用render函数中的redux-form this.props.input.value组件提供的Field,而不是使用本地状态。此外,在handleInputChange事件处理程序中,您必须在this.props.input.onChange(e.target.value)事件调用this.setState({ value: e.target.value })中调用handleInputBlur而不是this.props.input.onBlur(e.target.value)。您只需将其作为redux-form Field组件即可。

答案 1 :(得分:1)

我无法获得Steffen为我的方案工作的答案,如果您打开日期选择器,则http://react-day-picker.js.org/examples/?overlay中的示例无法正常模糊,请点击非活动状态部分日期选择器,然后在日期选择器外单击。我可能会在这一点上挑剔,他的解决方案可能更容易实现,但这就是我解决它的方法:

在DatePicker.js中,设置一个空数组,该数组将作为有效&lt;的集合。 DIV&GT;&#39; S。当触发onBlur时,调用一个递归函数,该函数采用根DatePicker&lt; div&gt ;,解析它的所有子节点,并将它们添加到空数组中。之后,检查document.activeElement以查看它是否在数组中。如果没有,则切换DatePicker小部件,否则不执行任何操作。

请注意,对于document.activeElement的检查必须在模糊后进行一次勾选,否则activeElement将为&lt;体&GT;

相关链接:

Get the newly focussed element (if any) from the onBlur event.

Which HTML elements can receive focus?

/**
*
* DatePicker
*
*/

import React from 'react';
import 'react-day-picker/lib/style.css';
import DayPicker, { DateUtils } from 'react-day-picker';

import styles from './styles.css';
import disabledDays from './disabledDays';

class DatePicker extends React.Component { // eslint-disable-line react/prefer-stateless-function
  constructor(props) {
    super(props);
    this.state = {
      selectedDay: new Date(),
    };

    this.validElements = [];
  }

  componentDidMount() {
    // Once DatePicker successfully sets a ref, component will mount
    // and autofocus onto DatePicker's wrapper div.
    if (this.refComponent) {
      this.refComponent.focus();
    }
  }

  setRefComponent = (e) => {
    if (e) {
      this.refComponent = e;
    }
  }

  findDatePickerDOMNodes = (element) => {
    if (element.hasChildNodes()) {
      this.validElements.push(element);
      const children = element.childNodes;
      for (let i = 0; i < children.length; i++) {
        this.validElements.push(children[i]);
        this.findDatePickerDOMNodes(children[i]);
      }
      return;
    }
  }

  handleDayClick = (e, day, { disabled }) => {
    if (disabled) {
      return;
    }
    this.setState({ selectedDay: day }, () => {
      this.props.changeDate(day);
      this.props.changeActiveDateWidget();
    });
  }

  handleBlur = () => {
    // Since DatePicker's wrapper div has been autofocused on mount, all
    // that needs to be done is to blur on anything that's not the DatePicker.
    // DatePicker has many child divs that handle things like day, week, month...
    // invoke a recursive function to gather all children of root DatePicker div, then run a test against valid DatePicker elements. If test fails, then changeActiveDateWidget.
    setTimeout(() => {
      const rootDatePickerElement = document.getElementById('datePickerWidget');
      this.findDatePickerDOMNodes(rootDatePickerElement);
      if (!this.validElements.includes(document.activeElement)) {
        this.props.changeActiveDateWidget();
      }
    }, 1);
  }

  render() {
    const { selectedDay } = this.state;
    return (
      <div
        className={styles.datePicker}
        onBlur={this.handleBlur}
        // tabIndex necessary for element to be auto-focused.
        tabIndex="1"
        ref={this.setRefComponent}
      >
        <DayPicker
          initialMonth={selectedDay}
          disabledDays={disabledDays}
          selectedDays={(day) => DateUtils.isSameDay(selectedDay, day)}
          onDayClick={this.handleDayClick}
          id="datePickerWidget"
        />
      </div>
    );
  }
}

DatePicker.propTypes = {
  changeDate: React.PropTypes.func,
  changeActiveDateWidget: React.PropTypes.func,
};

export default DatePicker;

并且在DateInput.js中,单击输入可能会导致切换触发两次,所以我只是将其设置为在单击输入时始终切换为true:

render() {
    const { input, meta } = this.props;
    const { dateValue, activeDateWidget } = this.state;
    return (
      <div className={styles.dateInput}>
        <input
          {...input}
          className="form-control"
          type="text"
          value={dateValue}
          onClick={() => { this.setState({ activeDateWidget: true }); }}
        />

答案 2 :(得分:1)

有点老问题,但我找不到简单的答案,所以这就是我所做的:

onBlur={(e) => { 
  var picker = document.querySelector(".DayPicker")
  if(!picker.contains(e.relatedTarget)){
    this.setState({showDayPicker: false}) 
  }
}}

我设置了一个隐藏DayPicker的标志,如果模糊不是来自点击DayPicker,否则会保持DayPicker显示