如何onFocus和onBlur连接到React Date Picker的React / Redux表单字段? [英] How to onFocus and onBlur a React/Redux form field that's connected to React Date Picker?

查看:93
本文介绍了如何onFocus和onBlur连接到React Date Picker的React / Redux表单字段?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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



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



当我关注该字段时,我希望弹出日期选择器,但是如果我单击的位置不是日期选择器,则希望它消失。



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



https://jqueryui.com/datepicker/



我最终遇到的三种情况是:


  1. 我单击该字段,将弹出日期选择器,但是除非我选择一个日期或再次单击该字段,否则不会消失。


  2. 我单击该字段,日期选择器会弹出,但是如果我单击任意位置,它将很快消失,因为输入字段的onBlur


  3. 我单击该字段,日期选择器弹出,自动获取日期,聚焦,适当模糊,除非当我单击非< body>或datepicker。


我首先尝试使用一个兄弟级的空div来包装整个页面,所以当我单击空的div,它将正确切换日期选择器。可以在z-indexes和position上正常工作:固定,直到我更改了日期选择器的月份,这似乎重新渲染了日期选择器,并弄乱了单击的顺序,再次导致了情况2)。



我最近的尝试是将datepicker div弹出时自动聚焦,因此当我模糊非datepicker的任何内容时,它将切换datepicker。从理论上讲,这是可行的,只是datepicker是一个具有许多嵌套< div>位于其中以控制日,周,月,禁用日...因此,当我单击天时,它会记录模糊效果,因为我单击的是天 < div> ; ,而不是根'datepicker'

> ,这是最初的重点。



上述解决方案是调整'datepicker'< div> 的onBlur,以便仅当document.activeElement为< ; body>,但这仅在我不单击其他表单字段的情况下才有效。



WizardFormPageOne.js:

 函数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名称= first component = {DateInput} className = form-control />
< / div>
...

导出默认reduxForm({
形式: wizardForm,
destroyOnUnmount:false,
})(WizardFormPageOne);

DateInput.js:

  import从'react'进行反应; 
'./styles.css'中的
导入样式;
导入DatePi来自 ../DatePicker的点击;

类DateInput扩展了React.Component {// eslint-disable-line react / prefer-stateless-function
Constructor(props){
super(props ;;

this.state = {
dateValue:new Date(),
activeDateWidget:false,
};
}

changeDate =(日期)=> {
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>
);
}
}

导出默认的DateInput;

DatePicker.js:

  import从'react'进行反应; 
导入 react-day-picker / lib / style.css;
import DayPicker,{DateUtils},来自 react-day-picker; $ .b.b.b.b.b.b.b.b.b.b.bs中的

导入样式。
的 disabledDays来自 ./disabledDays;


类DatePicker扩展了React.Component {// eslint-disable-line react / prefer-stateless-function
构造函数(props){
super(props) ;
this.state = {
selectedDay:new Date(),
};
}

componentDidMount(){
if(this._input){
this._input.focus();
}
}

handleDayClick =(e,day,{disabled})=> {
e.stopPropagation();
,如果(已停用){
返回;
}
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>
);
}
}

导出默认的DatePicker;

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



http://screencast.com/t/kZuIwUzl



日期选择器可以正确切换,但单击另一个字段时除外,这时它将停止正确地模糊/切换。我所有的修改都使我陷入了上面列出的三种情况之一。

解决方案

我无法获得史蒂芬的答案对于我的情况,以及 http://react-day-picker.js中的示例如果打开日期选择器,单击日期选择器的非活动部分,然后在日期选择器外部单击,.org / examples /?overlay 不会正确模糊。现在我可能会很挑剔,他的解决方案可能更容易实现,但这是我要做的解决方案:



在DatePicker.js中,设置一个空用作有效<的集合的数组div>。触发onBlur时,调用以root DatePicker<为根的递归函数。 div>,解析所有子项,然后将其添加到空数组中。之后,检查document.activeElement以查看它是否在数组中。如果不是,则切换DatePicker窗口小部件,否则不执行任何操作。



请注意,对document.activeElement的检查必须在模糊之后一刻完成,否则activeElement将<正文>。



相关链接:



从onBlur事件中获取新关注的元素(如果有的话)。



哪些HTML元素可以获得焦点?

  / ** 
*
* DatePicker
*
* /

import来自反应;
导入 react-day-picker / lib / style.css;
import DayPicker,{DateUtils},来自 react-day-picker; $ .b.b.b.b.b.b.b.b.b.b.bs中的

导入样式。
的 disabledDays来自 ./disabledDays;

