You are on page 1of 10

Komponenty JSF z użyciem AJAXA

1 Koncepcja ogólna
Podstawowym założeniem przy pisaniu kontrolek JSF jest jak największe zminimalizowanie powielania kodu pisanego w
JSP z użyciem javascriptu i znaczników HTML realizującego identyczną (uwspólnioną funkcjonalność), tak by programista
używający takiej kontrolki musiał jedynie:

zaimplementować metody abstrakcyjne modelu związanego z daną kontrolką,


utworzyć minimalny zestaw zmiennych konfiguracyjnych dla komponentu modelu,
użyć kontrolki na stronie JSP poprzez taga, któremu zostanie przekazana referencja do beana modelu wspomnianego
wcześniej.

Zadanie to powinno zostać zrealizowane poprzez napisanie custom komponentów dekorujących istniejące kontrolki JSF w
taki sposób by pisać możliwie mało kodu po stronie komponentów (klas Javy) a maksymalnie wykorzystywać to co już
istnieje.

1.1 Generyczne tagi udostępniające funkcje biznesowe dla requestów ajaxowych


Do realizacji komponentów wykorzystujących technologię AJAX wybrano framework DWR. Dostarcza ona bardzo prosty
sposób dostępu do metod biznesowych udostępnianych przez POJO komponenty z poziomu języka javascript.
Udostępnianie metod danego komponentu realizuje się poprzez jego zadeklarowanie w pliku dwr.xml znajdującego się w
WEB-INF/ Domyślnie DWR udostępnia dla javascriptu wszystkie metody publiczne udostępnianego beana. Jeżeli chcemy je
ograniczyć używamy jednego z następujących tagów:

exclude
include
auth

Zwracane rezultaty mogą występować w dwóch postaciach:

1. Skonwertowanych obiektów javascriptowych, których pokazanie na stronie będzie oprogramowane przez


zdefiniowanie odpowiedniego handlera javascriptowego opakowującego dane z tych obiektów w odpowiednie
tagi XHTML'owe,
2. XHTML'owej treści zwracanej do bezpośredniego pokazania na stronie - szczególnie zalecane w przypadku
konieczności dołączenia treści, która ma zawierać kontent z kontrolkami JSFa.

W celu udostępnienia możliwości wykonywania żądań ajaxowych na opisanych powyżej komponentach niezbędne jest
dołączenie odpowiednich plików javascriptu generowanych dynamicznie przez framework dwr. W tym celu napisano tag
enableAJAX który należy umieszczać w takim miejscu w pliku JSP by renderowany przez niego output został zamieszczony
w <head> strony html. Przykładowe jego użycie:

<cst:enableAJAX id="enableAJAX" dwrBeanNames="WorkFlowAjaxService,JSFViewRetriever"/>

Jego parametry to identyfikator, który musi być unikalny w zakresie komponentów na stronie (w zasadzie można powielać
ten z przykładu) oraz identyfikatory javascript'owe udostępnianych komponentów (atrybuty javascript w tagu create w pliku
dwr.xml), separatorem jest przecinek.

W celu udostępnienia prostej generycznej formy odwoływania się do metod biznesowych poprzez ajaxa napisano dwa tagi:

onClickAJAX - który generuje <a href> z odpowiednio wypełnioną metoda onClick. Przykładowe użycie
zaprezentowano poniżej:

C:\doce\kontrolki_jsf\koncepcja_jsf_ajax.xml page 1 of 10
<cst:onClickAJAX id="onClickPrev" beanName="JSFViewRetriever" operation="refreshTable"
operationParamValues="#{tableBean.beanName},#{tableBean.currentPage + 1}"
replyHandler="reloadTable" render="#{not tableBean.lastPage}">
<h:outputText value="next" />
</cst:onClickAJAX>

gdzie:

1. beanName to javascriptowa nazwa beana,


2. operation to nazwa metody tego beana, która ma zostać wywołana,
3. operationParamValues to parametry wywołania poprzedzielane separatorem przecinka,
4. replyHandler to nazwa funkcji javascriptowej, która jest wywoływana przez engine ajaxowy w momencie
zwracania rezultatów wywołania metody biznesowej,
5. render to wartość logiczna wskazująca na to czy komponent powinien wyrenderować swoją zawartość.

