quarta-feira, 23 de abril de 2008

Auto-completar Ajax

Autor: Elcio Ferreira <http://elcio.com.br/ajax/autocompletar>

Algumas pessoas tem escrito perguntando como se produz um efeito semelhante ao Google Suggest. Esta série de artigos e o script que a acompanha são uma maneira de tentar responder a esta dúvida.

Exemplo do efeito desejado:

O script

Embora a idéia original seja bastante simples, o controle de eventos do teclado é bastante complexo e exige um bocado de código, além de um bom conhecimento de DOM. Além disso, é muito interessante que esse tipo de coisa seja produzida no formato de um script reaproveitável, o que exige uma série de outros conhecimentos que tornam o código complexo demais para um artigo simples.

Assim, resolvi escrever sobre algumas coisas:

  1. Como usar este script para implementar um recurso de auto-completar em seu site.
  2. Como personalizar este recurso.
  3. Como escrever um script como este, que trata eventos do navegador, inclusive do IE bugado.
  4. Como escrever código ajax usando JSON, uma das maneiras mais práticas de se fazer ajax.
  5. Como escrever código javascript que seja facilmente reaproveitado.

Como usar este script

Tentei construir um script que você possa reaproveitar sem ter o trabalho de entendê-lo todo. Nas partes posteriores desta série de artigos vamos entender este script passo a passo, e pretendo dar dicas de como construir código reaproveitável como este. Então vamos começar analisando o resultado, isto é, aproveitando este código numa página para ver como será a experiência de quem quiser usá-lo sem entendê-lo.

Para começar, copie para seu site os arquivos:

Em seguida inclua estes arquivos na seção head do seu HTML, assim:

<link rel="stylesheet" href="css/autocompletar.css" />
<script src="js/events.js"></script>
<script src="js/xmlhttp.js"></script>
<script src="js/autocompletar.js"></script>

Seu formulário HTML precisa ter a class autocompletar e o campo de formulário que vai ser preenchido terá o id completeaqui. Defina então a largura do seu campo de formulário junto com a da caixa de sugestões, no CSS:

.completeaqui,div#completando{
width:300px;
}

Fazendo a busca via Ajax, com JSON

A maneira mais simples de usar este script é fazê-lo buscar os dados do servidor via Ajax, no formato JSON. Para isto, basta fazer sua página PHP, ASP ou seja lá o que estiver usando, retornar um Array de strings formatado em JSON. (Vou falar mais de JSON na quarta parte desta série.)

Veja um exemplo disso na página python que estou usando neste tutorial: ap, fi. É esta página que meu script vai buscar.

window.onload=function(){
ac_registraJSON(document.forms[0],"q","busca.pt?q=")
}

Onde "busca.pt?q=" é o endereço de onde esse script vai buscar os dados. O primeiro argumento (document.forms[0]) é o formulário que contém o campo, e o segundo ("q") o nome do campo.


ANEXO 1: <script src="js/events.js">

/*
Tratamento de eventos crossbrowser
Baseado em http://simon.incutio.com/archive/2003/11/06/easytoggle
Elcio Ferreira - 2004 - http://elcio.locaweb.com.br
*/

function addEvent(obj, evType, fn){
if(obj.addEventListener)obj.addEventListener(evType,fn,true)
if(obj.attachEvent)obj.attachEvent("on"+evType,fn)
}


function getSource(e){
if(typeof e=='undefined')var e=window.event;
var source=typeof e.target!='undefined'?e.target:typeof e.srcElement!='undefined'?e.srcElement:true
if(source.nodeType == 3)source=source.parentNode;
return source
}

ANEXO 2:<script src="js/xmlhttp.js">
try{
xmlhttp = new XMLHttpRequest();
}catch(ee){
try{
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
}catch(e){
try{
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}catch(E){
xmlhttp = false;
}
}
}

