Tabele w JavaServer Faces - znacznik <h:dataTable>

Z Jacek Laskowski - Wiki Projektanta Java EE

Spis treści

Wprowadzenie

Jednym ze sposobów wyświetlania danych w aplikacjach internetowych jest wyświetlanie ich w postaci tabel. Korzysta się wtedy ze znacznika HTML table. W zasadzie każdy znacznik HTML ma swój odpowiednik w znacznikach JSF (poprzez mechanizm znaczników JSP) i nie inaczej jest z table. Odpowiadającym znacznikowi table w JSF jest znacznik - h:dataTable (dokładniej znacznik dataTable, który tradycyjnie przypisuje się do przestrzeni nazw h i od tej pory będzie tak nazywany).

Korzystając z h:dataTable otrzymujemy mechanizm układania danych w postaci wierszy i kolumn. Rozkład kolumnowy uzyskujemy za pomocą znaczników h:column, natomiast nagłówki i stopki całej tabeli lub pojedyńczej kolumny uzyskuje się dzięki znacznikowi f:facet. Wiersze są naturalnym wynikiem działania iteracji znacznika h:dataTable.

Podczas uruchomienia h:dataTable, odpowiadający mu komponent JSF odpytuje źródło danych (wskazanie atrybutu value) o dostępne dane i iteruje po nich każdorazowo przypisując aktualna wartość do zmiennej wskazanej przez atrybut var. Pojedyńcze wiersze (bądź rekordy) są w ten sposób dostępne poprzez var i rozmieszczenie danych w tabeli kontrolowane jest poprzez zagnieżdżone znaczniki pomocnicze - h:column i f:facet.

Zastosowanie znacznika h:dataTable jest możliwe po zdefiniowaniu aliasu dla przestrzeni nazw h w nagłówku strony, który wskazuje na http://java.sun.com/jsf/html (jest to wymagane przez JSP, kiedy wprowadzana jest nowa biblioteka znaczników i nie jest faktycznym adresem miejsca z definicją biblioteki). Po zdefiniowaniu aliasu możemy skorzystać z jego znaczników i jednym z nich jest właśnie dataTable.

Znacznik h:dataTable odpowiada kontrolce javax.faces.component.html.HtmlDataTable, która (przy pomocy renderera[1]) prezentuje dane w postaci tabeli.

Dokumentacja znacznika h:dataTable (w języku angielskim) znajduje się pod adresem http://java.sun.com/javaee/javaserverfaces/1.2/docs/tlddocs/h/dataTable.html.

Przykład 1 - Wprowadzenie do h:dataTable

Rozpocznijmy poznawanie h:dataTable od wprowadzającego przykładu, który pozwoli zobrazować to, co za chwilę będzie opisane. Nie jest to wyrafinowany przykład, ale wystarczy, aby poznać elementy i użycie znacznika h:dataTable.

Strona index.jsp z dataTable

Najpierw prezentacja strony JSP - index.jsp.

<%@ page contentType="text/html; charset=ISO-8859-2"%>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>

<f:view>
  <h:dataTable var="akronim" value="#{encyklopedia.akronimy}" border="1" width="100%">
    <f:facet name="header">
      <h:outputText value="Encyklopedia skrótów" />
    </f:facet>
    <h:column>
      <f:facet name="header">
        <h:outputText value="Skrót" />
      </f:facet>
      <h:outputText value="#{akronim.skrot}" />
    </h:column>
    <h:column>
      <f:facet name="header">
        <h:outputText value="Pełna nazwa" />
      </f:facet>
      <h:outputText value="#{akronim.pelnaNazwa}" />
    </h:column>
    <f:facet name="footer">
      <h:outputText value="Pozycji: #{encyklopedia.size}" />
    </f:facet>
  </h:dataTable>
</f:view>