onActionAJAXDecorator - który dekoruje swoje dzieci poprzez dodanie wywołania żądania ajaxowego do atrybutu
obsługi zdarzenia, które wskazujemy w odpowiednim parametrze (w przypadku onClickAJAX było to tylko onClick, tutaj
mamy możliwość określenia):

<cst:onActionAJAXDecorator id="onActionAJAXDecorato#{task.id}" beanName="WorkFlowAjaxService"


operation="findTaskById" operationParamValues="#{task.id}" replyHandler="displayResult"

<h:commandLink action="taskDetails" actionListener="#{taskListBean.selectTask}">


<h:outputText value="#{task.name}" />
</h:commandLink>
</cst:onActionAJAXDecorator>

gdzie:

1. beanName to javascriptowa nazwa beana,


2. operation to nazwa metody tego beana, która ma zostać wywołana,
3. operationParamValues to parametry wywołania poprzedzielane separatorem przecinka,
4. replyHandler to nazwa funkcji javascriptowej, która jest wywoływana przez engine ajaxowy w momencie
zwracania rezultatów wywołania metody biznesowej,
5. startEventAttributeName to nazwa atrybutu zdarzenia pod który będzie podczepione wywołanie żądania
ajaxowego np. onclick, onmouseover itp.
6. endEventAttributeName nazwa atrybutu zdarzenia, pod który ma być podczepiona opcjonalna akcja
kończąca obsługę zdarzenia,
7. endEventAttributeValue wartość atrybutu zdarzenia, pod który ma być podczepiona opcjonalna akcja
kończąca obsługę zdarzenia.

1.2 Obsługa javascriptowa rezultatów zwracanych z metod biznesowych


Jak już wspomniano w koncepcji zwracane rezultaty mogą być dwojakiego rodzaju:

skonwertowane obiekty javascript (do konwersji typów niestandardowych niezbędne jest dodanie odpowiedniej
konfiguracji w pliku dwr.xml):

<convert match="pl.ufg.sif.workflow.TaskVO" converter="bean"></convert>


<convert match="pl.ufg.sif.workflow.TransitionVO" converter="bean"></convert>

przykładowy handler obsługi prezentuje poniższy kod:

