-1

I have an autocompleted search box and I want to add a feature so the results would be hidden when I click somewhere outside input or div with the results. I had some thoughts but it failed. Any ideas on how to solve it?

Here is the link to the search box: Search box

Ahmad Habib
  • 2,053
  • 1
  • 13
  • 28
xerox28
  • 11
  • 2
  • Questions seeking debugging help must include the shortest code necessary to reproduce it **in the question itself.** NB - **Please don't abuse the code blocks to get around this requirement**. – Paulie_D Dec 15 '20 at 09:37

3 Answers3

1

You can simply bind an onblur function to the input element.

The element that is selected already triggers the onblur event. Therefore, we should wait for a brief delay e.g. ~300ms after the inputBox loses focus so that the click on the selected element gets recognized first.

Solution

inputBox.onblur = function() {
  setTimeout(function() {
    searchWrapper.classList.remove('active');
  }, 300);
};

Other thoughts:

In JavaScript, you should use single quotes over double quotes because it is easier to escape characters and print HTML for example. There is no downside on the other hand: When should I use double or single quotes in JavaScript?

Please note, that sometimes it makes more sense to use the data-attribute or ids instead of query selectors. The query selector needs more time to evaluate the path (it is slower). And if you want to select multiple elements you can use querySelectorAll().

Executable (modified your version)

function select(element) {
  let selectData = element.textContent;
  inputBox.value = selectData;
  icon.onclick = () => {
    webLink = "https://www.google.com/search?q=" + selectData;
    linkTag.setAttribute("href", webLink);
    linkTag.click();
  }
  searchWrapper.classList.remove("active");
}

function showSuggestions(list) {
  let listData;
  if (!list.length) {
    userValue = inputBox.value;
    listData = '<li>' + userValue + '</li>';
  } else {
    listData = list.join('');
  }
  suggBox.innerHTML = listData;
}

let suggestions = [
  'Channel',
  'CodingLab',
  'CodingNepal',
  'YouTube',
  'YouTuber',
  'YouTube Channel',
  'Blogger',
  'Bollywood',
  'Vlogger',
  'Vechiles',
  'Facebook',
  'Freelancer',
  'Facebook Page',
  'Designer',
  'Developer',
  'Web Designer',
  'Web Developer',
  'Login Form in HTML & CSS',
  'How to learn HTML & CSS',
  'How to learn JavaScript',
  'How to became Freelancer',
  'How to became Web Designer',
  'How to start Gaming Channel',
  'How to start YouTube Channel',
  'What does HTML stands for?',
  'What does CSS stands for?',
];

// getting all required elements
const searchWrapper = document.querySelector(".search-input");
const inputBox = searchWrapper.querySelector("input");
const suggBox = searchWrapper.querySelector(".autocom-box");
const icon = searchWrapper.querySelector(".icon");
let linkTag = searchWrapper.querySelector("a");
let webLink;

// if user press any key and release
inputBox.onkeyup = (e) => {
  let userData = e.target.value; //user enetered data
  let emptyArray = [];
  if (userData) {
    icon.onclick = () => {
      webLink = "https://www.google.com/search?q=" + userData;
      linkTag.setAttribute("href", webLink);
      console.log(webLink);
      linkTag.click();
    }
    emptyArray = suggestions.filter((data) => {
      //filtering array value and user characters to lowercase and return only those words which are start with user enetered chars
      return data.toLocaleLowerCase().startsWith(userData.toLocaleLowerCase());
    });
    emptyArray = emptyArray.map((data) => {
      // passing return data inside li tag
      return data = '<li>' + data + '</li>';
    });
    searchWrapper.classList.add("active"); //show autocomplete box
    showSuggestions(emptyArray);
    let allList = suggBox.querySelectorAll("li");
    for (let i = 0; i < allList.length; i++) {
      //adding onclick attribute in all li tag
      allList[i].setAttribute("onclick", 'select(this)');
    }
  } else {
    searchWrapper.classList.remove("active"); //hide autocomplete box
  }
}

