Как onFocus и onBlur поле формы React / Redux, подключенное к React Date Picker?

У меня есть эта простая редукционная форма v6 с вводом, который отображает настраиваемый компонент, который заполняет его значение с помощью средства выбора response-day.

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

Я предпочел response-day-picker другим, потому что он не зависит от moment.js и работает с моими текущими настройками.

Когда я фокусирую поле, я хочу, чтобы всплывала кнопка выбора даты, но если я нажимаю где-нибудь, кроме нее, я хочу, чтобы она исчезла.

По сути, я хочу, чтобы мой DatePicker React работал как jQueryUI:

https://jqueryui.com/datepicker/

Я получаю три сценария:

  1. Я щелкаю поле, всплывает указатель даты, но он не исчезнет, ​​если я не выберу дату или не щелкну поле еще раз (это слишком жестко для наших нужд).

  2. Я щелкаю поле, всплывает указатель даты, но он исчезает СЛИШКОМ быстро, если я щелкну в любом месте, поскольку поле ввода onBlur вызывается до того, как он обработает событие щелчка для средства выбора даты, чтобы заполнить поле выбранной датой.

  3. Я щелкаю поле, всплывает указатель даты, автоматически фокусируется, размывается должным образом, за исключением случаев, когда я щелкаю что-либо, кроме или указателя даты.

Сначала я попытался использовать пустой div-брат-брат, который обертывает всю страницу, поэтому, когда я щелкаю пустой div, он правильно переключает datepicker. Это работало нормально с z-индексами и position: fixed, пока я не изменил месяц datepicker, который, кажется, повторно отображает datepicker и испортил порядок щелчка, что снова привело к ситуации 2).

Моя самая последняя попытка - автоматически сфокусировать div datepicker, когда он появляется, поэтому, когда я размываю что-либо, кроме datepicker, он будет переключать datepicker. Теоретически это работало, за исключением того, что datepicker - это компонент со множеством вложенных ‹div> внутри него для управления днем, неделей, месяцем, отключенными днями ... поэтому, когда я нажимаю 'день', он регистрирует размытие, потому что я ' m щелкнув по «day» <div>, а не по корневому «datepicker» <div>, на котором изначально был сделан упор.

Решением вышеизложенного было настроить onBlur 'datepicker' <div> так, чтобы он переключал datepicker только тогда, когда document.activeElement имеет ‹body>, но это работает, только если я не нажимаю другое поле формы.

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

Средство выбора даты переключается правильно, за исключением случаев, когда щелчок по другому полю прекращает правильное размытие / переключение. Все мои попытки привели меня к одному из трех сценариев, перечисленных выше.


person Kyle Truong    schedule 15.10.2016    source источник


Ответы (3)


Вы можете взять этот пример http://react-day-picker.js.org/examples/?overlay и внесите небольшие изменения, чтобы сделать его совместимым с redux-form v6. Вместо использования локального состояния вы должны использовать this.props.input.value, предоставленный компонентом redux-form Field внутри вашей функции рендеринга. Кроме того, в обработчике событий handleInputChange вы должны вызвать this.props.input.onChange(e.target.value) вместо this.setState({ value: e.target.value }) и в handleInputBlur вызове события this.props.input.onBlur(e.target.value). Это все, что вам нужно сделать, чтобы он работал как Field компонент редукционной формы.

person Steffen    schedule 15.10.2016

Мне не удалось заставить ответ Штеффена работать для моего сценария и примера в http://react-day-picker.js.org/examples/?overlay не размывается должным образом, если вы открываете средство выбора даты, щелкаете неактивные части средства выбора даты, а затем щелкните за пределами средства выбора даты. Возможно, я придираюсь к этому моменту, и его решение, вероятно, намного проще реализовать, но вот что я сделал для его решения:

В DatePicker.js установите пустой массив, который будет служить набором действительных ‹div>. Когда запускается onBlur, вызовите рекурсивную функцию, которая берет корневой DatePicker ‹div>, анализирует все его дочерние элементы и добавляет их в пустой массив. После этого проверьте document.activeElement, чтобы увидеть, есть ли он в массиве. Если нет, то переключите виджет DatePicker, иначе ничего не делайте.

Обратите внимание, что проверка document.activeElement должна выполняться через один тик после размытия, иначе activeElement будет ‹body>.

Ссылки по теме:

Получите вновь сфокусированный элемент (если есть) из событие onBlur.

Какие элементы HTML могут получать фокус?

/**
*
* 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 }); }}
        />
person Kyle Truong    schedule 16.10.2016
comment
Возможно, вы захотите проверить другие компоненты средства выбора даты. На мой взгляд, открытие / закрытие фокуса должно правильно обрабатываться компонентом. В качестве обходного пути вы можете обернуть компонент внутри компонента следующим образом: github.com/nkbt/react- щелчок по странице. Если вы открыты для других реализаций, обратите внимание на этот компонент: airbnb.io/react-dates/ - person Steffen; 16.10.2016
comment
response-page-click выглядит потрясающе и избавит нас от многих головных болей. Я выбрал Airbnb datepicker первым, хотя я просто не мог запустить простой пример в приложении create-react-app, не говоря уже о том, чтобы выяснить, не будет ли он конфликтовать с зависимостями из mxstbr / react-steamlainplate. - person Kyle Truong; 16.10.2016

Немного старый вопрос, но я не мог найти простого ответа, поэтому вот что я сделал:

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

Я устанавливаю флаг, который скрывает DayPicker, если размытие не возникает из-за нажатия на DayPicker, в противном случае он сохраняет отображение DayPicker

person Wojciechu    schedule 08.05.2018