Ways to conceal a dropdown menu when the user clicks away from it

Here is a simplified example of the input dropdown I am currently working on.

The functionality is as follows: when you focus on the input field, a dropdown menu appears. If you click on one of the options in the dropdown, that option gets populated in the input field and the dropdown disappears. I have implemented this using the onfocus event along with my custom functions dropdown(); and undropdown();.

I am facing a challenge where I need to make the dropdown disappear when someone clicks outside of it. Using the onblur event successfully hides the dropdown, but it also prevents the input from being populated when an option is clicked. This happens because the onblur function triggers first, making the input(); function inaccessible since the dropdown is already hidden.

If I add an onclick event to the body or a parent element, it registers the input's onfocus as a click, causing the dropdown to show and hide immediately without giving the user a chance to select an option.

Any guidance on how to sequence the functions properly so they execute in the correct order without conflicting with each other would be greatly appreciated.

You can access the JSFiddle demonstration here.

function input(pos) {
  var dropdown = document.getElementsByClassName('drop');
  var li = dropdown[0].getElementsByTagName("li");
  document.getElementsByTagName('input')[0].value = li[pos].innerHTML;
  undropdown(0);
}
function dropdown(pos) {
  document.getElementsByClassName('content')[pos].style.display = "block"
}
function undropdown(pos) {
  document.getElementsByClassName('content')[pos].style.display = "none";
}
.drop {
  position: relative;
  display: inline-block;
  vertical-align: top;
  overflow: visible;
}
.content {
  display: none;
  list-style-type: none;
  border: 1px solid #000;
  padding: 0;
  margin: 0;
  position: absolute;
  z-index: 2;
  width: 100%;
  max-height: 190px;
  overflow-y: scroll;
}
.content li {
  padding: 12px 16px;
  display: block;
  margin: 0;
}
<div class="drop">
  <input type="text" name="class" placeholder="Class" onfocus="dropdown(0)"/>
  <ul class="content">
    <li onclick="input(0)">Option 1</li>
    <li onclick="input(1)">Option 2</li>
    <li onclick="input(2)">Option 3</li>
    <li onclick="input(3)">Option 4</li>
  </ul>
</div>

PS: Besides addressing the issue mentioned above, I welcome suggestions for refining the title of this question to make it more discoverable for those facing a similar dilemma.

Answer №1

When dealing with the onblur event, you have the option to invoke a function that triggers the undropdown(0); method after a brief setTimeout delay. Here is an example:

function set() {
  setTimeout(function(){ 
    undropdown(0);
  }, 100);
}

HTML

<input type="text" name="class" placeholder="Class" onfocus="dropdown(0)" onblur="set()" />

No additional modifications are necessary.

function input(pos) {
  var dropdown = document.getElementsByClassName('drop');
  var li = dropdown[0].getElementsByTagName("li");

  document.getElementsByTagName('input')[0].value = li[pos].innerHTML;
  undropdown(0);
}

function dropdown(pos) {
  document.getElementsByClassName('content')[pos].style.display= "block"
}

function undropdown(pos) {
  document.getElementsByClassName('content')[pos].style.display= "none";
}

function set() {
  setTimeout(function(){ 
    undropdown(0);
  }, 100);
}
.drop {
            position: relative;
            display: inline-block;
            vertical-align:top;
            overflow: visible;
        }

        .content {
            display: none;
            list-style-type: none;
            border: 1px solid #000; 
            padding: 0;
            margin: 0;
            position: absolute;
            z-index: 2;
            width: 100%;
            max-height: 190px;
            overflow-y: scroll;
        }

        .content li {
            padding: 12px 16px;
            display: block;
            margin: 0;
        }
<div class="drop">
        <input type="text" name="class" placeholder="Class" onfocus="dropdown(0)" onblur="set()" />
        <ul class="content">
            <li onclick="input(0)">Option 1</li>
            <li onclick="input(1)">Option 2</li>
            <li onclick="input(2)">Option 3</li>
            <li onclick="input(3)">Option 4</li>
        </ul>
    </div>

Answer №2

To enhance the dropdown's accessibility, you can use the `tabindex` attribute to make it focusable. In the input's `blur` event listener, only hide the dropdown if the focus did not shift to the dropdown itself (refer to this solution for determining where the focus moved to).

