点击HTML5 datalist选项时执行操作

46

我正在使用一个 <datalist> 元素。

<datalist id="items"></datalist>

使用 AJAX 来填充列表

 function callServer (input) {
    xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function(){
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200){
            //return the JSON object
            console.log(xmlhttp.responseText);
            var arr = JSON.parse(xmlhttp.responseText);
            var parentDiv = document.getElementById('items');
            parentDiv.innerHTML = "";
            //fill the options in the document
            for(var x = 0; x < arr.length; x++) {
                var option = document.createElement('option');
                option.value = arr[x][0];
                option.innerHTML = arr[x][1];
                //add each autocomplete option to the 'list'
                option.addEventListener("click", function() {
                  console.log("Test");
                });
                parentDiv.appendChild(option);
            };

        }
    }
    xmlhttp.open("GET", "incl/search.php?value="+input.value, true);
    xmlhttp.send();
}

然而,当我在数据列表中点选一个选项时无法触发事件。比如当我输入 "Ref F" 时,如果出现 "Ref flowers" 这个选项,我需要执行一个事件,但是我不知道该怎么做。

option.addEventListener("click", function() {
option.addEventListener("onclick", function() {
option.addEventListener("change", function() {

请提供相同的 jsfiddle 。我认为您正在错误的时间附加事件。您需要在将元素附加到 DOM 后绑定事件。 - Rayon
你可以使用setTimeout与input事件来避免每次触发输入:https://dev59.com/xaPia4cB1Zd3GeqP3cEw#69008887 - TomoMiha
10个回答

59
抱歉挖出这个问题,但我遇到了类似的问题并找到了一个解决方案,这个解决方案对你也应该适用。

function onInput() {
    var val = document.getElementById("input").value;
    var opts = document.getElementById('dlist').children;
    for (var i = 0; i < opts.length; i++) {
      if (opts[i].value === val) {
        // An item was selected from the list!
        // yourCallbackHere()
        alert(opts[i].value);
        break;
      }
    }
  }
<input type='text' oninput='onInput()' id='input' list='dlist' />

<datalist id='dlist'>
  <option value='Value1'>Text1</option>
  <option value='Value2'>Text2</option>
</datalist>

这个解决方案是基于Stephan Mullers的解决方案。它应该也适用于动态填充的数据列表。
不幸的是,无法确定用户是通过点击数据列表中的项目、按下Tab键选择,还是手动输入整个字符串来进行选择。

3
你必须区分thatpaste事件,它们是非常不同的路径,可能无法达到相同的终点。 - vsync
筛选器在IE中无法工作,我们有其他解决方案吗? - user2090762
在IE11上,这与Firefox完全相同。您使用的是哪个版本的IE,有什么问题无法正常工作? - chillichief
1
对我来说,使用document.getElementById('dlist').childNodes并没有得到预期的结果,但是将其切换为document.getElementById('dlist').children完美地解决了问题。 - undefined
使用children而不是childNodes可能是个好主意,因为childNodes还会返回注释节点,我已经相应地修改了我的答案。谢谢提供的信息。 - undefined

22
由于 <datalist> 元素缺少可用事件,因此没有办法从建议中选择一个选项,除非观察输入事件(changeinput等)。如果想检查是否从列表中选择了某一项,您应该将每次更改与可用选项进行比较。这意味着当用户手动输入准确的值时,事件也会触发,而无法阻止此操作。详见我在此处的答案:Determine if an element was selected from HTML 5 datalist by pressing enter key

document.querySelector('input[list="items"]').addEventListener('input', onInput);

function onInput(e) {
   var input = e.target,
       val = input.value;
       list = input.getAttribute('list'),
       options = document.getElementById(list).childNodes;

  for(var i = 0; i < options.length; i++) {
    if(options[i].innerText === val) {
      // An item was selected from the list!
      // yourCallbackHere()
      alert('item selected: ' + val);
      break;
    }
  }
}
<input list="items" type="text" />
<datalist id="items">
  <option>item 1</option>
  <option>item 2</option>
</datalist>


谢谢提供代码片段,但是它在我的解决方案中无法工作,我猜测这是因为数据列表被不断地重新填充和清空。 - Hobbyist
2
如果您使用了<option value="item 1">选项,则需要使用options[i].value而不是innerText,这样就可以完美地运行。 - Mike Mellor

20

使用 keydown

与其他答案相反,确实可以检测选项是键入还是从列表中选择的。

键入和<datalist>点击都会触发输入框的keydown监听器,但只有键盘事件具有 key 属性。因此,如果触发了没有 key 属性的keydown事件,您就知道它是从列表中点击的。

演示:

const opts = document.getElementById('dlist').childNodes;
const dinput = document.getElementById('dinput');
let eventSource = null;
let value = '';

dinput.addEventListener('keydown', (e) => {
  eventSource = e.key ? 'input' : 'list';
});
dinput.addEventListener('input', (e) => {
  value = e.target.value;
  if (eventSource === 'list') {
    alert('CLICKED! ' + value);
  }
});
<input type="text" id="dinput" list="dlist" />

<datalist id="dlist">
  <option value="Value1">Text1</option>
  <option value="Value2">Text2</option>
</datalist>

请注意,如果被点击的值已经在盒子中,则不会触发警报,但这可能是希望的行为。(这也可以通过使用额外的跟踪变量,在 keydown 监听器中切换来添加。)


1
太棒了!这非常聪明。我试图将鼠标和键盘事件都连接到输入上,但很奇怪的是,尽管在下拉菜单中点击选项,它仍然会引发键盘事件。这似乎是区分两者之间的唯一方法。 - powersource97
很好的解决方案。不幸的是,Safari(版本15.1)在选择datalistoption时不会触发keydown事件,因此eventSource将无法获得更新并保持在最后状态。 - David Wolf
这个答案的代码片段在 Chrome(Mac)中按照描述正常工作,但在Firefox(Mac)中不行。(在后者中,警告从未出现。) - Jon Schneider
它在Windows上的Firefox上也无法工作。我猜这里只有基于Chromium的浏览器才能工作。那真是不幸。 - dzek
@Let'sApp 显然它没有被使用 - undefined

5

Datalist实际上没有事件(不是所有浏览器都支持),但可以通过以下方式检测是否选择了datalist选项:

<input type="text" list="datalist" />

<datalist id="datalist">
  <option value="item 1" />
  <option value="item 2" />
</datalist>


window.addEventListener('input', function (e) {
 let event = e.inputType ? 'input' : 'option selected'
 console.log(event);
}, false);

演示


1
这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - thelearner

3

Shob的回答是唯一可以检测到选项被点击,同时不会在中间文本与选项匹配时触发(例如:如果有人键入“Text1”以查看选项“Text11”,“Text12”等,则不会触发,即使“Text1”在数据列表中)。

然而,原始答案似乎在新版本的Firefox上不起作用,因为keydown事件在单击时不会触发,所以我进行了适应性处理。

let keypress = false;
document.getElementById("dinput").addEventListener("keydown", (e) => {
    if(e.key) {
        keypress = true;
    }
});
document.getElementById("dinput").addEventListener('input', (e) => {
    let value = e.target.value;
    if (keypress === false) {
        // Clicked on option!
        console.debug("Value: " + value);
    }
    keypress = false;
});
<input type="text" id="dinput" list="dlist" />

<datalist id="dlist">
  <option value="Value1">Text1</option>
  <option value="Value2">Text2</option>
</datalist>


2

数据列表不支持点击监听器,而且OnInput非常耗费资源,每次都要检查整个列表是否有任何更改。

我的解决方法是使用:

document.querySelector('#inputName').addEventListener("focusout", onInput);

每当客户点击输入文本框,然后点击其他地方时,都会触发FocusOut事件。如果他们先点击了文本框,然后再点击其他地方,我认为他们已经输入了他们想要的值。

为了检查值是否有效,您可以像输入一样进行检查:

   function onInput(e) {
        var val = document.querySelector('#inputName').value;
        options = document.getElementById('datalist').childNodes;
        for(var i = 0; i < options.length; i++) {
             if(options[i].innerText === val) {
                  console.log(val);
                  break;
             }
        }
    }

1
<input type="text" id="buscar" list="lalista"/>
<datalist id="lalista">
  <option value="valor1">texto1</option>
  <option value="valor2">texto2</option>
  <option value="valor3">texto3</option>  
</datalist>
//0 if event raised from datalist; 1 from keyboard
let idTimeFuekey = 0;  
buscar.oninput = function(){
    if(buscar.value && idTimeFuekey==0) {
        alert('Chévere! vino desde la lista')
    }
};
buscar.onkeydown = function(event){
    if(event.key){ //<-- for modern & non IE browser, more direct solution
        window.clearInterval(idTimeFuekey);
        idTimeFuekey = window.setInterval(function(){ //onkeydown --> idTimeFuekey++ (non 0)
            window.clearInterval(idTimeFuekey);
            idTimeFuekey = 0 //after 500ms onkeydown --> 0 (could work 500, 50, .. 1)
        }, 500) 
    }
}

互联网浏览器 - Alfredo Massai
也许尝试在那里解释你的代码。 - TourEiffel

0

数据列表点击事件

更新的解决方案:使用事件inputType

对于datalist元素,不存在'click'监听器。以下解决方案利用了与'input'元素上的事件监听器相关的inputType事件,我认为这是最简洁的方法。不幸的是,并非所有的供应商都遵循Firefox的方法。如果他们这样做,我就不需要使用navigator.userAgent来尝试确定使用的浏览器,以便“知道”要遵循的方法。但是,以下示例可以扩展到其他所有的浏览器。

  <body>
    <form>
      <label for="customer_name">Name</label>
      <input id="customer_name" list="customer_list" type="text" data-list="customer" />
      <datalist id="customer_list">
        <option value="Chrome"></option>
        <option value="Edge"></option>
        <option value="Firefox"></option>
        <option value="Opera"></option>
        <option value="Safari"></option>
      </datalist>
    </form>
  </body>
  <script>
    const DATA_LIST = document.querySelectorAll('[data-list]')
    DATA_LIST.forEach((element) => {
      element.addEventListener('input', (event) => {
        const browser = navigator.userAgent
        /////  FIREFOX on WINDOWS  /////
        if (browser.includes('Firefox')) {
          //  KEYBOARD EVENT  //
          if (event.inputType != 'insertReplacementText') {
            return
          }
          //  MOUSE CLICK  //
          if (event.inputType === 'insertReplacementText') {
            alert(`Mouse clicked '${event.target.value}' in the datalist...\nusing Firefox browser for Windows`)
          }
        }
        /////  CHROME & Edge on WINDOWS  /////
        if (browser.includes('Chrome')) {
          //  KEYBOARD EVENT  //
          if (event.inputType) {
            return
          }
          //  MOUSE CLICK  //
          if (!event.inputType) {
            alert(`Mouse clicked '${event.target.value}' in the datalist...\nusing a Chromium browser for Windows`)
          }
        }
      })
    })
  </script>


这将适用于Windows上的Chrome、Edge和Firefox。你可以添加更多的逻辑,比如对Mac上的Safari使用browser.includes('Macintosh')。但是,处理事件的逻辑可能需要进行调整,因为我没有在Mac上进行测试。然后,你还需要对iOS和Android等进行同样的操作。但是,按照这种方法是可行的。除此之外,如果所有供应商都遵循与Firefox相同的方式,这实际上只需要6行代码。哎呀! - undefined

0

在遇到这个问题并且找不到合适的解决方案后,我尝试了一下。

我的做法是查看给定输入事件的“inputType”,以及来自上面的事件切换变量,如下所示:

eventSource = false;

const selector = document.getElementById("yourElementID");

selector.addEventListener('input', function(evt) {
    if(!eventSource) {
        if(evt.inputType === "insertReplacementText") {
            console.log(selector.value);
        }
    }
});

selector.addEventListener('keydown', function(evt) {
    eventSource = !evt.key;
});

如果您想允许用户搜索字段,但仅在从数据列表本身进行选择时触发特定的函数/事件,则此方法适用。希望能有所帮助!

编辑:忘记提到这是通过Firefox完成的,并未在其他浏览器上进行测试。


-1

至少在Firefox浏览器中,onselect事件可以在input标签上使用。

<input type="text" id="dinput" list="dlist" onselect="alert(this.value)"/>

<datalist id="dlist">
  <option value="Value1">Text1</option>
  <option value="Value2">Text2</option>
</datalist>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接