Mostrar rótulos datalist, mas enviar o valor real

99

Atualmente, o <datalist>elemento HTML5 é compatível com a maioria dos navegadores principais (exceto Safari) e parece uma maneira interessante de adicionar sugestões a uma entrada.

No entanto, parece haver algumas discrepâncias entre a implementação do valueatributo e o texto interno no <option>. Por exemplo:

<input list="answers" name="answer">
<datalist id="answers">
  <option value="42">The answer</option>
</datalist>

Isso é tratado de forma diferente por navegadores diferentes:

Chrome e Opera:
Datalist no Chrome / Opera

FireFox e IE 11:
Datalist no FireFox

Depois de selecionar um, a entrada é preenchida com o valor e não com o texto interno. Só quero que o usuário veja o texto ("A resposta") no menu suspenso e na entrada, mas transmita o valor 42ao enviar, como selectfaria.

Como posso fazer com que todos os navegadores tenham a lista suspensa mostrando os rótulos (texto interno) dos <option>s, mas enviar o valueatributo quando o formulário for enviado?

Stephan Muller
fonte
1
Acho que o Firefox e o IE11 estão errados aqui; de acordo com as especificações Um e Dois , parece que value=""deve ter precedência sobre uma string dentro das tags, sempre que houver um value=""declarado. Portanto, a sugestão seria tornar "a resposta" seu atributo de valor.
TylerH
1
@TylerH Não - o Firefox e o IE11 estão corretos aqui: O atributo label fornece um rótulo para o elemento. O rótulo de um elemento de opção é o valor do atributo de conteúdo do rótulo, se houver, ou, se não houver, o valor do atributo de texto IDL do elemento. w3.org/TR/html5/forms.html#attr-option-label
easwee
1
@easwee Isso apenas diz que o rótulo deve ser o que está no rótulo se houver um rótulo, e que o valor deve ser o que está no valor se houver um valor. Não especifica qual tem precedência.
TylerH

Respostas:

116

Observe que datalistnão é o mesmo que a select. Ele permite que os usuários insiram um valor personalizado que não está na lista, e seria impossível buscar um valor alternativo para essa entrada sem defini-lo primeiro.

As maneiras possíveis de lidar com a entrada do usuário são enviar o valor inserido como está, enviar um valor em branco ou evitar o envio. Essa resposta trata apenas das duas primeiras opções.

Se você deseja proibir totalmente a entrada do usuário, talvez selectseja uma escolha melhor.


Para mostrar apenas o valor de texto de optionna lista suspensa, usamos o texto interno para ele e omitimos o valueatributo. O valor real que queremos enviar é armazenado em um data-valueatributo personalizado :

Para enviar data-value, temos que usar um <input type="hidden">. Nesse caso, deixamos de fora o name="answer"na entrada regular e o movemos para a cópia oculta.

<input list="suggestionList" id="answerInput">
<datalist id="suggestionList">
    <option data-value="42">The answer</option>
</datalist>
<input type="hidden" name="answer" id="answerInput-hidden">

Desta forma, quando o texto na entrada original for alterado podemos usar javascript para verificar se o texto também está presente no dataliste buscar o seu data-value. Esse valor é inserido na entrada oculta e enviado.

document.querySelector('input[list]').addEventListener('input', function(e) {
    var input = e.target,
        list = input.getAttribute('list'),
        options = document.querySelectorAll('#' + list + ' option'),
        hiddenInput = document.getElementById(input.getAttribute('id') + '-hidden'),
        inputValue = input.value;

    hiddenInput.value = inputValue;

    for(var i = 0; i < options.length; i++) {
        var option = options[i];

        if(option.innerText === inputValue) {
            hiddenInput.value = option.getAttribute('data-value');
            break;
        }
    }
});

O id answere answer-hiddenna entrada regular e oculta são necessários para o script saber qual entrada pertence a qual versão oculta. Desta forma, é possível ter vários inputs na mesma página com um ou mais datalists fornecendo sugestões.

Qualquer entrada do usuário é enviada como está. Para enviar um valor vazio quando a entrada do usuário não estiver presente no datalist, mude hiddenInput.value = inputValueparahiddenInput.value = ""


Exemplos de trabalho de jsFiddle: javascript simples e jQuery

Stephan Muller
fonte
2
como você lidaria com opções com o mesmo texto interno, mas com valores de dados diferentes? por exemplo, jsfiddle.net/7k3sovht/78
bigbearzhu
1
Pelo que eu posso dizer, não há como diferenciar entre as duas opções. Como não há evento do usuário para ouvir, é impossível saber qual dos dois ele realmente selecionou; tudo o que sabemos é qual valor acabou no campo de entrada.
Stephan Muller
@StephanMuller ótima resposta para alguém que está aprendendo mais HTML5 como eu, obrigado! Como você limparia a caixa de texto da demonstração após o envio?
Chris22
Pure jquery document.querySelector ('input [list]'). AddEventListener ('input', function (e) {var value = $ (this) .val (); var list = $ (this) .attr ('list' ); var id = $ (este) .attr ('id'); $ ('#' + lista + 'opção'). each (função () {if ($ (este) .val () == valor) { $ ('#' + id + '- oculto'). val ($ (this) .attr ('data-value'));}});});
Piotr Kazuś,
@StephanMuller obrigado por compartilhar, muito útil, estou executando conforme você codificou, porém eu obtenho o valor dos dados apenas ao usar backspace na entrada, não ao enviar ou mesmo selecionar um item diferente, o que pode estar errado?
digitai
49

