Geovation/photos

View on GitHub
src/components/PhotoPage/SelectControlMultipleValue.js

Summary

Maintainability
D
2 days
Test Coverage
// https://material-ui.com/demos/autocomplete/#react-select

/* eslint-disable react/prop-types, react/jsx-handler-names */

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Select from 'react-select';
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import NoSsr from '@material-ui/core/NoSsr';
import TextField from '@material-ui/core/TextField';
import Paper from '@material-ui/core/Paper';
import Chip from '@material-ui/core/Chip';
import MenuItem from '@material-ui/core/MenuItem';
import CancelIcon from '@material-ui/icons/Cancel';
import { emphasize } from '@material-ui/core/styles/colorManipulator';

const styles = theme => ({
  root: {
    flexGrow: 1,
    width:'100%',
    // margin:15,
    textAlign: 'center',
  },
  input: {
    display: 'flex',
    padding: 0,
  },
  valueContainer: {
    display: 'flex',
    flexWrap: 'wrap', //default
    flex: 1,
    alignItems: 'center',
    // overflow: 'hidden',  // default
  },
  chip: {
    margin: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 4}px`,
  },
  chipFocused: {
    backgroundColor: emphasize(
      theme.palette.type === 'light' ? theme.palette.grey[300] : theme.palette.grey[700],
      0.08,
    ),
  },
  noOptionsMessage: {
    padding: `${theme.spacing.unit}px ${theme.spacing.unit * 2}px`,
  },
  singleValue: {
    fontSize: 16,
  },
  placeholder: {
    position: 'absolute',
    left: 2,
    fontSize: 16,
  },
  paper: {
    position: 'absolute',
    zIndex: 1,
    marginTop: theme.spacing.unit,
    left: 0,
    right: 0,
  },
  divider: {
    height: theme.spacing.unit * 2,
  },
  cssUnderline: {
    '&:after': {
      borderBottomColor: theme.palette.secondary.main,
    },
  },
});

function NoOptionsMessage(props) {
  return (
    <Typography
      color="textSecondary"
      className={props.selectProps.classes.noOptionsMessage}
      {...props.innerProps}
    >
      {props.children}
    </Typography>
  );
}

function inputComponent({ inputRef, ...props }) {
  return <div ref={inputRef} {...props} />;
}

function Control(props) {
  return (
    <TextField
      fullWidth
      InputProps={{
        inputComponent,
        inputProps: {
          className: props.selectProps.classes.input,
          inputRef: props.innerRef,
          children: props.children,
          ...props.innerProps,
        },
      }}
      {...props.selectProps.textFieldProps}
    />
  );
}

function Option(props) {
  return (
    <MenuItem
      buttonRef={props.innerRef}
      selected={props.isFocused}
      component="div"
      style={{
        fontWeight: props.isSelected ? 500 : 400,

      }}
      {...props.innerProps}
    >
      {props.children}
    </MenuItem>
  );
}

function Placeholder(props) {
  return (
    <Typography
      color="textSecondary"
      className={props.selectProps.classes.placeholder}
      {...props.innerProps}
    >
      {props.children}
    </Typography>
  );
}

function SingleValue(props) {
  return (
    <Typography className={props.selectProps.classes.singleValue} {...props.innerProps}>
      {props.children}
    </Typography>
  );
}

function ValueContainer(props) {
  return <div id='valueContainer' className={props.selectProps.classes.valueContainer}>{props.children}</div>;
}

function MultiValue(props) {
  return (
    <Chip
      tabIndex={-1}
      label={props.children}
      className={classNames(props.selectProps.classes.chip, {
        [props.selectProps.classes.chipFocused]: props.isFocused,
      })}
      onDelete={props.removeProps.onClick}
      deleteIcon={<CancelIcon {...props.removeProps} />}
    />
  );
}

function Menu(props) {
  return (
    <Paper square className={props.selectProps.classes.paper} {...props.innerProps}>
      {props.children}
    </Paper>
  );
}

const components = {
  Control,
  Menu,
  MultiValue,
  NoOptionsMessage,
  Option,
  Placeholder,
  SingleValue,
  ValueContainer,
};

class SelectControlMultipleValue extends React.Component {
  state = {
    single: null,
    multi: null,
    options: [],
    menuIsOpen: false,
  };

  onBlurMenuShouldClose = true;
  options = [];
  items = [];

  handleInputChange = (input) => {
    let options;
    if (input==='') {
      options = this.options;
    }
    else{
      options = this.items;
    }
    this.setState({ options });
  }

  handleChange = name => values => {
    const valueFormated = values.length ? values[values.length - 1].key : '';
    this.props.handleChange(valueFormated);

    if (values.length !== 0) {
      let current_data = {...this.props.field.data};
      const findvaluePath = this.findOptions(this.props.field.data,values[values.length - 1].key);

      this.setState({ [name] : findvaluePath });
      // this.props.getValuesSelected(findvaluePath,this.props.selectId);

      Object.values(findvaluePath).forEach(value => {
        current_data = current_data[value.key].children;
        if (!current_data) {
          current_data = {};
        }
      });
      const new_values = this.changeOptions(current_data);
      this.options = new_values;
      this.setState({ options: new_values });
      this.controlMenuVisibility(current_data,new_values);
    }
    else {
      // this.props.getValuesSelected([],this.props.selectId);
      this.setState({ [name]: [] });
      this.initializeOptions(this.props.field.data)
      this.controlMenuVisibility(this.props.field.data,[]);
    }
  };

  controlMenuVisibility = (data,values) => {
    if (values.length === 0) {
      this.onBlurMenuShouldClose = true;
    }
    else {
      this.onBlurMenuShouldClose = false;
    }

    if (Object.entries(data).length === 0) {
      this.setState({ menuIsOpen: false });
    }
    else {
      this.setState({ menuIsOpen: true });
    }
  }

  changeOptions = (data) => {
    let values = [];
    Object.entries(data).forEach(([key,value])=>{
      values.push({ label: value.label, key: key });
    });
    return values;
  }

  findOptions = (tree,key_to_find) => {

    const stack = [];
    let listWithNodes = [];

    function findPathOfFoundedNode(tree,key_to_find) {
      Object.entries(tree).forEach(([key,value]) => {
        if (key_to_find === key){
          const foundedNode = { label:value.label,key:key }
          listWithNodes = [...stack,foundedNode];
        }
        if(value.children){
          stack.push({ label:value.label, key:key });
          findPathOfFoundedNode(value.children,key_to_find);
          stack.pop();
        }
      });
    }
    findPathOfFoundedNode(tree,key_to_find);
    return listWithNodes;
  }

  getItems = (tree) => {
    let items = [];

    function getNodesInLowestHierarchy(tree){
      Object.entries(tree).forEach( ([key,value]) => {
        if (!value.children) {
          items.push({ label: value.label, key: key });
        }
        else {
          getNodesInLowestHierarchy(value.children);
        }
      });
    }

    getNodesInLowestHierarchy(tree);
    return items;
  }

  initializeOptions = (data) => {
    const options = Object
                  .entries(data)
                  .map(([key,value]) => ({label: value.label, key: key }));
    this.options = options;
    this.setState({ options });
  }

  componentDidMount(){
    this.items = this.getItems(this.props.field.data);
    this.initializeOptions(this.props.field.data);
  }

  render() {
    // TODO add propTypes
    const { classes, theme, field} = this.props;

    const selectStyles = {
      input: base => ({
        ...base,
        color: theme.palette.text.primary,
        '& input': {
          font: 'inherit',
        },
      }),
    };
    return (
      <div className={classes.root}>
        <NoSsr>
          <Select
            classes={classes}
            styles={selectStyles}
            components={components}
            value={this.state.multi}
            onChange={this.handleChange('multi')}
            menuPosition='fixed'
            placeholder={field.placeholder}
            options={this.state.options}
            isMulti
            getOptionValue={(option) => (option['label'])}
            noOptionsMessage={() => field.noOptionsMessage}
            onInputChange={(e) => this.handleInputChange(e)}
            menuIsOpen={this.state.menuIsOpen}
            onFocus={() => this.setState({ menuIsOpen: true })}
            onBlur={() => {
              if (this.onBlurMenuShouldClose) {
                this.setState({ menuIsOpen: false });
              }
            }}
          />
        </NoSsr>
      </div>
    );
  }
}

// TODO: describe the props.
SelectControlMultipleValue.propTypes = {
  classes: PropTypes.object.isRequired,
  theme: PropTypes.object.isRequired,
};

export default withStyles(styles, { withTheme: true })(SelectControlMultipleValue);