Search via ajax during typing in input text (autocomplete)

8

I made a search using ajax that when clicking a button triggers a request with the searched term and returns with data that fills combobox html ( select ). So far so good. However, I found it interesting to change this behavior from clicking the fetch button to, instead, being checked in onkeyup() while typing.

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

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

Just not wanted:

  • That the user always had to click to search e;
  • Make too many unnecessary requests.
  • Ideas?

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

    form_bus.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><linkhref="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>
    

    search_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
    
        
    asked by anonymous 02.01.2017 / 17:39

    9 answers

    10

    You can do something like this:

    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 to perform a search only 500ms after pressing the last key, storing a timer in the .data() collection of the #termo_busca element. Each keyup clears this timer and sets another 500ms timeout before the automatic search.

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

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

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

    One tip, do not use POST to do research (though I left POST in the example). The POST method 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 providers.

        
    11.01.2017 / 19:27
    4

    If your question is weight that multiple requisitions will result. An alternative is to bring all the data to the select, and delete it as it is typed. This way you would avoid all of these requests:

    $('#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>Nomefornecedor:<inputtype="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>
        
    10.01.2017 / 19:17
    2

    form_bus.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);
                    }
                });
            }
        });
    
    });
    
        
    02.01.2017 / 17:56
    2

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

    Before making a request, you test the variable to see if the time has passed. If so, execute the request and update the variable with the new threshold. Otherwise, jump.

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

    My javascript is a bit 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.
        });
    
        
    10.01.2017 / 19:10
    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);
                    }
                });
            }
        });
    
    });
    

    In this way you will perform a new Ajax, in case the user wanted to be different from the one you were looking for in the last Ajax.

        
    10.01.2017 / 19:53
    2

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

    or else load all the data in json and query on that result, with no more requests on the server .. there is a microframework from 2kb that you can use for this.

    The results are loaded into 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 is simple:

    <input id="myinput" />
    
        
    12.01.2017 / 21:35
    1

    No 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>
    

    No 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 have an idea

        
    12.01.2017 / 22:34
    1

    Test this HTML example, to be honest I do not know the item limit that can be added to the DATALIST, but it's a test question. There 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>
    

        
    13.01.2017 / 01:33
    1

    I combine Angular Material with WebApi, however I use asp.net mvc

    link

    To search cities and states, you can put a delay to delay the query and combine the maximum of words and it is very agile and easy, does not 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.

        
    13.01.2017 / 18:35