W zasadzie śmiało można stwierdzić, że (poza pewnymi wyjątkami) zawartość strony powinna być zrozumiała dla każdego twórcy przynajmniej jednej strony aplikacji internetowej, czy to w HTML, czy w innym języku znaczników. Wyjaśnienie poszczególnych elementów pojawi się dalej w tekście, ale zanim to nastąpi bazuję na intuicji (zweryfikujemy ją już niedługo).

Najważniejsze, aby zauważyć sekcję związaną z tabelą w JSF, która zawiera się pomiędzy znacznikami h:dataTable. Dwa obowiązkowe atrybuty to wspomniane var i value. Pozostałe - border i width - są opcjonalne i mało interesujące, aby poświęcić im więcej czasu niż to co już zrobiliśmy. Istnieje więcej opcjonalnych atrybutów i ich poznanie pozostawiam jako pracę samodzielną, w przysłowiowej wolnej chwili.

Komponent zarządzany - encyklopedia i klasa pomocnicza

Szczęśliwie twórcy JSF skorzystali z ostatnich dokonań nowoczesnych metodyk tworzenia oprogramowania. Jednymi z ciekawszych są wykorzystanie mechanizmów działania kontenera IoC[1] oraz, jako implikację pierwszego, oparcie szkieletu na POJO[1]. Ich obecność zauważalna jest podczas tworzenia komponentów zarządzanych i ich konfiguracji w faces-config.xml. Nie ma konieczności odszukiwania zależności, które dostarczy za nas kontener JSF oraz równie istotne, nie ma konieczności włączania naszych klas aplikacji do hierarchii klas JSF.

Przyjrzyjmy się temu w kolejnej części przykładu.

package beans;

import java.util.ArrayList;
import java.util.List;

public class Encyklopedia {
  private List<Akronim> akronimy = new ArrayList<Akronim>();
  {
    akronimy.add(new Akronim("JSF", "JavaServer Faces"));
    akronimy.add(new Akronim("JSP", "JavaServer Pages"));
    akronimy.add(new Akronim("ASP", "ActiveServer Pages"));
    akronimy.add(new Akronim("ASF", "Apache Software Foundation"));
  }

  public List<Akronim> getAkronimy() {
    return this.akronimy;
  }
  
  public int getSize() {
    return this.akronimy.size();
  }
}
package beans;

public class Akronim {
  private String skrot;

  private String pelnaNazwa;

  public Akronim(String skrot, String pelnaNazwa) {
    this.skrot = skrot;
    this.pelnaNazwa = pelnaNazwa;
  }

  public String getPelnaNazwa() {
    return pelnaNazwa;
  }

  public void setPelnaNazwa(String pelnaNazwa) {
    this.pelnaNazwa = pelnaNazwa;
  }

  public String getSkrot() {
    return skrot;
  }

  public void setSkrot(String skrot) {
    this.skrot = skrot;
  }

  public String toString() {
    return this.skrot + "=" + this.pelnaNazwa;
  }
}

Klasy - beans.Encyklopedia oraz beans.Akronim - są zwykłymi klasami bez wpływów zastosowanego rozwiązania, w tym przypadku JSF. Jest to o tyle przydatne, że kolejne użycia klas nie wymagają JSF i mogą swobodnie żyć w innym środowisku, np. EJB 3 (ktoś mógłby zapytać, a dlaczego podałem tę technologię jako alternatywną, ale zapewniam, że jest to jedynie wskazanie na technologię komplementarną[1]). Poza zależnością klasy Encyklopedia od Akronim nie ma innej zależności z otaczającym światem (to właśnie zaleta stosowania POJO).

Konfiguracja JSF - plik faces-config.xml

Powiązanie klas Encyklopedia i Akronim ze światem JSF następuje wyłącznie deklaratywnie poprzez plik konfiguracyjny JSF, tj. faces-config.xml. W naszym przykładzie, plik definiuje (konstruuje) komponent zarządzany o nazwie encyklopedia o zasięgu pojedyńczego zlecenia klienta (ang. request), którego typ to beans.Encyklopedia.

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

<!DOCTYPE faces-config PUBLIC
  "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">

