Как скрыть раскрывающийся список, когда пользователь щелкает за его пределами

Ниже приведена упрощенная версия раскрывающегося списка ввода, с которым я работаю.

основная сводка того, что он делает: если вы сосредоточитесь на входе, появится выпадающее меню. Если щелкнуть один из параметров в раскрывающемся списке, параметр заполнит входные данные, и раскрывающийся список исчезнет. Это достигается с помощью onfocus и функции, которые я назвал dropdown(); и undropdown();.

Я нахожусь в дилемме, где я не могу заставить выпадающий список исчезнуть, когда кто-то щелкает в другом месте. если я использовать onblur, он успешно скрывает раскрывающийся список, но если вы нажмете на опцию, она не заполняет вход, это потому, что onblur функция запускается первой и, следовательно,input(); функция не запускается, потому что раскрывающийся список уже скрыт.

если вы ставите onclick на теге body или другом родителе он рассматривает onfocus как щелчок, где он работает, это

3 ответов


в этом случае On onblur вы можете вызвать функцию, которая запускает undropdown(0); после очень крошечного setTimeout почти мгновенно. Вот так:

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

HTML-код

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

никаких других изменений не требуется.

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>

вы можете сделать выпадающий список фокусируемым с tabindex, а на входе blur прослушиватель событий только скрыть раскрывающийся список, если фокус не пошел в раскрывающийся список (см. когда onblur происходит, как я могу узнать, какой элемент фокус пошел 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>

принятый ответ-наивный и ненадежный подход. У меня была труднодоступная ошибка в сложном приложении, потому что я использовал setTimeout чтобы дать ~ 200ms задержку, чтобы браузер мог обрабатывать выпадающий щелчок, прежде чем произойдет событие размытия. Хотя он отлично работал на каждой установке, с которой я его тестировал, у некоторых пользователей были проблемы, в частности у пользователей с более медленными машинами.

правильный способ-проверить relatedTarget on focusout событие:

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

relatedTarget на focusout содержит ссылка на элемент, который получает фокус. Это надежно работает в каждом браузере, который я тестировал до сих пор (я не тестировал IE10 и ниже, только IE11 и Edge).