<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import CaptionLegend from './CaptionLegend';
import ChoiceSet from '../../BuiltInFormControls/ChoiceSet';

const OTHER_VALUE = '_OTHER_VALUE';

class MultipleChoiceItemInput extends React.Component {
  static propTypes = {
    formItem: PropTypes.shape({
      identifier: PropTypes.string.isRequired,
      properties: PropTypes.shape({
        style: PropTypes.string,
        caption: PropTypes.string.isRequired,
        multiple: PropTypes.bool,
        choices: PropTypes.arrayOf(PropTypes.shape({
          caption: PropTypes.string.isRequired,
          value: PropTypes.string.isRequired,
        }).isRequired).isRequired,
        other: PropTypes.bool,
        other_caption: PropTypes.string,
      }).isRequired,
    }).isRequired,
    onChange: PropTypes.func.isRequired,
    onInteract: PropTypes.func.isRequired,
    value: PropTypes.any, // eslint-disable-line react/forbid-prop-types
    valueInvalid: PropTypes.bool,
  };

  static defaultProps = {
    value: null,
    valueInvalid: false,
  };

  constructor(props) {
    super(props);

    let otherValue = '';
    if (this.isOtherSelected()) {
      if (this.isMultiple()) {
        const choiceValues = this.props.formItem.properties.choices.map(choice => choice.value);
        otherValue = this.props.value.find((
          selectedChoiceValue => !choiceValues.includes(selectedChoiceValue)
        ));
      } else {
        otherValue = this.props.value;
      }
    }

    this.state = { otherValue };
  }

  onChange = (newValue) => {
    this.userDidInteract();

    if (this.props.formItem.properties.multiple) {
      const actualValue = newValue.filter(choiceValue => choiceValue !== OTHER_VALUE);
      if (newValue.includes(OTHER_VALUE)) {
        actualValue.push(this.state.otherValue);
      }
      this.props.onChange(actualValue);
    } else if (newValue === OTHER_VALUE) {
      this.props.onChange(this.state.otherValue);
    } else {
      this.props.onChange(newValue);
    }
  }

  getValueForChoiceSet = () => {
    if (this.isMultiple()) {
      const originalValue = this.props.value || [];
      const typecastValue = (Array.isArray(originalValue) ? originalValue : [originalValue])
        .map(singleValue => singleValue.toString());
      if (this.isOtherSelected()) {
        typecastValue.push(OTHER_VALUE);
      }

      return typecastValue;
    }

    if (this.isOtherSelected()) {
      return OTHER_VALUE;
    }

    if (this.props.value != null) {
      return this.props.value.toString();
    }

    return null;
  }

  isMultiple = () => ['checkbox_horizontal', 'checkbox_vertical'].includes(this.props.formItem.properties.style)

  isOtherSelected = () => {
    if (!this.props.formItem.properties.other) {
      return false;
    }

    const choiceValues = this.props.formItem.properties.choices.map(choice => choice.value);

    if (this.props.formItem.properties.multiple) {
      return !this.props.value.every((
        selectedChoiceValue => choiceValues.includes(selectedChoiceValue)
      ));
    }

    return this.props.value != null && !choiceValues.includes(this.props.value);
  }

  userDidInteract = () => {
    this.props.onInteract(this.props.formItem.identifier);
  }

  otherValueDidChange = (event) => {
    this.userDidInteract();
    this.setState(
      { otherValue: event.target.value },
      () => this.onChange(this.getValueForChoiceSet()), // recalculate value
    );
  }