<faces-config>
 <managed-bean>
  <managed-bean-name>encyklopedia</managed-bean-name>
  <managed-bean-class>beans.Encyklopedia</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
 </managed-bean>
</faces-config>

Atrybut var

Atrybut var służy do nadania nazwy obiektowi, który reprezentuje dane w pojedyńczej iteracji po danych ze źródła, tj. dane w pojedyńczym wierszu. Nie ma potrzeby definiować typu obiektu, ponieważ wynika to z typu w klasie, która stanowi element kolekcji (w tym przypadku będzie to typ beans.Akronim). Nazwa związana jest jednoznacznie z typem obiektu, a więc jednocześnie wyznacza dozwolone metody. Należy potraktować nazwę wskazaną przez var jako zmienną lokalną, która jest widoczna w dowolnym miejscu w h:dataTable. Podobnie jak w przypadku zmiennych lokalnych w Javie, jej nazwa może pojawić się w kolejnych h:dataTable i będzie to za każdym razem nowa reprezentacja danych, włącznie z możliwością zmiany typu.

Atrybut value

Atrybut value jest wskazaniem na źródło danych wierszowych, które będą wskazywane przez atrubut var (o dozwolonych źródłach danych będzie napisane w dalszej części). Atrybut value akceptuje wartości następujących typów (wraz z ich podtypami):

  • java.lang.Object[]
  • java.util.List
  • javax.servlet.jsp.jstl.sql.Result
  • java.sql.ResultSet
  • pojedyńcza instancja dowolnego typu
  • javax.faces.model.DataModel

W powyższym przykładzie, wiersz tabeli jest reprezentowany przez obiekt o nazwie akronim, który pochodzi z wywołania metody getAkronimy() na komponencie zarządzanym o nazwie encyklopedia, tj. beans.Encyklopedia. W tym przykładzie źródłem danych jest lista typu java.util.List, którą otrzymamy z wywołania metody beans.Encyklopedia.getAkronimy().

Znacznik <h:column>

Do tej pory nasze spojrzenie na h:dataTable dotyczyło wierszy tabeli. Delikatnie pomijaliśmy temat kolumn, chociaż nawet bardzo podstawowe użycie znacznika h:dataTable wymaga od nas skorzystania z h:column.

Znacznik h:column określa informację, jaka będzie wyświetlana w pojedyńczej kolumnie. Naturalnie, ilość kolumn implikowana jest przez ilość znaczników h:column. Kiedy połączymy to z naszą dotychczasową wiedzą nt. h:dataTable zauważymy, że potrafimy już zdefiniować wszystkie aspekty tabeli - wiersze implikowane są automatycznie poprzez źródło danych wraz z informacjami wyświetlanymi w poszczególnych kolumnach.

Podobnie jak h:dataTable, znacznik h:column należy do przestrzeni nazw h, więc użycie h:dataTable zapewnia nam poprawne użycie h:column.

Znacznik <f:facet>

Za pomocą znacznika f:facet możemy definiować elementy tabeli, które wychodzą poza ramy danych wierszowych, tj. nagłówek i stopka całej tabeli jak i kolumn. Czy znacznik dotyczy tabeli lub kolumny wyznaczanane jest przez jej pozycję, a konfigurację pojedyńczej cechy wskazuje atrybut name. Należy, więc pamiętać o miejscu występowania znacznika. Jeśli f:facet występuje poza zasięgiem znacznika h:column wtedy definicja dotyczy całej tabeli, natomiast umieszczenie go w jego zasięgu modyfikuje cechę zawierającej kolumny.

Atrybut name może przyjmować dwie wartości - header oraz footer w zależności, czy ustawiamy wartość dla nagłówka czy stopki, odpowiednio.

Należy pamiętać, że podobnie jak obowiązkowy znacznik f:view, znacznik f:facet należy do przestrzeni nazw f i należy umieścić odpowiednią deklarację w nagłówku strony JSF (chociaż jak wiadomo nie trzeba się o to martwić, gdyż bez tej deklaracji sam f:view nie będzie działać, a tym samym i f:facet i cała strona JSF).

