skip to Main Content

I really do like the autocomplete function, but is it possible to change it so it’s blank when I press inside of the textbox, and only displays countries when I start type something in? I’m not sure how I should do this so I would appreciate if someone could tell/show me where to fix that.

When I start typing something, like "Alb", and then remove this and click outside of the textbox, then click inside of it again the previous results is still there

Code Snippet

// Sample list of autocomplete options
var countries = [
  "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", "Argentina", "Armenia", "Australia", "Austria", "Azerbaijan",
  "The Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bhutan", "Bolivia",
  "Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei", "Bulgaria", "Burkina Faso", "Burundi"
];

// Get references to the input field and the dropdown list
var input = document.getElementById("country-name");
var autocompleteList = document.getElementById("suggestion-list");

// Function to display the dropdown list
function showAutocompleteList() {
  autocompleteList.style.display = 'block';
}

// Function to hide the dropdown list
function hideAutocompleteList() {
  autocompleteList.style.display = 'none';
}

// Event listener for input field
input.addEventListener("input", function() {
  var inputValue = input.value;
  autocompleteList.innerHTML = '';

  // Filter and display matching items
  countries.forEach(function(country) {
    if (country.toLowerCase().startsWith(inputValue.toLowerCase())) {
      var item = document.createElement("li");
      item.textContent = country;
      item.classList.add("autocomplete-item"); // Add the "autocomplete-item" class
      item.addEventListener("click", function() {
        input.value = country;
        autocompleteList.innerHTML = '';
      });
      item.addEventListener("mouseenter", function() {
        item.classList.add("active"); // Add the "active" class on hover
      });
      item.addEventListener("mouseleave", function() {
        item.classList.remove("active"); // Remove the "active" class when not hovering
      });
      autocompleteList.appendChild(item);
    }
  });

  showAutocompleteList(); // Display the list when input changes
});

// Event listener for "keydown" on the input field
input.addEventListener("keydown", function(e) {
  if (e.key === "Tab") {
    e.preventDefault(); // Prevent the default tab behavior

    // Handle the Tab key behavior for selecting an item
    var activeItem = autocompleteList.querySelector(".autocomplete-item.active");
    if (activeItem) {
      input.value = activeItem.textContent;
      autocompleteList.innerHTML = '';

      // Redirect to the corresponding HTML page
      var countryUrl = 'Countries/' + activeItem.textContent.toLowerCase() + '.html';
      window.location.href = countryUrl;
    }
  }
});

// Event listener for clicking on the list items
autocompleteList.addEventListener("click", function(e) {
  if (e.target.classList.contains("autocomplete-item")) {
    // If the clicked element has the "autocomplete-item" class
    input.value = e.target.textContent;
    // Redirect to the corresponding HTML page
    var countryUrl = 'Countries/' + e.target.textContent.toLowerCase() + '.html';
    window.location.href = countryUrl;
    e.preventDefault(); // Prevent the default click behavior
  }
});

// Event listener to close the dropdown when clicking outside of the input field
document.addEventListener("click", function(e) {
  if (e.target !== autocompleteList && e.target !== input) {
    hideAutocompleteList();
  }
});

// Event listener for clicking inside the input field to display the dropdown
input.addEventListener("click", showAutocompleteList);
<header>
  <h1>Select a country</h1>
</header>
<main>
  <div id="content-wrapper">
    <div id="get-container">
      <input type="text" id="country-name" placeholder="Enter a country" autocomplete="off">
      <div id="suggestion-list" class="autocomplete-items"></div>

      <div id="error-message"></div>
    </div>
    <div id="story-container">
      <div id="generated-text"></div>
      <button id="clear">Clear</button>
    </div>
  </div>
</main>

2

