如何对连接到 React Date Picker 的 React/Redux 表单字段进行 onFocus 和 onBlur?
How to onFocus and onBlur a React/Redux form field that's connected to React Date Picker?
我得到了这个简单的 v6 redux-form,它带有一个输入,该输入呈现一个自定义组件,该组件使用 react-day-picker 填充其值。
https://github.com/gpbl/react-day-picker
我选择 react-day-picker 而不是其他的,因为它不依赖于 moment.js 并且适用于我当前的设置。
当我聚焦该字段时,我希望弹出日期选择器,但如果我单击不是日期选择器的任何地方,我希望它消失。
基本上,我希望我的 React 日期选择器像 jQueryUI 一样工作:
https://jqueryui.com/datepicker/
我最终遇到的三种情况是:
我点击该字段,弹出日期选择器,但不会消失,除非我select一个日期或再次点击该字段(这对我们的需求来说太死板了)。
我单击该字段,弹出日期选择器,但如果我单击任何地方,它会很快消失,因为输入字段的 onBlur 在它处理日期选择器的单击事件以填充该字段之前被调用选择日期。
我单击该字段,弹出日期选择器,自动聚焦,正确模糊,除非我单击任何不是
或日期选择器的东西。
我首先尝试使用包围整个页面的同级空白 div,因此当我单击空白 div 时,它会正确切换日期选择器。这对 z-indexes 和 position: fixed 有效,直到我更改了日期选择器的月份,这似乎重新渲染了日期选择器并弄乱了点击的顺序,这又导致了情况 2)。
我最近的尝试是在日期选择器弹出时自动对焦 div,所以当我模糊任何不是日期选择器的东西时,它会切换日期选择器。这在理论上是可行的,除了日期选择器是一个组件,里面有许多嵌套的 < div> 来控制日、周、月、禁用日……所以当我点击 'day' 时,它注册模糊,因为我单击的是 'day' <div>
,而不是最初关注的根 'datepicker' <div>
。
上述问题的解决方案是调整 'datepicker' <div>
的 onBlur,使其仅在 document.activeElement 为
时才切换日期选择器,但这仅在以下情况下有效我没有单击其他表单域。
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
日期选择器正确切换,除非单击另一个字段,此时它会正确停止 blurring/toggling。我所有的修补要么导致我做出上面列出的三种情况之一。
你可以拿这个例子 http://react-day-picker.js.org/examples/?overlay 做一些小的修改,使其与 redux-form v6 兼容。您应该在渲染函数中使用 redux-form Field
组件提供的 this.props.input.value
而不是使用本地状态。此外,在 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
组件工作所需要做的全部。
我无法让 Steffen 的答案适用于我的场景,如果您打开日期选择器,单击日期选择器的非活动部分,http://react-day-picker.js.org/examples/?overlay 中的示例不会正确模糊,然后在日期选择器外部单击。我可能在这一点上吹毛求疵,他的解决方案可能更容易实施,但我是这样做的:
在 DatePicker.js 中,设置一个空数组作为有效 < div> 的集合。当 onBlur 被触发时,调用一个递归函数,该函数采用根 DatePicker < div>,解析它的所有子项,并将它们添加到空数组中。之后,检查 document.activeElement 以查看它是否在数组中。如果不是,则切换 DatePicker 小部件,否则,什么都不做。
请注意,document.activeElement 的检查必须在模糊后一个 tick 完成,否则 activeElement 将为
。
相关链接:
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中,点击输入可能会导致切换触发两次,所以我只是将其设置为点击输入时始终切换为真:
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 }); }}
/>
这个问题有点老了,但我找不到简单的答案,所以这就是我所做的:
onBlur={(e) => {
var picker = document.querySelector(".DayPicker")
if(!picker.contains(e.relatedTarget)){
this.setState({showDayPicker: false})
}
}}
如果模糊不是来自单击 DayPicker,我将设置一个隐藏 DayPicker 的标志,否则它会保持 DayPicker 显示
我得到了这个简单的 v6 redux-form,它带有一个输入,该输入呈现一个自定义组件,该组件使用 react-day-picker 填充其值。
https://github.com/gpbl/react-day-picker
我选择 react-day-picker 而不是其他的,因为它不依赖于 moment.js 并且适用于我当前的设置。
当我聚焦该字段时,我希望弹出日期选择器,但如果我单击不是日期选择器的任何地方,我希望它消失。
基本上,我希望我的 React 日期选择器像 jQueryUI 一样工作:
https://jqueryui.com/datepicker/
我最终遇到的三种情况是:
我点击该字段,弹出日期选择器,但不会消失,除非我select一个日期或再次点击该字段(这对我们的需求来说太死板了)。
我单击该字段,弹出日期选择器,但如果我单击任何地方,它会很快消失,因为输入字段的 onBlur 在它处理日期选择器的单击事件以填充该字段之前被调用选择日期。
我单击该字段,弹出日期选择器,自动聚焦,正确模糊,除非我单击任何不是
或日期选择器的东西。
我首先尝试使用包围整个页面的同级空白 div,因此当我单击空白 div 时,它会正确切换日期选择器。这对 z-indexes 和 position: fixed 有效,直到我更改了日期选择器的月份,这似乎重新渲染了日期选择器并弄乱了点击的顺序,这又导致了情况 2)。
我最近的尝试是在日期选择器弹出时自动对焦 div,所以当我模糊任何不是日期选择器的东西时,它会切换日期选择器。这在理论上是可行的,除了日期选择器是一个组件,里面有许多嵌套的 < div> 来控制日、周、月、禁用日……所以当我点击 'day' 时,它注册模糊,因为我单击的是 'day' <div>
,而不是最初关注的根 'datepicker' <div>
。
上述问题的解决方案是调整 'datepicker' <div>
的 onBlur,使其仅在 document.activeElement 为
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
日期选择器正确切换,除非单击另一个字段,此时它会正确停止 blurring/toggling。我所有的修补要么导致我做出上面列出的三种情况之一。
你可以拿这个例子 http://react-day-picker.js.org/examples/?overlay 做一些小的修改,使其与 redux-form v6 兼容。您应该在渲染函数中使用 redux-form Field
组件提供的 this.props.input.value
而不是使用本地状态。此外,在 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
组件工作所需要做的全部。
我无法让 Steffen 的答案适用于我的场景,如果您打开日期选择器,单击日期选择器的非活动部分,http://react-day-picker.js.org/examples/?overlay 中的示例不会正确模糊,然后在日期选择器外部单击。我可能在这一点上吹毛求疵,他的解决方案可能更容易实施,但我是这样做的:
在 DatePicker.js 中,设置一个空数组作为有效 < div> 的集合。当 onBlur 被触发时,调用一个递归函数,该函数采用根 DatePicker < div>,解析它的所有子项,并将它们添加到空数组中。之后,检查 document.activeElement 以查看它是否在数组中。如果不是,则切换 DatePicker 小部件,否则,什么都不做。
请注意,document.activeElement 的检查必须在模糊后一个 tick 完成,否则 activeElement 将为
。相关链接:
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中,点击输入可能会导致切换触发两次,所以我只是将其设置为点击输入时始终切换为真:
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 }); }}
/>
这个问题有点老了,但我找不到简单的答案,所以这就是我所做的:
onBlur={(e) => {
var picker = document.querySelector(".DayPicker")
if(!picker.contains(e.relatedTarget)){
this.setState({showDayPicker: false})
}
}}
如果模糊不是来自单击 DayPicker,我将设置一个隐藏 DayPicker 的标志,否则它会保持 DayPicker 显示