10

I have two elements:

<input type="text" name="pmTo" id="pmTo" onkeyup="offerSuggs('none');" onfocus="showSelect();" onblur="hideSelect();" />

and a dynamically created one (this is all part of an auto suggest program):

$suggString .= '<div name="'.$value.'" id="'.$value.'" class="suggMouseOver" onmouseover="highlightDivs(this);" onmouseout="blurDivs(this);" onclick="fillInput(this);">'.$value.'</div>';

Okay, and then there are the event functions that correspond to the onblur of the input element and then the onclick of the div element:

function hideSelect() {
            offerSuggs('checkFinal');
            document.getElementById('nameSugg').style.display="none";
            }


function fillInput(elemName) {
            document.getElementById('pmTo').value=elemName.id;
            }

EDIT: How do I get the onclick to trigger correctly on the element they click on without the onblur event hiding the div first which makes the onclick meaningless? I would still like to retain the functionality of the suggestion div disappearing when the textbox loses focus, however.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
linus72982
  • 1,418
  • 2
  • 16
  • 31

3 Answers3

10

I think you are short on answers because your question is confusing. Presumably you have an input that, when focussed, shows a list of suggestions based on the characters entered into the input.

If the user uses the cursor to select an item, then I suppose the blur event of the input fires before the click event of the div and the the div is set to display:none before the click fires, and hence misses the div.

The fix is to call the onblur listener after a short timeout, so:

  <input ... onblur="setTimeout(function(){hideSelect();}, 100);">

Test in a number of browsers, you may need to set the timeout to 200ms or so. It doesn't matter if there's a short, visible delay after the blur event before the suggestions disappear (i.e. a bit too long is better than a bit too short).

Make sure the suggestions don't obscure anything important on the page or users may find them more of a hindrance than a help. :-)

RobG
  • 142,382
  • 31
  • 172
  • 209
  • It worked. It's weird, I had tried using a timeout before and didn't think to try it again as later I found other things wrong in the code that was probably causing the issue. By the way, you missed the 'e' in timeout - easy kill though, thanks. – linus72982 Apr 11 '11 at 02:51
  • 1
    RobG, you are my hero. I'm up-voting a couple of your questions because this saved my life. – FAtBalloon Mar 06 '13 at 05:35
  • @linus72982, no need to specify parameters in Javascript if you're not using them. – Rodrigo Setti Mar 10 '14 at 19:31
8

The accepted answer will work as a quick fix, but by relying on a setTimeout, you assume that the user will only keep clicking down for less than n milliseconds before they release (just picture someone hesitating on a click). To be more sure that the click will go through, you could set a longer timeout, but that means your hide-able element will stay visible that much longer after the blur.

So let's look at the root of the problem.

The click event failing to go through is a result of the events being fired in the following order:

  1. mousedown
  2. blur
  3. mouseup
  4. click

So by the time the mouseup/click events are ready to fire, the blur listener has been called and the element you had once been hovering over has already disappeared.

Here's a general fix (based on the fact that the mousedown event fires first) that should work:

var searchEl = $('#search');
var listEl = $('#dropdown');
var keepListOpen = false;

searchEl
  .on('focus', function() {
    listEl.show();
  })
  .on('blur', function() {
    // Hide the list if the blur was triggered by anything other than
    //  one of the list items
    if (!keepListOpen) {
      listEl.hide();
    }
  });

listEl.find('li')
  .on('mousedown', function(event) {
    // Keep the list open so the onClick handler can fire
    keepListOpen = true;
  })
  .on('click', function(event) {
    // Proof that the list item was clicked
    alert('clicked option');
  });

$(window).on('mouseup', function(event) {
  // Return the keepListOpen setting to its default and hide the list

  // *NOTE* We could have tied this handler to the list items, 
  // but it wouldn't have fired if a mousedown happened on a
  // list item and then the user dragged the mouse pointer 
  // out of the area (or out of the window)
  if (keepListOpen) {
    listEl.hide();
    keepListOpen = false;
  }
});

// Bind to `window.top` if your page might be displayed in an iframe
// $(window.top).on('mouseup', function(event) {
//  if (keepListOpen) {
//    listEl.hide();
//    keepListOpen = false;
//  }
//});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<input id="search" type="text" autocomplete="off" placeholder="Click Here">

<ul id="dropdown" style="display: none;">
  <li>Click Me 1</li>
  <li>Click Me 2</li>
  <li>Click Me 3</li>
</ul>
Chris
  • 2,174
  • 28
  • 37
  • This is the best answer and should be the accepted answer!! The solution is so simple - You just need to use the mousedown event instead of click – Ester Kaufman Mar 01 '20 at 11:38
1

The solution is so simple - you just need to use the mousedown event instead of the click event.

The order of the events is:

  1. mousedown - of the div element which you wanted to use in its click event

  2. blur - of the other element, that your logic is inside.

  3. click - of the div element

You can save a flag in the mousedown event and then, in the onBlur logic, check the flag to know if need to call the click function manually

<input onblur="do();" />
<button type="submit" (mousedown)="submitDown()" (click)="save()"></button>

submitDown() {
  saveCalled = true;
}
do(){
....
  if(saveCalled){
     saveCalled = false;
     save();
  }
}
Ester Kaufman
  • 708
  • 10
  • 20