27 stycznia 2008

Tabele na stronach aplikacji JSF

W artykule "Tabele o dynamicznej ilości kolumn w aplikacji JSF" opisałem, w jaki sposób prezentować na stronach JSF tabele o nieustalonej ilości kolumn z użyciem komponentów dataTable i columns z biblioteki Tomahawk. Miał to być jeden z elementów większej układanki, której ostatecznym celem jest zbudowanie komponentu JSF – w formie kompozycji Facelets – adresującego problem, o którym pisałem z kolej w artykule "Prezentacja dużych tabel na stronach HTML". A problemem jest to, że – jak tam napisałem – "chciałbym mieć komponent, który umożliwi prezentację dużej tabeli na stronie HTML, przy czym tabela ta, jest zarówno bardzo długa, jak i bardzo szeroka". Dobrą wiadomością jest, że problem jest już niemalże rozwiązany, tzn. komponent ten powstał i działa niemalże tak jakbym sobie tego życzył, tyle że nie dało się go zaimplementować stosując podejście z użyciem wspomnianych komponentów Tomahawk. W sukurs przyszedł inny komponent, również z biblioteki Tomahawk, a mianowicie dataList. Dzisiaj napiszę więc o innym podejściu do implementacji tabel "o dynamicznej ilości kolumn w aplikacji JSF". Podejściu bazującym na komponencie dataList.

Zacznijmy od modelu danych. Żeby nie utrudniać, załóżmy że nagłówki – tj. nagłówek górny i boczny – oraz treść prezentowana w tabeli to będą zwykłe napisy, a więc nasz model danych może się opierać na obiektach klasy String. Poniżej interfejsy oraz przykładowe implementacje modelu danych potrzebnego dla naszej tabeli.

public interface TableDataModel {
public List<String> getColsHeaders();

public List<RowDataModel> getRowsDataModel();
}

public interface RowDataModel {
public String getRowHeader();

public List<String> getCellsList();
}

public class BasicTableDataModel implements TableDataModel {
private List<String> colsHeaders;

private List<RowDataModel> rowsDataModel;

public BasicTableDataModel(String[] colsHeaders) {
this.colsHeaders =
new ArrayList<String>(Arrays.asList(colsHeaders));
}

public List<String> getColsHeaders() {
return colsHeaders;
}

public List<RowDataModel> getRowsDataModel() {
if (rowsDataModel == null)
rowsDataModel = new ArrayList<RowDataModel>();

return rowsDataModel;
}

public void addRow(RowDataModel rowDataModel) {
getRowsDataModel().add(rowDataModel);
}
}

public class BasicRowDataModel implements RowDataModel {
private String rowName;

private List<String> cellsList;

public BasicRowDataModel(String rowName, String[] cellsArray) {
this.rowName = rowName;
this.cellsList =
new ArrayList<String>(Arrays.asList(cellsArray));
}

public List<String> getCellsList() {
return cellsList;
}

public String getRowHeader() {
return rowName;
}
}

Implementacje modelu danych pokazane powyżej zostały skonstruowane pod kątem łatwości użycia w kodzie przykładowym, więc ziarno zarządzane JSF, którego użyjemy do budowy strony jest niezwykle proste. Oto jego kod, w całości poświęcony inicjalizacji danych:

public class TableBackingBean {
private BasicTableDataModel dataModel;

public BasicTableDataModel getDataModel() {
if (dataModel == null) {
dataModel = new BasicTableDataModel(
new String[] { "Ford", "Opel", "BMW" });

dataModel.addRow(new BasicRowDataModel(
"2005", new String[] {"12", "8", "10" }));

dataModel.addRow(new BasicRowDataModel(
"2006", new String[] {"-6", "2", "12" }));

dataModel.addRow(new BasicRowDataModel(
"2007", new String[] {"16", "18", "-2" }));
}

return dataModel;
}
}

O komponencie dataList możemy myśleć, nieco upraszczając, jak o implementacji pętli "for each". Użyjemy go więc jako iteratora po elementach kolekcji modelu danych i w ten sposób zbudujemy tabelę HTML. Oto, jak wygląda nasza strona:

<table>
<tr>
<td> </td>
<t:dataList var="headerCell"
value="#{tableBackingBean.dataModel.colsHeaders}">

<td>#{headerCell}</td>
</t:dataList>
</tr>

<t:dataList var="bodyRow"
value="#{tableBackingBean.dataModel.rowsDataModel}">

