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.
<textarea>
element which takes user input.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();
}
}
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>
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”
}
];
Add the keyup event listener to the textarea.
// listen for @…
txt.addEventListener(‘keyup’, handleKeyUp);
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);
}
}
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”;
}
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();
}
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;
}
You are done :)
Thanks for reading ! learn more…
☞ The Complete JavaScript Course 2019: Build Real Projects!
☞ JavaScript Bootcamp - Build Real World Applications
☞ JavaScript Programming Tutorial - Full JavaScript Course for Beginners
☞ Best JavaScript Frameworks, Libraries and Tools to Use in 2019
☞ What JavaScript Framework You Should Learn to Get a Job in 2019?
☞ Best JavaScript Frameworks, Libraries and Tools to Use in 2019
☞ Microfrontends — Connecting JavaScript frameworks together (React, Angular, Vue etc)
☞ Do we still need JavaScript frameworks?
#javascript #typescript #web-development