Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Autocomplete] Add ability to append to value instead of replacing it #19267

Closed
1 task done
jdoklovic opened this issue Jan 16, 2020 · 8 comments
Closed
1 task done
Labels
component: autocomplete This is the name of the generic UI component, not the React module! support: Stack Overflow Please ask the community on Stack Overflow

Comments

@jdoklovic
Copy link

  • I have searched the issues of this repository and believe that this is not a duplicate.

Summary 💡

I have an autocomplete that parses the input and provides different sets of options depending where you are in the text. Think about this like autocompleting a "query" field:

  1. user types "proj" and gets options: [project, projectPermission]
    1.1 user selects "project" and "project" is the current input value.
  2. user types and gets options: [=,!=]
    2.1 user selects "=" and the value is updated to "project ="
  3. user types m and gets options: [myProject,mystery,mattsProject,...]
    3.1 user selects "myProject" and value is now "project = myProject"

Currently I have the options displaying properly with free solo and it "works" if I ignore the options and just keep typing, but as soon as I click on an option it replaces the entire value with the option clicked.

Currently I'm just using the Autocomplete component so I'm not sure if there's a way to swipe the onClick handler of the options and get it to do what I want and maintain all the other functionality. I haven't tried useAutocomplete yet, so I'm not sure if that would help.

Examples 🌈

essentially I want to build something like this:
jql-editor

@oliviertassinari oliviertassinari added the component: autocomplete This is the name of the generic UI component, not the React module! label Jan 16, 2020
@mikkopursuittechnology
Copy link

@jdoklovic Have you tried using multiple?

@oliviertassinari oliviertassinari added the support: Stack Overflow Please ask the community on Stack Overflow label Jan 17, 2020
@support
Copy link

support bot commented Jan 17, 2020

👋 Thanks for using Material-UI!

We use GitHub issues exclusively as a bug and feature requests tracker, however,
this issue appears to be a support request.

For support, please check out https://material-ui.com/getting-started/support/. Thanks!

If you have a question on StackOverflow, you are welcome to link to it here, it might help others.
If your issue is subsequently confirmed as a bug, and the report follows the issue template, it can be reopened.

@support support bot closed this as completed Jan 17, 2020
@oliviertassinari
Copy link
Member

oliviertassinari commented Jan 17, 2020

@jdoklovic It's supported, you would need to:

  1. Configure getOptionLabel to render the correct input value (the full string)
  2. Configure renderOption to render the correct option value (the incremental change string)

@mikkopursuittechnology Thanks for the interest in the topic :). I believe multiple isn't necessary.

@jdoklovic
Copy link
Author

@oliviertassinari I'm not sure I'm following this. perhaps I'm misunderstanding what getOptionLabel and renderOption are used for. I assumed getOptionLabel was to display the labels in the drop down and renderOption was to render the option in the dropdown.

It seems like you're saying getOptionLabel is used to determine the new value for the input?

@jdoklovic
Copy link
Author

@oliviertassinari Sorry, one last question... I have this somewhat working, but was just wondering if it's expected that getOptionLabel is called multiple times for the same option?

I'm seeing that when I click an option, getOptionLabel gets called 3 times.

@nobodyme
Copy link

nobodyme commented Feb 8, 2021

I was also looking to make something like this.

@nobodyme
Copy link

class ExpressionBuilder extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      inputValue: this.props.value ? this.props.value : '',
      fakeValue: ''
    }

    this.filterOptions = this.filterOptions.bind(this);
    this.suggestItems = this.suggestItems.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }
  
  suggestItems(value) {
    /**
     * return items to suggest, based upon context
     */
    return ['blah', 'nomore'];
  }
  
  getCurrentWord(text) {
    const words = text.split(" ");
    return words[words.length - 1];
  }
  
  filterOptions(options, { inputValue }) {
    const items = this.suggestItems(inputValue);
    let currentWord = this.getCurrentWord(inputValue);
    const matched_chars = items.filter((char) => {
      return char.indexOf(currentWord.toLowerCase()) !== -1;
    });
    return matched_chars;
  }

  handleChange(event) {
    const value = event.target.value;
    this.setState({
      inputValue: value
    });

    /**
     * Small hack to reset the internal state of the material autocompleter,
     * to allow it to suggest the previously suggested value,
     * if the input in totally wiped, and also reset highlight
     */
    if (value === '') {
      this.setState({
        fakeValue: ''
      });
    }
  }

  render() {
    return (
      <Autocomplete
        id="expression-builder"
        disableClearable
        options={filters}
        autoHighlight={true}
        value={this.state.fakeValue}
        inputValue={this.state.inputValue}
        filterOptions={this.filterOptions}
        onChange={(event, newInputValue) => {
          const words = this.state.inputValue.split(" ");
          words.pop();
          words.push(newInputValue);
          const newWord = words.join(" ");
          this.setState({
            inputValue: newWord
          });
        }}
        freeSolo={true}
        style={{ width: 300 }}
        renderInput={(params) => {
          return <TextField className="expressionBuilderField" {...params} variant="outlined" onChange={this.handleChange}/>
        }}
      />
    );
  }
}

export default ExpressionBuilder;

This implementation worked for me!

@Nikita-Filonov
Copy link

class ExpressionBuilder extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      inputValue: this.props.value ? this.props.value : '',
      fakeValue: ''
    }

    this.filterOptions = this.filterOptions.bind(this);
    this.suggestItems = this.suggestItems.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }
  
  suggestItems(value) {
    /**
     * return items to suggest, based upon context
     */
    return ['blah', 'nomore'];
  }
  
  getCurrentWord(text) {
    const words = text.split(" ");
    return words[words.length - 1];
  }
  
  filterOptions(options, { inputValue }) {
    const items = this.suggestItems(inputValue);
    let currentWord = this.getCurrentWord(inputValue);
    const matched_chars = items.filter((char) => {
      return char.indexOf(currentWord.toLowerCase()) !== -1;
    });
    return matched_chars;
  }

  handleChange(event) {
    const value = event.target.value;
    this.setState({
      inputValue: value
    });

    /**
     * Small hack to reset the internal state of the material autocompleter,
     * to allow it to suggest the previously suggested value,
     * if the input in totally wiped, and also reset highlight
     */
    if (value === '') {
      this.setState({
        fakeValue: ''
      });
    }
  }

  render() {
    return (
      <Autocomplete
        id="expression-builder"
        disableClearable
        options={filters}
        autoHighlight={true}
        value={this.state.fakeValue}
        inputValue={this.state.inputValue}
        filterOptions={this.filterOptions}
        onChange={(event, newInputValue) => {
          const words = this.state.inputValue.split(" ");
          words.pop();
          words.push(newInputValue);
          const newWord = words.join(" ");
          this.setState({
            inputValue: newWord
          });
        }}
        freeSolo={true}
        style={{ width: 300 }}
        renderInput={(params) => {
          return <TextField className="expressionBuilderField" {...params} variant="outlined" onChange={this.handleChange}/>
        }}
      />
    );
  }
}

export default ExpressionBuilder;

This implementation worked for me!

Nice solution, this exactly what I needed. Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: autocomplete This is the name of the generic UI component, not the React module! support: Stack Overflow Please ask the community on Stack Overflow
Projects
None yet
Development

No branches or pull requests

5 participants