<tr>
<td>#{bodyRow.rowHeader}</td>

<t:dataList var="cell" value="#{bodyRow.cellsList}">
<td>#{cell} %</td>
</t:dataList>
</tr>
</t:dataList>
</table>

Ziarno zarządzane o nazwie tableBackingBean to naturalnie instancja klasy TableBackingBean. Efektem końcowym jest tabela prezentująca procentowy wzrost, lub spadek, ilości sprzedawanych aut przez poszczególnych producentach w poszczególnych latach.

16 stycznia 2008

Przymiarki do programowania na palmtopy z Windows Mobile

Jakiś czas temu wszedłem w posiadanie ekscytującej, ale jak się okazuje umiarkowanie praktycznej zabawki – palmtopa z Windows Mobile 6 a dokładniej E-TEN Glofiish X500+. Naturalnie, spowodowało to, że zacząłem się interesować programowaniem tego typu zabawek. Zacząłem od rozpoznania branży, czyli systematyki. Owocem tych działań jest artykuł "Terminologia ze świata urządzeń mobilnych". Teraz przyszła kolej na następny krok, czyli rozpoznanie możliwości i dostępnych opcji programowania. Oczywiście, pierwsze wysiłki koncentrowały się na platformie Java ME (J2ME). Szybko okazało się jednak, że na moim urządzeniu nie ma środowiska uruchomieniowego Javy i trzeba było uruchomić Google. Niestety wyniki poszukiwania nie były zachęcające. Wygląda na to, że Java ME dla Windows Mobile nie jest opcją łatwo dostępną. Równolegle ze studiami nad platformą Java ME prowadziłem rozpoznanie możliwości funkcjonalnych mojego Glofiish’a a właściwie systemu Windows Mobile 6. Tutaj przyszło kolejne rozczarowanie – dostępne aplikacje są dosyć prymitywne. Rozumiem, że Office Mobile nie koniecznie musi posiadać zaawansowaną funkcjonalność znaną z tego typu aplikacji dla PC, ale spodziewałbym się, że chociaż aplikacja budzika sprosta moim oczekiwaniom. Tymczasem okazuje się, że nie jest ona nawet w stanie odtworzyć pliku .mp3 jako dźwięku dzwonka. Odtwarza tylko pliki .wav i to takie kodowane w najpodlejszej jakości. Żeby było śmieszniej, jako dźwięk dla połączenia przychodzącego jak najbardziej nadaje się plik .mp3. A propos – jako dzwonek dla połączenia przychodzącego mogę wybierać tylko z pośród plików umieszczonych w konkretnym katalogu. Jest więc specjalna aplikacja, która służy do "importowania" plików muzycznych, aby mogły być one użyte jako dzwonek połączenia a jedyne, co ona robi to właśnie kopiowanie importowanego pliku do właściwego katalogu. Na tym kończę krytykę i przechodzę do konkluzji. Jako że standardowa aplikacja budzika jest bardzo słaba – a jest to aplikacja bardzo dla mnie ważna – postanowiłem zmierzyć się z zadaniem implementacji takiej właśnie aplikacji. Środowisko wokół platformy Java ME nie napawa optymizmem, postanowiłem więc odłożyć na bok uprzedzenia i zbadać możliwości programowania z użyciem platformy .NET. W końcu .NET wspiera także język Java, tyle że nazywa go J#. Pierwsze kroki w świecie .NET przyniosły pierwsze rozczarowania – okazało się, że język J# nie jest wspierany na platformie Windows Mobile, a co gorsze, aby zaimplementować aplikację budzika trzeba i tak programować w C++. Czemu musi być C++? Głównie dlatego, że chcę aby aplikację dało się uruchomić jednym kliknięciem/dotknięciem z ekranu głównego systemu, a to oznacza że musi to być swego rodzaju wtyczka (ang. plugin) w formie biblioteki .dll. Poza tym, potrzebny będzie dostęp do funkcji systemowych Win32. Póki co nie będzie więc ani Javy ani .NET (i dobrze) tylko koszmarek C++. Znalazłem już nawet niezłą książkę na ten temat, czyli "Programming Microsoft Windows CE, Second Edition" autorstwa Douglas’a Boling’a ale programowanie pod Windows i to w C++ jakoś mnie odraża więc nie wiem co z tego wyjdzie. Zobaczymy.

14 stycznia 2008

Tabele o dynamicznej ilości kolumn w aplikacji JSF

