07 października 2007

Konwertery w JavaServer Faces część 3

Ostatni odcinek z serii Konwertery w JavaServer Faces - Konwertery w JavaServer Faces część 2 - zakończyłem na przedstawieniu konwertera dostarczanego przez JSF - f:convertDateTime. Dzięki f:convertDateTime mogłem skoncentrować się na tworzeniu aplikacji, nie przywiązując dużej uwagi do konwersji danych między użytkownikiem a aplikacją, tj. na stronie JSF wyświetlana była data w postaci ciągu znaków, podczas gdy w aplikacji oczekiwałem typu java.util.Date. Zamiast trudzić się konwersją samodzielnie, zleciłem JSF wykonanie jej za mnie. Dzięki f:convertDateTime mogłem zadeklarować czy interesuje mnie pełna data z godziną, czy wyłącznie dzień oraz jej format. Podkreślam słowo deklaracja jako nakreślenie moich wymagań w aplikacji JSF (cecha deklaratywności wymagań jest często zapominana w przedstawianiu technologii Korporacyjnej Javy, a przecież jest jej jednym z główych atutów). Poprzez deklaratywne określenie wymagań, zlecam ich realizację JSF, a nie samemu się tym param. Zaleta jest oczywista: mniej czasu muszę spędzić na budowaniu aplikacji.

W tym odcinku przedstawię kolejną funkcjonalność JSF dotyczącą konwersji danych, chociaż tak na prawdę to przygotuję podłoże dla jej wprowadzenia w jeszcze kolejnym odcinku (niejeden z zaawansowanych użytkowników JSF zapewne zastanawia się teraz, ile jeszcze można opisywać konwertery - jedynie niewielki wycinek funkcjonalności JSF. Z radością oznajmiam, że długo, bardzo długo ;-)).

Rozpocznę od modyfikacji aplikacji i wprowadzeniu nowej strony strona3.jsp, która od tej pory stanie się stroną główną aplikacji.

<%@page contentType="text/html" pageEncoding="UTF-8"%>

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

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Mini-aplikacja JSF - konwerter</title>
</head>
<body>
<f:view>
<h:form>
<h:selectOneMenu id="pracownik" value="#{pracownikAgent.wybranyPracownik}">
<f:selectItems value="#{pracownikAgent.pracownicySelectItems}" />
</h:selectOneMenu>
<h:message for="pracownik" errorStyle="color: red"/>
<br/>
<h:commandButton value="Zatwierdź" actionListener="#{pracownikAgent.wykonajAkcje}"/>
<br/>
<h:messages showSummary="true" errorStyle="color: red" />
</h:form>
</f:view>
</body>
</html>
Strona zawiera kilka nowych kontrolek, których przedstawienie pomoże w zrozumieniu tematu głównego - konwerterów JSF. Zacznę od h:selectOneMenu. Jej zadaniem jest wyświetlenie listy rozwijalnej zbudowanej na bazie listy dostarczanej przez kolejną kontrolkę f:selectItems i zapisanie wyboru w obiekcie wskazanym przez value (w tym przypadku będzie to atrybut wybranyPracownik ziarna zarządzanego pracownikAgent). Lista pozycji dostarczana jest przez kontrolkę f:selectItems z listy dostarczanej przez obiekt wskazany przez atrybut value. Elementy listy muszą być typu javax.faces.model.SelectItem, z których pobiera się dane potrzebne do stworzenia listy, np. identyfikator, wyświetlany tekst, itp.

Lista pozycji w menu oraz wybór użytkownika kontrolowane są przez ziarno zarządzane pracownikAgent, który reprezentowany jest przez klasę pl.jaceklaskowski.konwerter.PracownikAgent.

package pl.jaceklaskowski.konwerter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.faces.event.ActionEvent;
import javax.faces.model.SelectItem;

public class PracownikAgent {

private String wybranyPracownik;
private Pracownik pracownik;

public void wykonajAkcje(ActionEvent event) {
System.out.println("Wykonano akcję z pracownikiem: " + wybranyPracownik);
}

public Collection<SelectItem> getPracownicySelectItems() {
Collection<SelectItem> pracownicy = new ArrayList<SelectItem>();
for (Pracownik p : getPracownicy()) {
pracownicy.add(new SelectItem(p.getNumer(), p.getImie()));
}
return pracownicy;
}

public List<Pracownik> getPracownicy() {
return TymczasowaBazaDanych.zwrocWszystkichPracownikow();
}

public String getWybranyPracownik() {
return wybranyPracownik;
}

public void setWybranyPracownik(String wybranyPracownik) {
this.wybranyPracownik = wybranyPracownik;
}

public Pracownik getPracownik() {
return pracownik;
}

public void setPracownik(Pracownik pracownik) {
this.pracownik = pracownik;
}
}
Każdy z atrybutów value kontrolek h:selectOneMenu oraz f:selectItems związany jest z metodą odczytująca (ang. getter) oraz, w przypadku h:selectOneMenu, modyfikująca (ang. setter).

