Desenvolvendo um auto-complete com PHP e Ajax

** Script atualizado para versão 1.1, clique aqui para ver os detalhes **

Desenvolvendo uma solução de sistema de gerência para o Dep. Comercial do ComuniWeb recebi inúmeros pedidos de “mas não pode aparecer os nomes dos clientes quando vou digitando?”. Ok não foi tão lindo e poético assim, na verdade o que acontecia era que o auto-complete padrão do IE causava uma confusão enorme nos usuários (usuário é usuário né?).

Me vi então cara a cara com um problema, como resolver este problema? Precisava que um script ao tempo que a pessoa digitava fizesse uma busca no banco para retornar a lista de clientes e o seu vendedor, isso tudo num browser. Se o tmepo hoje fosse alguns meses atrás (não muitos) eu falaria “impossível, estamos falando de web, nao delphi ou VB!”. Alás que surgiu o xmlHttpRequest e tudo mudou…


Fui então a busca de vários exemplos de auto-complete ou “google-suggest” (vantagens de se dominar a tecnologia…) e achei várias soluções que utilizavam diferentes métodos, porém nenhuma que utilizasse AJAX (salvo o próprio Google…). Confiem em mim quando alguém quer tornar um arquivo javascript ilegivel, essas pessoas conseguem, e o google o fez com toda categoria, variaveis de uma letra só e todos estoque de armas…

Portanto comecei com o arroz com feijão:

<input name="string" id="string" onkeyup="sendAjaxReq(this.value)" type="text" />

A idéia era bem simples, a cada tecla uma requisição AJAX era feita, um arquivo PHP fazia a busca no banco e retornava um arquivo XML com a lista de resultados, algo como:

<root>
<item id="286" label="temos"></item>
<item id="288" label="ter"></item>
<item id="332" label="tecnol%F3gico%2C"></item>
</root>

foi simples o bastante converter esta lista em um div flutuante com uma unordered list (UL) e mostrar ela junto ao campo. Portanto o príncipio já estava la, você digita e clicando no item efetua a escolha… nada amigável né?

Faltava então buscar uma solução como a usada pelo google utilizando as setas (up e down) para navegar entre os resultados e usar o ENTER ou TAB para fazer a escolha. Para isso me apoiei em um script que já havia encontrado pela web, só para pegar a semantica.

Com isso terminei com um script que fazia a busca no banco, navegava por teclado ou mouse e enfim.. funcionava. quando terminei esta implementação já achei X novas oportunidades de utiliza-lo novamente decidi então tornar o script mais genérico de forma a utilizar ele de forma mais simples, e é isso que vocês podem conferir e utlizar.

Exemplo: veja aqui
Download do arquivo (com exemplos): dmsAutoComplete.zip

Espero que seja útil, pelomenos para algumas pessoas.

Known bugs: No firefox na primeira vez que a lista é populada ela aparece toda em uma linha só, sem quebra de linhas.

This post is also available in: Inglês

