JBoss Seam dostarcza adnotację @DataModel, która oznacza kolekcję danych (listę, mapę, zbiór, tablicę) jako typ odpowiadający javax.faces.model.DataModel. Dodatkową adnotacją @DataModelSelection określamy pole egzemplarza jako przechowujące wybór użytkownika w h:dataTable i ponownie temat obsłużony. Niewiele uproszczeń dostarczają obie adnotacje, ale w dobie adnotacji programowanie bez nich stało się jakieś takie niemodne.
Przykład opisany w dokumentacji JBoss Seam 2.0.3.CR1 dotyczący tematu @DataModel to 1.3. Clickable lists in Seam: the messages example i większość z mojego przykładu była na nim wzorowana z akompaniamentem książki Beginning JBoss® Seam: From Novice to Professional wydawnictwa Apress.
Zacznijmy od prezentacji komponentu kategoriaAgent reprezentowanego klasą pl.jaceklaskowski.seam.KategoriaAgent:
package pl.jaceklaskowski.seam;Na uwagę zasługuje miejsce "przyłożenia" adnotacji @DataModel oraz @DataModelSelection. W zasadzie nie ma co opisywać, po co i dlaczego, mając na uwadze ich działanie (warto zapoznać się z ich dokumentacją javadoc dla pełnego obrazu).
import java.util.ArrayList;
import java.util.List;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Factory;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.datamodel.DataModel;
import org.jboss.seam.annotations.datamodel.DataModelSelection;
import org.jboss.seam.log.Log;
@Name("kategoriaAgent")
@Scope(ScopeType.SESSION)
public class KategoriaAgent {
@In
private Kategoria kategoria;
@DataModel
private List<Kategoria> kategorie = new ArrayList<Kategoria>();
@DataModelSelection
@Out(required = false)
private Kategoria kategoriaWybrana;
@Logger
private Log log;
public void dodaj() {
log.info("Zapis kategorii \"#{kategoria.nazwa}\"");
kategorie.add(kategoria);
}
public void wybierz() {
log.info("Wybrano kategorię \"#{kategoriaWybrana.nazwa}\"");
}
public void skasuj() {
log.info("Kasowanie kategorii \"#{kategoriaWybrana.nazwa}\"");
kategorie.remove(kategoriaWybrana);
kategoriaWybrana = null;
}
@Factory("kategorie")
public void pobierzKategorie() {
log.info("Wykonanie metody pobierzKategorie()");
Kategoria rodzic = new Kategoria("Rodzic", "Kategoria nadrzędna", null);
Kategoria potomek = new Kategoria("Potomek", "Kategoria podrzędna", rodzic);
kategorie.add(rodzic);
kategorie.add(potomek);
}
}
Jako adnotację wspierającą należy wskazać @Factory, która wskazuje metodę będącą fabryką danych dla struktury o nazwie wskazanej jako jej wartość (w moim przykładzie będzie to pole egzemplarza o nazwie kategorie).
Najważniejszą zmianą, która powoduje, że pobranie danych nie następuje przy każdym żądaniu jest @Scope(ScopeType.SESSION). Dzięki tej adnotacji określamy, że zakres dostępności komponentu kategoriaAgent to czas sesji, więc wykonanie metody pobierzKategorie() będzie następowało podczas pierwszego pobrania danych do tabeli w sesji i tak długo, jak sesja będzie aktywna tak długo nie nastąpi kolejne wywołanie tej metody.
Dodając do tego użycie adnotacji @Out przy polu kategoriaWybrana określa udostępnienie wyboru klienta "światu zewnętrznemu", tj. nastąpi utworzenie zmiennej kategoriaWybrana o zasięgu EVENT (równoznaczne z zasięgiem żądania).
Pozostaje zaprezentować stronę XHTML, w której skorzystam z utworzonych struktur danych - kategoria.xhtml:
<?xml version="1.0" encoding="utf-8"?>Wdrożenie aplikacji na serwer aplikacyjny Apache Geronimo i uruchomienie strony http://localhost:8080/seam-richfaces-tree/seam/kategoria.xhtml:
<!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:s="http://jboss.com/products/seam/taglib"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
<head>
<title>Administracja kategoriami</title>
</head>
<body>
<f:view>
<h:form>
<s:validateAll>
<h:panelGrid columns="2">
Nazwa: <h:inputText value="#{kategoria.nazwa}" required="true" />
Opis: <h:inputText value="#{kategoria.opis}" required="true" />
</h:panelGrid>
</s:validateAll>
<h:messages />
<h:commandButton value="Dodaj" action="#{kategoriaAgent.dodaj}" />
<br />
<h:outputText value="Brak kategorii" rendered="#{kategorie.rowCount==0}" />
<h:dataTable var="ktgria" value="#{kategorie}" rendered="#{kategorie.rowCount>0}">
<h:column>
<f:facet name="header">
<h:outputText value="Nazwa" />
</f:facet>
<h:commandLink value="#{ktgria.nazwa}" action="#{kategoriaAgent.wybierz}" />
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Opis" />
</f:facet>
<h:outputText value="#{ktgria.opis}" />
</h:column>
<h:column>
<h:commandButton value="Delete" action="#{kategoriaAgent.skasuj}" />
</h:column>
</h:dataTable>
<h3><h:outputText value="#{kategoriaWybrana.nazwa}" /></h3>
<div><h:outputText value="#{kategoriaWybrana.opis}" /></div>
</h:form>
</f:view>
</body>
</html>
a na konsoli Geronimo:
23:43:30,187 INFO [KategoriaAgent] Wykonanie metody findMessages()Zatwierdzenie formularza z danymi nowej kategorii i kolejne komunikaty aplikacji na konsoli Geronimo:
23:45:34,546 INFO [Version] Hibernate Validator 3.0.0.GANazwa jest aktywnym odnośnikiem do akcji na serwerze i wybranie dowolnej skutkuje kolejnymi komunikatami:
23:45:34,671 INFO [KategoriaAgent] Zapis kategorii "Nowa kategoria"
23:46:52,781 INFO [KategoriaAgent] Wybrano kategorię ""Właśnie! Dlaczego zawsze w komunikacie mam poprzednią wartość #{kategoriaWybrana.nazwa}? Przez "poprzednią" dla pierwszego wywołania będzie to wartość pusta, a dla kolejnych...hmmm...właśnie poprzednia.
Pytanie konkursowe: Jakie 2 adnotacje upraszczają pracę z h:dataTable w komponentach seamowych?
>Dlaczego zawsze w komunikacie mam poprzednią wartość #{kategoriaWybrana.nazwa}?
OdpowiedzUsuńHmm, w polu kategoriaWybrana powinna byc wartosc poprawna, ale zostanie ona wyeksportowana do konktekstu dopiero po wywolaniu wybierz(), dlatego EL wyciaga wczesniejsza wartosc. Strzelam bo wlasnie zdemontowalem swoja piaskownice i przenosze sie do nowej, wiec sprawdzic nie moge :)
No i jeszcze zdaje mi sie, ze @Out ma domyslnie scope taki sam jak komponent, wiec tu bedzie to Session, a nie Event.
Pozdrawiam,
Krzysiek