Search via ajax during input text ( autocomplete )

Asked

Viewed 12,142 times

9

I did a search using ajax that when clicking a button triggers a request with the term searched and returns with data that fills a combobox html (select). So far so good. However, I found it interesting to change this behavior of clicking on the search button, to instead be checked on onkeyup() during typing.

I happen to feel that there will be an extra charge on the server and I would like to know if you suggest any practice that does not cost so much. Some different technique doing some sort of cache which I do not know, or instead of searching every keystroke raised, check after a minimum number x of keystrokes be typed (etc).

Another issue: But what if I limit the search to more than 3 characters and then there is some occurrence of exact 3 characters?

Only nay would like:

  1. That the user always had to click to search and;
  2. Making too many unnecessary requests.

Ideas?

Follow code below with a brief example of what I’m trying to do:

formulario_search.php

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>

    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/> <!-- Css do Bootstrap opcional, só coloquei pra carregar um estilinho básico e mostrar o íconezinho da lupa -->

    <script>
    $(document).ready(function()
    {
        $('#termo_busca').keyup(function()
        {
            $.ajax({
              type: 'POST',
              url:  'busca_ajax.php',
              data: {
                  nome: $("#termo_busca").val()
              },
              success: function(data) 
              {
                $('#listafornecedores').html(data);
              }
            });
        });

    });
    </script>
    <style>
        body { font-size:14pt; padding:5%; }
        #listafornecedores { width:500px; }
    </style>
</head>
<body>

    Nome fornecedor: <input type="text" id="termo_busca" name="termo_busca">
    <button id="btn_busca">
        Procurar <span class='glyphicon glyphicon-search'></span>
    </button>

    <br><br>

    <form action="" method="post">
    <b>Fornecedores encontrados com o termo pesquisado:</b><br>
    <select id="listafornecedores" onclick="if( $('#listafornecedores').html() == '' ){ alert('Sem resultados carregados.\n Faça uma busca digitando o nome de um fornecedor.');}"></select>

    <br>
    ... outros campos ...
    <br>
    <input type="submit" name="submit" value="Salvar">
    </form>

</body>
<html>

busca_ajax.php

<?php
/*
    --Tabela fornecedores usada para teste:

    CREATE TABLE `fornecedores` (
      `id` int(11) NOT NULL,
      `nome` varchar(255) NOT NULL
    ) ENGINE=MyISAM DEFAULT CHARSET=latin1;

    ALTER TABLE `fornecedores`
      ADD PRIMARY KEY (`id`),
      ADD KEY `idx_fornecedor_nome` (`nome`);

    ALTER TABLE `fornecedores`
      MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

    --Massa de testes: fornecedores disponíveis para busca
    INSERT INTO `fornecedores` (`nome`) VALUES ('Samsung'),('Microsoft'), ('Apple'),('Fornecedor Homônimo RJ'),('Fornecedor Homônimo SP'),('Fornecedor Homônimo MG');

*/


//MySQLi DESENVOLVIMENTO
$db_host="localhost";
$db_port="5432";
$db_name="testdb";
$db_user="root";
$db_password="";

$dbcon = mysqli_connect( $db_host, $db_user, $db_password );
mysqli_select_db($dbcon,$db_name);
mysqli_query($dbcon,"SET NAMES 'utf8'");

$nome = mysqli_real_escape_string($dbcon,$_POST["nome"]);

// se enviar nome vazio, não carregar nada
if(trim($nome)==''){ die(); }

$query = "SELECT * FROM fornecedores WHERE nome like '$nome%'";

$result = mysqli_query($dbcon,$query);

$options='';

while($linha = mysqli_fetch_assoc($result))
{
     $options.='<option value="'.$linha["id"].'">'.$linha["nome"].'</option>';
}

echo $options;  // isto voltará na variável data do ajax
  • 2

    I could suggest JQuery autocomplete, but would evade the scope of the question...

  • 1

    I recommend using a lib to do this, as suggested in the comment above. Regarding a smaller number of requests, I believe it is not possible, since an autocomplete has to respond to any letter provided by the user. If the data is few, you can provide them in the initial request.

  • 1

    You can also add a small delay after the event keyup: https://api.jquery.com/delay/ . Just be careful and balance with the response time expectation.

9 answers

12


You can do something like that:

var cache = {};