59 comentários sobre “Desenvolvendo um auto-complete com PHP e Ajax

  1. Rafael, peguei seu script no blog e funcionou corretamente no meu servidor. Porém, quando eu tento fazer um auto-completar para buscar em um BD, ele não exibe nenhum resultado.

    Para testar, fiz uma coisa simples. Conexão com BD, busca nomes dos usuários do site e a variável $ostring recebe valores:

    include(“conexao.php”) //funciona corretamenet
    $query = “select nome from usuarios”;
    $result = mysql_query($query);
    while($palavra = mysql_fetch_array($result))
    { $ostring = $palavra[“nome”]; }

    Somente adicionei esse código e não alterei nada, mas não está listando nada. Testados no IE e Firefox, nenhum resultado.
    Obrigado.

  2. correção do código anterior:
    while($palavra = mysql_fetch_array($result))
    { $ostring = $ostring . ” ” . $palavra[“nome”]; }
    //recebe o texto já guardado na variavel $ostring + valor do array

  3. Caro felipe

    Para mostrar os resultados de uma pesquisa no BD, para cada linha do banco você deve repetir o processo de anexar um novo
    item à lista como abaixo

    $item = $xmlDoc->createElement(\’item\’);
    $item = $root->appendChild($item);
    $item->setAttribute(\’id\’,$key);
    $item->setAttribute(\’label\’,rawurlencode($label));

    No seu código, faça o seguinte

    while($palavra = mysql_fetch_array($result))
    {
    $item = $xmlDoc->createElement(\’item\’);
    $item = $root->appendChild($item);
    $item->setAttribute(\’id\’,$key);
    $item->setAttribute(\’label\’,rawurlencode($palavra[”nome”]));
    }

    e substitua $key pelo valor da sua chave primaria no banco.

    Qualquer dúvida estamos ai

  4. Rafael, eu to tentando executar (localhost) o seu script usando o pacote Wamp= Windows+apache+mysql, mas não está funcionando.
    Eu nunca trabalhei com xml nem ajax… vc poderia mi explicar como eu faço pra colocar esse esquema de auto-complete pra funcionar?

    Obrigado,

  5. Rafael, achei o problema. O codigo php não eh executado na minha maquina qdo a tag inicio do php começa soh com "<?"

  6. Encontrei uma limitação no script, por isso to trabalhando na versão 1.1 dele.

    Caso alguem já queira adiantar, para poder utilizar mais de um AC em cada página, va no arquivo .js e substituia AC por me em todas as linhas de 222 até 230.

    Vou concertar alguns outros bugs em relação ao Firefox e lanço a nova versão

  7. Soluções de autocompletar existe algumas que funcionam da forma como você criou. Mas é legal a iniciativa. Mas eu vou dar uma olhada no teu código e ver se posso contribuir com alguma coisa.

  8. Realmente acabei encontrando algumas referências quando terminei o script. Mas mesmo assim aceitei como um desafio e decidi ver de onde vinha o problema e como formular a melhor solução.

    Ainda tenho alguns bugs pra resolver, mais em termos de layout, e o script venho muito a calhar com os sistemas que gerencio, em menos de 5 minutos conisgo adicionar a função a uma página e ter ela funcionando, quem programa sabe como é bom isso.

    Sugestões e melhorias são sempre bem vindas Benjamin, obrigado!

  9. Rafael, como poderia ser feito para usar o sistema de auto complete, em mais de um campo, com consulta a banco de dados, em tabelas diferentes?

  10. Adriano,

    verifique a atualização do script v1.1, nele fiz alterações para permitir mais de um autocomplete por pagina.
    Desta forma você teria 2 arquivos de consulta PHP digamos um chamado searchuser.php e outro searchcliente.php cada um recebe uma string, busca no banco e retorna o resultado.

    No seu formulário voce teria dois campos, digamos user e cliente e logo no fim voce instanciaria dois autocompletes, em variaveis diferentes e com targets diferentes, exemplo:

    var AC = new dmsAutoComplete(‘user’,’acDiv’);
    AC.ajaxTarget = ‘searchuser.php’;

    var ACC = new dmsAutoComplete(‘cliente’,’acDiv’);
    ACC.ajaxTarget = ‘searchcliente.php’;

    Pronto, vc tem dois autocompletes rodando na página. Lembro que isso só vale para a versão 1.1, portanto repita o doewnload e certifique que esta com a versão certa.

    Qlqr coisa pode mandar por email as duvidas q te ajudo.

  11. Rafael,

    gostei muito do seu script, porem, nao consigo filtar a consulta no banco. Como poderia fazer isso, pois soh consigo trazer todos os registros sem filtro pelas iniciais digitadas.

  12. Cleodon,

    No outro tópico onde falo da versão 1.1 do script eu respondi um comentario como o seu, voce precisa incluir uma condição “where” na sua qyery que busque somente os campos que começãm com o que foi digitado, veja um exemplo:

    $query = “select palestran from palestra WHERE palestran LIKE ‘”.$_POST[string].”%’”;

    o % em SQL significa coringa, com isso ele retorna tudo que começa com string.

  13. Implantei este script buscando os registros do banco de dados e está redondo… Porém estou precisando colocar uma barra de rolagem na div que imprime os resultados.
    To utilizando overflow:auto; no css e a barra aparece blz e funciona 100% no firefox, mas no IE (pra variar), ela não funciona. Quando clica nela ela some.
    Alguém pode dar um help aí?

    Desde já agradeço.

    Até

  14. Rafael… eu baixei o arquivo zipado… e tentei executalo mas não funciona… mesmo eu não tendo alterado nenhuma virgula!

    No exemplo do seu site ele funciona normal… tentei fuçar no código mas naum achei solução não!

    ele simplesmente naum demonstra nenhuma reação, é como se fosse só um campo text comum.

    mas o sistema é bom! pena que naum tenha funcionado aqui!

  15. Marcel,

    Acredito que o problema possa estar na configuração do servidor, ou no caminho relativo aos arquivos, algo como o script não esta se encontrando, se puder me envie mais informações por email, ou use o firebug do firefox para debugar o script.

    Obrigado

  16. Olá Rafael achei esse código o maximo, mas estou quebrando a cabeca em uma coisa, eu estou tentando que ele me traga de uma tabela do banco de dados nao só uma palavra e sim tudo que esta dentro da tabela. por exemplo, eu tenho um tabela com o id, nome e telefone, mas se eu coloco o autocompletar para buscar o nome ele só tras um dos nomes, por exemplo “Carlos da Silva”, eu coloco a letra “c” e só me vem “Carlos” e gostaria que vinhece o nome completo, tem como vc me dar uma ajuda? Ah também poderia me dizar como tirar a janela alert do javascript quando vc escolhe um nome.
    Ah, sem querer abusar, mais uma coisinha, tem com dizar ao codigo para ele nao diferenciar do maiusculo do minusculo.

    Agradeco desde já, um abraco.

  17. Rivair,

    Obrigado!
    Bem quanto ao minisculo e maisculo, esta adaptaçao deve ser feita no arquivo PHP que retorna os resultado, na busca no banco de dados pra ser exato, o que vai com o arquivo e apenas um exemplo de uso.
    Mesma coisa para os varios campos que voce descreveu, o script trabalha com id e label, mas este label pode ser algo como “fulano (ful@ano.com) – 16/12” ou qualquer informaçao que voce desejar retornar.
    A funçao do alert esta logo abaixo da declaraçao do script e pode ser adequada ao seu uso, editando o javascript.

    Sinta-se livre para explorar o script.

  18. Olá Rafael, valeu mesmo pela luz, a resposta estava na minha cara e eu nao tinha visto, fico te devendo uma, e de novo parabéns pelo script está me quebrando um galhao.

    T+ Rivair

  19. Olá Rafael!

    Muito bom o Scritp, mas não funcionou aqui no server, nem com Ruindows (php 4.x e 5.x) e no Linux (php 4).

    Ambos possuem o domXML habilitado!

    O estranho é que ao digitar as iniciais aparece uma pequena caixinha, comose fosse aparecer algo, asm não aparece nada!

    Monitorando as requisições, não parece que está sendo enviada …

    Por um acaso saberia informar o que pode estar errado?

    Um abraço e parabéns pelo artigo!

  20. Igor,

    Isso significa que o PHP não tá retornando nada. Faz o seguinte executa o arquivo php diretamente, passando “?string=a” e ve se ele retorna algum erro ou alguma coisa, já vai te dá 80% das dicas q vc precisa pra ver oq esta ocorrendo.
    Qlqr coisa me encaminha o resultado que dou uma olhada.

    Abraço!!

  21. Olá Rafael,

    Então, fiz o que falei e consegui resolver o problema.

    Vou colocar aqui os passos:

    1) chamei diretamente o arquivo http://localhost/ajax/dms/dmsAC.php?string=re
    E recebi a seguinte mesnagem de erro:
    Erro no processamento de XML: caracteres inúteis após um elemento do documento
    Posição: http://localhost/ajax/dms/dmsAC.php?string=re
    Número da linha 2, Coluna 1:Notice: Undefined index: string in c:\arquivos de programas\apache group\Apache\arquivos\ajax\dms\dmsAC.php on line 50
    ^

    2) Fui até a linha 50 do arquivo dmsAC.php e troquei:
    if ($_POST[‘string’] != ”){
    por:
    if ( isset($_POST[‘string’]) && $_POST[‘string’] != ”){

    3) Funcionou!

    É isto aí, muito obrigado !!!

  22. Ahh sim,
    Esqueci de falar que só funcionou no php 5.x!
    No 4.x ele reclama que não existe a função createelement(), apesar de existir e estar hailitado o DOM:
    domxml
    DOM/XML enabled
    DOM/XML API Version 20020815
    libxml Version 20623
    HTML Support enabled
    XPath Support enabled
    XPointer Support enabled

    É isto aí,

    Um abraço

  23. Olá Rafael,

    Estou migrando seu código para PHP 4.x, porém parece que o .js também tem uma incompatibilidade, já que ao digitar a no campo aparece uma listagem com tudo null …

    Veja o teste em http://www.comp.ufscar.br/~igorvc/dms2/example.htm

    Bem, vou ver se tento debugar e ver onde está a incompatibilidade!

    Assim que eu terminar a migração, postarei aqui as modificações, daí mais gente poderá utilizar!

    Um abraço

  24. Cara, o seu exemplo funcionou perfeitamente — com o PHP5, e SEM ‘php.ini’.

    Assim que renomeei o php.ini-recommended, o script deixou de funcionar.

    Não achei nada sobre ‘como ativar o DOM’, e tem outro porém: todos os meus outros scripts estão em PHP4 (mysql_), e seria um trabalhão passá-los todos pro PHP5.

    Vou esperar pra ver o que o Igor consegue com o PHP4, mas enquanto isso… Como faço pra ativar o DOM no PHP5 depois de renomear o php.ini…? o_O

  25. Guilherme,
    Verifica uma coisa no PHP5, algumas (ou todas) versões estão vindo com o “short_tags” OFF, isso significa que <? não funciona, somente <?php. Isso pode explicar. Quanto ao DOM ele já esta habilitado por padrão no PHP5.
    Para fazer o script funcionar em PHP4 é bem simples, só substituir todos DOM por strings de XML mesmo, montar o XML na mão. Ou usar classes de PHP populares.

  26. Ôpa! Fiz esta versão pelo ‘método lusitano’, e agora está funcionando no PHP4! =)

    
     * @package dmsAutoComplete
     * @version 1.0
     */
     
    /**
    * Função de auxilio para exemplo, ela filtra o array
    * retornando apenas as entradas que se iniciam com
    * a string recebida
    * 
    * Filter function used in example, it filters an array
    * returning only entries starting with the given string
    *
    * @param item
    */
    
    function arrfilter(&$item){
    	return preg_match('/^'.$_REQUEST['string'].'/',$item);
    }
    
    //Create XML header
    header("Content-type:application/xml; charset=utf-8");
    
    
    //Begining of the XML file
    echo "\n";
    echo "\n";
    
    /**
     * :pt-br:
     * Definir Lista (itens) a ser mostrada.
     * 
     * Neste passo podemos realizar buscas em banco de dados, filtrar arrays
     * Ou qualquer outra tarefa que retorne um resultado baseado no string
     * recebido
     * 
     * :en:
     * Define list to be returned
     * 
     * In this step we could do a database search, filter arryas or perform
     * other actions which would return a resultig list based on an input
     * string
     */
    if ($_REQUEST['string'] != ''){
    	//Fazer filtro ou busca
    	//Filter ou search
    	//SQL, Array, etc...
    	$ostring = "O cuidado em identificar pontos críticos na revolução dos costumes nos obriga à análise dos conhecimentos estratégicos para atingir a excelência. Acima de tudo, é fundamental ressaltar que o acompanhamento das preferências de consumo cumpre um papel essencial na formulação dos procedimentos normalmente adotados. Podemos já vislumbrar o modo pelo qual a estrutura atual da organização agrega valor ao estabelecimento dos métodos utilizados na avaliação de resultados. 
    			  No entanto, não podemos esquecer que a competitividade nas transações comerciais possibilita uma melhor visão global das diretrizes de desenvolvimento para o futuro. Por conseguinte, a adoção de políticas descentralizadoras maximiza as possibilidades por conta das direções preferenciais no sentido do progresso. Nunca é demais lembrar o peso e o significado destes problemas, uma vez que o entendimento das metas propostas causa impacto indireto na reavaliação do retorno esperado a longo prazo. A prática cotidiana prova que a percepção das dificuldades assume importantes posições no estabelecimento do fluxo de informações. É claro que a execução dos pontos do programa obstaculiza a apreciação da importância do investimento em reciclagem técnica. 
    			  Não obstante, a consulta aos diversos militantes oferece uma interessante oportunidade para verificação do remanejamento dos quadros funcionais. Neste sentido, a determinação clara de objetivos acarreta um processo de reformulação e modernização das condições inegavelmente apropriadas. A certificação de metodologias que nos auxiliam a lidar com o desenvolvimento contínuo de distintas formas de atuação pode nos levar a considerar a reestruturação dos relacionamentos verticais entre as hierarquias. 
    			  Gostaria de enfatizar que o consenso sobre a necessidade de qualificação representa uma abertura para a melhoria do orçamento setorial. Pensando mais a longo prazo, o fenômeno da Internet estimula a padronização dos paradigmas corporativos. O que temos que ter sempre em mente é que o desafiador cenário globalizado talvez venha a ressaltar a relatividade do impacto na agilidade decisória. 
    			  Assim mesmo, a expansão dos mercados mundiais aponta para a melhoria de todos os recursos funcionais envolvidos. O incentivo ao avanço tecnológico, assim como o julgamento imparcial das eventualidades faz parte de um processo de gerenciamento dos níveis de motivação departamental. Todavia, o novo modelo estrutural aqui preconizado desafia a capacidade de equalização da gestão inovadora da qual fazemos parte. É importante questionar o quanto a valorização de fatores subjetivos garante a contribuição de um grupo importante na determinação dos índices pretendidos. O empenho em analisar a contínua expansão de nossa atividade auxilia a preparação e a composição das formas de ação. ";
    	$available = array_unique(explode(" ",$ostring));
    	
    	$results = array_filter($available,'arrfilter');
    	
    	//Construir elementos ITEM
    	//built ITEM elements
    	foreach($results as $key=>$label){
    		//Cadastrar na lista manualmente
    		//Add to list manually
    		echo "  \n";
    		
    		//rawurlencode evita problemas de charset
    		//rawurlencode avoids charset problems
    	}
    }
    // close XML document
    echo "";
    ?>
    

    Estava matutando sobre ‘como fazer o barato case-INsensitive’…
    Isso mais tarde vai puxar uma coluna d’um MySQL.

    Bem, mais uma vez agredeço de montão a sua força, e depois te mando um e-mail mostrando onde pretendo usar o seu snippet! ; )

    Um forte abraço de novo, e muito obrigado pela força!

  27. Olá Rafael!!
    Cara tou começando agora na area do Ajax e ainda não vi o php5, gostaria que se possivel vc me desse uma mão e colocasse como que se faz para fazer a pesquisa no BD, tou apanhando demais, eu vi o que vc postou para o Felipe Rômulo Silva mais não consegui pegar, depois do while, como que fica a “$ostring”??, fiz o que vc postou mais não funcionou com o select não, acho que o que eu deve estar errado.
    Fica assim, um abraço pra vc!!
    Francis Bento Marques

  28. Beleza Rafael?

    Me diz uma coisa, se eu quiser utilizar um WebService para retornar as informações, eu já tenho um que retorna um XML no formato que vc desenhou, só que o método precisa ser invocado, como eu faria isto?

    Valeu!

    Raphael

  29. Raphael,

    Existe uma variavel do script que voce pode mudar a URL para o que desejar. Seria algo como:

    AC.ajaxTarget = ‘sua_url/servico’;

    Entendeu?

    abraço!

  30. Valeu Rafael, vou fazer alguns testes ainda, mas na primeira tentativa ele não rodou com webservices, pois o método precisa ser invocado.
    Mas vou ver com mais paciência hoje a tarde.

    T

  31. Cara, vou ser bem sincero, voce me ajudou muito =) vlw mesmo. Exatemante disso que eu estava precisando =)

  32. Parabéns pelo script, não quebrou um galho meu, mas sim uma árvore.

    obrigado!

  33. Para pegar valores de um BD fiz o seguinte while (assim como mostra um exemplo acima )
    Porém qualquer coisa que eu digite ele me traz todos os registros do banco.
    alguém já fez com BD ?
    obrigado

    while($palavra = pg_fetch_array($result))
    {
    $key = $palavra[‘codigo’];
    $item = $xmlDoc->createElement(‘item’);
    $item = $root->appendChild($item);
    $item->setAttribute(‘id’,$key);
    $item->setAttribute(‘label’,rawurlencode($palavra[‘nome’]));
    $ostring = $ostring . ” ” .$palavra[‘nome’] ;
    }

  34. $query = “SELECT * FROM teste ORDER BY nome ASC” ;
    $result = pg_query($conn,$query);

    coloquei (*) sendo que os campos sao:
    codigo(int) nome(varchar) email(varchar)

    obrigado pela atenção !

  35. Ronaldo,
    É preciso que a query busque as palavras de acordo com o que ela recebe, voce precisa incluir ai algum requisito WHERE, tipo:

    SELECT * FROM teste WHERE nome LIKE ‘”.$_POST[‘string’].”%’ ORDER BY nome ASC

    Com isso ele vai procurar por nome onde o nome começe com a string passada.

  36. Muito obrigado Dohms !
    E olha que voce já havia dito isto acima ehehehe.
    Funcionou uma beleza, mas fiz o seguinte. Comentei uma linha para que ele nao repetisse as palavras digitadas!

    foreach($results as $key=>$label) {
    if (class_exists(‘DOMDocument’)){
    //Cadastrar na lista
    //Add to list
    $item = $xmlDoc->createElement(‘item’);
    // $item = $root->appendChild($item); comentada por ronaldo
    $item->setAttribute(‘id’,$key);
    $item->setAttribute(‘label’,rawurlencode($label));
    //rawurlencode evita problemas de charset
    //rawurlencode avoids charset problems
    }else{
    $xmlDoc .= ”;
    }
    }

  37. Eu rodei o script da forma que veio e deu certinho agora eu não consigo colocar ele pra buscar no BD como vc falou no post abaixo:
    =====================================================
    Rafael Dohms on 14 Jul 2006 at 11:18 #

    Caro felipe

    Para mostrar …
    No seu código, faça o seguinte….

    while($palavra = mysql_fetch_array($result))
    {
    $item = $xmlDoc->createElement(\’item\’);
    $item = $root->appendChild($item);
    $item->setAttribute(\’id\’,$key);
    $item->setAttribute(\’label\’,rawurlencode($palavra[”nome”]));
    }

    e substitua $key pelo valor da sua chave primaria no banco.

    Qualquer dúvida estamos ai
    =================================================
    Substitui $key por $id como falou, comentei a linha string do texto grande e coloquei o while dai deste post so que não aparece nada.
    Teria como vc me enviar um exemplo com uma busca no banco de dados em uma tabela retornando os valores de um campo?
    Te agradeço pela ajuda desde já,
    Abraço
    Márcio

  38. Olá, estive testando o código, gostei muito.
    Mas estou com um problema, o Código não roda no IE.
    No firefox está tudo normal. Estou usando com ASP.

    Abraço

  39. Como se faz com POSTGRES, Acessando o banco.? poederia ajuda-me ? estou precisando muito.

    Obrigado pela atenção. .

  40. Olá Rafael, blz?
    Adaptei o script pra meu uso com o banco de dados e tudo mais.
    A única coisa que to quebrando a cabeça é pra não ficar aparecendo o alert, mas apenas qdo dou enter emcima de um item da lista ficar digitado.
    Consegui tirar o alert, mas qdo dou enter no item da lista o input fica em branco. obs: isso acontece deixando o alert ou sem.
    Sabe como faço pra resolver isso?
    Desde já agradeço e parabenizo-lhe pelo script, muito bom mesmo.

    Abs.
    Alisson!

    • Alisson,

      Infelizmente faz muito tempo que atualizei com este codigo e nao me lembro se isto era possivel na epoca portanto acredito que não esta incorporado no codigo.

Os comentários estão desativados.