  renderOtherInput = () => {
    if (!this.isOtherSelected()) {
      return null;
    }

    return (
      <input
        type="text"
        className="form-control"<commit_msg>Fix behavior of multiple-choice checkbox input with an 'other' option (closes #2204)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import CaptionLegend from './CaptionLegend';
import ChoiceSet from '../../BuiltInFormControls/ChoiceSet';

const OTHER_VALUE = '_OTHER_VALUE';

class MultipleChoiceItemInput extends React.Component {
  static propTypes = {
    formItem: PropTypes.shape({
      identifier: PropTypes.string.isRequired,
      properties: PropTypes.shape({
        style: PropTypes.string,
        caption: PropTypes.string.isRequired,
        multiple: PropTypes.bool,
        choices: PropTypes.arrayOf(PropTypes.shape({
          caption: PropTypes.string.isRequired,
          value: PropTypes.string.isRequired,
        }).isRequired).isRequired,
        other: PropTypes.bool,
        other_caption: PropTypes.string,
      }).isRequired,
    }).isRequired,
    onChange: PropTypes.func.isRequired,
    onInteract: PropTypes.func.isRequired,
    value: PropTypes.any, // eslint-disable-line react/forbid-prop-types
    valueInvalid: PropTypes.bool,
  };

  static defaultProps = {
    value: null,
    valueInvalid: false,
  };

  constructor(props) {
    super(props);

    let otherValue = '';
    if (this.isOtherSelected()) {
      if (this.isMultiple()) {
        const choiceValues = this.props.formItem.properties.choices.map(choice => choice.value);
        otherValue = this.props.value.find((
          selectedChoiceValue => !choiceValues.includes(selectedChoiceValue)
        ));
      } else {
        otherValue = this.props.value;
      }
    }

    this.state = { otherValue };
  }

  onChange = (newValue) => {
    this.userDidInteract();

    if (this.isMultiple()) {
      const choiceValues = this.props.formItem.properties.choices.map(choice => choice.value);
      const providedValues = newValue.filter(choiceValue => choiceValues.some(providedValue => providedValue === choiceValue));
      if (newValue.includes(OTHER_VALUE)) {
        this.props.onChange([...providedValues, this.state.otherValue]);
      } else {
        this.props.onChange(providedValues);
      }
    } else if (newValue === OTHER_VALUE) {
      this.props.onChange(this.state.otherValue);
    } else {
      this.props.onChange(newValue);
    }
  }

  getValueForChoiceSet = () => {
    if (this.isMultiple()) {
      const originalValue = this.props.value || [];
      const typecastValue = (Array.isArray(originalValue) ? originalValue : [originalValue])
        .map(singleValue => singleValue.toString());
      if (this.isOtherSelected()) {
        typecastValue.push(OTHER_VALUE);
      }

      return typecastValue;
    }

    if (this.isOtherSelected()) {
      return OTHER_VALUE;
    }

    if (this.props.value != null) {
      return this.props.value.toString();
    }

    return null;
  }

  isMultiple = () => ['checkbox_horizontal', 'checkbox_vertical'].includes(this.props.formItem.properties.style)

  isOtherSelected = () => {
    if (!this.props.formItem.properties.other) {
      return false;
    }

    const choiceValues = this.props.formItem.properties.choices.map(choice => choice.value);

    if (this.isMultiple()) {
      return !this.props.value.every((
        selectedChoiceValue => choiceValues.includes(selectedChoiceValue)
      ));
    }

    return this.props.value != null && !choiceValues.includes(this.props.value);
  }

  userDidInteract = () => {
    this.props.onInteract(this.props.formItem.identifier);
  }

  otherValueDidChange = (event) => {
    this.userDidInteract();
    this.setState(
      { otherValue: event.target.value },
      () => this.onChange(this.getValueForChoiceSet()), // recalculate value
    );
  }

  renderOtherInput = () => {
    if (!this.isOtherSelected()) {
      return null;
    }

    return (
      <input
        type="text"
        className="form-control"
{'fraction_non_alphanumeric': 0.13127210795231234, 'fraction_numerical': 0.0005240403511070353, 'mean_word_length': 2.914871794871795, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5563760', 'n_tokens_mistral': 2233, 'n_tokens_neox': 2213, 'n_words': 383}