Źródła danych - DataModel

Znacznik h:dataTable poprzez atrybut value pozyskuje kolekcję danych, po których następuje iteracja. Dane mogą pochodzić z różnych źródeł, jednakże możliwość korzystania z tego czy innego źródła wiąże się z dostępnością klasy dziedziczącej po javax.faces.model.DataModel. Klasa DataModel dostarcza metod pomocniczych związanych z obsługą pobierania danych i iterowania po nich. Wszystkie źródła danych to innymi słowy pewne modele danych na podobieństwo modeli tabel w bibliotece JFC/Swing. Jest to realizacja wzorca MVC (Model-View-Controller), gdzie rolę modelu stanowi komponent zarządzany, natomiast widok reprezentowany jest przez stronę JSF ze znacznikiem h:dataTable.

Mimo, że do tej pory nie wspominaliśmy o modelach i nie korzystaliśmy z nich we wprowadzającym przykładzie, to warto zaznaczyć, że tak na prawdę to korzystaliśmy z jednego nie wprost. Każdorazowo, kiedy przekarzemy dane typu innego niż DataModel, czy jego rozszerzenia, h:dataTable opakuje je w jeden z poniższych podtypów. Skoro jest to robione automatycznie należy jedynie pamiętać, że atrubut value przyjmuje dane różnego typu (nawet nie będącego podklasą DataModel), natomiast h:dataTable i tak ostatecznie pracuje na typie DataModel.

Standardowa implementacja JSF udostępnia kilka modeli, które obsługują obszerny wachlarz możliwości.

  • ArrayDataModel - typ modelu, który opakowuje dane typu Object[].
  • ListDataModel - typ modelu, który opakowuje wartość pustą albo java.util.List
  • ResultDataModel - typ modelu, który odpowiada danym typu javax.servlet.jsp.jstl.sql.Result.
  • ResultSetDataModel - typ modelu, który odpowiada danym typu java.sql.ResultSet.
  • ScalarDataModel - typ modelu, który opakowuje pojedyńczy obiekt dowolnego typu.

Dla dociekliwych: obsługa źródeł danych następuje w klasie javax.faces.component.UIData. Klasa realizująca zadania h:dataTable - javax.faces.component.html.HtmlDataTable jedynie dostarcza obsługi atrybutów wspierająnych przez klientów HTML. Obie klasy są standardowymi klasami i zainteresowani mają możliwość rozszerzeniach ich o własne implementacje bez względu na środowisko JSF, w którym tym komponentom przyjdzie pracować (np. Apache MyFaces czy JSF RI).

Przyjrzyjmy się kilu przykładom, aby zrozumieć różnicę między nimi.

Przykład 2 - h:dataTable i ArrayDataModel

Pierwszy przykład z serii przykładów dotyczących dostępnych modeli dotyczy wykorzystania ArrayDataModel. Dla uproszczenia przykładu, jedyną zmianą jaką wprowadziliśmy jest implementacja klasy Encyklopedia, która dostarcza danych, po których nastąpi iteracja.

package beans;

public class Encyklopedia {

  private Akronim[] akronimy = new Akronim[] { 
      new Akronim("JSF", "JavaServer Faces"), 
      new Akronim("JSP", "JavaServer Pages"),
      new Akronim("ASP", "ActiveServer Pages"), 
      new Akronim("ASF", "Apache Software Foundation") };

  public Akronim[] getAkronimy() {
    return this.akronimy;
  }

  public int getSize() {
    return this.akronimy.length;
  }
}

Pozostałe elementy aplikacji - strona index.jsp oraz faces-config.xml - pozostają bez zmian.

Przykład 3 - h:dataTable i ListDataModel

Kolejnym akceptowanym modelem dla h:dataModel jest ListDataModel. Jego użycie prezentował pierwszy, wprowadzający przykład. Przypomnijmy go.

package beans;

import java.util.ArrayList;
import java.util.List;

