How can I make a browser display all datalist options when a default value is set?
Asked Answered
P

12

43

I have an HTML form with a datalist and where the value is set with PHP, like

<input list="values" value="<?php echo $val; ?>">
 <datalist id="values">
  <option value="orange">
  <option value="banana">
 </datalist>

I want the user to see the options in the datalist, as well as the current value from the PHP. However, the "autocomplete" action causes values from the list that don't match (or start with) the current value to be hidden from the list, say if $val='apple'. Is there any way to avoid that, or is this behaviour fixed by the browser?

Prediction answered 27/5, 2016 at 8:36 Comment(2)
Your question is not clear.Molybdenite
Please explain how the question is not clear.Prediction
S
10

<datalist> uses autocomplete functionality so this is normal behaviour.

For example if you set input value to be 'o' you will see just orange in datalist options. It checks word from first letter. But if you set input value to 'a' then you won't see options at all.

So if you already have value in input then nothing will be shown in datalist options except that value if it exists. It doesn't behave like select.

Workaround for this would be to use jquery like this for example:

$('input').on('click', function() {
  $(this).val('');
});
$('input').on('mouseleave', function() {
  if ($(this).val() == '') {
    $(this).val('apple');
  }
});

Full test here: https://jsfiddle.net/yw5wh4da/

Or you could use <select>. To me it is better solution in this case.

Siftings answered 27/5, 2016 at 9:25 Comment(4)
I realise this behaviour is normal but I want to know if there is a way to change it. I suspect not. I think the workaround in my case will be to have a second input element with the list, which fills the first on submission, but this is quite specific to my use-case.Prediction
If you mean to change it with plain HTML then, as I know, it is not possibleSiftings
@Dave W: I think this is a pretty normal use case. W3C just wants to focus on adding more bloat to CSS instead of fixing basic features.Tyner
"Select" is an option, but it also has a feature that may not satisfy the OP question. With "Select" you cannot enter a brand new value into the list, you are constrained to the options in the HTML. Often what you want is a text input field that can take a new value but also has a list of pre-defined options as well.Overarch
N
10

I found using click and mouseleave events to be not ideal, as they won't work using keyboard controls.

So I came up with this helper function:

Update: trigger a blur() event after input

As commented by @Joe Platano we can improve the datalist behavior – by triggering a blur() after the input event (at least for chromium/blink based browsers).

keepDatalistOptions('.keepDatalist');

function keepDatalistOptions(selector = '') {
  // select all input fields by datalist attribute or by class/id
  selector = !selector ? "input[list]" : selector;
  let datalistInputs = document.querySelectorAll(selector);
  if (datalistInputs.length) {
    for (let i = 0; i < datalistInputs.length; i++) {
      let input = datalistInputs[i];
      input.addEventListener("input", function(e) {
        e.target.setAttribute("placeholder", e.target.value);
        e.target.blur();
      });
      input.addEventListener("focus", function(e) {
        e.target.setAttribute("placeholder", e.target.value);
        e.target.value = "";
      });
      input.addEventListener("blur", function(e) {
        e.target.value = e.target.getAttribute("placeholder");
      });
    }
  }
}
<div style="display:flex; gap:1em;">
  <div>
    <h3>Keep datalist options</h3>
    <input class="keepDatalist" type="text" list="data" value="apple">
    <datalist id="data">
  <option value="apple">
  <option value="orange">
  <option value="banana">
</datalist>
  </div>
  <div>
    <h3>Default behavior</h3>
    <input type="text" list="browser" value="">
    <datalist id="browser">
  <option value="firefox">
  <option value="opera">
  <option value="safari">
</datalist>
  </div>
</div>

The main concept is basically the same:

  1. save the current input value to placeholder attribute
  2. reset the value on focus to show all datalist options
  3. swap actual input value with placeholder on input

This function also updates the current value on input event – otherwise you would lose the current value after navigating via tab key to the next input.

Why you shouldn't mess with the default behavior of form elements

For instance, Firefox won't accept this hack – and probably for good reasons.

When working on forms your first priorities should always be:

  • reliability:
    Form data can successfully be submitted even in sh... less modern browsers
  • accessibility:
    custom hacks should also work for visually impaired people using a screen reader like NVDA or JAWS

So you should always "triple/quadruple check" if your customized form element behavior provides a reliable and predictable user experience.

