Introduzione
Le regole per la navigazione tra le pagine che compongono una user interface realizzata in JSF possono essere definiti in diversi modi. Nella versione 1 del framework il solo punto di configurazione era il file faces-config.xml
, mentre con la versione 2 sono possibili diverse opzioni che descriveremo nel seguito.
Implicit Navigation
In JSF 2 è implementato un meccanismo automatico di risoluzione delle pagine chiamato implicit navigation (navigazione implicita). Esso consiste sostanzialmente nella possibilità di utilizzare i nomi delle view per definire le regole di navigazione, lasciando a JSF il compito di ricercare la view corretta all’interno dell’applicazione deployata. La navigazione implicita si applica nel momento in cui non esistono altre regole definite nel file di configurazione. Per utilizzare la navigazione implicita abbiamo due possibilità:
- inserire il nome della view direttamente nell’attributo
action
di un componente JSF; - restituire il nome della view attraverso l’outcome di una managed bean.
Vediamo i due casi nell’esempio seguente di index.xhtml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html"> <h:head> <title>JSF 2.0 Navigation</title> </h:head> <h:body bgcolor="white"> <h2>Implicit Navigation</h2> <h:form> <h3>Utilizzando la proprietà action</h3> <h:commandButton action="page2" value="Page2" /> </h:form> <h:form> <h3>Utilizzando l'output di un managed bean</h3> <h:commandButton action="#{navigationController.moveToPage2}" value="Page2" /> </h:form> </h:body> </html> |
Nel primo commandButton
l’action
è valorizzato con la stringa page2
. Quando il pulsante è premuto JSF risolverà la vista page2
con page2.xhtml
e la ricercherà nella directory corrente. Analogamente accadrà alla selezione del secondo pulsante. Il metodo moveToPag2
del bean navigationController
sarà eseguito e l’output interpretato come detto sopra.
1 2 3 4 5 6 7 |
@ManagedBean @RequestScoped public class NavigationController { public String moveToPage2(){ return "page2"; } } |
Forward vs Redirect
Nell’esempio presentato quando ci siamo mossi dalla pagina index.xhtml
verso la pagina page2.xhtml
l’url mostrato nel browser è rimasto index.xhtml
. Questo perché JSF 2 di default esegue un forward e non una redirect verso la nuova pagina, conseguentemente l’url del browser è sempre uno passo indietro rispetto anna navigazione. E’ importante comprendere la differenza tra questi due casi, in particolare per quanto riguarda il reload della pagina dal browser:
Forward:
- Il forward è eseguito internamente dalla servlet;
- Il browser non è cosciente del fatto che ci sia stato un forward e quindi l’url nella barra degli indirizzi rimane invariato;
- Eseguendo un reload della pagina dal browser esso risottometterà la request originale dall’url originale.
Redirect:
- Un redirect è un processo in due fasi, in cui l’applicazione web indica al browser di recuperare un secondo URL, che differisce dall’originale;
- Un reload eseguito dal browser non ripeterà la request originale ma semplicemente richiederà al server la seconda url;
- La redirect è leggermente più lenta rispetto al forward essendo un processo a due fasi;
- Gli oggetti collocati nello scope originale non saranno disponibili nella seconda request.
Se vogliamo utilizzare la redirect piuttosto che il forward è sufficiente appendere la stringa faces-redirect=true
agli outcome come nel seguente esempio:
1 |
<h:commandButton action="page2?faces-redirect=true" value="Page2" /> |
File di Configurazione
L’opzione di configurare la navigazione nel file faces-config.xml
è ovviamente ancora valida in JSF 2 ed è comunque utile per definire casi di navigazione di default. Iniziamo con analizzare un caso semplice ma utile di navigazione per la gestione del login utente. Inseriamo quindi il seguente codice html nella pagina index.xhtml
:
1 2 3 4 5 6 7 8 9 10 |
<h2>Logon form</h2> <h:form> <h:panelGrid columns="2"> <h:outputLabel value="Username"/> <h:inputText value="#{loginController.username}"/> <h:outputLabel value="Password"/> <h:inputText value="#{loginController.password}"/> <h:commandButton action="#{loginController.login}" value="Login" /> </h:panelGrid> </h:form> |
loginController
come segue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@ManagedBean @RequestScoped public class LoginController { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String login() { if ( this.username.equalsIgnoreCase( "admin" ) && this.password.endsWith( "s3cr3t" ) ) { return "success"; } return "failure"; } } |
1 2 |
Unable to find matching navigation case with from-view-id '/index.xhtml' for action '#{loginController.login}' with outcome 'failure' |
in cui ci avvisa che non esiste regola di navigazione che partendo dalla vista index.xhtml
ed applicando l’action loginController.login
con output failure
definisca la vista successiva. Per farlo inseriamo la navigation-rule
nel file faces-config.xml
come segue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?xml version="1.0" encoding="UTF-8"?> <faces-config version="2.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"> <navigation-rule> <from-view-id>index.xhtml</from-view-id> <navigation-case> <from-action>#{loginController.login}</from-action> <from-outcome>success</from-outcome> <to-view-id>login-success.xhtml</to-view-id> </navigation-case> <navigation-case> <from-action>#{loginController.login}</from-action> <from-outcome>failure</from-outcome> <to-view-id>login-failure.xhtml</to-view-id> </navigation-case> </navigation-rule> </faces-config> |
che fa esattamente quanto JSF ci ha chiesto nel messaggio ovvero a partire dalla vista (from-view-id
) index.xhtml
ed a seguito dell’applicazione dell’action (from-action
) loginController.login
definisce due percorsi di navigazione rispettivamente per l’outcome (from-outcome
) success
e failure
verso le pagine (to-view-id
) login-success.xhtml
o login-failure.xhtml
.
In generale un elemento navigation-rule
può contenere zero o più elementi navigation-case
. Il navigation-case
definisce una serie di criteri di corrispondenza. Quando questi criteri sono soddisfatti, l’applicazione passare alla pagina definita dall’elemento to-view-id
contenuto nello stesso elemento navigation-case
.
Navigazione Condizionale
JSF 2 fornisce un meccanismo flessibile di navigazione condizionale che permette di implementare regole di navigazione molto complesse. Consideriamo come esempio il caso in cui vogliamo gestire la possibilità che la password del nostro abbia una validità limitata nel tempo.
Per gestire questa situazione abbiamo due possibilità. La prima consiste nell’utilizzare l’outcome del metodo login ad esempio prevedendo il risultato expired
ed inserendo un navigation-case
nel faces-config.xhtml
che redireziona l’utente verso la pagina expired-password.xhtml
. Ma questo non aggiunge nulla a quanto visto sopra.
La seconda possibilità consiste nell’inserire una condizione nel navigation-case nel modo seguente:
1 2 3 4 5 6 7 8 9 10 11 12 |
<navigation-case> <from-action>#{loginController.login}</from-action> <from-outcome>success</from-outcome> <if>#{loginController.passwordAge lt 50}</if> <to-view-id>login-success.xhtml</to-view-id> </navigation-case> <navigation-case> <from-action>#{loginController.login}</from-action> <from-outcome>success</from-outcome> <if>#{loginController.passwordAge ge 50}</if> <to-view-id>expired-password.xhtml</to-view-id> </navigation-case> |
in cui le espressioni guidano il flusso di navigazione per password con età maggiore o uguale a 50 (ge
sta per >=) verso la relativa pagina di gestione.
Vediamo un altro interessante caso di navigazione condizionale utilizzando le manaded properties. Supponiamo di avere il seguente frammento di codice html nel file index.xhtml
:
1 2 3 4 5 6 7 8 9 10 |
<h2>Navigazione con manage properties</h2> <h:form> <h:commandLink action="#{navigationController.showPage}" value="Page1"> <f:param name="pageId" value="1" /> </h:commandLink> <br/> <h:commandLink action="#{navigationController.showPage}" value="Page2"> <f:param name="pageId" value="2" /> </h:commandLink> </h:form> |
Ai due commandLink sono è aggiunto il parametro pageId
alla request inviata al managed bean navigationController
così definito:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@ManagedBean @RequestScoped public class NavigationController { // this managed property will read value from request parameter pageId @ManagedProperty(value="#{param.pageId}") private String pageId; public void setPageId(String pageId) { this.pageId = pageId; } // condional navigation based on pageId public String showPage() { if (pageId == null) { return "index"; } if (pageId.equals("1")) { return "page1"; } else if (pageId.equals("2")) { return "page2"; } else { return "index"; } } } |
La proprietà pageId
è annotata come ManagedProperty
quindi JSF recupera il valore da iniettare dalla request.
Conclusioni
Le regole di navigazione definite negli esempi precedenti sono molto specifiche in quanto applicate a casi ben definiti. Nel seguito vediamo come sia possibile ottenere delle regole più generali attraverso pochi accorgimenti.
Innanzitutto se il tag from-view-id
è omesso, la regola definita nello specifico navigation-rule
è applicabile a tutte le pagine dell’applicazione. Il tag inoltre supporta l’utilizzo delle wildcard con le quali è possibile indicare un sottoinsieme di pagine per cui vale la regola. Nell’esempio seguente da qualunque pagina il logout comporta il ritorno alla pagina di login.
1 2 3 4 5 6 7 |
<navigation-rule> <from-view-id>/*</from-view-id> <navigation-case> <from-outcome>logout</from-outcome> <to-view-id>login.xhtml</to-view-id> </navigation-case> </navigation-rule> |
Anche gli elementi from-outcome
e from-action
sono opzionali, consentendo di specificare, ad esempio, una navigazione di default per gli errori.
Codice Sorgente
Il codice sorgente degli esempi in questo post è scaricabile in jsf-navigation.