public class Encyklopedia {
  private List<Akronim> akronimy = new ArrayList<Akronim>();
  {
    akronimy.add(new Akronim("JSF", "JavaServer Faces"));
    akronimy.add(new Akronim("JSP", "JavaServer Pages"));
    akronimy.add(new Akronim("ASP", "ActiveServer Pages"));
    akronimy.add(new Akronim("ASF", "Apache Software Foundation"));
  }

  public List<Akronim> getAkronimy() {
    return this.akronimy;
  }

  public int getSize() {
    return this.akronimy.size();
  }
}

Przykład 4 - h:dataTable i ResultDataModel

I kolejny przykład, tym razem z użyciem ResultDataModel. Jest to połączenie możliwości iterowania po danych w JSTL za pomocą obiektu typu Result, która reprezentuje wynik uruchomienia zapytania SQL za pomocą znaczników JSTL.

Tym razem modyfikacji ulegnie strona JSP, na której zawarte są wywołania SQL do bazy danych poprzez JSTL. Nie jest to rekomendowane podejście, a jedynie sposób na prototypowanie aplikacji. Zdecydowanie niepolecane podejście, które znalazło swoje miejsce w JSF jako przykład integracji JSF z JSTL.

Na uwagę zasługuje modyfikacja wskazania źródła danych, z którego pobierane są dane wierszowe (requestScope w JSF jest odpowiednikiem obiektu request w JSP).

<%@ page contentType="text/html; charset=ISO-8859-2"%>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %>

<sql:setDataSource dataSource="jdbc/AkronimyDB" />
<sql:query var="akronimy" scope="request">
    SELECT * FROM AKRONIMY;
</sql:query>

<f:view>
  <h:dataTable var="akronim" value="#{requestScope.akronimy}" border="1" width="100%">
    <f:facet name="header">
      <h:outputText value="Encyklopedia skrótów" />
    </f:facet>
    <h:column>
      <f:facet name="header">
        <h:outputText value="Skrót" />
      </f:facet>
      <h:outputText value="#{akronim.skrot}" />
    </h:column>
    <h:column>
      <f:facet name="header">
        <h:outputText value="Pełna nazwa" />
      </f:facet>
      <h:outputText value="#{akronim.pelnaNazwa}" />
    </h:column>
    <f:facet name="footer">
      <h:outputText value="Pozycji: #{encyklopedia.size}" />
    </f:facet>
  </h:dataTable>
</f:view>

Przykład 5 - h:dataTable i ResultSetDataModel

Kolejny niepolecany acz istniejący model akceptowany przez h:dataTable. Pozwala na pobieranie danych bezpośrednio z obiektu java.sql.ResultSet, który jest wynikiem zapytania do bazy danych poprzez JDBC.

Podobnie jak w poprzednich przykładach (poza jednym związanym z JSTL) modyfikacji ulegnie wyłącznie komponent zarządzany - encyklopedia.

package beans;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Encyklopedia {

  private ResultSet rs = null;
  
  public ResultSet getAkronimy() {
    Statement stmt = ...;
    rs = stmt.executeQuery("SELECT * FROM AKRONIMY");
    return rs;
  }

  public int getSize() {
    try {
      return rs.getRow();
    } catch (SQLException e) {
      return 0;
    }
  }
}

Przykład 6 - h:dataTable i ScalarDataModel

I na koniec gratka dla koneserów, czyli prezentacja modelu ScalarDataModel, którego celem jest udostępnienie pojedyńczego obiektu (w tym przypadku typu Akronim) dla h:dataTable.

package beans;

public class Encyklopedia {

  public Akronim getAkronimy() {
    return new Akronim("JSF", "JavaServer Faces");
  }

  public int getSize() {
    return 1;
  }
}

Przykład 7 - h:dataTable i obsługa wyboru wiersza

Najczęstszym problemem z jakim borykają się osoby rozpoczynające pracę z h:dataTable jest (nie)znajomość obsługi wyboru wiersza w tabeli. Poniższy przykład prezentuje jedno z możliwych rozwiązań tej kwestii. Zacznijmy od prezentacji "odrestaurowanego" komponentu zarządzanego - encyklopedia.

