domenica 31 agosto 2008

Ajax con DWR e Spring

Ricordate l'articolo del quale parlavo di come sfruttare le richieste asincrone Ajax per popolare delle select in un form con dati provenienti da una classe factory senza aggiornare la pagina.... Bene, in quell'articolo ho scritto che la tecnica l'avrei sfruttata per un progetto sul quale lavoro. Be! Alla fine non l'ho fatto...

Su consiglio di un mio collaga ho usato un framework di nome DWR.

Tale framework, in parole semplici, permette di trasformare gli oggetti java ottenuti dalla richiesta Ajax in oggetti javascript e di essere utilizzati per i propri scopi all'interno della pagina web.



Quindi data la sua semplicità di configurazione, uso e integrazione con altri framework ho preferito utilizzarlo.

Proprio la sua integrazione mi ha convinto. L'applicazione sulla quale lavoro usa Spring per implementare il pattern dell'IoC (Inversion of Controll) e dato che DWR si integra con spring ho rispettato l'architettura dell'applicazione web.

DWR in se per se è un jar, dwr.jar per l'appunto, scaricabile dal sito con le altre librerie necessarie e che solitamente già si trovano in un'applicazione web.

Il suo principio di funzionamento e molto semplice ha una servlet che risponde alle richieste dei file javascript. Alla richiesta esso va a guardare all'interno del suo file di configurazione gli oggetti da restituire in formato javascript e i metodi che deve mettere a disposizione e ne restituisce lo script creato dalla servlet da utilizzare per richiamare le funzioini e usare gli oggetti restituiti da tale funzione.

Semplice il principio tanto semplice la configurazione e l'utilizzo.

Per prima cosa bisogna mappare nel web.xml dell'applicazione la servlet di dwr in questo modo:

.....

<servlet>
<display-name>DWR Servlet</display-name>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>


....

quindi quando avverrà una richiesta al application server con path ../dwr/<NomeScript.js> risponderà la servlet.

Per quanto riguarda Spring il suo file di configurazione è così settato nella parte che ci interessa:

......

<bean id="myAppDS"
class="org.springframework.jndi.JndiObjectFactoryBean">

<property name="jndiName">
<value>jdbc.myApplication.multidatasource</value>
</property>
</bean>


<bean id="ricercaDao" class="it.myapplication.dao.RicercaDao">
<property name="ds"><ref bean="
myAppDS"/></property>
</bean>


<bean id="ricercaNegoziBl" class="it.myapplication.bl.negozi.RicercaNegoziBl">
<property name="ricercaDao"><ref bean="ricercaDao"/></property>
</bean>


....