Wciśnięcie przycisku Zatwierdź spowoduje wykonywanie akcji-słuchacza wykonajAkcje(). Akcja operuje na wybranym użytkowniku z listy rozwijalnej. I właśnie to będzie przedmiotem dalszej prezentacji konwerterów JSF. Wybór użytkowika to wybór identyfikatora pozycji na liście. Użytkownik wybiera pozycję Jacek, jednakże dla programisty aplikacji dostępny jest jako "0" (typu znakowego nie numerycznego!). Sposób reprezentacji pozycji w liście zależy od wyboru konstrukcji listy z SelectItem, która jest zwracana przez metodę PracownikAgent.getPracownicySelectItems() (odpowiada temu wyrażenie pracownikAgent.pracownicySelectItems na stronie JSF).

Warto wspomnieć o wykorzystaniu atrybutu actionListener kontrolki h:commandButton. Atrybut actionListener wymaga wskazania akcji-słuchacza, która będzie wywołana podczas aktywacji kontrolki (w tym przypadku wciśnięcia przycisku), tj. metody o sygnaturze public void actionListener(javax.faces.event.ActionEvent). Jest istotna różnica w jej korzystaniu versus metody wskazanej przez atrybut action, jednakże jest to temat sam w sobie i pozostawiam jego zgłębienie zainteresowanym (zachęcam do dzielenia się odpowiedziami jako komentarze do notatki).

I na zakończenie prezentacji zmian, wspomnę o klasie pl.jaceklaskowski.konwerter.TymczasowaBazaDanych, która jak sama nazwa wskazuje jest tymczasowa, aż na scenę wkroczy JPA (a już nie mogę doczekać się tego momentu). Jej celem jest dostarczanie danych dla aplikacji - taka mini baza danych. Nie ma się czym zajmować, bo pójdzie w odstawkę niebawem.

package pl.jaceklaskowski.konwerter;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TymczasowaBazaDanych {

private static Map<Long, Pracownik> bazaDanychPracownikow = new HashMap<Long, Pracownik>();
static {
Date dzisiaj = Calendar.getInstance().getTime();
Pracownik jacek = new Pracownik(0, "Jacek", dzisiaj);
Pracownik agata = new Pracownik(1, "Agatka", dzisiaj);
bazaDanychPracownikow.put(jacek.getNumer(), jacek);
bazaDanychPracownikow.put(agata.getNumer(), agata);
}

public static List<Pracownik> zwrocWszystkichPracownikow() {
return new ArrayList<Pracownik>(bazaDanychPracownikow.values());
}

public static void wstaw(Pracownik pracownik) {
bazaDanychPracownikow.put(pracownik.getNumer(), pracownik);
}

public static Pracownik odszukaj(long numer) {
return bazaDanychPracownikow.get(numer);
}
}
Pozostaje zaprezentować konfigurację aplikacji JSF - faces-config.xml.

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

<faces-config version="1.2"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
<managed-bean>
<managed-bean-name>pracownik</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.konwerter.Pracownik</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>pracownikAgent</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.konwerter.PracownikAgent</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>
Uruchomienie aplikacji to wyświetlenie strony strona3.jsp.

, gdzie zatwierdzenie wyboru przez wciśnięcie przycisku Zatwierdź będzie wiązało się z wyświetleniem wybranej pozycji jako...wartość znakowa (de facto jest to liczba opakowana jako ciąg znaków).

deployed with moduleid = konwerter
Initializing Sun's JavaServer Faces implementation (1.2_04-b20-p03) for context '/konwerter'
Wykonano akcję z pracownikiem: 1
1 zamiast Agata, czy pełnego egzemplarza typu Pracownik?! Czy tego potrzebuje programista JSF? Niekoniecznie. Rozwiązanie tego problemu w kolejnej odsłonie artykułu. Na zakończenie dodam, że wciąż niewykorzystałem pełni możliwości drzemiących w JSF i rozwiązanie będzie oparte o jedną z nich. JSF ma jeszcze wiele do zaoferowania zdejmując trud konwersji z barków twórcy aplikacji.

Gotowy projekt aplikacji Konwerter dostępny jest jako jsf-konwerter-czesc3.zip.