Add autocomplete to your text area
Phuoc Nguyen
Posted on December 20, 2023
Autocomplete is a super helpful feature that can save users lots of time and effort when filling out forms or typing in text areas.
For example, when filling out an address form, autocomplete can suggest the complete address as the user types in the first few letters or numbers of their street, city, or zip code. This not only saves time but also ensures accuracy.
Another great use for autocomplete is when searching for products on an e-commerce website. As the user types in their query, autocomplete can suggest relevant product names or keywords that match their search criteria. This makes it easy to find what you're looking for quickly and easily.
Autocomplete can also be a lifesaver in fields like email addresses, usernames, and phone numbers where there may be a limited set of valid options to choose from. By suggesting possible options as the user types, autocomplete makes it easier to enter accurate information without having to remember exact details.
In this post, we'll walk you through the steps to implement autocomplete functionality in a text area using JavaScript. Get ready to level up your user experience!
Finding the current word
To enable our autocomplete feature, it's crucial that we track the current word being typed in a text area. We can accomplish this by utilizing the input
event and the selectionStart
property.
Here's how it works: when the user types into the text area, we can retrieve the value of the text area using textarea.value
and find the index of the cursor using textarea.selectionStart
. With these values, we can iterate backwards through each character until we find either a space or a newline character. This gives us the start index of the current word.
We've created a handy findIndexOfCurrentWord
function that returns the beginning index of the current word. Here's a sample code snippet to help you visualize how it works:
const findIndexOfCurrentWord = () => {
// Get current value and cursor position
const currentValue = textarea.value;
const cursorPos = textarea.selectionStart;
// Iterate backwards through characters until we find a space or newline character
let startIndex = cursorPos - 1;
while (startIndex >= 0 && !/\s/.test(currentValue[startIndex])) {
startIndex--;
}
return startIndex;
};
Once we have the index of the current word, we can easily extract it using string manipulation.
textarea.addEventListener('input', () => {
const currentValue = textarea.value;
const cursorPos = textarea.selectionStart;
const startIndex = findIndexOfCurrentWord();
// Extract just the current word
const currentWord = currentValue.substring(startIndex + 1, cursorPos);
});
Finding matching words
Finding matching words is a breeze once we have the current word. We simply loop through the suggestions
array and check if each suggestion contains the word currently entered in the text area.
const suggestions = [
'white', 'yellow', 'blue', 'red', 'green', 'black', 'brown', 'azure', 'ivory', 'teal',
];
const matches = suggestions.filter((suggestion) => suggestion.indexOf(currentWord) > -1);
Building the suggestions
To display our suggestions, we'll loop through the matches
array and create a new div
element for each match. We'll set the text content of each div
to match the suggestion and add a class to style it properly.
To replace the current word with a new one, we'll add an event listener to each suggestion div
that listens for a click
event. When the user clicks on a suggestion, we'll get the text content of that div
and replace the current word in our text area with it.
Here's some sample code:
suggestionsEle.innerHTML = '';
matches.forEach((match) => {
const option = document.createElement('div');
option.innerText = match;
option.classList.add('container__suggestion');
option.addEventListener('click', function() {
replaceCurrentWord(this.innerText);
suggestionsEle.style.display = 'none';
});
suggestionsEle.appendChild(option);
});
This code replaces the current word with the selected suggestion and removes all suggestions from the page. The replaceCurrentWord()
function updates the value of our text area to replace the current word with the new one.
const replaceCurrentWord = (newWord) => {
const currentValue = textarea.value;
const cursorPos = textarea.selectionStart;
const startIndex = findIndexOfCurrentWord();
const newValue = currentValue.substring(0, startIndex + 1) +
newWord +
currentValue.substring(cursorPos);
textarea.value = newValue;
textarea.focus();
textarea.selectionStart = textarea.selectionEnd = startIndex + 1 + newWord.length;
};
In the function above, we've set focus back on our text area and move the cursor to the position after the new word is inserted. To do this, we'll use the selectionStart
and selectionEnd
properties, which represent the starting and ending positions of the selected text in a text area or input field. We'll set both properties to the same value to move the cursor to the new position.
Position the suggestions element
To position the suggestions element, we can use the same technique as we did to position the caret element in the previous post. We can get the coordinates of the current cursor using the getBoundingClientRect()
method on our caret element. Then, we can use those coordinates to set the top
and left
properties of our suggestions element.
Check out this code snippet for an example:
const rect = caretEle.getBoundingClientRect();
suggestionsEle.style.top = `${rect.top + rect.height}px`;
suggestionsEle.style.left = `${rect.left}px`;
Using this technique, we can ensure that our suggestions element is always positioned directly beneath the current word, which makes it easier for users to select from our list of suggested words.
Navigating with ease
We want our autocomplete feature to be as user-friendly as possible. One way to achieve this is by allowing users to navigate between suggested items using the arrow keys. This makes it easy for them to quickly scan through our list of suggestions without having to move their mouse or touchpad.
To make this possible, we can add event listeners for the keydown
event on our text area. This will allow us to check whether the user has pressed one of the supported keys, such as the up and down arrow keys. If they have, we'll update a variable that keeps track of which suggestion is currently focused.
Users can also select a suggestion by pressing the Enter key or close the suggestion list by pressing the Escape key.
Here's some sample code to help you visualize this idea:
let currentSuggestionIndex = -1;
textarea.addEventListener('keydown', (e) => {
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Escape'].includes(e.key)) {
return;
}
const suggestions = suggestionsEle.querySelectorAll('.container__suggestion');
const numSuggestions = suggestions.length;
if (numSuggestions === 0) {
return;
}
e.preventDefault();
switch (e.key) {
case 'ArrowDown':
// ...
break;
case 'ArrowUp':
// ...
break;
case 'Enter':
// ...
break;
case 'Escape':
// ...
break;
default:
break;
}
});
In this code snippet, we've added an event listener for the keydown
event on our text area. We then check if the user has pressed certain keys (like down, up arrow, Enter, or Escape) using their respective key
property.
If they have, we prevent the default action of the key (which is usually to move the cursor or scroll the page) and retrieve all our suggestion elements using document.querySelectorAll('.container__suggestion')
. After that, we check if we have any suggestions available and update our currentSuggestionIndex
variable based on which arrow key was pressed.
For instance, when users press the down arrow key, we execute the following code:
suggestions[
clamp(0, currentSuggestionIndex, numSuggestions - 1)
].classList.remove("container__suggestion--focused");
currentSuggestionIndex = clamp(
0,
currentSuggestionIndex + 1,
numSuggestions - 1
);
suggestions[focusedSuggestionIndex].classList.add("container__suggestion--focused");
To make sure that our current suggestion index is always valid, we use a handy clamp
function. This function needs three things: a minimum value, the value we want to clamp, and a maximum value. Then, it gives us back the clamped value, which is guaranteed to be no less than the minimum and no greater than the maximum. Easy peasy.
const clamp = (min, value, max) => Math.min(Math.max(min, value), max);
We use a function in our code to make sure that currentSuggestionIndex
doesn't go beyond the number of suggestions or below 0. This helps us avoid errors caused by out-of-bounds indices and ensures that our autocomplete feature works correctly.
To make the user experience even better, we highlight the currently selected suggestion by adding a special class (container__suggestion--focused
) to it. At the same time, we remove that class from the previously selected suggestion.
You have the power to customize how the current focused item looks and feels. For instance, in this example, we added a different background color to make it stand out from the other items.
.container__suggestion--focused {
background: rgb(226 232 240);
}
Now that this code is in place, users can effortlessly navigate between suggestions using only their keyboard. This means our autocomplete feature is even more user-friendly and accessible.
Let's take a look at the final demo.
Conclusion
By following these simple steps, you can easily implement autocomplete functionality in a text area using JavaScript. Of course, this is just a basic example - you have the freedom to customize the suggestions, the appearance of the dropdown menu, and the behavior of the text area to fit your specific needs.
To take things up a notch, you could also add shortcuts, such as using the up and down arrows to navigate between suggestions. And when a suggestion item is selected, pressing Enter could be the same as clicking it. With a little creativity, the possibilities are endless!
It's highly recommended that you visit the original post to play with the interactive demos.
If you found this series helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!
If you want more helpful content like this, feel free to follow me:
Posted on December 20, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
July 24, 2024