UWAGA: Przykładu nie udało mi się uruchomić z Apache MyFaces 1.1.3 na Apache Tomcat 5.5.17, a jedynie z JSF RI 1.1. Problem polega na niemożności wywołania metody komponentu zarządzanego o zasięgu request (http://forum.java.sun.com/thread.jspa?forumID=427&threadID=681118). Znasz rozwiązanie, napisz!
package beans;

import java.util.ArrayList;
import java.util.List;

import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;

public class Encyklopedia {

  private ListDataModel model = new ListDataModel();

  public Encyklopedia() {
    // pobierz dane do modelu w konstruktorze, bądź w metodzie getAkronimy
    model.setWrappedData(pobierzAkronimy());
  }

  public DataModel getAkronimy() {
    // moglibyśmy pobrać dane dopiero tutaj
    return this.model;
  }

  public int getSize() {
    return model.getRowCount();
  }

  protected List pobierzAkronimy() {
    // pobierz akronimy z bazy danych bądź innego źródła, np. systemu pliku
    // tym razem tworzymy dane lokalnie
    List<Akronim> akronimy = new ArrayList<Akronim>();
    akronimy.add(new Akronim("JSF", "JavaServer Faces"));
    akronimy.add(new Akronim("JSP", "JavaServer Pages"));
    akronimy.add(new Akronim("ASP", "ActiveServer Pages"));
    akronimy.add(new Akronim("ASF", "Apache Software Foundation"));
    return akronimy;
  }

  public String wykonajAkcjeNaWybranymWierszu() {
    Akronim akronim = (Akronim) model.getRowData();
    System.out.printf("Wybrano %s %n", akronim);
    return "success";
  }
}

Klasa została udekorowana komentarzami, aby wyjaśnić stosowane konwencje. Najważniejszym elementem komponentu zarządzanego encyklopedia jest metoda wykonajAkcjeNaWybranymWierszu, która pobiera aktualny wiersz w modelu za pomocą metody DataModel.getRowData() i operuje na nim (w tym przypadku wyświetla wybrany akronim na konsolę).

Odpowiadająca komponentowi strona JSF - index.jsp - wygląda następująco:

<%@ page contentType="text/html; charset=ISO-8859-2"%>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>

<f:view>
  <h:form>
    <h:dataTable var="akronim" value="#{encyklopedia.akronimy}" border="1" width="100%">
      <f:facet name="header">
        <h:outputText value="Encyklopedia skrótów" />
      </f:facet>
      <h:column>
        <f:facet name="header">
          <h:outputText value="Skrót" />
        </f:facet>
        <h:commandLink action="#{encyklopedia.wykonajAkcjeNaWybranymWierszu}">
          <h:outputText value="#{akronim.skrot}" />
        </h:commandLink>
      </h:column>
      <h:column>
        <f:facet name="header">
          <h:outputText value="Pełna nazwa" />
        </f:facet>
        <h:outputText value="#{akronim.pelnaNazwa}" />
      </h:column>
      <f:facet name="footer">
        <h:outputText value="Pozycji: #{encyklopedia.size}" />
      </f:facet>
    </h:dataTable>
  </h:form>
</f:view>

Różnica między powyższą wersją a poprzednią polega na opakowaniu h:dataTable w h:form (odpowiednik znacznika form w HTML) oraz dodanie znacznika h:commandLink w kolumnie Skrót ze wskazaniem na akcję do wykonania - encyklopedia.wykonajAkcjeNaWybranymWierszu.

DataModelListener i DataModelEvent

Istnieją dwie klasy, których zastosowania nie udało mi się doszukać, jednakże dla kompletności artykułu wspominam o nich. Pierwsza z nich - javax.faces.model.DataModelListener - reprezentuje słuchacza, który jest zainteresowany w zdarzeniach typu javax.faces.model.DataModelEvent generowanych podczas konstruowania tabeli. Dobrym materiałem do zrozumienia działania obu jest dokumentacja javadoc dla DataModel, szczególnie metody modyfikujące właściwości instancji (setRowIndex(int) oraz setWrappedData(Object)).

Modyfikując nasz przykładowy komponent zarządzany - encyklopedia jak poniżej otrzymamy zdarzenia typu DataModelEvent podczas tworzenia tabeli.

package beans;

import java.util.ArrayList;
import java.util.List;

import javax.faces.model.DataModel;
import javax.faces.model.DataModelEvent;
import javax.faces.model.DataModelListener;
import javax.faces.model.ListDataModel;

public class Encyklopedia {

  private ListDataModel model = new ListDataModel();

  public Encyklopedia() {
    // pobierz dane do modelu w konstruktorze, bądź w metodzie getAkronimy
    model.setWrappedData(pobierzAkronimy());
    model.addDataModelListener(new DataModelListener() {
      public void rowSelected(DataModelEvent evt) {
        System.out.printf("Zdarzenie na wierszu %s [%s]%n", evt.getRowIndex(), evt.getRowData());
      }
    });
  }

  public DataModel getAkronimy() {
    // moglibyśmy pobrać dane dopiero tutaj
    return this.model;
  }

  public int getSize() {
    return model.getRowCount();
  }

  protected List pobierzAkronimy() {
    // pobierz akronimy z bazy danych bądź innego źródła, np. systemu pliku
    // tym razem tworzymy dane lokalnie
    List<Akronim> akronimy = new ArrayList<Akronim>();
    akronimy.add(new Akronim("JSF", "JavaServer Faces"));
    akronimy.add(new Akronim("JSP", "JavaServer Pages"));
    akronimy.add(new Akronim("ASP", "ActiveServer Pages"));
    akronimy.add(new Akronim("ASF", "Apache Software Foundation"));
    return akronimy;
  }

  public String wykonajAkcjeNaWybranymWierszu() {
    Akronim akronim = (Akronim) model.getRowData();
    System.out.printf("Wybrano %s %n", akronim);
    return "success";
  }
}

Uruchomienie przykładu wiąże się z następującymi wpisami na konsolę serwera aplikacyjnego:

Zdarzenie na wierszu -1 [null]
Zdarzenie na wierszu 0 [JSF=JavaServer Faces]
Zdarzenie na wierszu 1 [JSP=JavaServer Faces]
Zdarzenie na wierszu 2 [ASP=ActiveServer Pages]
Zdarzenie na wierszu 3 [ASF=Apache Software Foundation]
Zdarzenie na wierszu 4 [null]
Zdarzenie na wierszu -1 [null]

A więc mamy 4 wiersze w tabeli (indeksy 0-3). Zdarzenia odpowiadające wierszom o indeksie -1 to zdarzenia wskazujące na brak wiersza podczas generowania zdarzenia (rozpoczęcie i zakończenie tworzenia tabeli), a wiersz o indeksie 4 jest wierszem kończącym (markerem).

Dla dociekliwych: Jaka będzie ostateczna postać naszej tabeli po zmianie konstruktora komponentu zarządzanego na poniższy?

public Encyklopedia() {
  // pobierz dane do modelu w konstruktorze, bądź w metodzie getAkronimy
  model.setWrappedData(pobierzAkronimy());
  model.addDataModelListener(new DataModelListener() {
    public void rowSelected(DataModelEvent evt) {
      System.out.printf("Zdarzenie na wierszu %s [%s]%n", evt.getRowIndex(), evt.getRowData());
      DataModel dataModel = evt.getDataModel();
      if (!dataModel.isRowAvailable()) {
        if (evt.getRowIndex() == 4) {
          System.out.printf("...dodajemy wiersz o indeksie %s%n", evt.getRowIndex());
          List akronimy = (List) dataModel.getWrappedData();
          akronimy.add(evt.getRowIndex(), new Akronim("IoC", "Inversion Of Control"));
        }
      }
    }
  });
}

Odnośniki

    Osobiste