Descrivo brevemente (anche perchè questo articolo non è basato su spring e l'inversion of control), abbiamo detto a spring che abbiamo tre SpringBean:

- myAppDs > questo bean fornisce il datasorce jndi che ha una connessone al db.

- ricercaDao > che fornisce dati dal db e che referenzia il bean myAppDs

- ricercaNegoziBl > è il bean che implementa la nostra business logic per la ricerca dei dati necessari a popolare le select e che ovviamente referenzia il bean ricercaDao.

Bene! Anzi no! Volevo evitare ma è meglio che spieghi cosa fa spring con  quel property.. ref.. quindi apriamo una parentesi(

come ho detto spring permette di utilizzare il pattern dell'inversion of control... in parole molto povere va a inizializzare i bean che ci servono al posto nostro fornendoli come singleton e solo al momento giusto.... Meglio commentare il file di configurazione, ad esempio nella dichiarazione del bean ricercaNegoziBl abbiamo

<property name="ricercaDao"><ref bean="ricercaDao"/></property>

ora guardate un attimo come è fatta la classe RicercaNegoziBl:

.....

public class RicercaNegoziBl implements IRicercaNegoziBl {

private IRicercaDao ricercaDao;

public void setRicercaDao(IRicercaDao ricercaDao) {
this.ricercaNegoziDao = ricercaDao;
}


......

quindi non abbiamo detto altro a spring che quando richiamiamo la nostra bl all'interno di essa vi è una proprietà ricercaDao. Quindi lui si aspetta (usa la logica dei POJO) che vi sia il metodo setRicercaDao(IRicercaDao ricercaDao), che lo richiama e ci fornisce il singleton della classe che effetua la ricerca sul db e che a sua volta avrà settato il datasource. Non ci rimane poi che ultizzare la classe di ricerca nella nostra bl..

spero di essere stato chiaro ) chiusa parentesi.

Vi risparmio il codice implementato nella bl per impostare i parametri da passare alla classe di ricerca, dato che allo scopo dell'articolo poco ci interessa, così vi illustro solo l'interfaccia che implementa:

import java.util.List;

public interface IRicercaNegoziBl {
public List getPossibiliValoriRegioni();
public List getPossibiliValoriProvince(String regione);
public List getPossibiliValoriCitta(String regione, String provincia);
public List getPossibiliValoriCap(String regione, String provincia, String citta);
}


in sequanza la classe che implementa questa interfaccia deve avere quattro metodi. Rispettivamente uno che restituisce l'elenco delle regioni dei negozi presenti sul db, uno le province di quella regione, le città di quella regione e provincia e in fine i cap di quella regione provincia e città.

Ora diciamo alla servlet di DWR che javascript deve fornirci e glielo diciamo nel suo file di configurazione che deve essere messo nella cartella WEB-INF:

<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN" "http://getahead.org/dwr//dwr20.dtd">
<dwr>
<allow>
<create creator="spring" javascript="SelectNegozi">
<param name="beanName" value="ricercaNegoziBl" />
<include method="getPossibiliValoriProvince"/>
<include method="getPossibiliValoriRegioni"/>
<include method="getPossibiliValoriCitta"/>
<include method="getPossibiliValoriCap"/>
</create>
<convert converter="collection" match="java.util.Collection"/>

</allow>
</dwr>


Abbiamo detto al dwr di fornirci uno javascript chiamato SelectNegozi.js e che il bean da trasformare in js è un SpringBean (quindi va a cercare nel file di configurazione di spring) che è chiamato ricercaNegoziBl dopo gli diciamo che lo javascript dovrà trasformarci in funzioni javascript i metodi che gli abbiamo indicato co in tag <include nethod ../> (nel nostro caso tutti è quattro). Infine gli abbiamo detto che i dati restituiti saranno di tipo Collection il quale provvederà a trasformarli in array javascript

Bene!! DWR ora è configurato passiamo alla jsp e al javascript che ultilizza il javascript restituito da dwr.

Prima il javascript negozi.js:

function validaSelectNegozi(){
if (document.ricerca.regione.value == "" || document.ricerca.provincia.value == "" ||document.ricerca.regione.value == "Regione" || document.ricerca.provincia.value == "Provincia") {
alert("Selezionare almeno la Regione e la Provincia"); return false;
}else{
document.ricerca.submit();return;
}
}
var arrayvalori = {"regione":"Regione", "provincia":"Provincia", "citta":"Citta", "cap":"Cap"};
function resetForm(campo, label) {
dwr.util.removeAllOptions(campo);
dwr.util.addOptions(campo, [label]);
}
function resetAllDopo(nameSelect) {
var tmp = false;
for (var chiave in arrayvalori) {
if (tmp) {
resetForm(chiave, arrayvalori[chiave]);
} else {
if (chiave == nameSelect) {
tmp = true;
}
}
}
}
function infasaSelect(padre, currSel) {
var sel = dwr.util.getValue(padre);
var presente = false;
for (var chiave in arrayvalori) {
if (sel == arrayvalori[chiave]||sel=="") {
presente = true;
resetAllDopo(chiave);
}
}
if (sel !== "" && !presente) {
for (var chiave in arrayvalori) {
if (chiave == currSel) {
resetAllDopo(currSel);
}
}
var funzione = function (lista) {
if (lista.length == 0) {
resetForm(currSel, arrayvalori[currSel]);
//disableSubmit();
} else {
//enableSubmit();
resetForm(currSel, arrayvalori[currSel]);
dwr.util.addOptions(currSel, lista);
//dwr.util.setValue(currSel, arrayvalori[currSel]);
}
};
if (padre == "regione") {
SelectNegozi.getPossibiliValoriProvince(dwr.util.getValue(padre), funzione);
}
if (padre == "provincia") {
SelectNegozi.getPossibiliValoriCitta(dwr.util.getValue("regione"), dwr.util.getValue(padre), funzione);
}
if (padre == "citta") {
SelectNegozi.getPossibiliValoriCap(dwr.util.getValue("regione"), dwr.util.getValue("provincia"), dwr.util.getValue(padre), funzione);
}
}
}


Non sto a spiegare tutto il javascript ma solo la parte in grassetto che è la parte che richiama il javascript SelectNegozi.js restituito da dwr. Notate che abbiamo delle normali funzioni javascript che rispecchiano i metodi della nostra business. Il parametro "funzione" passato ai metodi è il riferimento alla funzione da richiamare una volta ottenuti i dati e alla quale li deve passare. In pratica è come se richiamassimo e usassimo la nostra business logic all'interno di javascript, figo vero?!! Notate anche dwr.util che è una li breria messa a disposizione da dwr per interagire con gli oggetti del DOM.

Per ultimo ecco le porzioni di codici che ci interessano della jsp

....

<head>

...

<script type='text/javascript' src='/myapp/dwr/util.js'></script>
<script type='text/javascript' src='/myapp/dwr/interface/SelectNegozi.js'></script>
<script type='text/javascript' src='/myapp/dwr/engine.js'></script>
<script type='text/javascript' src='/myapp/s/js/negozi.js'></script>

...

</head>

....

<form name="ricerca" id="ricerca" title="Trova negozio" action="/myapp/d/assistenza/ng1/ricercanegozi.do" method="post">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td><label for="regione">Regione:</label>
<select name="regione" id="regione" title="Regione" tabindex="180" class="regione" onchange="infasaSelect('regione','provincia')">
<option value="" selected="selected">Regione</option>
<nested:iterate id="reg" property="listaRegioni">
<nested:notEmpty name="reg">
<option value="<nested:write name="reg" />"><nested:write name="reg" /></option>
</nested:notEmpty>
</nested:iterate>
</select></td>
<td><label for="provincia">Provincia:</label>
<select name="provincia" id="provincia" title="Provincia" tabindex="200" class="provincia" onchange="infasaSelect('provincia','citta')" >
<option value="" selected="selected">Provincia</option>

</select></td>
<td><label for="citta">Citta:</label>
<select name="citta" id="citta" title="Citta" tabindex="220" class="citta" onchange="infasaSelect('citta','cap')">
<option value="" selected="selected">Citta</option>

</select></td>
<td><label for="cap">Cap:</label>
<select name="cap" id="cap" title="Cap" tabindex="240" class="cap">
<option value="" selected="selected">Cap</option>

</select></td>
<td class="punto"><img src="/cda187/s/img/ico_punto187.jpg" alt="PUNTO NEGOZIO" title="PUNTO NEGOZIO" /></td>
</tr>
<tr>
<td colspan="5"><input type="checkbox" name="assistenza" id="assistenza" value="assistenza" title="Visualizza solo i punti di assistenza" tabindex="260" />
Visualizza <strong>solo</strong> i punti di assistenza</td>

</tr>
</table>
<input type="image" src="/myapp/s/img/btn_cerca.jpg" title="Cerca" value="cerca" tabindex="280" class="btn" onclick="javascript:return validaSelectNegozi();"/>
</form>

....

Notate i link agli javascript, mentre negozi.js è un file presente nell'applicazione i precedenti sono forniti da dwr.

Restando su questi solo quelli generati dalle nostre classi devono avere il path /dwr/interface/.

Bene spero di avervi incuriosito. Approfondite la cosa sul sito ufficiale di DWR e vedrete quante idee vi verrano in mente per le vostre applicazioni web in java e ajax.


Nessun commento:

Posta un commento