Answers


    1. Added the focus event listener to clear the list when input field is clicked.

    2. Updated the showAutocompleteList() function to only display the list if the input value is not empty.

    3. Updated the input’s input event listener to only filter and display the list if the input has at least one character.

    4. Modified the input’s keydown event listener to check for the length of the input value before displaying the list.

    var countries = [
      "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", "Argentina", "Armenia", "Australia", "Austria", "Azerbaijan",
      "The Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bhutan", "Bolivia",
      "Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei", "Bulgaria", "Burkina Faso", "Burundi"
    ];
    
    // Get references to the input field and the dropdown list
    var input = document.getElementById("country-name");
    var autocompleteList = document.getElementById("suggestion-list");
    
    // Function to display the dropdown list
    function showAutocompleteList() {
      if (input.value !== '') {
        autocompleteList.style.display = 'block';
      }
    }
    
    // Function to hide the dropdown list
    function hideAutocompleteList() {
      autocompleteList.style.display = 'none';
    }
    
    // Event listener for input field
    input.addEventListener("input", function() {
      var inputValue = input.value;
      autocompleteList.innerHTML = '';
    
      // Only filter and display the list if there is at least one character
      if (inputValue.length > 0) {
        countries.forEach(function(country) {
          if (country.toLowerCase().startsWith(inputValue.toLowerCase())) {
            let item = document.createElement("li");
            item.textContent = country;
            item.classList.add("autocomplete-item"); // Add the "autocomplete-item" class
            item.addEventListener("click", function() {
              input.value = country;
              autocompleteList.innerHTML = '';
            });
            item.addEventListener("mouseenter", function() {
              item.classList.add("active"); // Add the "active" class on hover
            });
            item.addEventListener("mouseleave", function() {
              item.classList.remove("active"); // Remove the "active" class when not hovering
            });
            autocompleteList.appendChild(item);
          }
        });
        showAutocompleteList(); // Display the list when input changes
      } else {
        hideAutocompleteList(); // Otherwise, hide the list
      }
    });
    
    // Event listener for "keydown" on the input field
    input.addEventListener("keydown", function(e) {
      if (e.key === "Tab") {
        e.preventDefault(); // Prevent the default tab behavior
    
        // Handle the Tab key behavior for selecting an item
        var activeItem = autocompleteList.querySelector(".autocomplete-item.active");
        if (activeItem) {
          input.value = activeItem.textContent;
          autocompleteList.innerHTML = '';
    
          // Redirect to the corresponding HTML page
          var countryUrl = 'Countries/' + activeItem.textContent.toLowerCase().replace(/ /g, "-").toLowerCase() + '.html';
          window.location.href = countryUrl;
        }
      }
    
      // Only display the list if the input has at least one character
      if (input.value.length > 0) {
        showAutocompleteList();
      } else {
        hideAutocompleteList(); // Otherwise, hide the list
      }
    });
    
    // Event listener for clicking on the list items
    autocompleteList.addEventListener("click", function(e) {
      if (e.target.classList.contains("autocomplete-item")) {
        // If the clicked element has the "autocomplete-item" class
        input.value = e.target.textContent;
        // Redirect to the corresponding HTML page
        var countryUrl = 'Countries/' + e.target.textContent.toLowerCase().replace(/ /g, "-").toLowerCase() + '.html';
        window.location.href = countryUrl;
        e.preventDefault(); // Prevent the default click behavior
      }
    });
    
    // Event listener to close the dropdown when clicking outside of the input field
    document.addEventListener("click", function(e) {
      if (e.target !== autocompleteList && e.target !== input) {
        hideAutocompleteList();
      }
    });
    
    // Event listener for clicking inside the input field to display the dropdown
    input.addEventListener("focus", function() {
      if (input.value.length > 0) {
        showAutocompleteList();
      }
    });
    <header>
      <h1>Select a country</h1>
    </header>
    <main>
      <div id="content-wrapper">
        <div id="get-container">
          <input type="text" id="country-name" placeholder="Enter a country" autocomplete="off">
          <div id="suggestion-list" class="autocomplete-items"></div>
    
          <div id="error-message"></div>
        </div>
        <div id="story-container">
          <div id="generated-text"></div>
          <button id="clear">Clear</button>
        </div>
      </div>
    </main>
    Login or Signup to reply.
  1. Changes from original code:

    • Changed to append a <div> for item since it is invalid to append <li> to a div
    • Changed to use data attributes for items and not value – the value on an li can only be a integer and only on li in an ol ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li
    • put the lower case value to avoid doing it on match
    • used .filter() for the match to array values
    • used const not var on cached values
    • used a proper CSS class to and toggle that on the show/hide to simplify that code and follow best practice
    • created functions to be called for various actions
    • took out hard coded Country/ and put it as a data attribute on the container and not in code
    • moved the event handler from the list generation and used delegated events (see populateList() for an updated code for that
    • added a focus event for the input to show the list
    • Minor CSS added to better illustrate visually what was happening including the mouseenter and mouseleave on the list items. Added a border to the actual list just to show what was where (non of this is part of the question answer but illustrates visually what is going on.

    Suggestion: rather than removing and adding to the DOM for the list, consider generating them all then showing/hiding using the function I created showElement(element, shouldHide) instead, called within the filter portion of the code

    // Sample autocomplete options
    const countries = [
      "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", "Argentina", "Armenia", "Australia", "Austria", "Azerbaijan",
      "The Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bhutan", "Bolivia",
      "Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei", "Bulgaria", "Burkina Faso", "Burundi"
    ];
    
    // cache references
    const input = document.getElementById("country-name");
    const autocompleteList = document.getElementById("suggestion-list");
    
    function showElement(element, shouldHide) {
      element.classList.toggle('hide-me', !shouldHide);
    }
    
    function clearElement(element) {
      element.innerHTML = '';
    }
    
    function showValue(element, value) {
      element.value = value;
    }
    
    function getUri(el, inputElem) {
      const eTarg = el;
      const isItem = eTarg &&
        eTarg.classList &&
        eTarg.classList.contains("autocomplete-item");
      console.log(eTarg, isItem);
      if (isItem) {
        const myName = eTarg.dataset.linkname;
        inputElem.value = eTarg.textContent;
        const parentContainer = eTarg.closest('.get-container');
        const urlRoot = parentContainer && parentContainer.dataset ? parentContainer.dataset.urlroot : "notfound/";
        const countryUrl = urlRoot + myName + '.html';
        return countryUrl;
      }
      return 'woops';
    }
    
    function populateList(listParent, words, filterValue, listTarget) {
      const activeClassName = 'active';
      const filterLower = filterValue.toLowerCase();
      const holder = document.createDocumentFragment();
      words.filter((word) => {
        return word.toLowerCase().startsWith(filterLower);
      }).forEach(function(word) {
        let el = document.createElement("div");
        el.textContent = word;
        el.classList.add("autocomplete-item");
        el.dataset.linkname = word.toLowerCase();
        holder.appendChild(el);
      });
      listParent.appendChild(holder);
    }
    
    input.addEventListener("input", function(event) {
      const inputValue = this.value;
      clearElement(autocompleteList);
      populateList(autocompleteList, countries, inputValue, this);
      showElement(autocompleteList, true);
    });
    
    input.addEventListener("keydown", function(ev) {
      if (ev.key !== "Tab") return;
      ev.preventDefault(); // Prevent tab to next element (non-standard)
      const activeItem = autocompleteList.querySelector(".autocomplete-item.active");
      if (activeItem) {
        const countryUrl = getUri(activeItem, input);
        showValue(input, activeItem.textContent);
        clearElement(autocompleteList);
        console.log('donothing:', countryUrl);
        // Redirect to the corresponding HTML page
        // window.location.href = countryUrl;
      }
    });
    
    autocompleteList.addEventListener("mouseenter", function(event) {
      // ref: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_delegation
      const itemClass = "autocomplete-item";
      const activeClassName = 'active';
      const eTarg = event.target;
      const parent = event.currentTarget;
      parent.classList.add("parents-fun");
      const currActive = parent.querySelector(`.${itemClass}.${activeClassName}`);
      if (currActive) {
        //remove from prior item
        currActive.classList.remove(activeClassName);
      }
      if (eTarg && eTarg.classList && eTarg.classList.contains(itemClass)) {
        eTarg.classList.add(activeClassName);
        showValue(input, eTarg.textContent);
      }
    }, true);
    
    autocompleteList.addEventListener("mouseleave", function(event) {
      // ref: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_delegation
      const itemClass = "autocomplete-item";
      const activeClassName = 'active';
      const eTarg = event.target;
      const parent = event.currentTarget;
      if (!!parent && !!parent.classList) {
        parent.classList.remove("parents-fun");
      }
      if (eTarg && eTarg.classList && eTarg.classList.contains("autocomplete-item")) {
        eTarg.classList.remove(activeClassName);
      }
    });
    
    // Event listener for clicking on the list items
    autocompleteList.addEventListener("click", function(event) {
      const eTarg = event.target;
      const countryUrl = getUri(eTarg, input);
      console.log('donothing would go to:', countryUrl);
      //window.location.href = countryUrl;
      event.preventDefault(); // Prevent the default click behavior
    });
    
    // hide dropdown when clicking outside input
    document.body.addEventListener("click", function(event) {
      const shouldShow = (event.target === autocompleteList || event.target === input);
      showElement(autocompleteList, shouldShow);
    });
    
    document.getElementById("country-name")
      .addEventListener("focus", function(event) {
        this.dispatchEvent(new Event("input", {
          'bubbles': true
        }));
      });
    .hide-me {
      display: none;
    }
    
    .autocomplete-items.parents-fun {
      border: solid #0000FF 1px;
    }
    
    .autocomplete-items .autocomplete-item {
      padding: 1px;
    }
    
    .autocomplete-items .autocomplete-item.active {
      border: solid 1px #00FF00;
      padding: 0px;
    }
    
    .country-name:focus {
      border: 1px solid #00FF00;
      background-color: #00FF0020;
      padding: 0.25rem;
    }
    <header>
      <h1>Select a country</h1>
    </header>
    <main>
      <div id="content-wrapper">
        <div id="get-container" class="get-container" data-urlroot="Countries/">
          <input type="text" id="country-name" class="country-name" placeholder="Enter a country" autocomplete="off" />
          <div id="suggestion-list" class="autocomplete-items"></div>
          <div id="error-message"></div>
        </div>
        <div id="story-container">
          <div id="generated-text"></div>
          <button id="clear">Clear</button>
        </div>
      </div>
    </main>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search