17

I want to implement something like a tag editor. However, it's meant just for those tags so I want the user to see the autocomplete suggestions popup without having to type something like @ or #, just the text itself.

I have something that kinda works, but the popup displays in strange positions on the screen:

  • when I first type something and the popup appears, it appears somewhere near the top-left corner of the screen
  • after the first entity is created, when press SPACE and start typing again, the popup appears a couple of pixels to the right from it's intuitive position (i.e. under the first letter of the word)

Here is an example of a well-known editor of this kind (although not implemented with Draft), so you can get a better understanding of what I want to implement.

Gmail email composer

First things first, here is the function that triggers the suggestions popup:

private onChange(editorState: EditorState) {
  const content = editorState.getCurrentContent();
  const selection = editorState.getSelection();
  const currentBlock = content.getBlockForKey(selection.getAnchorKey());

  if (selection.isCollapsed()) {
    const blockText = currentBlock.getText();
    const wordMeta = getWordAt(blockText, selection.getAnchorOffset());
    const categoryRegex = /([\w]*)/;
    const matches = wordMeta.word.match(categoryRegex);
    const existingEntity = currentBlock.getEntityAt(wordMeta.begin);

    if (!existingEntity && matches) {
      const categorySearch = matches[1];
      const selection = window.getSelection();
      if (this.state.autoComplete.search !== categorySearch && selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);
        const boundingRect = getRangeBoundingClientRect(range);

        this.setState((prevState: StateType) => {
          let state = {
            autoComplete: {
              active: true,
              search: categorySearch,
              searchMeta: {
                begin: wordMeta.begin,
                end: wordMeta.end,
              },
            },
            selectionRect: prevState.selectionRect,
          };

          if (prevState.autoComplete.active === false) {
            state.selectionRect = boundingRect;
          }

          return state;
        });
      }
    }
  }

  this.props.onChange(editorState);
}

Here is the getWordAt function:

function getWordAt(text: string, pos: number): WordMeta
{
  const left = text.slice(0, pos + 1).search(/\S+$/);
  const right = text.slice(pos).search(/\s/);

  if (right < 0) {
    return {
      word: text.slice(left),
      begin: left,
      end: text.length,
    };
  }

  return {
    word: text.slice(left, right + pos),
    begin: left,
    end: right + pos,
  };
}

What would be a better way of handling the position of the popup and maybe even the strategy for autocompletion of this kind as well? Thank you!

Victor
  • 11,135
  • 16
  • 65
  • 128
  • If it's simply a positioning issue (and not otherwise timing or behavior based), and it's consistent across browsers, might investigate just adding some CSS to coerce the popup to the desirable position – Greg Rozmarynowycz Jan 22 '18 at 19:06
  • @GregRozmarynowycz It's not just the positioning. The approach Facebook offers for entity creation is based on [@-handles](https://draftjs.org/docs/advanced-topics-decorators.html#content), but my case seems not to be correctly achievable using that approach. I am looking for some kind of approach that is not hacky in any way and will produce correct results! – Victor Jan 23 '18 at 14:32
  • @Victor you are done with showing autosuggest without typing @? And now you are facing issue with positioning ? Or you need solution for autosuggest and position both? – cauchy Jan 24 '18 at 18:59
  • Both! The thing you see in my code snippet is very error-prone and is very hard to extend later... that's why I though a Regex solution is not ok so I asked here! But yes! I need help with both positioning and autosuggest – Victor Jan 24 '18 at 20:26
  • As I understand, your problem is that you don't know how many pixels you need to push the suggestions box to the right relative to the input field, so that it appears right below the user's cursor? Have you seen this: https://stackoverflow.com/q/6930578? – Al.G. Jan 24 '18 at 21:03
  • Well I am already using `getRangeBoundingClientRect` but that is not the only problem. the problem is that I somehow need to trigger the autocompletion for the text that is not included in any entity so I can get its bounding rectangle and display the autocompletion – Victor Jan 24 '18 at 21:39
  • How on earth did this get 11 upvotes?! Something smells fishy here... – Liam Feb 19 '18 at 11:31
  • @Liam, what's the problem? You can't just say that without an explanation. – Victor Feb 20 '18 at 07:36
  • This question is far too broad/vague. IMO it should be closed. That's why 1 month and two bounties later you still don't have an answer. – Liam Feb 20 '18 at 09:32
  • Too vague? I have provided screenshots, explanations and code snippets along with what I want to achieve. Should I write a thesis on this subject so it's not too vague for you? – Victor Feb 20 '18 at 09:46
  • Quite the opposite, the question should be answerable. The reason your not getting any answers is because it's not. You've asked for too much and no amount of bounties is going to fix that. It's also lacking a [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) of your problem. – Liam Feb 20 '18 at 15:08
  • @Victor Can you post a fiddle or pen to show the problem? – karthick Feb 23 '18 at 16:09
  • @karthick I will try to put it up as simple as possible – Victor Feb 23 '18 at 19:11

1 Answers1

1

Instead Of draft-js-typeahead - TypeaheadEditor is a react component that wraps draft's Editor. You can use React-Autosuggest component that meets the requirements. It has custom rendering that works natively with React elements. It's fast and pretty easily customizable. Have full control over suggestions rendering.

We can make it handle JS objects instead of plain strings.

  1. the onSuggestionSelected props is a callback to get the selected suggestion
  2. suggestionRenderer method takes suggestion and returns React markup

Check out React-Autosuggest.

You can use above component by using a custom block renderer, it is possible to introduce complex rich interactions within the frame of your editor.

You have to break your head to achieve what you want to, its not straight forward. This was my suggestion through which you might achieve it but its not that easy.

Varsha
  • 938
  • 9
  • 18
  • `react-autosuggest` seems to only support one selected item out of the box which is not hard to get even without `draft-js`. My problem comes from needing more items that can be selected. – Victor Feb 20 '18 at 07:41
  • Then behaviors such as navigating between the selected elements with left/right keys or deleting them with Delete or Backspace or right-click and copy would be extremely hard to achieve. This is why I want to achieve this with DraftJS, the Facebook team has already implemented these behaviors in a pretty successful way – Victor Feb 20 '18 at 08:23
  • You could, however, implement something like this: - User selects an item from the autosuggest. - On selection, add the selected item to a list that is maintained outside of the autosuggest. - After item is selected, clear the input, so that user could add another item to the list. **OR** There is a way to achieve it with `react-autosuggest` using `renderInputComponent` function - which is used only if you need to customize the rendering of the input. I think you should give it a shot. – Varsha Feb 20 '18 at 08:25
  • Yes, you are right and you should use DraftJS but with that you can use this component only for that element to achieve exactly what you want. – Varsha Feb 20 '18 at 08:29