W większości przypadków tabele mają z góry określoną liczbę, znaczenie i nazwy kolumn a zmienna jest liczba wierszy. Kolumny tabeli określają wtedy nijako typ obiektu a wiersze to poszczególne obiekty zgodne z tym typem. Nie wszystkie tabele mają jednak tą naturę. Czasem nieznana jest także liczba kolumn, np. w przypadku, gdy w tabeli chcemy pokazać stopę bezrobocia w wybranych krajach i wybranych okresach, latach albo miesiącach. Dziś pokażę, jak można zaimplementować taką tabelę w aplikacji JSF z użyciem komponentów dataTable i columns z biblioteki Tomahawk. Zacznijmy od końca, czyli jak będzie wyglądało to, co zbudujemy.


A teraz kod źródłowy strony. Zmienna o nazwie dataBean to ziarno zarządzane klasy DataBean, której kod pokaże później.

<t:dataTable var="period" value="#{dataBean.periodsList}">
<t:column>
<h:outputText value="#{period.name}" />
</t:column>

<t:columns value="#{dataBean.countriesList}" var="country">
<f:facet name="header">
<h:outputText value="#{country.name}" />
</f:facet>

<h:outputText value="#{dataBean.aggregateData.unemploymentRate} %" />
</t:columns>
</t:dataTable>

Kluczem do zrozumienia tego mechanizmu jest klasa javax.faces.model.DataModel. Wyrażenia dataBean.periodsList oraz dataBean.countriesList są właśnie tego typu, co widać w kodzie wspomnianej klasy DataBean. Poniżej jej kod źródłowy.

import java.util.ArrayList;

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

public class DataBean {
private DataModel periodsList;

private DataModel countriesList;

public DataModel getPeriodsList() {
if (periodsList == null)
periodsList = new ListDataModel(fetchPeriodsList());

return periodsList;
}

public DataModel getCountriesList() {
if (countriesList == null)
countriesList = new ListDataModel(fetchCountriesList());

return countriesList;
}

public AggregateData getAggregateData() {
if (getPeriodsList().isRowAvailable() && getCountriesList().isRowAvailable()) {
return getData(
(Country) getCountriesList().getRowData(),
(Period) getPeriodsList().getRowData());
}

return null;
}

private ArrayList<Period> fetchPeriodsList() {
ArrayList<Period> periods = new ArrayList<Period>();

periods.add(new Period("Styczen 2007"));
periods.add(new Period("Luty 2007"));
periods.add(new Period("Marzec 2007"));

return periods;
}

private ArrayList<Country> fetchCountriesList() {
ArrayList<Country> countries = new ArrayList<Country>();

countries.add(new Country("Polska"));
countries.add(new Country("Litwa"));
countries.add(new Country("Rumunia"));

return countries;
}

private AggregateData getData(Country country, Period period) {
return new AggregateData(
country.getName().length() + period.getName().length());
}
}

Działa to więc tak, że dostarczamy kolekcje (typu javax.faces.model.DataModel) obiektów dla nagłówków poziomych i pionowych (metody getPeriodsList() i getCountriesList()) oraz implementujemy metodę (w tym wypadku jest to getAggregateData()), która dostarcza danych dla poszczególnych komórek "ciała" tabeli. Używając metody getRowData() klasy javax.faces.model.DataModel dowiadujemy się o obiektach będących w nagłówkach dla bieżącej komórki tabeli i na tej podstawie pobieramy odpowiednie dane. Zwróćmy uwagę, że pobranie danych oddelegowałem do metody getData(Country country, Period period). To jak pobieramy dane nie jest meritum tego artykułu, więc umieściłem tam kod-zaślepkę, ale w normalnym przypadku było by tu zapewne wywołanie jakiejś metody odpowiedniego serwisu logiki biznesowej, która z kolei pobierałaby dane z bazy danych. Dla kompletności podam jeszcze kod źródłowy klasy AggregateData. Analogicznie wyglądają klasy Country i Period. Są to ziarna JavaBeans z jednym atrybutem o nazwie name i konstruktorem.

public class AggregateData {
private int unemploymentRate;

public AggregateData(int unemploymentRate) {
this.unemploymentRate = unemploymentRate;
}

public int getUnemploymentRate() {
return unemploymentRate;
}

public void setUnemploymentRate(int unemploymentRate) {
this.unemploymentRate = unemploymentRate;
}
}