A solução que uso é a seguinte:

<input list="answers" id="answer">
<datalist id="answers">
  <option data-value="42" value="The answer">
</datalist>

Em seguida, acesse o valor a ser enviado ao servidor usando JavaScript da seguinte forma:

var shownVal = document.getElementById("answer").value;
var value2send = document.querySelector("#answers option[value='"+shownVal+"']").dataset.value;


Espero que ajude.

Hezbullah Shah
fonte
Estou ficando indefinido algum motivo?
Dejell
1
Solução de problemas com console.log em cada etapa. Certifique-se de usar o mesmo valor de id que está chamando. E verifique os pontos-e-vírgulas também.
Hezbullah Shah
1
esta solução é uma ótima ideia! - consegui, simplesmente, implementar esta solução em minutos. primeiro atualizei meu inputelemento com o atributo data-value="1". Então escrevi o seguinte bloco nos locais onde o valor estava sendo pego e usado:if (typeof $(this).attr('data-value') != 'undefined') { var id = $(this).attr('name'); val = $('#'+id).find('option[value="'+val+'"]').attr('data-value'); }
WEBjuju
1
Mas e se você tiver duas legendas iguais com alguns valores de dados diferentes
Richard Aguirre
1
Obrigado Hezbullah Shah você realmente economizou muitas horas
ABODE
5

Sei que pode ser um pouco tarde, mas me deparei com isso e queria saber como lidar com situações com vários valores idênticos, mas chaves diferentes (conforme o comentário de bigbearzhu).

Portanto, modifiquei ligeiramente a resposta de Stephan Muller:

Um datalist com valores não únicos:

<input list="answers" name="answer" id="answerInput">
<datalist id="answers">
  <option value="42">The answer</option>
  <option value="43">The answer</option>
  <option value="44">Another Answer</option>
</datalist>
<input type="hidden" name="answer" id="answerInput-hidden">

Quando o usuário seleciona uma opção, o navegador o substitui input.valuepelo valueda datalistopção em vez do innerText.

O código a seguir verifica a existência de um optioncom isso value, coloca-o no campo oculto e substitui o input.valuecom o innerText.

document.querySelector('#answerInput').addEventListener('input', function(e) {
    var input = e.target,   
        list = input.getAttribute('list'),
        options = document.querySelectorAll('#' + list + ' option[value="'+input.value+'"]'),
        hiddenInput = document.getElementById(input.getAttribute('id') + '-hidden');

    if (options.length > 0) {
      hiddenInput.value = input.value;
      input.value = options[0].innerText;
      }

});

Como consequência, o usuário vê o que quer que a opção innerTextdiga, mas o id exclusivo de option.valueestá disponível no envio do formulário. Demo jsFiddle

Cobbystreet
fonte
Boa adição :)
Stephan Muller
1

Ao clicar no botão de pesquisa, você pode encontrá-lo sem um loop.
Basta adicionar à opção um atributo com o valor que você precisa (like id) e pesquisar por ele específico.

$('#search_wrapper button').on('click', function(){
console.log($('option[value="'+ 
$('#autocomplete_input').val() +'"]').data('value'));
})
Gal Albalak
fonte
-11

Usando PHP, descobri uma maneira bastante simples de fazer isso. Pessoal, basta usar algo assim

<input list="customers" name="customer_id" required class="form-control" placeholder="Customer Name">
            <datalist id="customers">
                <?php 
    $querySnamex = "SELECT * FROM `customer` WHERE fname!='' AND lname!='' order by customer_id ASC";
    $resultSnamex = mysqli_query($con,$querySnamex) or die(mysql_error());

                while ($row_this = mysqli_fetch_array($resultSnamex)) {
                    echo '<option data-value="'.$row_this['customer_id'].'">'.$row_this['fname'].' '.$row_this['lname'].'</option>
                    <input type="hidden" name="customer_id_real" value="'.$row_this['customer_id'].'" id="answerInput-hidden">';
                }

                 ?>
            </datalist>

O código acima permite que o formulário carregue o id da opção também selecionada.

23r0
fonte
Isso parece uma bagunça e cheira a injeção de SQL. Não faça isso.
Fusseldieb 01 de
@ 23r0, algum feedback sobre por que isso pode ter sido rejeitado; esta questão é especificamente sobre o código de front-end. Incluir código de backend (como PHP) pode ser desnecessário e confuso. Com relação ao HTML, espero que você obtenha apenas o último valor para customer_id_realsubmetido, não o valor selecionado.
João