Nicolasanicolau answered 2/1, 2022 at 17:33 Comment(6)
This is a very good answer. I ended up modifying it and using it in production.Tetherball
how I trigger this keepDatalistOptions function? I copied the script and html part but it doesnt work. is this keepDatalistOptions('.keepDatalist'); just as it is in <script> </script>?Domesday
Your script block should be at the bottom of your <body> tag. If you need to call the function from your head you need to wrap it an a DOM content ready event like so window.addEventListener('DOMContentLoaded', (e) => {keepDatalistOptions('input[list]')});Nicolasanicolau
Great yes this was just a confusion on my side thanks!! I am just hobbyist :) btw, I added document.activeElement.blur(); as last line in the change listener function. So when a data list element gets selected, the focus will be removed. otherwise, while staying on focus I can not select the datalist directlyDomesday
@Joe Platano: awesome! I actually planned to include exactly this blur() trigger today – unfortunately, I got stuck on firefox's datalist handling: You need a double click to see all options, but you can't trigger this programmatically via dispatch event. So If you find a solution for this problem (or other optimizations) I'd strongly encourage to add your insights in an answer (seriously, this is an old question, but there's still a lot more to discover). But be careful: form functionality is tricky enough – we always have to choose between fancyness and reliability;)Nicolasanicolau
@Joe Platano: Finally I've included your suggestion! But the firefox behavior is still a challenge ... At least you don't need a document selector – you can just add this to the input event listener.Nicolasanicolau
A
7

HTML:

<input list="values" placeholder="select">
<datalist id="values">
  <option value="orange">
  <option value="banana">
  <option value="lemon">
  <option value="apple">
</datalist>

JS:

$('input').on('click', function() {
    $(this).attr('placeholder',$(this).val());
  $(this).val('');
});
$('input').on('mouseleave', function() {
  if ($(this).val() == '') {
    $(this).val($(this).attr('placeholder'));
  }
});
Angelenaangeleno answered 12/2, 2017 at 11:49 Comment(0)
L
2

The solutions above are great, but if the user naturally just clicks next to the placeholder value, inside the input box, intending to keep and continue it, it will become completely erased.

So based on the solutions above, I did the following solution for me:

HTML:

<input id="other" list="values" value="<?php echo $val; ?>">
 <datalist id="values">
  <option value="orange">
  <option value="banana">
 </datalist>

JS:

jQuery(document).ready(function ($) {
    function putValueBackFromPlaceholder() {
        var $this = $('#other');
        if ($this.val() === '') {
            $this.val($this.attr('placeholder'));
            $this.attr('placeholder','');
        }
    }

    $('#other')
        .on('click', function(e) {
            var $this = $(this);
            var inpLeft = $this.offset().left;
            var inpWidth = $this.width();
            var clickedLeft = e.clientX;
            var clickedInInpLeft = clickedLeft - inpLeft;
            var arrowBtnWidth = 12;
            if ((inpWidth - clickedInInpLeft) < arrowBtnWidth ) {
                $this.attr('placeholder',$this.val());
                $this.val('');
            }
            else {
                putValueBackFromPlaceholder();
            }
        })
        .on('mouseleave', putValueBackFromPlaceholder);
});

For me there're many input boxes on the page, and I want only this one to have this behavior, so I work with id and have it "personal." If you wish to change it to the more generic function, you'll have to bind putValueBackFromPlaceholder with the element, on which the click happened, and the event itself.

