šŸ› ļø Typeahead: Displaying predictions

Hey ,

I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.

Please upgrade (use this link) access this content.

I'm super eager to help you learn more!

šŸ› ļø Typeahead: Displaying predictions

For this Typeahead, we want to help users search for a country. If they enter J into the Typeahead, we show countries that begin with J.

Shows four countries when I type j into the input field..

A list of countries

To predict what users are typing, you need a list of possible choices. Since weā€™re helping users type a country, we need to have a list of countries in the world.

And we have this list already. You can find it in your JavaScript file.

const countries = [
  // ...
  { name: 'Japan' },
  { name: 'Jersey' },
  // ...
]

Showing a list of predictions

To show a list of predictions, we need to:

  1. Find out what the user typed
  2. Filter countries according to what they typed
  3. Make HTML elements for each prediction
  4. Show the list

Finding out what the user typed

We need to get what the user typed immediately after they type into the input field. Here, we can use the input event.

const typeahead = document.querySelector('.typeahead')
const input = typeahead.querySelector('input')

input.addEventListener('input', event => {
  // Get what the user typed
})

Weā€™ll use the value property to get what the user typed. Weā€™ll also use trim to remove any whitespace thatā€™s present.

input.addEventListener('input', event => {
  // Get what the user typed
  const input = event.target
  const inputValue = input.value.trim()
  console.log(inputValue)
})
Logging inputValue into the console.

Filtering the list of countries

If the user typed J into the Typeahead. We want to show a list of countries that begin with J. Here, we have Jamaica, Japan, Jersey, and Jordan.

We can use startsWith to check if a country starts with J. startsWith lets you check whether a string starts with the string you specified.

'My name'.startsWith('M') // true
'My name'.startsWith('My') // true
'My name'.startsWith('Z') // false

Weā€™ll use filter to filter the countries array.

input.addEventListener('input', event => {
  // ...

  const matches = countries.filter(country => {
    const name = country.name
    return name.startsWith(inputValue)
  })

  console.log(matches)
})

If you typed J into the Typehead, youā€™ll see the four countries I mentioned.

Console shows Jamaica, Japan, Jersey, and Jordan.

But if you typed j (lower case) into the Typeahead, youā€™ll see nothing.

Console shows empty array.

This is because startsWith is case sensitive. You need to make sure the cases match.

'My name'.startsWith('M') // true
'My name'.startsWith('m') // false

To make sure cases match, we can convert both inputValue and country.name to lower case.

input.addEventListener('input', event => {
  // ...
  const inputValue = input.value.trim().toLowerCase()

  const matches = countries.filter(country => {
    const name = country.name.toLowerCase()
    return name.startsWith(inputValue)
  })
})

Both J and j should match Jamaica, Japan, Jersey, and Jordan now.

Making HTML Elements

We want to create a list of elements to show in the DOM. Each country element we create should be a <li> because we will put them inside an <ul>.

Hereā€™s the HTML we need:

<ul>
  <li>Jamaica</li>
  <li>Japan</li>
  <li>Jersy</li>
  <li>Jordan</li>
</ul>

There are two methods to create this list.

First method:

  1. Create a <li> with document.createElement for each country in matches
  2. Add country.name as the <li>'s textContent
  3. Add each <li> to <ul> with appendChild

The second method:

  1. Create an array of <li> elements with map
  2. Convert the array into a string with join
  3. Assign the HTML string to .typeahead's list innerHTML

You already know how to do the first method. Weā€™ll practice the second method for the Typeahead.

Here, you need to create an array that contains the necessary innerHTML.

// This is what we want
const listItems = [
  '<li>Jamaica</li>',
  '<li>Japan</li>',
  '<li>Jersey</li>',
  '<li>Jordan</li>'
]

You already have an array of objects that contain the name of each country.

console.log(matches)
// [
//  { name: 'Jamaica' },
//  { name: 'Japan' },
//  { name: 'Jersey' },
//  { name: 'Jordan' },
// ]

You can get the array by running matches through a map function. In the map function, weā€™ll extract the countryā€™s name from each country object. And we will wrap them inside <li> element.

input.addEventListener('input', event => {
  // ...
  const listItems = matches.map(country => {
    return `<li>${country.name}</li>`
  })

  console.log(listItems)
})
Created array with <li> strings

Then, we use join to create the innerHTML for <ul>.

input.addEventListener('input', event => {
  // ...
  const listItems = matches.map(country => {
    return `<li>${country.name}</li>`
  })
    .join('')
})

Showing the list

To show the list, we set the <ul>'s innerHTML to listItems.

const ul = typeahead.querySelector('ul')
input.addEventListener('input', event => {
  // ...
  ul.innerHTML = listItems
})

Then, we display the list by removing the hidden attribute.

const ul = typeahead.querySelector('ul')
input.addEventListener('input', event => {
  // ...
  ul.innerHTML = listItems
  ul.removeAttribute('hidden')
})
Shows predictions.

Some UX improvements

Right now, the Typeahead shows all possible predictions if a user types a space into the input field.

Shows all countries.

This shouldnā€™t happen.

If the input field is empty, we want to hide the list. In this case, we donā€™t even need to find matches, so weā€™ll do an early return.

input.addEventListener('input', event => {
  const input = event.target
  const inputValue = input.value.trim().toLowerCase()

  // Hides list if user typed nothing
  // (or empties input field)
  if (!inputValue) return ul.setAttribute('hidden', true)
})
Hides Typeahead list when user empties the input field.

Hiding the list of predictions

If the user clicks outside the Typeahead, we want to close the list of predictions.

Hereā€™s the code. (Youā€™ve done this many times already. Iā€™m going to let you figure out why it works šŸ˜‰).

document.addEventListener('click', event => {
  if (!event.target.closest('.typeahead')) {
    ul.setAttribute('hidden', true)
  }
})

Thatā€™s it!

Note: You can remove the countries items from the <ul> in the index.html at this point. We donā€™t need it anymore.