$(document).ready(function() {
    addKeyupEvent($('#termo_busca'));
}   

function addKeyupEvent(element) {
    element.keyup(function(e) {
        var keyword = $(this).val();
        clearTimeout($.data(this, 'timer'));

        if (e.keyCode == 13)
            updateListData(search(keyword, true));
        else
            $(this).data('timer', setTimeout(function(){
                updateListData(search(keyword));
            }, 500));
    });
}

function search(keyword, force) {
    if (!force && keyword.length < 4) 
        return '';

    if(cache.hasOwnProperty(keyword))
        return cache[keyword];

    $.ajax({
        type: 'POST',
        url:  'busca_ajax.php',
        async: false,
        data: {
           nome: keyword
        },
        success: function(data) {
           cache[keyword] = data;
           return data;
        },
        error: function() {
            throw new Error('Error occured');
        }
    });
}

function updateListData(data) {
     $('#listafornecedores').html(data);
}

What code above does is perform a search only 500ms after pressing the last key, storing a timer in the collection .data() of the element #termo_busca. Each keyup cleans this timer and sets another 500ms wait time before auto search.

If you press the "enter" key (very common in searches), the search will be performed automatically.

However if the term has already been searched, then we return the cached list.

I’ve separated the responsibilities, the function addKeyupEvent adds the event keyup to any desired element, the function search receives the searched word and whether the automatic search should be forced (in case the user presses the ENTER) and the function updateListData only updates the vendor list.

A tip, don’t use the POST to do research (however I left the POST in the example). The method POST is basically used to create resources on the server, which is not your case. What you want is to get (GET) a server resource, which in this case is a list of vendors.

  • Delay the search during typing by 0.5 seconds, canceling the search if more characters are typed during this short time interval, as you did, tb is very useful. + 1 tb.

4

If your question is weight that several requests will result. An alternative is to bring all the data to the select, and go eliminating them as they are being typed. That way I would avoid all these requisitions:

$('#termo_busca').on('keypress change', function() {
     $("#listafornecedores").val("");
     $('#listafornecedores option').each(function() {
       if ($(this).html().toLowerCase().indexOf($('#termo_busca').val().toLowerCase()) == -1) {
         $(this).css("display", "none");
       } else {
         $(this).css("display", "block");
       }
     });

   });
body {
  font-size: 14pt;
  padding: 5%;
}
#listafornecedores {
  width: 500px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Nome fornecedor:
<input type="text" id="termo_busca" name="termo_busca">
<button id="btn_busca">
  Procurar <span class='glyphicon glyphicon-search'></span>
</button>

<br>
<br>

<form action="" method="post">
  <b>Fornecedores encontrados com o termo pesquisado:</b>
  <br>
  <select id="listafornecedores" onclick="if( $('#listafornecedores').html() == '' ){ alert('Sem resultados carregados.\n Faça uma busca digitando o nome de um fornecedor.');}">
    <option id="vazio" value="0"></option>
    <option value="1">Samsung</option>
    <option value="2">Microsoft</option>
    <option value="3">Apple</option>
    <option value="4">Fornecedor Homônimo RJ</option>
    <option value="5">Fornecedor Homônimo SP</option>
    <option value="6">Fornecedor Homônimo MG</option>

  </select>

  <br>... outros campos ...
  <br>
  <input type="submit" name="submit" value="Salvar">
</form>

  • For little things, like guys, I usually bring it from the bank anyway.

  • What’s wrong with doing it this way?

  • No problem. _ I’ll test it yet. But I already gave +1 for good will.

  • Oh yes. And can also bring all "invisible" too, and not only when it goes typing, as I did there . :)

  • 1

    @Antonioalexandre believes that it would be better to already bring everything previously loaded and go filtering on the screen, than, go all the time on the server looking for new suppliers. It would be faster this way and avoid unnecessary traffic to your server ;)

  • Actually this example using "Suppliers" was just one example. Perhaps a bad example because when you think of suppliers you think of a small number, suddenly "products" would be better. I think that if the search will come with more than a hundred records already begins to be valid this ajax effort. But yes, there are cases and cases. But the answers are good, I think I’ll need to take a piece of each to create a generic component that is good in most cases.

  • 1

    @Antonioalexandre . I see so, the more registration you have and more requests to make, more weight will bring. With a model as I put it, Voce will make only one request. And if the problem is that at first all the records were on display, just make everyone invisible until the customer starts searching. But making multiple requests... would be kind of unnecessary. Even if you do the search after typing 3 characters, the number of requests will still be large

  • 1

    I see this, in a system looking for people names: Alone has 5 thousand records, after typing the first 4 characters if I take only the names that start with the term and we do not have the middle, we imagine that this number has dropped to about 100, then these 100 are loaded, instead of the 5 thousand and with its code adapted I clean up during the typing of the person without having to make new requests, I would make new request only if those 4 first characters typed change.

  • @Antonioalexandre thinking so... I think it’s a good even kk :)

Show 4 more comments

2

formulario_search.php

