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.
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.
<%@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>
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.
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).
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;
}
}
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.
Pozostaje zaprezentować konfigurację aplikacji JSF - faces-config.xml.
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);
}
}
Uruchomienie aplikacji to wyświetlenie strony strona3.jsp.
<?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>
, 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).
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.
deployed with moduleid = konwerter
Initializing Sun's JavaServer Faces implementation (1.2_04-b20-p03) for context '/konwerter'
Wykonano akcję z pracownikiem: 1
Gotowy projekt aplikacji Konwerter dostępny jest jako jsf-konwerter-czesc3.zip.
Metoda wskazana przez atrybut actionListener zwraca typ void, przyjmuje jako argument javax.faces.event.ActionEvent i po wykonaniu metody wyświetla stronę, z której została wywołana. Natomiast metoda wskazana przez action nie przyjmuje żadnego argumentu, ale zwraca wartość typu String identyfikującą nową stronę do wyświetlenia - "navigation mapping". Zgadza się?
OdpowiedzUsuńA do czego może przydać się ActionEvent? Sam jeszcze go do niczego nie użyłem. Z actionListenera wykorzystywałem jedynie to, że po wywołaniu pokazuje tę samą stronę.