<ul class="content" tabindex="-1"></ul>
input.addEventListener('blur', function(e) {
  if(!e.relatedTarget || !e.relatedTarget.classList.contains('content')) {
    undropdown(0);
  }
});

function input(e) {
  var dropdown = document.getElementsByClassName('drop');
  var li = dropdown[0].getElementsByTagName("li");
  document.getElementsByTagName('input')[0].value = e.target.textContent;
  undropdown(0);
}
[].forEach.call(document.getElementsByTagName('li'), function(el) {
  el.addEventListener('click', input);
});
function dropdown(pos) {
  document.getElementsByClassName('content')[pos].style.display = "block"
}
function undropdown(pos) {
  document.getElementsByClassName('content')[pos].style.display = "none";
}
var input = document.getElementsByTagName('input')[0];
input.addEventListener('focus', function(e) {
  dropdown(0);
});
input.addEventListener('blur', function(e) {
  if(!e.relatedTarget || !e.relatedTarget.classList.contains('content')) {
    undropdown(0);
  }
});
.drop {
  position: relative;
  display: inline-block;
  vertical-align: top;
  overflow: visible;
}
.content {
  display: none;
  list-style-type: none;
  border: 1px solid #000;
  padding: 0;
  margin: 0;
  position: absolute;
  z-index: 2;
  width: 100%;
  max-height: 190px;
  overflow-y: scroll;
  outline: none;
}
.content li {
  padding: 12px 16px;
  display: block;
  margin: 0;
}
<div class="drop">
  <input type="text" name="class" placeholder="Class" />
  <ul class="content" tabindex="-1">
    <li>Option 1</li>
    <li>Option 2</li>
    <li>Option 3</li>
    <li>Option 4</li>
  </ul>
</div>

Answer №3

Using an accepted answer may not always be the best solution. I encountered a tricky bug in a sophisticated application because I relied on a setTimeout function to introduce a delay of approximately 200ms for the browser to process dropdown clicks before triggering a blur event. Although this method seemed successful during my testing, it caused problems for some users, especially those with slower computer systems.

A more reliable approach is to check the relatedTarget property within the focusout event:

input.addEventListener('focusout', function(event) {
  if(!isDropdownElement(event.relatedTarget)) {
    // hide the dropdown
  }
});

The relatedTarget in the focusout event contains a reference to the element that is gaining focus. This method has proven to work consistently across all browsers I have tested, excluding IE10 and earlier versions, but including IE11 and Edge.

Answer №4

If you're looking to create a custom dropdown menu, check out this helpful example from W3Schools: https://www.w3schools.com/howto/howto_custom_select.asp

The example provided on the website demonstrates how focus is managed within the custom dropdown menu:

An overarching click-handler is used to detect clicks outside of the dropdown list:

document.addEventListener("click", closeAllSelect);
. This setup ensures that all dropdown menus are closed when a user clicks anywhere in the document.

Additionally, when a user makes a selection from the dropdown list, the click event is immediately halted by using e.stopPropagation(); inside the selection-handler function.

This approach eliminates the need for a timer workaround and streamlines the functionality of the custom dropdown menu.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Unable to modify the height and color of tabs in Angular 6

I am in the process of learning Angular, CSS, and HTML. I have been using MatTabsModule to create tabs, but I am having trouble adjusting the height, background, and other properties of the tabs. I am struggling to override the default properties of MatTab ...

Tips for maintaining accessibility to data while concealing input information

Is it possible to access data submitted in a form even if the inputs were hidden by setting their display to none? If not, what approach should be taken to ensure that data can still be sent via form and accessed when the inputs are hidden? ...

Text at the center of a series of circles on canvas

Can someone assist me in aligning my text at the center of 5 concentric circles? Below is the code I have written, but it doesn't seem to be working. Any suggestions on what modifications I need to make to achieve this would be greatly appreciated. Th ...

What is the best way to manage numerous asynchronous post requests in AngularJS?