$(document).ready(function()
{

    $('#termo_busca').keyup(function()
    {
        var cep_field = $(this);
        if(cep_field.val() >= 8){ //altere a constante caso esteja usando mascara de campo
            $.ajax({
                type: 'POST',
                url:  'busca_ajax.php',
                data: {
                    nome: $("#termo_busca").val()
                },
                success: function(data)
                {
                    $('#listafornecedores').html(data);
                }
            });
        }
    });

});
  • Sorry, but I don’t understand where this solves the user problem. Apparently he wants to know a way to make fewer requests to the server.

  • It helps a little if in this check I use with a character counting function. But . val() returns the value of the field and not the amount of characters used. Bad mood is right. But thanks for the willingness to help. Only this answer so I do not really have to mark as chosen answer. It is open to those who have better ideas.

  • 1

    I had not understood the function of that IF. However, doing so compromises the usability of your tool. Think of the following case, I want to search "Bad Mood", then type’M' and come no results. I’m going to assume that there is no such word, instead of typing more letters until you complete the required number of letters.

  • Truth, I agree that this can happen. If I end up using a solution like this I will need to put a warning for the person to need to type at least 3 characters.

  • I understood that the user wanted to decrease the number of rquisitions by restricting the search Rigger only when the zip code was already complete, this code is very basic I would particularly use a regex to clean the analyzed content and use a $.post call, but it was just a simple code to exemplify a simple way of resolution, better ways can always be implemented.

  • 1

    Instead I would use the twitter typehead plugin that would abstract that search layer for you. https://twitter.github.io/typeahead.js/examples/

  • It looks good tb Bruno. Thanks for the link.

Show 2 more comments

2

You can restrict requests on time by using a session variable or cookie.

Before making a request, you test the variable to see if time has passed. If yes, execute the request and update the variable with the new limit. Otherwise it jumps.

By adjusting the interval, you get a good response time and less overhead.

My javascript is a little rusty, but I will comment on the actions in the code

$('#termo_busca').keyup(function()
    {

          // o cookie não existe ou já passou da hora?
          // executa o $.ajax()
        $.ajax({
          type: 'POST',
          url:  'busca_ajax.php',
          data: {
              nome: $("#termo_busca").val()
          },
          success: function(data) 
          {
            $('#listafornecedores').html(data);
          }
        });
         // senão, pula e cria o cookie com o limite de tempo.
    });

2

$(document).ready(function () {

    $('#termo_busca').keyup(function () {
        var termo = $(this).val();
        var fazerAjax = true;
        for (var i = 0; i < listaFornecedores.length; i++) { //Lista de fornecedores do ultimo Ajax
            if (listaFornecedores[i].search(termo) != -1) //Caso o que o usuario esteja digitado tenha ainda tenha na lista do ultimo ajax não realizar o ajax de novo.
                fazerAjax = false;
        }
        if (termo.length >= 3 && fazerAjax) { //Só fazer o ajax caso o termo de busca seja maior e igual a 3 e o texto digitado seja diferente de todos os fornecedores do ultimo ajax
            $.ajax({
                type: 'POST',
                url: 'busca_ajax.php',
                data: {
                    nome: termo
                },
                success: function (data) {
                    $('#listafornecedores').html(data);
                }
            });
        }
    });

});

This way you will perform a new Ajax, if what the user wanted is different than what you searched for in the last Ajax.

  • This idea of checking what is already on the list before sending another consultation is very good. Because if with 4 characters it is already bringing a result box, suddenly if 5 follows the previous 4 I will with java-script even kill those who start to escape the scope. I’ll still test the code but +1 for the idea.

  • I hope it helps.

2

The link here has a practical example, can help you, loading result from the third or second letter.

or else load all data into json and query that result, no more requests on the server.. there is a microframework 2kb can used for this.

Results are loaded in arrays.

var input = document.getElementById("myinput");

    // Show label but insert value into the input:
new Awesomplete(input, {
    list: [
        { label: "Belarus", value: "BY" },
        { label: "China", value: "CN" },
        { label: "United States", value: "US" }
    ]
});

And the input becomes simple:

<input id="myinput" />
  • Good tb. + 1 We can use this tb in the final solution.

  • I, in particular, always do this. A call stores all the values and I only search the values already loaded. But I do it using the JQuery autocomplete, and not creating something new.

1

Test this HTML example, to be honest I do not know the item limit that can be added to the DATALIST, but it is a matter of tests. Ai the solution without overloading the server and without reinventing the wheel.

<html>
<head>
    <title>teste datalist</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>

<input name="idestados" list="listaestados" >         

