Sintassi Java Server Faces - ICEFaces

Definizione in pagine xhtml

che hanno in testa la seguente dichiarazione

<ui:composition
    template="/WEB-INF/includes/templates/main-template.jspx"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ice="http://www.icesoft.com/icefaces/component"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:c="http://java.sun.com/jstl/core"
      xmlns:geodrop="http://www.geodrop.com/jsf">

template permette di definire tutto quello che ci sta attorno al contenuto che invece e definito nella pagina in questione

Richiamo dei metodi

Per comunicare con i bean dalle pagine web vi sono diversi metodi

<ice:inputSecret id="oldPasswordInput" value="#{loginBean.oldPassword}" required="true" />

In questo modo si accede a un metodo accessorio getOldPassword() che restituirà la variabile _oldPassword con o senza _ non è importante. Che sia di tipo private invece si. Anche se la variabile non esiste funziona ugualmente l'importante è il nome del metodo dentro il bean e che sia di tipo pubblic. I metodi accessori sono stati fatti per restituire la variabile con lo stesso nome del metodo, ma si possono usare anche restituendo altre variabili o parti di variabili, nel caso di liste per esempio.
<ice:commandLink value="A" actionListener="#{contactsEditor.filterContactsByFirstLetterListener}" />

In questo modo si accede a un metodo con una firma di questo genere public void filterContactsByFirstLetterListener(ActionEvent event)
<ice:commandButton value="invia" action="#{smsEditor.backToHomePage}" immediate="true"/>

Che si riferisce a un metodo normale senza particolari regole, che come in questo caso può restituire una regola di navigazione definita su faces-config.xml.

public String backToHomePage() {
    return Pages.HOME_PAGE.name();
    }

Le differenze fondamentali tra metodi richiamati con action e metodi richiamati con actionListener sono queste:
  • Il metodo richiamato con action non prende parametri e deve restituire una stringa, che corrisponde ad una navigation rule.
  • Invece in caso di actionListener, il metodo prende un ActionEvent e restituisce void

L'action listener esegue qualcosa e resta nella stessa pagina mentre invece l'action normalmente fa qualcosa e poi dice di andare ad un'altra pagina

Vediamo un caso più complesso una form di input

<ice:inputText id="editFilterContactsBy" value="#{contactsEditor.filterContactsBy}" valueChangeListener="#{contactsEditor.filterContactsListener}" onkeyup="iceSubmitPartial(form, this, event);" />

Chiamare due bean con il click di un solo bottone

Questa è una cosa troppo bella e certe volte utile, per esempio se uso un bean per salvare le informazioni e un altro per visualizzarle. Dopo il salvataggio dovrò ricaricare le informazioni quindi questo codice può essere utile.

<ice:commandButton id="submitButton" type="submit" value="#{user_msgs['submit']}"  actionListener="#{backingBean.addBillingProfile}" action="#{guestBean[onCreateReloadRule]}"  />

Attenzione a non farsi ingannare dal modo di chiamare il bean è la stessa cosa sto usando solo la sintassi da facelets.

IF

userProfileBean è il pseudonimo del bean definito in faces-config.xml
editPersonalInformation è il metodo che restituisce un boolean
! è l'operatore diverso per cambiare il valore di ritorno da true a false

<c:if test="#{!userProfileBean.editPersonalInformation}">
        <!-- contenuto -->
</c:if>

Un altro modo di fare l'IF che ho usato dentro una data table è il seguente

<ice:outputText value="#{contactRecord.name} (#{contactRecord.group ? contactRecord.size : '' })" styleClass="#{contactRecord.optedOut ? 'opted-out' : ''}" />

qui ci sono due if per un solo output text.
Sempre dentro la data table ho usato un altra maniera per mettere in and che con && non fuzionava
visible="#{!contactRecord.group and contactRecord.stored}"

Un altro if che ho incontrato è quello che verifica se una lista è vuota

<c:if test="#{not empty contactsImportBean.validContacts}">

Vediamo un caso con l'operatore boolean OR

<c:if test="#{pageTemplateBean.prepaidAccount || pageTemplateBean.postpaidAccount}">
<c:if test="${allowEdit == null}">
    <c:set var="allowEdit" value="true" />
</c:if>
<c:if test="#{showErrors == false}">
<c:if test="#{!empty backingBean and !empty configureListener}">

Una funzione bellissima che conta il numero di caratteri. Se è maggiore di 50 restituisce la sottostringa dei primi 50 caratteri altrimenti da il messaggio intero. join fa l'unione di due stringhe passategli, infatti la risultante è una stringa dei primi 50 caratteri con postfisso..

<ice:outputText value='#{(fn:length(message.message) > 50) ? fn:join(fn:substring(message.message, 0, 50), "...") : message.message}' />

required attribute

<ice:outputLabel id="oldPasswordLabel" value="Vecchia Password"></ice:outputLabel>
<ice:inputSecret id="oldPasswordInput" value="#{loginBean.oldPassword}" required="true" />                    
<ice:message for="oldPasswordInput" id="oldPasswordMsg" errorClass="error" />