C:\doce\kontrolki_jsf\koncepcja_jsf_ajax.xml page 2 of 10
<script type="text/javascript">
<!--
var displayResult = function(data)
{

for (x in transitions) {

}
//-->
</script>

XHTML'owa treść, którą możemy bezpośrednio dołączyć do odpowiedniego obiektu DOM'a. Treść ta powinna być
zwracana w obiekcie klasy pl.ufg.sif.workflow.jsfcomponents.ajax.AjaxTableContent skonwertowanym
automatycznie (poprzez zadeklarowany konwerter DWRowy) do odpowiedniego obiektu javascriptowego.
Przykładową implementację handlera prezentuje poniższy kod:

var reloadTable = function(data)


{

Highlights

Uwaga,

jeżeli w zwracanym kodzie XHTML jest javascript to by on był przebudowany należy wywołać metodę runScript, którą
dołączono w pliku commons-ajax.js a której przekazujemy jako parametr id obiektu, do którego podpieliśmy
otrzymane body, tak by mogła ona sparsować zawartego w nim javascripta i go zevaluować, by był on "widziany"
przez engine javascript'owy.

Obiekt AjaxViewContent zawiera divId, który jest przewidziany jako identyfikator obiektu DOM'a, do którego chcemy
dołączyć body jako innerHTML.

2 Ramki dociągane AJAX'em oparte na JSF i Facelets


W przypadku, kiedy rezultat zwracany przez metodę biznesową wywoływaną przez zapytanie AJAXowe ma skomplikowaną
strukturę, która później ma być przetwarzana na formę XHTML'ową najlepszym rozwiązaniem jest zastosowanie wzorca
ramki (view JSF'owe), której zawartość jest pobierana poprzez specjalny komponent JSFViewRetriever . Komponent ten
na podstawie przekazanej mu nazwy view face'owego pobiera kontent na uprawnieniach żądania ajaxowego (rola, sesja).

C:\doce\kontrolki_jsf\koncepcja_jsf_ajax.xml page 3 of 10
Schemat pobierania view przez AJAXa

Zastosowanie tego wzorca najczęściej wymagać będzie zmiany modelu (zarządzanych beanów JSF bądź komponentów
springowych o zasięgu sesyjnym), na podstawie, którego renderowane jest owo view, którego kontent ma być zwrócony.
Najlepszym rozwiązaniem będzie stworzenie dla konkretnego celu beana zarządzanego o scop'ie sesyjnym, którego
właściwości będą podmieniane a następnie będzie pobierane view generowane na podstawie tak zmienionego modelu.
Bean ten powinien posiadać pewne abstrakcyjne metody, które muszą być zaimplementowane przez programistę
używającego danej kotrolki w konkretnym już celu np. kiedy mamy kontrolke tabeli dociąganej dynamicznie taką
abstrakcyjną metodą będzie metoda zwracająca model danych dziedzinowych, które mają być wyświetlone w formie tabeli.

Na powyższym diagramie kontrolka wygenerowana przez JSF'a (AjaxEnabled UI Control) przy określonym zdarzeniu
javascript'owym wysyła żądanie ajaxowe do komponentu JSFViewRetriever W żądaniu tym przekazywany jest
identyfikator modelu (beana zarządzanego sesyjnego), który powinien zostać zaktualizowany na podstawie parametrów:
updateModelParams. Komponent JSFViewRetriever aktualizuje model a następnie pobiera kontent view ze strony JSF i
zwraca go jako rezultat żądania ajaxowego opakowanego w obiekt AjaxViewContent.

3 Dynamiczne generowanie formularzy


Wszelkie formularze o możliwym dynamicznym charakterze (którego nie możemy określić poprzez użycie standardowych
komponentów jsf'owych użytych na stronie JSP - ich schemat nie jest ustalany na etapie procesowania/kompilacji stron jsp)
docelowo należy implementować poprzez custom tagi dekorujące tagi JSF'owe, które obudowują bezpośrednio zmieniający
sie kontent. Przykładowo jeżeli mamy tabelę, która ma mieć dynamiczną ilość oraz rodzaj kolumn (definiowaną poza stroną
jsp) dekorujemy komponent HTMLDataTable. W przypadku kiedy mielibyśmy formularz o nieustalonym na etapie kompilacji
schemacie dekorowalibyśmy komponent formularza JSF itp.

4 Wykorzystanie szablonowania FACELET'owego


W celu uproszczenia dewelopmentu bardzo użytecznym podejściem jest tworzenie kontrolek z użyciem tagów
Facelet'owych. Podstawowym parametrem przekazywanym do takiego taga powinien być bean zarządzany stanowiący

C:\doce\kontrolki_jsf\koncepcja_jsf_ajax.xml page 4 of 10
model dla danego view. Poniżej zaprezentowano przykładowe rozwiązanie taga faceletowego dla tabel sortowalnych
dociąganych ajaxem:

Tag faceletowy table.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"


"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<div xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:cst="http://abg.com.pl/jsf/ajax"
xmlns:t="http://myfaces.apache.org/tomahawk"
xmlns:c="http://java.sun.com/jstl/core"
xmlns="http://www.w3.org/1999/xhtml" id="tableForAjax">
<ui:composition>
<div id="#{tableBean.divId}">
<f:view>
<h:form id="table#{tableBean.beanName}">
<h:outputLabel for="TheDataTable" value="Zadania" />
<cst:ajaxAwareTable id="ajaxTable#{tableBean.beanName}" tableBean="#{tableBean}">
<t:dataTable id="TheDataTable" styleClass="tableBorder"
headerClass="head" rowClasses="isNormal,isDark"
columnClasses="standardTable_Column"
rowIndexVar="rowIndex" cellspacing="0" cellpadding="4" width="90%">

<c:if test="${not empty tableBean.customColumnPage}">


<ui:include src="#{tableBean.customColumnPage}"></ui:include>
</c:if>

</t:dataTable>
</cst:ajaxAwareTable>
<f:verbatim><br/></f:verbatim>

<div align="center" class="width=100%">


<h:outputText value="Strona #{tableBean.currentPage + 1} z #{tableBean.pageCount}" />
<f:verbatim><br/><br/></f:verbatim>
<cst:onClickAJAX id="onClickPrev" beanName="JSFViewRetriever" operation="refreshTable"
operationParamValues="#{tableBean.beanName},#{tableBean.currentPage - 1}"
replyHandler="reloadTable" render="#{not tableBean.firstPage}">
<h:outputText value="prev" />
</cst:onClickAJAX>

<cst:onClickAJAX id="onClickNext" beanName="JSFViewRetriever" operation="refreshTable"


operationParamValues="#{tableBean.beanName},#{tableBean.currentPage + 1}"
replyHandler="reloadTable" render="#{not tableBean.lastPage}">
<h:outputText value="next" />
</cst:onClickAJAX>

C:\doce\kontrolki_jsf\koncepcja_jsf_ajax.xml page 5 of 10
</div>
</h:form>
</f:view>
</div>
</ui:composition>
</div>

W powyższym kodzie przekazany obiekt modelu znajduje się pod zmienną: #{tableBean} Zastosowanie taga
faceletowego w znacznym stopniu upraszcza konstrukcję view, gdyż nie musimy wtedy wiekszości komponentów JSF i ich
ustawień wprowadzać do kodu Javy naszego custom komponentu JSF. W powyższym rozwiązaniu w celu generowania
dynamicznie kolumn JSF'owych wprowadzono tag cst:ajaxAwareTable. Tag ten nie generuje całego kontentu
tworzonego przez wyżej wylistowanego taga faceletowego a jedynie dekoruje t:dataTable przez dodanie do niego
kolumn, dzięki czemu oszczędzamy znacznie na jego rozmiarach i ilości parametrów, które są do niego przekazywane.
Gdyby zdecydować się na to, że nie mamy taga faceletowego musielibyśmy całe drzewo kontrolek JSF związane z
rozmieszczeniem przycisków next, prev i innymi elementami wyglądu tworzyć poprzez komponent JSF co wymagałoby wiele
kodu i elementów konfiguracyjnych.

Highlights

Reasumując im więcej wyglądu zrobimy poprzez tag faceletowy tym:

mniej będziemy musieli pisać w komponencie JSF,

mniejsza będzie liczba zmiennych konfiguracyjnych naszego custom komponentu, których będziemy musieli się
nauczyć.

Dynamiczne (kiedy nie możemy określić konkretnego kształtu na etapie kompilacji) elementy muszą być jednak tworzone w
custom kontrolkach JSF. Wtedy również najlepiej dekorować już istniejące kontrolki, tak by kod kontrolki tworzącej
dynamiczny fragment view był jak najprostszy i jak najwięcej konfiguracji było przeniesione na stronę JSP czy tag
faceletowy

5 Komponent tabeli dociaganej AJAXem


Komponent tabeli dociąganej AJAXem bazuje na modelu ramek dociąganych ajaxem opisanym dwa rozdziały wcześniej. W
celu realizacji tabeli dociąganej ajaxem trzeba:

1. Zaimplementować klasę beana stanowiącego model danych tabeli,


2. Zdefiniować i skonfigurować beana zarządzanego poprzez konfigurację JSF,
3. Zdefiniować conajmniej dwie strony JSP (ramkę i podstawową - chyba że podstawową już mamy).

5.1 Strony JSP


W celu stworzenia nowej strony z tabelką musimy stworzyć dwie strony JSP:

stronę główna, w której używamy następujacego fragmentu kodu:

<ui:composition template="_template/maintemplate.xhtml">
<ui:define name="javascriptExtension">
<script type="text/javascript"
src="#{facesContext.externalContext.request.contextPath}/_js/overlibmws.js">
</script>
<script type="text/javascript"

C:\doce\kontrolki_jsf\koncepcja_jsf_ajax.xml page 6 of 10
src="#{facesContext.externalContext.request.contextPath}/_js/prototype.js">
</script>
<script type="text/javascript"
src="#{facesContext.externalContext.request.contextPath}/_js/ajax-commons.js">
</script>
<cst:enableAJAX id="enableAJAX" dwrBeanNames="JSFTableViewRetriever"></cst:enableAJAX>
</ui:define>

<ui:define name="workArea">
<cst:ajaxDoLoadTable tableBean="#{casesTableBacking}" />

</ui:define>
</ui:composition>

Tag ajaxDoLoadTable powoduje dodanie na stronę javascriptu, który w onLoad doładowuje ramkę tabeli
zdefiniowanej w stronie ramki opisanej poniżej Niezbędnym parametrem jest tableBean który wskazuje na bean
modelu tabeli opisany w dalszej części.

stronę ramki, która definiuje już konkrentną zawartość tabeli:

<?xml version="1.0" encoding="UTF-8" ?>


<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:cst="http://abg.com.pl/jsf/ajax"
version="2.0">
<f:view>

<cst:ajaxEnabledTable tableBean="#{casesTableBacking}" />

</f:view>
</jsp:root>

Tag ajaxEnabledTable służy do wygenerowania właściwej treści tabeli na podstawie beana modelu. Referencje do
beana modelu przekazujemy w parametrze tableBean. Bean ten jest beanem zarządzanym modelu tabeli o scopie
sesyjnym. Jego implementacja musi dziedziczyć z klasy abstrakcyjnej AbstractAjaxTableManagedBean (czytaj
następny rozdział).

5.2 Komponent modelu


Komponent modelu tabeli ajaxowej zawiera w sobie zaimplementowaną metodę zwracającą dane oraz wszelkie niezbędne
elementy konfiguracyjne jak np. nazwy kolumn, domyślny porządek sortowania, które są konfigurowane w warstwie plików
konfiguracyjnych beanów zarządzanych JSF. Konfiguracja została rozbita na:

konfigurację instancji konkretnej tabeli. Klasa tego beana powinna dziedziczyć po klasie
AbstractAjaxTableManagedBean Dziedziczenie to wymagać będzie implementacji metody: DataModel
Przykładową implementację prezentuje klasa CasesTableManagedBean. Poniżej zaprezentowano
przykładową konfigurację beana modelu wraz z opisem poszczególnych parametrów:

C:\doce\kontrolki_jsf\koncepcja_jsf_ajax.xml page 7 of 10
<managed-bean>
<description>Bean testowy z lista spraw</description>
<managed-bean-name>casesTableBacking</managed-bean-name>
<managed-bean-class>
pl.ufg.sif.workflow.jsfcomponents.ajax.CasesTableManagedBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>config</property-name>
<value>#{ajaxAwareTableConfig}</value>
</managed-property>
<managed-property>
<property-name>viewName</property-name>
<value>/cases_ajax_frame.jsf</value>
</managed-property>
<managed-property>
<property-name>beanName</property-name>
<value>casesTableBacking</value>
</managed-property>
<managed-property>
<property-name>columnNamesBundle</property-name>
<value>casesColumnesNames</value>
</managed-property>
<managed-property>
<property-name>columnPropertiesBundle</property-name>
<value>casesColumnesProperties</value>
</managed-property>
<managed-property>
<property-name>columnCommandsBundle</property-name>
<value>casesColumnCommands</value>
</managed-property>
<managed-property>
<property-name>rowsPerPage</property-name>
<value>3</value>
</managed-property>
<managed-property>
<property-name>defaultSortColumn</property-name>
<value>name</value>
</managed-property>
<managed-property>
<property-name>customColumnPage</property-name>
<value>../table/cases_columns.xhtml</value>
</managed-property>
</managed-bean>

Znaczenie poszczególnych elementów konfiguracyjnych:

1. config - komponent z konfiguracją ogólną (obiekt klasy


pl.ufg.sif.workflow.jsfcomponents.ajax.table.AJAXAwareTableConfig), ,

C:\doce\kontrolki_jsf\koncepcja_jsf_ajax.xml page 8 of 10
2. viewName - nazwa strony ramki (tej z zawartością docelową tabeli),
3. beanName - nazwa beana modelu (bean potrzebuje wiedziec pod jaka nazwą go w JSF'ie osadzono),
możliwe jest zdefiniowanie na podstawie jednej implementacji kilku konfiguracji,
4. columnNamesBundle - nazwa klucza bundle pod którą umieszczono nazwy dla etykiet poszczególnych
kolumn
5. columnPropertiesBundle - nazwa klucza bundle pod którą umieszczono nazwy propertiesów które mają być
wyświetlane jako reprezentacja poszczególnych kolumn,
6. columnCommandsBundle - nazwa klucza bundle pod którą umieszczono action binding expressions dla
ewentualnych linków na poszczególnych kolumnach,
7. rowsPerPage - domyślna liczba wierszy na stronę,
8. defaultSortColumn - domyślna kolumna sortowania,
9. customColumnPage - (opcjonalne) jeżeli chcemy mieć jakies niestandardowe kolumny to ustawiając ten
parametr inkludujemy sobie stronę JSP z własną definicją kolumn np.:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"


"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<div xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:cst="http://abg.com.pl/jsf/ajax"
xmlns:t="http://myfaces.apache.org/tomahawk"
xmlns="http://www.w3.org/1999/xhtml" id="tableForAjax">
<ui:composition>

<t:column>
<h:commandLink action="#{casesListBean.chooseCase}">
<h:outputText value="#{item.name}"/>
</h:commandLink>
</t:column>
<t:column>
<h:commandLink action="#{casesListBean.chooseCase}" >
<h:outputText value="#{item.crDate}"/>
</h:commandLink>
</t:column>
</ui:composition>
</div

konfigurację ogólną rzadko zmieniającą się znajdującą się w beanie klasy


pl.ufg.sif.workflow.jsfcomponents.ajax.table.AJAXAwareTableConfig

<managed-bean>
<description>
Bean z ogolna (rzadko zmieniana konfiguracja do
AbstractAjaxTableManagedBean oraz komponentu
AJAXAwareTableComponent
</description>
<managed-bean-name>ajaxAwareTableConfig</managed-bean-name>
<managed-bean-class>

C:\doce\kontrolki_jsf\koncepcja_jsf_ajax.xml page 9 of 10
pl.ufg.sif.workflow.jsfcomponents.ajax.table.AJAXAwareTableConfig
</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
<managed-property>
<property-name>ajaxViewRetrieverBeanName</property-name>
<value>JSFTableViewRetriever</value>
</managed-property>
<managed-property>
<property-name>bundleBase</property-name>
<value>jsf.ajax.table.TableResources</value>
</managed-property>
<managed-property>
<property-name>itemVar</property-name>
<value>item</value>
</managed-property>
<managed-property>
<property-name>rowsPerPageList</property-name>
<list-entries>
<value-class>java.lang.Integer</value-class>
<value>3</value>
<value>5</value>
<value>7</value>
<value>10</value>
<value>25</value>
<value>35</value>
</list-entries>
</managed-property>

</managed-bean>

Znaczenie poszczególnych elementów:

1. ajaxViewRetrieverBeanName - javascriptowa nazwa beana dwrowego używanego do obsługi ramek


ajaxowych (praktycznie nie będzie się to zmieniać),
2. bundleBase - baza resourcow definicji dla komponentów tabel definiowanych przez tego beana
konfiguracyjnego,
3. itemVar - nazwa zmiennej wiersza w definicji kolumny,
4. rowsPerPageList - kolekcja liczby wierszy na stronę jako dane do pola typu select którym użytkownik określa
ile chce mieć wierszy na stronie.

C:\doce\kontrolki_jsf\koncepcja_jsf_ajax.xml page 10 of 10

You might also like