类DatePicker扩展了React.Component {// eslint-disable-line react / prefer-stateless-function
Constructor(props){
super(props ;;
this.state = {
selectedDay:new Date(),
};

this.validElements = [];
}

componentDidMount(){
//一旦DatePicker成功设置了参考,组件将安装
//并将其自动聚焦到DatePicker的包装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(让i = 0; i< children.length; i ++){
this.validElements.push(children [i]);
this.findDatePickerDOMNodes(children [i]);
}
回报;
}
}

handleDayClick =(e,day,{disabled})=> {
,如果(已禁用){
返回;
}
this.setState({selectedDay:day},()=> {
this.props.changeDate(day);
this.props.changeActiveDateWidget();
});
}

handleBlur =()=> {
// //由于DatePicker的包装div已自动聚焦在安装上,因此
//所需要做的就是模糊除DatePicker之外的所有内容。
// DatePicker有许多子div,可以处理诸如日,周,月之类的东西...
//调用递归函数来收集根DatePicker div的所有子项,然后对有效的DatePicker元素进行测试。如果测试失败,则更改ActiveDateWidget。
setTimeout(()=> {
const rootDatePickerElement = document.getElementById('datePickerWidget');
this.findDatePickerDOMNodes(rootDatePickerElement);
if(!this.validElements。 include(document.activeElement)){
this.props.changeActiveDateWidget();
}
},1);
}

render(){
const {selectedDay} = this.state;
return(
< div
className = {styles.datePicker}
onBlur = {this.handleBlur}
//要使元素自动聚焦所需的tabIndex 。
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,
};

导出默认的DatePicker;

在DateInput.js中,单击输入可能会导致切换触发两次,所以我只设置了如果单击输入,它将始终切换为true:

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


I've got this simple v6 redux-form with an input that renders a custom component that populates its value using react-day-picker.

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

I chose react-day-picker over others because it doesn't depend on moment.js and works with my current set up.

When I focus the field, I want the datepicker to pop up, but if I click anywhere that's not the datepicker, I want it to disappear.

Essentially, I want my React datepicker to work like the jQueryUI one in:

https://jqueryui.com/datepicker/

The three scenarios I end up with are:

  1. I click the field, the datepicker pops up, but will not disappear unless I select a date or click the field again (this is too rigid for our needs).

  2. I click the field, the datepicker pops up, but will disappear TOO quickly if I click anywhere, as the input field's onBlur gets called before it processes the click event for the datepicker to populate the field with the chosen date.

  3. I click the field, the datepicker pops up, gets auto-focused, blurs properly, except when I click anything that's not < body> or the datepicker.

I first tried to use a sibling empty div that wraps the whole page, so when I click the empty div, it'll toggle the datepicker properly. This worked OK with z-indexes and position: fixed until I changed the datepicker's month, which seems to re-render the datepicker and messed with the order of the clicking, which led to situation 2) again.

My most current attempt is to auto-focus the datepicker div when it pops up, so when I blur anything that's not the datepicker, it will toggle the datepicker. This worked in theory, except the datepicker is a component with many nested < div>'s inside it to control day, week, month, disabled days... so when I click a 'day', it registers a blur because I'm clicking the 'day' <div>, not the root 'datepicker' <div>, which is what was initially focused.

The solution to the above was to tweak 'datepicker' <div>'s onBlur such that it will only toggle the datepicker when document.activeElement is < body>, but that only works if I don't click another form field.

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;

Here's a screencast of the issue I'm having now:

http://screencast.com/t/kZuIwUzl

The datepicker toggles properly, except when clicking on another field, at which point it stops blurring/toggling properly. All my tinkering either led me make to one of the three scenarios listed above.

解决方案

I couldn't get Steffen's answer to work for my scenario, and the example in http://react-day-picker.js.org/examples/?overlay doesn't blur properly if you open the date picker, click the non-active parts of the date picker, then click outside the date picker. I may be nitpicking at this point, and his solution is probably far easier to implement, but here's what I did to solve it:

In DatePicker.js, set an empty array that will serve as a collection of valid < div>'s. When onBlur is triggered, invoke a recursive function that takes the root DatePicker < div>, parses all it's children, and add them to the empty array. After that, check document.activeElement to see if it's in the array. If not, then toggle the DatePicker widget, else, do nothing.

Note that the check for document.activeElement must be done one tick after the blur, or else activeElement will be < body>.

Related links:

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;

and in DateInput.js, clicking the input may cause the toggle to trigger twice, so i just set it to always toggle true if clicking the input:

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 }); }}
        />

这篇关于如何onFocus和onBlur连接到React Date Picker的React / Redux表单字段?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