$scope.savekbentry = function (value) { console.log('save clicked'); console.log(value); console.log($scope.kbentry.kbname); $scope.kbentry.mode = value; var kbname = $scope.kbentry.kbname; var kbd ...

Is it possible to adjust the height of ion.RangeSlider?

Currently, I am using the ion.RangeSlider in my project, which can be accessed here: Although it functions well, I am interested to know if there is a way to increase the size/height of the slider. I have attempted adjusting the height and padding but hav ...

What is the best way to display grouped items horizontally in an ASP.NET ListView?

My ASP ListView currently displays items vertically in columns with GroupItemCount set to 3: A D G B E H C F I Is there a way to adjust the display so that it groups items horizontally in rows like this? A B C D E F G H I This is the ListVi ...

What is the most effective way to create a live preview using AngularJS and CSS?

Is there a way to dynamically change the background color of the body based on the hexadecimal value entered in a textfield, without using jQuery? I want the change to happen live as the user types. The current code works but it doesn't feel right. I ...

What types of devices are compatible with the different versions of the Android operating system?

I am currently working on a jquerymobile application for Android versions 2, 3, and 4, as well as iPhone, iPad, and iPod touch. I am analyzing user-agent strings using jQuery and jquerymobile. After testing with an emulator, I have noticed that all device ...

Using Java to input values into empty slots within an HTML document

I have an HTML file with the following content: <body> <p>Hello! <b>[NAME]%</b></p> </body> Additionally, in my Java file, I have the following code snippet: String name = "John"; I would like to know: How c ...

Creating an Interactive and Engaging 3D Experience on Facebook with the Power of Javascript API

Looking for suggestions on a 3D API in JavaScript that can be used to create immersive applications on Facebook. Is there something similar to this one: ? Appreciate any insights. ...

How to access vue.js 3 single file component functions from within a script tag

Imagine having a single file component structured like this: <template> // content irrelevant </template> <script> export default { data() { return { redLocations: [ "Isfahaan", "Qom", ...

center the radio button with CSS3

I am experiencing difficulty focusing the pointer inside this customized radio button. <div class="radio"> <input id="left" type="radio" name="gender" value="left"> <label for="left">Left</label> <input id="right" ty ...

Utilize JavaScript to submit the FORM and initiate the 'submit' Event

Hey there! Here's the code I've been working on: HTML : <html> <body> <form enctype="multipart/form-data" method="post" name="image"> <input onchange="test();" ...

Angular 2 ensures that all of the columns receive updates

Currently, I am facing an issue with my angular 2 editable table created using ngFor. The problem arises when I attempt to edit one column, as all the columns get updated with the same value. One solution could be creating a new component, but I would pr ...

Issue with formatting and hexadecimal encoding in JavaScript

I am currently developing a LoRaWAN encoder using JavaScript. The data field received looks like this: {“header”: 6,“sunrise”: -30,“sunset”: 30,“lat”: 65.500226,“long”: 24.833547} My task is to encode this data into a hex message. Bel ...

methods for selecting the final class within a div

Within the given example, I am trying to extract the last class of a div which is named tex1. My goal is to display it in the console using jQuery like so: let last = $('#tex').attr('class').last(); console.log(last); // error <s ...

Alignment problem detected in Firefox and Safari, but works perfectly in Chrome

Currently dealing with a CSS problem on my website that is only appearing in Safari and Firefox, while Chrome displays it correctly. The images are not positioning correctly and are stacking instead of being placed in each box as intended. I've expe ...

Issues with handler event not firing properly using ASP.net, JavaScript, JQuery, and AJAX, despite successful AJAX implementation

I am currently utilizing a HTTP handler (ASHX) that is being invoked from the UI side through an AJAX function. The objective of this call is as follows: Upon loading the section, the status of the short code on the server will be displayed in the shortco ...

NodeJS: Implement a method to delete a file or folder using a path without the file extension (Strategic approach)

I am facing a challenge in deleting a file or folder based on a path that does not include an extension. Consider the path below: /home/tom/windows The name windows could refer to either a folder named windows OR a file named windows.txt Given that the ...

What is the best way to retrieve nested objects in JSON using JavaScript and an Ajax request?

I am facing an issue with a nested JSON structure and trying to access the data through an ajax request to populate an HTML table. Here is a sample of the JSON structure: { sum: { total: 2, }, data: [ { id: '1', attribu ...