lunedì 31 ottobre 2011

Creare un autocomplete con Spring e DWR

Una settimana fa avevo bisogno di fare un autocomplete in un textbox per un progetto in Java, in cui stavo lavorando. Mi serviva per fornire suggerimenti agli utenti. Ho deciso di fare tutto ciò utilizzando il DWR (Direct Web Remoting). DWR è una libreria che ci permette di esporrere un servizio server-side al javascript. Verrano create alcune librerie javascript al runtime che poi utilizzeremo.

In pratica l'utente inizia a scrivere nel textbox. Utilizzando l'evento "onkeyup" chiamo una funzione javascript che fa delle chiamate asincrone all'oggetto DWR. Quest'ultimo cercherà nel database se ci sono suggerimenti da visualizzare. Ritorna i suggerimenti che recupera dal database al javascript e quest'ultimo le visualizzerà all'utente. È molto semplice da implementare ed ora vi spiegherò come fare.

Passo 1:
Creo l’interfaccia AutoCompleteDao:

 public interface AutoCompleteDao {  
   List<String> getResults(String input) throws DataAccessException;  
 }  


Poi implemento l’interfaccia appena creata. Il metodo getResults non fa altro che una “like” sulla tabella del database per una certa stringa, che prende come parametro di input, e restituisce i risultati come una lista di stringhe:

 public class AutoCompleteImpl implements AutoCompleteDao {  
   String sql;  
   public AutoCompleteImpl(String sql) {  
     super();  
     this.sql = sql;  
   }  
   public List<String> getResults(String input)  
       throws DataAccessException {  
   // implementazione del metodo, semplicemente faccio una “like” con  
   // il parametro di input.  
   // return list di risultati.    
   }  
 }  


Passo 2:
Importo dwr.jar nel progetto e poi nel file web.xml aggiungo il DWR:

 <servlet>  
   <servlet-name>dwr</servlet-name>  
   <servlet-class>  
     org.directwebremoting.spring.DwrSpringServlet  
   </servlet-class>  
   <init-param>  
     <param-name>debug</param-name>  
     <param-value>true</param-value>  
   </init-param>  
   <init-param>  
     <param-name>scriptCompressed</param-name>  
     <param-value> false</param-value>  
   </init-param>  
   <init-param>  
     <param-name>crossDomainSessionSecurity</param-name>  
     <param-value>false</param-value>  
   </init-param>  
   <init-param>  
     <param-name>activeReverseAjaxEnabled</param-name>  
     <param-value>true</param-value>  
   </init-param>  
   <init-param>  
     <param-name>allowScriptTagRemoting</param-name>  
     <param-value>true </param-value>  
   </init-param>  
 </servlet>  
 <servlet-mapping>  
   <servlet-name>dwr</servlet-name>  
   <url-pattern>/dwr/*</url-pattern>  
 </servlet-mapping>  


Passo 3:
Dopodiché aggiungo nel file applicationContext.xml il seguente:

 <bean id="autoComplete" class="com.aldosinanaj.dwr.AutoCompleteDwr">  
   <dwr:remote javascript="AjaxAutoComplete"> //metodo javascript da invocare  
     <dwr:include method="getSuggestions"/>  
   </dwr:remote>  
   <property name="autoComplete" ref="autoComplete"></property>  
 </bean>  


Ho configurato il bean che sarà esposto nel javascript. Il tag “dwr:method” ci permette di dichiarare la funzione che verrà invocata da javascript.

Passo 4:
Ora creò la classe java che implementerà la funzione javascript definita al passo 3, ovvero la classe
com.aldosinanaj.dwr.AutoCompleteDwr.java
Riceve richieste da javascript, recupera i risultati dal database e li ritorna al javascript.

 public class AutoCompleteDwr {  
   private AutoCompleteDao autoComplete;  
   public String[] getSuggestions(String input) {  
     List<String> listaRisultati = autoComplete.getResults(input);  
     String[] arrayRisultati = listaRisultati.toArray(new String[]{});  
     return arrayRisultati;  
   }  
   public void setAutoComplete(AutoCompleteDao autoComplete) {  
     this.autoComplete = autoComplete;  
   }  
 }  


Passo 5:
Il textbox nella pagina jsp (autoComplete.jsp) verrà programmato come segue:

 <label>Cerca:</label>  
 <br>  
 <form:input path="cercaTextBox" size="23" id="cercaTextBox" onkeyup='autoComp(this.value);' />  
 <div class="divSuggerimenti" id="divSuggerimenti" style="display: none;" >  
   <div class="listaSuggerimenti" id="listaSuggerimenti" ></div>  
 </div>  


Utilizzo l’evento “onkeyup” per invocare la funzione javascript autoComp() che fa chiamate asincrone all’oggetto DWR. Non dimenticare di importare le funzioni javascript nella pagina. Le prime tre vengono creati al runtime.

 <script type='text/javascript'   
 src='${pageContext.request.contextPath}/dwr/interface/AjaxAutoCompleteIfaces.js'></script>  
 <script type='text/javascript' src='${pageContext.request.contextPath}/dwr/engine.js'></script>  
 <script type='text/javascript' src='${pageContext.request.contextPath}/dwr/util.js'></script>  
 <script type='text/javascript' src='${pageContext.request.contextPath}/js/autoComplete.js'></script>  


Passo  6:
Ora passiamo al javascript. Creò un file autoComplete.js con tutte le funzioni javascript che devo utilizzare.

 function autoComp(input) {  
   if (input.length >2){  
     AjaxAutoComplete.getSuggestions(input, callback);  
   }else{  
     document.getElementById("divSuggerimenti").style.display = "none";  
   }  
   rimuoviTutto();  
 }  
 function callback(msg) {  
   if (msg.length > 0) {  
     document.getElementById("divSuggerimenti").style.display = "block";  
     var listaSuggerimenti = document.getElementById("listaSuggerimenti");  
     var ul = document.createElement('ul');  
     for (var i = 0; i < msg.length; i++){  
       var li = document.createElement('li');  
       li.innerHTML = msg[i];  
       li.onclick = riempiTextBox(msg[i]);  
       ul.appendChild(li);  
     }  
     listaSuggerimenti.appendChild(ul);  
   }  
 }  
 function riempiTextBox(testo) {  
   document.getElementById("cercaTextBox").value = testo;  
   document.getElementById("divSuggerimenti").style.display = "none";  
   rimuoviTutto();  
 }  
 function rimuoviTutto() {  
   var listaSuggerimenti = document.getElementById("listaSuggerimenti");  
   var figli = listaSuggerimenti.childNodes;  
   for (var i = 0; i < figli.length; i++) {  
     listaSuggerimenti.removeChild(figli[i]);  
   }  
 }  


Passo  7:
L’ultimo passo è la creazione di un foglio di stile che lo chiamo autoComplete.css

 .divSuggerimenti {  
 position: relative;  
 width: 150px;  
 border: 1px solid #000;  
 }  
 .listaSuggerimenti ul {  
 margin: 0;  
 padding : 0;  
 list-style: none;  
 }  
 .listaSuggerimenti li:hover {  
 background-color: #eee;  
 color: #79AF2B;  
 font-weight: bold;  
 }  


Il risultato non sarà altro che: