Java Enterprise Edition spotkanie nr 14 Java Server Faces
Interfejs użytkownika przez WWW Typowe zagadnienia prezentacja danych użytkownikowi pobieranie danych od użytkownika nawigacja między stronami walidacja i18n Po co wszyscy mają rozwiązywać te same problemy Struts Webwork Struts 2
Java Server Faces Rozmieszczanie komponentów za pomocą znaczników JSP Łączenie zdarzeń HTTP z komponentami Łączenie zdarzeń generowanych przez komponenty z kodem wykonywanym na serwerze Łączenie komponentów na stronie do danych na serwerze Zalety rozdzielenie zachowania i prezentacji model programistyczny bliższy do tradycyjnych aplikacji biurkowych łatwiejszy podział zadań w dużych zespołach możliwość generowania interfejsu w różnych technologiach (oparte na serwletach)
Przykład (projekt Greeting) Bean package hello; public class PersonBean { String personname; public String getpersonname() { return personname; } } public void setpersonname(string personname) { this.personname = personname; } plik properties (messages.properties i odmiany) inputname_header=jsf Kisckstart prompt=tell us your name: greeting_text=welcome to JSF button_text=say Hello sign=!
Przykład: inputname.jsp <%@page contenttype="text/html"%> <%@page pageencoding="utf-8"%> <%@taglib uri="http://java.sun.com/jsf/html" prefix="h"%> <%@taglib uri="http://java.sun.com/jsf/core" prefix="f"%> <f:loadbundle basename="hello.messages" var="msg"/> <!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>enter your name page</title> </head> <body> <f:view> <h1> <h:outputtext value="#{msg.inputname_header}"/> </h1> <h:form id="helloform"> <h:outputtext value="#{msg.prompt}"/> <h:inputtext value="#{personbean.personname}"/> <h:commandbutton id="submit" action="x" value="#{msg.button_text}"/> </h:form> </f:view> </body> </html>
Przykład: greeting.jsp <%@page contenttype="text/html"%> <%@page pageencoding="utf-8"%> <%@taglib uri="http://java.sun.com/jsf/html" prefix="h"%> <%@taglib uri="http://java.sun.com/jsf/core" prefix="f"%> <f:loadbundle basename="hello.messages" var="msg"/> <!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>greeting page</title> </head> <body> <f:view> <h:outputtext value="#{msg.greeting_text}"/> <h:outputtext value="#{personbean.personname}"/> <h:outputtext value="#{msg.sign}"/> </f:view> </body> </html>
Przykład: faces-config.xml <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"> <navigation-rule> <from-view-id>/inputname.jsp</from-view-id> <navigation-case> <from-outcome>x</from-outcome> <to-view-id>/greeting.jsp</to-view-id> </navigation-case> </navigation-rule> <managed-bean> <managed-bean-name>personbean</managed-bean-name> <managed-bean-class>hello.personbean</managed-bean-class> <managed-bean-scope>request</managed-bean-scope> <managed-property> <property-name>personname</property-name> <property-class>java.lang.string</property-class> <value>placek</value> </managed-property> </managed-bean> </faces-config>
Przykład: web.xml <?xml version="1.0" encoding="utf-8"?> <web-app version="2.5" 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-app_2_5.xsd"> <servlet> <servlet-name>faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.facesservlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>faces Servlet</servlet-name> <url-pattern>/pages/*</url-pattern> </servlet-mapping> <session-config> <session-timeout>30</session-timeout> </session-config> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
Komponenty UI UI w JSF jest budowane z konfigurowalnych, nadających się do ponownego użycia komponentów Wszystkie komponenty rozszerzają UIComponentBase przykładowe komponenty: UICommand, UIOutput i jego podklasa UIInput, UIForm, UIMessage Komponenty mogą implementować jeden z kilku interfejsów określających ich zachowania, np. UICommand implementuje ActionSource2 Komponenty mogą być renderowane na stronie do różnych postaci komponenty i ich zachowania są zdefiniowane raz a prezentację można zmieniać autorzy stron mogą używać znaczników, które renderują ten sam komponent na różne sposoby, np. UICommand do przycisku lub do linku (commandbutton, commandlink) domyślnie dostępny jest render kit do HTML
Beany (projekt GuessNumber) faces-config.xml jest przetwarzany przy starcie aplikacji bean jest tworzony przy pierwszym odwołaniu do niego z jakiejś strony <managed-bean> <managed-bean-name>zgadywanka</managed-bean-name> <managed-bean-class>zgadywanka.zgadywanka</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> <managed-property> <property-name>minimum</property-name> <property-class>int</property-class> <value>0</value> </managed-property> <managed-property> <property-name>maximum</property-name> <property-class>integer</property-class> <value>10</value> </managed-property> </managed-bean> Zazwyczaj do beanów odwołujemy się przy pomocy wyrażeń wyliczających się z opóźnieniem #{}, a nie ${} (po obsłudze zdarzeń, konwersji i walidacji)
Nawigacja <navigation-rule> <from-view-id> /greeting.jsp </from-view-id> <navigation-case> <from-outcome> success </from-outcome> <to-view-id> /response.jsp </to-view-id> </navigation-case> </navigation-rule> statycznie <h:commandbutton id="submit" action="success" value="kliknij mnie" /> dynamicznie <h:commandbutton id="submit" action="#{bean.metoda}" value="kliknij mnie" /> w jednej regule może być kilka przypadków (navigation-case) w przypadku można wskazać z jakiej metody miał pochodzić wynik <from-action>#{logonform.logon}</from-action> można użyć naraz from-outcome i from-action lub tylko jednego z nich jeżeli metoda decydująca o nawigacji zwróci null, to strona jest wyświetlana ponownie bez tworzenia nowego widoku (nie są kasowane wprowadzone dane)
Command Link (projekt Link) Zamiast przycisku można użyć link z parametrem value <h:form id="helloform"> <h:outputtext value="#{msg.prompt}"/> <h:inputtext value="#{personbean.personname}"/> <h:commandlink id="submit" action="x" value="#{msg.button_text}"/> </h:form> lub z podelementem <h:form id="helloform"> <h:outputtext value="#{msg.prompt}"/> <h:inputtext value="#{personbean.personname}"/> <h:commandlink id="submit" action="x"> <h:outputtext value="#{msg.button_text}"/> </h:commandlink> </h:form> Przy renderowaniu linków do strony dodawany jest javascript, który generuje action event (będzie wyjaśnione później)
Resource bundle Pliki properties z tekstami umieszczamy razem z klasami Można je wskazywać globalnie w faces-config.xml <application>... <resource-bundle> <base-name>zgadywanka.zgadywanka</base-name> <var>msg</var> </resource-bundle>... </application> na każdej stronie oddzielnie <f:loadbundle var="msg" basename="zgadywanka.zgadywanka" /> Odwoływanie się na stronach <h:outputtext value="#{msg.alamakota}"/>
Dostęp z kodu (projekt getmessage) public static String getmessage(string basename, String key) { FacesContext context = FacesContext.getCurrentInstance(); ResourceBundle bundle; try { bundle = ResourceBundle.getBundle(basename, context.getviewroot().getlocale()); } catch (Exception e) { return null; } return bundle.getstring(key); }
Konwertery Rejestracja konwertera na komponencie możliwa jest na 4 sposoby: dowiązanie wartości komponentu do właściwości beana o typie odpowiadającym konwerterowi tylko dla najważniejszych typów i komponentów, które są dowiązywane wskazanie konwertera przy pomocy atrybutu converter <h:inputtext converter="javax.faces.convert.integerconverter" /> użycie zagnieżdżonego znacznika converter i wskazanie w nim właściwego konwertera (dla niektórych konwerterów można w ten sposób podać dodatkowe informacje na temat formatowania) zamiast pełnej nazwy można użyć identyfikatora konwertera (dla standardowych jest zdefiniowany w klasie, a dla niestandardowych wymieniony w faces-config.xml) <h:inputtext value="#{loginbean.age}" /> <f:converter converterid="integer" /> </h:inputtext> zagnieżdżenie znaczników convertdatetime lub convertnumber (tylko te dwa mają swoje znaczniki)
Konwertery c.d. Standardowe konwertery javax.faces.convert.bigdecimalconverter javax.faces.convert.bigintegerconverter javax.faces.convert.booleanconverter javax.faces.convert.byteconverter javax.faces.convert.characterconverter javax.faces.convert.datetimeconverter javax.faces.convert.doubleconverter javax.faces.convert.floatconverter javax.faces.convert.integerconverter javax.faces.convert.longconverter javax.faces.convert.numberconverter javax.faces.convert.shortconverter Standardowe konwertery mają standardowy komunikat błędu, np. dla BigIntegerConverter to "{0} must be a number consisting of one or more digits" parametr {0} jest zastępowany przez nazwę wskazaną w parametrze label elementu którego dotyczy konwerter Do zastąpienia stand. komunikatu błędu lub wskazania takowego dla niestand. konwertera dodaje się do komponentu atrybut convertermessage
Walidacja Standardowe walidatory (można dodawać swoje) DoubleRangeValidator czy liczba rzeczywista należy do zakresu LengthValidator czy długość napisu należy do zakresu LongRangeValidator czy liczba całkowita należy do zakresu Podobnie jak dla konwerterów są stand. komunikat błędu dla stand. walidatorów i można je podmieniać przy pomocy atrybutu validatormessage Trzy sposoby walidacji zagnieżdżenie znacznika odpowiadającego walidatorowi (validatedoublerange, validatelength lub validatelongrange) <h:inputtext id="quantity" size="4" value="#{item.quantity}" > <f:validatelongrange minimum="1"/> </h:inputtext> <h:message for="quantity"/> odwołanie się do metody wykonującej walidację przy pomocy atrybutu validate zagnieżdżenie znacznika validator
Wypisywanie komunikatów błędu Komunikaty błędu (od walidatorów, konwerterów oraz ze względu na wymagalność) wypisuje się przy pomocy znaczników message oraz messages dowiązanie następuje przy pomocy atrybutu for jeżeli było wiele komunikatów domyślnie wymieniane są w liście, chyba że zmienimy to przy pomocy atrybutu layout="table" <h:inputtext id="userno" value="#{usernumberbean.usernumber}"> <f:validatelongrange minimum="0" maximum="10" /> </h:inputtext> <h:commandbutton id="submit" action="success" value="submit" /> <h:message style="color: red; font-family: 'New Century Schoolbook', serif; font-style: oblique; text-decoration: overline" id="errors1" for="userno"/> Stand. komunikat wymagalności podmieniamy atr. requiredmessage (convertermessage, requiredmessage i validatormessage)
Zdarzenia Obiekty Event przenoszą informacje o komponencie, dla którego zaszło zdarzenie oraz informacje o zdarzeniu action event aktywowanie komponentu ActionSource (przycisku, odnośnika, itp.) value-changed event zmiana stanu UIInput, tylko jeżeli nie było błędów walidacji data-model event (zagadnienie zaawansowane) dotyczy wierszy komponentów UIData moment obsłużenia zdarzeń action i value-changed zależy od wartości atrybutu immediate Obsługa zdarzeń możliwa jest na dwa sposoby Aplikacja może rejestrować na komponentach obiekty Listener przez zagnieżdżenie w komponencie znaczników valuechangelistener lub actionlistener Odpowiedni atrybut komponentu może wskazywać metodę backing beana
Przykład (projekt ChooseLocale) <f:loadbundle basename="messages.bookstoremessages" var="bundle"/>... <f:view> <h2><h:outputtext value="#{bundle.chooselocale}" /></h2> <h:form> <h:panelgrid id="links" columns="4" summary="#{bundle.chooselocale}" title="#{bundle.chooselocale}" > <h:commandlink id="namerica actionlistener="#{localebean.chooselocalefromlink}"> <h:outputtext value="#{bundle.english}" /> </h:commandlink> <h:commandlink id="germany" actionlistener="#{localebean.chooselocalefromlink}"> <h:outputtext value="#{bundle.german}" /> </h:commandlink> <h:commandlink id="france" actionlistener="#{localebean.chooselocalefromlink}"> <h:outputtext value="#{bundle.french}" /> </h:commandlink> <h:commandlink id="samerica" actionlistener="#{localebean.chooselocalefromlink}"> <h:outputtext value="#{bundle.spanish}" /> </h:commandlink> </h:panelgrid> </h:form> </f:view>...
Przykład (projekt ChooseLocale) Tym razem sami implementujemy resource bundle W Javie lokale są reprezentowane przez obiekty java.util.locale a dane podlegające lokalizacji są przechowywane przez obiekty java.util.resourcebundle (zawiera pary klucz-wartość) import java.util.*; public class BookstoreMessages extends ListResourceBundle { static final Object[][] contents = { { "ChooseLocale", "Choose Your Preferred Locale" }, { "English", "English" }, { "German", "German" }, { "Spanish", "Spanish" }, { "French", "French" }, }; } public Object[][] getcontents() { return contents; }
Przykład (projekt ChooseLocale) LocaleBean przechowuje mapę indeksowaną identyfikatorami komponentów CommandLink public class LocaleBean { private HashMap<String, Locale> locales = null; public LocaleBean() { locales = new HashMap<String, Locale>(4); locales.put("namerica", new Locale("en", "US")); locales.put("samerica", new Locale("es", "MX")); locales.put("germany", new Locale("de", "DE")); locales.put("france", new Locale("fr", "FR")); } } public void chooselocalefromlink(actionevent event) { String current = event.getcomponent().getid(); FacesContext context = FacesContext.getCurrentInstance(); context.getviewroot().setlocale(locales.get(current)); }
Value-changed Listener (projekt ValueChangeListener) Listener jest używany tylko jeżeli wartość rzeczywiście się zmieniła! <h:inputtext id="name" value="#{personbean.personname}"> <f:valuechangelistener type="listener.namechanged" /> </h:inputtext> Kod obsługi zdarzenia umieszcza nową wartość w sesji public class NameChanged implements ValueChangeListener { public void processvaluechange(valuechangeevent event) throws... { if (null!= event.getnewvalue()) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("name", event.getnewvalue()); } } Do wartość z sesji łatwo się dostać na stronie wynikowej <h:outputformat title="thanks" value="#{msg.thankyou}"> <f:param value="#{sessionscope.name}"/> </h:outputformat> Komunikat jest składany z kawałków dzięki użyciu znacznika outputformat oraz wzorca java.text.messageformat thankyou=thank you, {0} for visiting our page
Restore View budowa widoku dla strony, podłączanie do komponentów obsługi zdarzeń oraz walidatorów zapisywanie widoku w FacesContext dla initial request widok jest pusty, dla postback uzupełniany jest o informacje zapamiętane na serwerze lub u klienta
Apply Request komponenty odczytują przy pomocy swojej metody decode() nowe wartości z parametrów żądania i dokonywana jest konwersja jeżeli konwersja się nie udała, to w FacesContext jest kolejkowany komunikat o błędzie niektóre komponenty modą mieć atrybut immediate ustawiony na true
skolejkowane zdarzenia są transmitowane do listenerów jakaś metoda decode albo listener może wykonać renderresponse() na aktualnym egzemplarzu FacesContext można wykonać przy pomocy FacesContext.responseComplete() (jak generujemy odpowiedź nie zawierającą komponentów JSF lub przekierowujemy do innej aplikacji)
Process Validations przeprowadzana jest walidacja wartości, a informacje o błędach są kolejkowane w FacesContext
skolejkowane zdarzenia są transmitowane do listenerów jakaś metoda decode albo listener może wykonać renderresponse() na aktualnym egzemplarzu FacesContext można wykonać przy pomocy FacesContext.responseComplete() (jak generujemy odpowiedź nie zawierającą komponentów JSF lub przekierowujemy do innej aplikacji) jeżeli były błędy następuje przejście do etapu render response
Update Model Values uaktualniane są właściwości beanów podpiętych do komponentów wejściowych jeżeli w apply request był błąd konwersji, to od razu następuje przejście do render response
Invoke application aplikacja obsługuje wysłanie formularza lub kliknięcie linku (obsługa application-level events) zazwyczaj domyślny NavigationHandler decyduje jaki będzie kolejny widok i go przestawia
Render response kontener JSP wykonuje rendering komponentów na podstawie użytych znaczników
Najczęściej spotykane atrybuty id unikalny identyfikator atrybutu trzeba używać składni natychmiastowej ${} jeżeli nie podamy jakiś zostanie wygenerowany immediate zawiera wartość logiczną i dotyczy komponentów UIInput oraz UICommand jeżeli true to obsługa zdarzeń, konwersja wartości i walidacja odbędą się podczas etapu apply request np. jeżeli przycisk i pole oba mają wartość true, to nowa wartość pola będzie dostępna podczas obsługi zdarzenia związanego z przyciskiem np. jeżeli pola mają false, jeden przycisk true, a drugi false, to użycie pierwszego pozwala kontynuować ignorując nowe wartości, a drugi je uwzględni
Dołączanie etykiet Dla wygody do pól formularza można dołączać etykiety <h:selectbooleancheckbox id="fanclub" binding="#{cashier.specialoffer}" /> <h:outputlabel for="fanclub" value="#{bundle.dukefanclub}" />... Zamiast parametru value można zagnieździć znacznik <h:selectbooleancheckbox id="fanclub" binding="#{cashier.specialoffer}" /> <h:outputlabel for="fanclub"> <h:outputtext id="fanclublabel" value="#{bundle.dukefanclub}" /> </h:outputlabel>...
Najczęściej spotykane atrybuty c.d. rendered wartość logiczna wskazująca czy komponent ma być pokazany wyrażenie EL może tylko odczytywać stan, ale nie może nic zmieniać style zawiera deklaracje CSS styleclass wskazuje nazwę klasy CSS value wskazuje zewnętrzne źródło danych, z którym ma być związana wartość komponentu UIOutput binding wskazuje zewnętrzne źródło danych, z którym ma być związany komponent UIOutput
Wzorce komunikatów outputformat pozwala wklejać parametry do wzorców <h:outputformat value="#{bundle.cartitemcount}"> <f:param value="#{cart.numberofitems}"/> </h:outputformat> Jeżeli na kluczu CartItemCount jest napis: Your shopping cart contains {0,choice,0#no items 1#one item 1< {0}} items. to komunikat daje trzy możliwości: Your shopping cart contains no items. Your shopping cart contains one item. Your shopping cart contains {0} items.