Con questo codice si riesce a obbligare che il campo inputSecret (vengono visualizzati i pallini e non le lettere che si digitano) sia obbligatorio e se l'utente lo lascia vuoto viene visualizzato un messaggio d'errore

partialSubmit e immediate attribute

Usando l'attributo partialSubmit="true" viene fatto un invio parziale dei dati di un componente dopo che questo ha perso il focus, prima che venga fatta una submit. In questo modo se il dato non è corretto verrà visualizzato un messaggio d'errore

L'attributo immediate="true" può essere usato per raggiungere i seguenti effetti:

  • permettere a un commandLink o a un commandButton di portare l'utente in un altra pagina senza processare alcun dato di un campo di input della schermata corrente. Permette la navigazione anche quando ci sono errori di validazione per esempio un Cancel button.
  • permettere a un commandLink o a un commandButton di attivare la logica di backend ignorando la validazione per alcuni campi della schermata corrente.
  • fare di uno a più input components "alta priorità" per la validazione, così se qualcuno di questi è invalido la validazione non viene eseguita per ogni componente a "bassa priorità" della stessa pagina. Questo può ridurre il numero di messaggi di errore mostrati.

Usare l'attributo immediate per un componente significa che il suo valore verrà validato durante la richiesta di apply nella fase del ciclo di vita del JSF.

ice:commandLink

Ecco un esempio di come usare un commandLink. Quello che viene visualizzato a browser è un link con la lettera O. L'attributo actionListener indica tramite faces-config.xml a quale bean ci si deve riferire. Dato che è un actionListener e non un action la firma del metodo deve essere void e con il parametro ActionEvent da cui si recupera il valore del link come si vede sotto. Il codice del metodo è facilmente comprensibile. Questo tag deve essere contenuto dentro un form.

<ice:commandLink value="O" actionListener="#{contactsEditor.filterContactsByFirstLetterListener}" />
public void filterContactsByFirstLetterListener(ActionEvent event)
    {
        HtmlCommandLink link;
        String value;
 
        link = (HtmlCommandLink) event.getComponent();
        value = (String) link.getValue();
 
        if (value.length() > 1)
            _showContactsStartingBy = null;
        else
            _showContactsStartingBy = value;
 
        updateVisibleContacts();
    }

<f:attribute …> Passaggio di parametri

Ecco un esempio di passaggio di parametri a un bean, in questo caso usiamo un commandButton

<ice:commandButton id="btnActivate" value="#{droplet_messages['droplet.configuration.activate']}" actionListener="#{backingBean.updateDropletConfiguration}">
    <f:attribute name="dropletConfigurationId" value="#{dropletConfiguration.dropletConfigurationId}"/>