inputBox.onblur = function() {
  setTimeout(function() {
    searchWrapper.classList.remove('active');
  }, 300);
};
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Poppins', sans-serif;
}

body {
  background: #644bff;
  padding: 0 20px;
}

::selection {
  color: #fff;
  background: #664AFF;
}

.wrapper {
  max-width: 450px;
  margin: 150px auto;
}

.wrapper .search-input {
  background: #fff;
  width: 100%;
  border-radius: 5px;
  position: relative;
  box-shadow: 0px 1px 5px 3px rgba(0, 0, 0, 0.12);
}

.search-input input {
  height: 55px;
  width: 100%;
  outline: none;
  border: none;
  border-radius: 5px;
  padding: 0 60px 0 20px;
  font-size: 18px;
  box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.1);
}

.search-input.active input {
  border-radius: 5px 5px 0 0;
}

.search-input .autocom-box {
  padding: 0;
  opacity: 0;
  pointer-events: none;
  max-height: 280px;
  overflow-y: auto;
}

.search-input.active .autocom-box {
  padding: 10px 8px;
  opacity: 1;
  pointer-events: auto;
}

.autocom-box li {
  list-style: none;
  padding: 8px 12px;
  display: none;
  width: 100%;
  cursor: default;
  border-radius: 3px;
}

.search-input.active .autocom-box li {
  display: block;
}

.autocom-box li:hover {
  background: #efefef;
}

.search-input .icon {
  position: absolute;
  right: 0px;
  top: 0px;
  height: 55px;
  width: 55px;
  text-align: center;
  line-height: 55px;
  font-size: 20px;
  color: #644bff;
  cursor: pointer;
}
<html lang="en" dir="ltr">

<head>
  <meta charset="utf-8">
  <title>Autocomplete Search Box</title>
  <link rel="stylesheet" href="style.css">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://kit.fontawesome.com/a076d05399.js"></script>
</head>

<body>
  <div class="wrapper">
    <div class="search-input">
      <a href="" target="_blank" hidden></a>
      <input type="text" placeholder="Type to search..">
      <div class="autocom-box">
        <!-- here list are inserted from javascript -->
      </div>
      <div class="icon"><i class="fas fa-search"></i></div>
    </div>
  </div>

</body>

</html>
F. Müller
  • 3,969
  • 8
  • 38
  • 49
  • It do not work because when I clicking on one of the results to display it in the input it wont work. – xerox28 Dec 15 '20 at 09:29
  • @xerox28 Yes, because you handle the click on the selected element. The blur event triggers when you select one of the elements. You can set a timeout for the blur function. 300ms in this case was sufficient. I have edited the solution. – F. Müller Dec 15 '20 at 09:58
0

I added this to end of javascript in that codepen

//<div class="wrapper">
document.onload=myfunc();
function myfunc(){
  document.addEventListener("click",function closeAutoComplete(){
    searchWrapper.classList.remove("active"); //hide autocomplete box 
  }
  )
  
}
JoePythonKing
  • 1,080
  • 1
  • 9
  • 18
  • It working, thank you :-) – xerox28 Dec 15 '20 at 09:39
  • But this is not efficient at all. Adding an event listener requires a lot of resources. Besides, this affects every single click you do. You only have to execute the hiding function once and not all the time. – F. Müller Dec 15 '20 at 10:07
  • I don't know how to measure efficiency. I've got just enough js to be dangerous. I just found a new event 'focusout' https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event . What a strange name 'blur' is, why not 'lostfocus'? I wonder how efficient blur is compared with any other event. Blur doesn't seem to bubble. If blur is more efficient, how much more? – JoePythonKing Dec 15 '20 at 11:06
0

you can bind a click event to html/body for the sake of hiding your results dropdown

document.addEventListener("click", function(event) {
  // here you can use event.target to exclude some conditionals
  if (event.target.id === "searchInput") return;
  // functionality of hiding your search dropdown
})
fengxh
  • 384
  • 2
  • 8