<datalist id="listaestados">
    <select>
        <option value="12"> Acre</option>
        <option value="27"> Alagoas </option>
        <option value="16"> Amapá </option>
        <option value="13"> Amazonas </option>
        <option value="29"> Bahia </option>
        <option value="23"> Ceará </option>
        <option value="53"> Distrito Federal </option>
        <option value="32"> Espírito Santo </option>
        <option value="52"> Goiás </option>
        <option value="21"> Maranhão </option>
        <option value="51"> Mato Grosso </option>
        <option value="50"> Mato Grosso do Sul </option>
        <option value="31"> Minas Gerais </option>
        <option value="15"> Pará </option>
        <option value="25"> Paraíba </option>
        <option value="41"> Paraná </option>
        <option value="26"> Pernambuco </option>
        <option value="22"> Piauí </option>
        <option value="33"> Rio de Janeiro </option>
        <option value="24"> Rio Grande do Norte </option>
        <option value="43"> Rio Grande do Sul </option>
        <option value="11"> Rondônia </option>
        <option value="14"> Roraima </option>
        <option value="42"> Santa Catarina </option>
        <option value="35"> São Paulo </option>
        <option value="28"> Sergipe </option>
        <option value="17"> Tocantins </option>
    </select>
</datalist>        


</body>

  • I liked it. I didn’t know this Html5 datalist tag yet. In this example of W3schools shows typing from the datalist: http://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_datalist Probably tb will be useful in the final solution. + 1

1

I combine Angular Material with Webapi, but I use Asp.net mvc

https://material.angularjs.org/1.1.1/demo/autocomplete

To search cities and states, you can put a delay to delay the query and combine as many words as possible and it’s very agile and easy, it doesn’t load both the query and the web api only returns the results that match the query, so it’s much easier to work like this.

1

In html:

 <div class="form-group col-lg-12">
            <div class="form-group has-feedback">

                <input type="text" class="form-control" id="busca" onkeyUp="carregar()" placeholder="Pesquisar M&eacute;dico"/>
                <span class="glyphicon glyphicon-search form-control-feedback"></span>
            </div>


        </div>

In javascript:

  function xmlhttp(){
            // XMLHttpRequest para firefox e outros navegadores
            if (window.XMLHttpRequest){
                return new XMLHttpRequest();
            }
            // ActiveXObject para navegadores microsoft
            var versao = ['Microsoft.XMLHttp', 'Msxml2.XMLHttp', 'Msxml2.XMLHttp.6.0', 'Msxml2.XMLHttp.5.0', 'Msxml2.XMLHttp.4.0', 'Msxml2.XMLHttp.3.0','Msxml2.DOMDocument.3.0'];
            for (var i = 0; i < versao.length; i++){
                try{
                    return new ActiveXObject(versao[i]);
                }catch(e){
                    alert("Seu navegador não possui recursos para o uso do AJAX!");
                }
            } // fecha for
            return null;
        } // fecha função xmlhttp

        //função para fazer a requisição da página que efetuará a consulta no DB
        function carregar(){
           a = document.getElementById('busca').value;
           ajax = xmlhttp();
           if (ajax){
               ajax.open('get','busca.php?nome='+a, true);
               ajax.onreadystatechange = trazconteudo;
               ajax.send(null);
           }
        }
        //função para incluir o conteúdo na pagina
        function trazconteudo(){
            if (ajax.readyState==4){
                if (ajax.status==200){
                    document.getElementById('searchlist').innerHTML = ajax.responseText;
                }
            }
        }

in PHP:

$nome = strtoupper($_GET['nome']);
require_once './controller/Pessoa_Controller.class.php';
require_once './beans/Pessoa.class.php';
require_once './servicos/PessoaListIterator.class.php';
$p = new Pessoa_Controller();
if($nome == ""){
    $nome = "%";
}else{
    $nome = "%".$nome."%";
}
$pessoaList_in = $p->lista($nome);
$pLista = new PessoaListIterator($pessoaList_in);
$pessoa =  new Pessoas();
while ($pLista->hasNextPessoas()){
    $pessoa = $pLista->getNextPessoas();
 ?>
<div class="col-xs-12 col-sm-4 col-lg-3">
 <div  class="list-group">
        <A href="lista.php?codigo=<?php echo $pessoa->getId(); ?>&&nome=<?php echo $pessoa->getNome(); ?>" class="btn btn-default list-group-item" id="#<?php echo $pessoa->getId(); ?>" role="button" aria-pressed="true" onclick="medico(<?php echo $pessoa->getId(); ?>);">
           <span><?php echo $pessoa->getNome(); ?></span> 
        </a>
  </div>    
</div>                                
<?php
}   
 ?>

I hope you give an idea

Browser other questions tagged

You are not signed in. Login or sign up in order to post.