DEV Community

loading...

How to show suggestions on typing using Javascript?

supunkavinda profile image Supun Kavinda Originally published at groups.hyvor.com ・4 min read

Hello, This is a short tutorial on how to show suggestions when the user types specific keyword in a textbox.

Showing suggestions on typing is something most app does. Here, I'm not talking about search text boxes, which always show you suggestions. (Google search box)

Let's assume that the user is typing in a <textarea>, and we need to show some suggestions when they are typing a username starting from @ (@Supun)

This requires some work to be done. Here's a good approach that I found effective.

  • We have a <textarea> element which takes user input.
  • We listen to the keyup event to check if user is typing what we need.

This article is written in Vanilla JS. You can use a library to manipulate DOM. But, I don't know any popular JS library that provides functions to get and set cursors. So, using Vanilla JS for that would be fine. Let's start!

Before we start, we need to helper functions to deal with cursor positions. (To get and set)

function getCursorPosition(el) {
    var start = 0, end = 0, normalizedValue, range, textInputRange, len, endRange;
    if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
        start = el.selectionStart;
        end = el.selectionEnd;
    } else {
        range = document.selection.createRange();

        if (range && range.parentElement() == el) {
            len = el.value.length;
            normalizedValue = el.value.replace(/\r\n/g, "\n");

            // Create a working TextRange that lives only in the input
            textInputRange = el.createTextRange();
            textInputRange.moveToBookmark(range.getBookmark());

            // Check if the start and end of the selection are at the very end
            // of the input, since moveStart/moveEnd doesn't return what we want
            // in those cases
            endRange = el.createTextRange();
            endRange.collapse(false);

            if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
                start = end = len;
            } else {
                start = -textInputRange.moveStart("character", -len);
                start += normalizedValue.slice(0, start).split("\n").length - 1;

                if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
                    end = len;
                } else {
                    end = -textInputRange.moveEnd("character", -len);
                    end += normalizedValue.slice(0, end).split("\n").length - 1;
                }
            }
        }
    }
    return {
        start: start,
        end: end
    };
}

function setCursorPosition(input, start, end) {
    if (arguments.length < 3) end = start;
    if ("selectionStart" in input) {
        setTimeout(function() {
            input.selectionStart = start;
            input.selectionEnd = end;
        }, 1);
    }
    else if (input.createTextRange) {
        var rng = input.createTextRange();
        rng.moveStart("character", start);
        rng.collapse();
        rng.moveEnd("character", end - start);
        rng.select();
    }
}
Enter fullscreen mode Exit fullscreen mode

Then, the textarea element which we are going to listen to. There's no problem if it is a input element. And, the suggestion view where we are going to show the suggestions in.

<textarea id="txt"></textarea>
<div id="suggestions"></div>
Enter fullscreen mode Exit fullscreen mode

Now, some useful variables.

var txt = document.getElementById("txt"),
    suggestions = document.getElementById("suggestions"),
    regex = /@([a-zA-Z0-9]*)$/; // the regex we are going to match

// some fake data
var userData = [
  {
    name: "Supun Kavinda",
    username: "SupunKavinda"
  },
  {
    name: "John Doe",
    username: "JohnDoe"
  },
  {
    name: "Anonymous",
    username: "Anonymous"
  }
];
Enter fullscreen mode Exit fullscreen mode

Add the keyup event listener to the textarea.

// listen for @...
txt.addEventListener('keyup', handleKeyUp);
Enter fullscreen mode Exit fullscreen mode

Then, our handler. Here we will match the string before cursor with /@[a-zA-Z0-9]*$/. If matched, we can suggestions of the users taken from a database. (Here I will use some fake data for users)

function handleKeyUp() {
   closeSuggestions();
   var cursor = getCursorPosition(txt),
      val = txt.value,
      strLeft = val.substring(0, cursor.start);

  var match = val.match(regex);

  if (match) {
     // fetch suggestions
     var username = match[1];
     findSuggestions(username);     
  }
}
Enter fullscreen mode Exit fullscreen mode

Finding, showing and closing suggestions...

function findSuggestions(username) {

  var matched = [];

  userData.forEach(function(data) {
    var dataUsername = data.username,
        pos = dataUsername.indexOf(username);

    if (pos !== -1) {
        matched.push(data);
    }
  });

  // you can also sort the matches from the index (Best Match)

  if (matched.length > 0) {
    showSuggestions(matched);
  }

}

function showSuggestions(matched) {
  // DOM creation is not that hard if you use a library ;
  suggestions.style.display = "block";
  suggestions.innerHTML = "";

  matched.forEach(function(data) {
    var wrap = document.createElement("div");
    suggestions.appendChild(wrap);

    var nameView = document.createElement("span");
    nameView.innerHTML = data.name;
    nameView.className = "name-view";

    var usernameView = document.createElement("span");
    usernameView.innerHTML = "@" + data.username;
    usernameView.className = "username-view";

    wrap.appendChild(nameView);
    wrap.appendChild(usernameView);

    // add the suggested username to the textarea
    wrap.onclick = function() {
        addToTextarea("@" + data.username + " ");
    }
  });
}

function closeSuggestions() {
    suggestions.style.display = "none";
}
Enter fullscreen mode Exit fullscreen mode

The function to add the username to the textarea.

function addToTextarea(valueToAdd) {

   var cursor = getCursorPosition(txt),
      val = txt.value,
      strLeft = val.substring(0, cursor.start),
      strRight = val.substring(cursor.start);

    // remove the matched part
    strLeft = strLeft.replace(regex, "");

    txt.value = strLeft + valueToAdd + strRight;

    // (textarea, positionToAdd)
    setCursorPosition(txt, strLeft.length + valueToAdd.length);

    txt.focus();

    closeSuggestions();

}
Enter fullscreen mode Exit fullscreen mode

Finally, let's add some CSS.

#txt {
  resize:none;
  width:450px;
  height:100px;
  border-radius:5px;
  border:1px solid #eee;
}
#suggestions {
  /* It's better to contain textarea
  and suggestions in a div and make this suggestions view
  absolute positioned */
  box-shadow:0 0 25px rgba(0,0,0,0.05);
  width:450px;
}
#suggestions > div {
  padding:10px;
}
.name-view {
  margin-right:5px;
}
.username-view {
  color: #aaa;
}
Enter fullscreen mode Exit fullscreen mode

You are done!

Possible Improvements

  • You can add keyboard navigation to select the suggestions.

Here's the JSFiddle Demo. And, I'm gonna publish this post as the first post of Javascriptians group on my website.

Hope it helped.

Discussion (0)

pic
Editor guide