0

I have a div element used as a toggle, and a select element which shall be shown/hidden in the way that:

(1) When clicking the toggle, the select shall be shown and focused, if it was hidden

(2) When clicking the toggle, the select shall be hidden, if it was visible

(3) When the select looses focus, it shall be hidden.

Now there is this edge case when the select has focus and user clicks the toggle -- then both events, onclick and onblur, are fired and cancel each other, at least in Firefox:

<div id="toggle">toggle</div>
<div>foobar</div>
<select id="select" multiple>
    <option>foo</option>
    <option>bar</option>
</select>
document.getElementById('toggle').onclick = function () {
    var select = document.getElementById('select');
    select.style.display = select.style.display === 'none' ? 'inline' : 'none';
        select.focus();
    };

document.getElementById('select').onblur = function () {
    this.style.display = 'none';
};

https://jsfiddle.net/2wrxykpd/

I tried checking event.explicitOriginalTarget in the onblur function (online hide select if target of the onblur event is not the toggle), which would work in Firefox, but not in Chrome.

So what would be the proper way to do this?

DonFuchs
  • 133
  • 7

2 Answers2

1

I found a solution myself, also see this related question.

The idea is to add mousedown and mouseup events to the toggle. Then the order of execution will be

(1) toggle.onmousedown

(2) select.onblur

(3) toggle.onmouseup

(4) toggle.onclick

That is, inside the mousedown/mouseup events we can set and reset an ignore flag; the flag will then only be set inside the onblur, in the onclick it will already have been reset.

So we got

document.getElementById('toggle').onclick = function () {
  var select = document.getElementById('select');
  select.style.display = select.style.display === 'none' ? 'inline' : 'none';
  select.focus();
};
document.getElementById('toggle').onmousedown = function () {
  document.getElementById('select').ignoreClickEvent = true;
};
document.getElementById('toggle').onmouseup = function () {
  document.getElementById('select').ignoreClickEvent = false;
};

document.getElementById('select').onblur = function () {
  if (!this.ignoreClickEvent) {
    this.style.display = 'none';
  }
};

https://jsfiddle.net/ezt0sonj/

DonFuchs
  • 133
  • 7
0

You can bind to focus on the toggle and then check against the relatedTarget:

var select = document.getElementById('select');
var toggle = document.getElementById('toggle');

toggle.addEventListener('focus', function (event) {
 if (event.relatedTarget !== select) {
   var isHidden = select.classList.toggle('hidden')

    if (!isHidden) {
      select.focus();
    }
  }
  
  toggle.blur()
});

select.addEventListener('blur', function () {
 select.classList.add('hidden')
});
.hidden {
  display: none !important;
}
<div>
  <button id="toggle">toggle</button>
</div>
<div>
  <select id="select" class="hidden" multiple>
    <option>foo</option>
    <option>bar</option>
  </select>
</div>
ArrayKnight
  • 6,956
  • 4
  • 17
  • 20