Layette answered 28/7, 2017 at 12:29 Comment(3)
Exactly what I wanted. Plus one small fix if ((inpWidth - clickedInInpLeft) < arrowBtnWidth) { if ($this.val() != "") { $this.attr('placeholder', $this.val()); $this.val(''); } }Vesting
@andre719mv, thank you, and I think that change is not a must - if it's empty, it will just have empty placeholder. Or you see other reasons to change it?Layette
I think this is a must, if you don`t want to lose your selected value by clicking on arrow second time. Steps to reproduce: 1. Select some value 2. Click on arrow 3. Click on arrow second time - your value and placeholder will become empty. Here is fixed version jsfiddle.net/7sugxf2vVesting
N
1

Another option, based on the proposed solution, that preserves the previous value or the new one chosen.

// Global variable to store current value
var oldValue = '';
// Blank value when click to activate all options
$('input').on('click', function() {
  oldValue = $(this).val();
  $(this).val('');
});
// Restore current value after click
$('input').on('mouseleave', function() {
  if ($(this).val() == '') {
    $(this).val(oldValue);
  }
});

https://jsfiddle.net/jpussacq/7Ldbc013/

Napoleon answered 2/11, 2018 at 12:43 Comment(0)
C
1

I tried many things and finally found this. hope developers like this.

<input id="mylist" list="mylistOptions" value="" 
onfocus="this.value=null;"
onchange="this.blur();"
>
<datalist id="mylistOptions">
  <option value="apple">
  <option value="orange">
  <option value="banana">
</datalist>
Clifford answered 8/6, 2023 at 11:15 Comment(0)
F
0

The cleanest option might be the one recommended by Mozilla.org.

input.onfocus = function () {
  datalist.style.display = 'block';
}

input.onblur = function () {
  datalist.style.display = 'none';
}



for (let option of datalist.options) {
  option.onclick = function () {
    input.value = this.value;
    datalist.style.display = 'none';
  }
}

datalist.style.width = input.offsetWidth + 'px';
datalist.style.left = input.offsetLeft + 'px';
datalist.style.top = input.offsetTop + input.offsetHeight + 'px';
datalist {
  position: absolute;
  background-color: lightgrey;
  font-family: sans-serif;
  font-size: 0.8rem;
}

option {
  background-color: #bbb;
  padding: 4px;
  margin-bottom: 1px;
  cursor: pointer;
}
<input
  list=""
  name="option"
  id="input"
  autocomplete="off"
>

<datalist id="datalist">
  <option>Carrots</option>
  <option>Peas</option>
  <option>Beans</option>
</datalist>
Fayina answered 5/5, 2021 at 10:5 Comment(1)
This didn't work for me until I set a 300ms timeout to onblur (otherwise it hides the datalist too soon for onclick to fire): setTimeout(function(){ datalist.style.display = 'none'; }, 300);Drexler
R
0

function InputDropList_Focus(elementID)
    {//input must have value and valuetemp attributes 
        
        const el=document.getElementById(elementID);
        el.setAttribute("valuetemp", el.value);
        el.value="";        
    }

function InputDropList_FocusOut(elementID)
    {       
        const el=document.getElementById(elementID);
    if(el.value !== el.getAttribute("valuetemp"))
            el.value=el.getAttribute("valuetemp");
    }
  
  function InputDropList_OnChange(elementID, action)
    {       
        const el=document.getElementById(elementID);
        el.setAttribute("valuetemp", el.value);
    
    el.blur();   
        
        if(action != null)
            action();
    }
  
  
function DoSmthOnChange(elementID)
{
    const el=document.getElementById(elementID);
    console.log("new value: " + el.value);
}
<input id="mylist" list="mylistOptions" value="apple" 
valuetemp="apple"
onfocus="InputDropList_Focus('mylist')"
onfocusout="InputDropList_FocusOut('mylist')"
onchange="InputDropList_OnChange('mylist', DoSmthOnChange('mylist'))"
>
<datalist id="mylistOptions">
  <option value="apple">
  <option value="orange">
  <option value="banana">
</datalist>
Rajab answered 10/12, 2021 at 12:30 Comment(1)
Welcome to Stack Overflow. Code is a lot more helpful when it is accompanied by an explanation. Stack Overflow is about learning, not providing snippets to blindly copy and paste. Please edit your question and explain how it answers the specific question being asked.Lyricism
U
0

This is an improved version of herrstriezel's answer (in es6)

function keepDatalistOptions(selector = 'input[list]') {
  // select all input fields by datalist attribute or by class/id
  document.querySelectorAll(selector).forEach(dataListInput => {
    let dataListValue = dataListInput.value
    dataListInput.addEventListener('focus', () => {
      dataListValue = dataListInput.value
      dataListInput.value = ''
    })
    dataListInput.addEventListener('change', () => {
      dataListValue = dataListInput.value
    })
    dataListInput.addEventListener('blur', () => {
      if (dataListInput.value === '') {
        dataListInput.value = dataListValue
      }
    })
  })
}
Ut answered 3/1, 2022 at 11:10 Comment(0)
S
0

I was having this problem when dynamically populating a <datalist>. Only a single <option> was displaying. My use case was an input type=search like a search engine input.

The trick is to set the .value of the input to a single non-breaking line space, or &nbsp;, or simply " ". (NOT "". There MUST be an empty space " ").

So, after you dynamically .append the options, myDatalist.value = " ";. All the options will display now.

Steakhouse answered 12/2, 2023 at 20:39 Comment(0)
L
0

To display all datalist options in Chrome with simplist working principles and minimal side effects, you may backup input.value, set input.value = ''; and then restore at the right time through proposed requestPostAnimationFrame.

const $ = (s, c = document) => c.querySelector(s);

const requestPostAnimationFrame = (cb)=>{
  // FIXME with a spec-compliant requestPostAnimationFrame implmentation
  requestAnimationFrame(()=>{
    setTimeout(()=>{
      cb(performance.now());
    }, 8);
  })
};

function main(){
  let showAllListOptions = (e)=>{
    let input = e.target;
    let value = input.value;
    if (value !== '') {
      input.value = '';
      requestPostAnimationFrame(()=>{
        input.value = value;
      });
    }
  };
  if (window.chrome) {
    let fruitInput = $('#fruit');
    fruitInput.addEventListener('focus', showAllListOptions);
    fruitInput.addEventListener('keydown', (e)=>{
      if (e.code === 'ArrowDown') {
        showAllListOptions(e);
      }
    });
    fruitInput.addEventListener('change', (e)=>{
      let input = e.target;
      if (document.activeElement === input) {
        input.blur();
      }
    });
  }
}
queueMicrotask(main);
<input id="fruit" value="orange" list="values" />
<datalist id="values">
  <option value="orange">orange</option>
  <option value="banana">banana</option>
</datalist>
Liederman answered 22/4, 2024 at 6:43 Comment(0)
Q
-7

Just paste the desired default value in input tag. No extra JavaScript coding needed!

        <input list="skills" value="DBA">

          <datalist name="skills" id="skills">

              <option value="PHP">Zend PHP</option>
              <option value="DBA">ORACLE DBA</option>
              <option value="APEX">APEX 5.1</option>
              <option value="ANGULAR">ANGULAR JS</option>

          </datalist>
Quinte answered 17/10, 2017 at 14:44 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.