</ice:commandButton>
public void updateDropletConfiguration(ActionEvent e) throws InvalidUserException
    {
        UserSession userSession;
        Integer dropletConfigurationId;
 
        userSession = new UserSession(getSession());
 
        dropletConfigurationId = (Integer) e.getComponent().getAttributes().get("dropletConfigurationId");

definizione variabili

se non sbaglio con il $ si accede a variabili definite nella pagina stessa con il # invece quelle definite nel bean

<c:if test="${allowEdit == null}">
        <c:set var="allowEdit" value="true" />
</c:if>

tooltip

<ice:panelGroup panelTooltip="staticTooltip" >
    <ice:outputText value="Hovering on this text will bring a panelTooltip" />
</ice:panelGroup>
 
<ice:panelTooltip id="staticTooltip"
    style="width: 300px; height: 200px; background: #FFFFFF;">
 
    <f:facet name="body">
            <h:outputText value="This panelTooltip is static. 
            It will be closed when the user moves the mouse out of the triggering component" />
    </f:facet>
 
</ice:panelTooltip>

Questa è la versione base del tooltip. L'elemento su cui deve essere attivato al mouse over l'evento di visualizzazione del del pannello sta all'interno del componente panelGroup. Questo si riferisce all'elmento sottostante tramite la proprietà panelTooltip.
Sotto vediamo la formattazione del tooltip e quello che viene visualizzato al mouse over contenuto dentro la facet.
Se uno dei due è annindato a un livello differente l'altro non lo vede.

File .properties

In un progetto può essere utile dover cambiare la lingua in base all'utente. Per far questo quando si deve visualizzare un testo invece di scriverlo a manina si utilizza un riferimento a un file .properties , quando si cambia questo file con un altro la lingua di tutta l'applicazione cambia.
I file .properties sono dei file di testo che funzionano con la logica della chiave = valore . I commenti si fanno con il #

# 1.1 Interface Components
page.sms.editor.title=SMS Editor
page.sms.editor.message.label=Message
recipients=Recipients

Il pezzo di testo sopra corrisponde al file sms-messages-en.properties , viene indicato anche il package in cui il file si trova. Poi vi è la variabile con la quale ci si deve riferire.
<f:loadBundle basename="com.geodrop.frontend.resources.localization.sms-messages-en" var="sms_msgs" />

Per utilizzare il file all'interno del codice si può fare qualcosa di simile
<h:outputText value="#{sms_msgs['recipients']}"/>

Definizione di una facelets

Per definire una facelets da usare all'interno del nostro componente. Dobbiamo fare in modo da avere nella pagina che la vuole usare la definizione

 xmlns:geodrop="http://www.geodrop.com/jsf"

Inoltre dentro il taglib.xml
<tag>
    <tag-name>languageSelector</tag-name>
    <source>includes/components/language-selector.xhtml</source>
</tag>

Dove il primo parametro indica come verrà chiamato il facelets dentro la pagina .jspx il secondo indica la localizzazione dello stesso.
Infine nel punto dove si vuole mettere il facelets va incluso il codice
<geodrop:contactsaddrBookEditor />

E' possibile al richiamo del facelets passare anche dei parametri in questo modo si può generalizzarlo e usare lo stesso facelets in posti diversi
<geodrop:languageSelector 
    backingBean="#{pageTemplateBean}" 
    languagesField="languages" 
    onChangeEvent="languageChanged"
    selectedCode="code" 
    selectedLanguageField="language" />

Poi all'interno del componente si utilizza in che questo modo
<c:forEach items="#{backingBean[languagesField]}" var="l">
    <span class="flag #{l.languageCode}"></span>
    <ice:outputLink  href="#{backingBean[onChangeEvent]}"> #{l.name} </ice:outputLink>
</c:forEach>

Un altro modo di passare un parametro dentro una facelets è il seguente:

<geodrop:contactsPaginateList bean="#{contactsGroupEditor}" rowsNumber="7" selectContactListener="selectContactListener" id="sx"/>

Poi all'interno del facelets si possono fare cose tipo questa:
<c:if test="${empty rowsNumber}">
    <c:set var="rowsNumber" value="15" />
</c:if>
<ice:dataTable id="contImportTable${id}" value="${contacts}" rows="${rowsNumber}" var="contact">

Ecco un altro esempio di facelets
Definito così

<geodrop:data3TablesForContactsImporter
    bean="#{contactsEditor}"
    exist="#{contactsEditor.alreadyExistContacts}"
    invalid="#{contactsEditor.invalidContacts}"
    sizeInvalid="sizeInvalidContacts"
    sizeExist="sizeAlreadyExistContacts"

Usato così
<c:if test="#{not empty exist}">
<ice:outputText value="#{sms_msgs['component']}  (#{bean[sizeExist]})" styleClass="notify" />

Il codice java dentro contactsEditor è questo
public List<ImportedContact> getInvalidContacts() { return _invalidContacts; }
public List<ImportedContact> getAlreadyExistContacts() { return _alreadyExistContacts; }
public int getSizeInvalidContacts(){ return _invalidContacts.size();}
public int getSizeAlreadyExistContacts(){ return _alreadyExistContacts.size();}

Paginazione di una data table

Il codice per paginare una data table è il seguente

<!-- Paginate the table -->
<ice:panelGrid columns="2" styleClass="contentPaginator">
<!-- Paginator Address Data Table -->
    <ice:dataPaginator id="dataScroll_3" for="messageRecipients" paginator="true"
                fastStep="3" paginatorMaxPages="3">
        <f:facet name="first">
            <span title="First Page" class="paginatorArrow first"></span>
        </f:facet>
        <f:facet name="last">
            <span title="Last Page" class="paginatorArrow last"></span>
        </f:facet>
        <f:facet name="previous">
            <span title="Previous Page" class="paginatorArrow previous"></span>
        </f:facet>
        <f:facet name="next">
            <span title="Next Page" class="paginatorArrow next"></span>
        </f:facet>
        <!--<f:facet name="fastforward">
            <ice:graphicImage url="http://localhost:8080/geodrop/style/images/arrow-ff.gif"  style="border:none;" title="Fast Forward"/>
        </f:facet>
        <f:facet name="fastrewind">
            <ice:graphicImage url="http://localhost:8080/geodrop/style/images/arrow-fr.gif" style="border:none;" title="Fast Backwards"/>
        </f:facet>-->
    </ice:dataPaginator>
 
    <!-- Data paginator -->
    <ice:dataPaginator id="dataScroll_2" for="messageRecipients" rowsCountVar="rowsCount" displayedRowsCountVar="displayedRowsCount" firstRowIndexVar="firstRowIndex"                    lastRowIndexVar="lastRowIndex" pageCountVar="pageCount" pageIndexVar="pageIndex">
       <ice:outputFormat value="{0} contacts found. Page {4} / {5}." styleClass="normal">
        <f:param value="#{rowsCount}"/>
        <f:param value="#{displayedRowsCount}"/>
        <f:param value="#{firstRowIndex}"/>
        <f:param value="#{lastRowIndex}"/>
        <f:param value="#{pageIndex}"/>
        <f:param value="#{pageCount}"/>
    </ice:outputFormat>
</ice:dataPaginator> 
</ice:panelGrid>

Vanno cambiate 2 cose:

  1. i due campi for dei dataPaginator devono avere il valore id della data table che si deve pagianare
  2. nella data table che si deve paginare bisogna aggiungere il numero di righe con il parametro rows="15" per sempio
Salvo diversa indicazione, il contenuto di questa pagina è sotto licenza Creative Commons Attribution-ShareAlike 3.0 License