ANEXO 3: <link rel="stylesheet" href="css/autocompletar.css" />
form.autocompletar{
position:relative;
top:0;
left:0;
}
div#completando{
position:absolute;
top:1.5em;
left:0px;
background:white;
}
div#completando ul{
margin:0 1px;padding:0;
border:1px solid black;
list-style:none;
}
div#completando li{display:inline}
div#completando a{
display:block;
padding:1px 5px;
}
div#completando a:hover,div#completando li.selecionado a{
background:blue;
color:white;
}

ANEXO 4:<script src="js/autocompletar.js">
//Variáveis Globais
ac_idx=0
ac_old=""
ac_showing=false

//Registra o autocompletar
function ac_registra(formulario,inputname,funcao){
ac_campo=formulario.elements[inputname]
addEvent(ac_campo, "keyup", ac_up)
addEvent(ac_campo, "keypress", ac_mudou)
addEvent(ac_campo, "focus", ac_focus)
addEvent(ac_campo, "blur", ac_blur)
ac_pegaLista=funcao
}
//Registra o autocompletar com um endereço Ajax/JSON
function ac_registraJSON(formulario,inputname,endereco){
ac_JSON=endereco
ac_registra(formulario,inputname,ac_getJSON)
}

//Função para obter o JSON
function ac_getJSON(t,func){
ac_f=func
xmlhttp.open("GET", ac_JSON+escape(t),true);
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4)
ac_f(eval(xmlhttp.responseText))
}
xmlhttp.send(null)
}

//Monta a lista a partir de um Array
function ac_fazLista(t){
ac_Lista=t
var ret="<ul>"
for(var i=0;i<ac_Lista.length;i++)
ret+="<li"+(i==ac_idx?' class="selecionado"':'')+
"><a href='javascript:ac_completa("+i+")'>"+
ac_getHTML(ac_Lista[i])+"</a></li>"
ret+="</ul>"
document.getElementById("completando").innerHTML=ret
}

//Obtém o texto do item da lista
function ac_getText(i){return typeof(i)=="string"?i:i[1]}
//Obtém o HMTL do item da lista
function ac_getHTML(i){return typeof(i)=="string"?i:i[0]}

//Atualiza a lista de acordo com o conteúdo do campo
function ac_mudado(){
ac_texto=ac_campo.value
if(ac_old!=ac_texto)ac_idx=0
ac_old=ac_texto
setTimeout('ac_pegaLista(ac_texto,ac_fazLista)',100)
}

//Esconde a lista
function ac_some(){
ac_showing=false
try{document.getElementById("completando").style.display="none"}catch(e){}
}

//Mostra a lista
function ac_aparece(){
ac_showing=true
try{
document.getElementById("completando").style.display="block"
}catch(E){
var d = document.createElement("div");
d.setAttribute("id","completando")
ac_campo.parentNode.appendChild(d)
}
}

//Clique no completar
function ac_completa(l){
try{
ac_texto=ac_campo.value=ac_Lista[l]
ac_campo.focus()
setTimeout('ac_aparece()',110)
ac_mudado()
}catch(e){}
}

//Controla o índice do item selecionado
function ac_chItem(kCode){
ac_idx+=kCode-39
if(ac_idx<0)ac_idx=ac_Lista.length-1
if(ac_idx>ac_Lista.length-1)ac_idx=0
ac_mudado()
}

/* Eventos no textbox: */
//KeyPress
function ac_mudou(e){
if(e.keyCode==40 || e.keyCode==38 || e.keyCode==13){
if(e.keyCode!=13){
if(!ac_showing)ac_aparece()
ac_chItem(e.keyCode)
}else{
if(ac_Lista[ac_idx]==ac_campo.value || ac_Lista.length==0)return true
if(ac_showing)ac_completa(ac_idx)
else return true
}
if(e.preventDefault)e.preventDefault()
return false
}
if(e.keyCode==27)ac_some()
setTimeout('ac_mudado()',1)
}
//Tratando eventos no IE bugado
function ac_up(e){
if(typeof e.target=="undefined" && (e.keyCode==40 || e.keyCode==38 || e.keyCode==8))ac_mudou(e)
}

//Focus
function ac_focus(e){
ac_aparece()
ac_mudado()
}
//Blur
function ac_blur(e){setTimeout('ac_some()',100)}

Nenhum comentário: