JSF Course part 1

advertisement
JSF cursus deel 1
oefeningen
JSF Basics en Facelets
in samenwerking met
HowITsDone
Oefening 1
• bestudeer de applicatie
• facelet template & /pages01/person/
• Java code:
• handler01: PersonHandler
• domeinobject: Persoon
• service: PersonService + MockImpl
• web.xml
• faces config
Domein bean
• Drie benaderingen:
• gebruik de bean direct in je pagina
<managed-bean>
<managed-bean-name>persoon</managed-bean-name>
<managed-bean-class>nl.howitsdone.example.domain.Persoon</managedbean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
• injecteer de bean in je handler
<managed-bean>
<managed-bean-name>personHandler1</managed-bean-name>
<managed-beanclass>nl.howitsdone.example.web.jsf.handler01.PersonHandler</managedbean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>persoon</property-name>
<value>#{persoon}</value>
</managed-property>
• manage de bean zelf in je handler
Oefening 2
• voeg zoekfunctie op bsn toe
• Tips:
• gebruik de juiste finder methode in de
personService
• realiseer je dat het bestaande sessie Persoon
object niet wordt vervangen door het
gevonden Persoon object
Oplossing oefening 2
• probleem: pagina moet gevonden
Persoon object gaan gebruiken
• oplossing 1:
• gebruik #{handler.bean.property} in plaats
van #{bean.property}
• oplossing 2 (niet uitgewerkt):
• plaats de bean zelf in sessie:
FacesContext.getCurrentInstance().getExternalContext()
.getSessionMap().put(“persoon”, mijnGevondenPersoon);
Oplossing oefening 2 (2)
• probleem: Persoon object dat bewaard
wordt is dezelfde instance als zoek object
• oplossing 1:
• verwijder injectie Persoon object en maak
elke keer een nieuwe aan
• oplossing 2 (niet uitgewerkt):
• gebruik een apart zoekveld in de handler
• oplossing 3 (niet uitgewerkt):
• injecteer een request Persoon object
Bean oefening 2
public class PersonHandler {
private PersonService personService;
private Persoon persoon = new Persoon();
public String save() {
LogUtil.println("save "+persoon);
personService.save(persoon);
return null;
}
public String findByBsn() {
LogUtil.println("findByBsn "+persoon.getBsn());
persoon = personService.findByBsn(persoon.getBsn());
return null;
}
public Persoon getPersoon() {
return persoon;
}
/**
public void setPersoon(Persoon persoon) {
System.out.println("injection of "+persoon);
this.persoon = persoon;
}
**/
}
dataTable
<h:dataTable value="#{handler.persons}" var="row" first="0" rows="10"
frame="box" columnClasses="bsn, name, birthdate, gender"
rowClasses="odd, even" headerClass="header">
<f:facet name="caption">#{messages.persons}</f:facet>
<h:column headerClass="bsn">
<f:facet name="header">#{messages.personId}</f:facet>
#{row.bsn}
</h:column>
<h:column >
<f:facet name="header">#{messages.personName}</f:facet>
#{row.voornaam} #{row.achternaam}
</h:column>
<h:column>
<f:facet name="header">#{messages.personBirthdate}</f:facet>
<h:outputText value="#{row.geboortedatum}">
<f:convertDateTime type="date" pattern="dd-MM-yyyy" timeZone="GMT+
</h:outputText>
</h:column>
<h:column>
<f:facet name="header">#{messages.personGender}</f:facet>
#{row.geslacht}
</h:column>
</h:dataTable>
dataTable data binding
• value attribute
•
•
•
•
•
•
Object (niet zo zinvol)
array
java.util.List
java.sql.ResultSet
javax.servlet.jsp.jstl.sql.Result
javax.faces.model.DataModel
• var attribute
• hiermee kun je huidige row aanspreken
dataTable styling
• columnClasses
• comma separated style classes per kolom
• rowClasses
• comma separated style classes per rij
• headerClass, geldt voor alle headers
• footerClass, geldt voor alle footers
• <h:column> specifiek
• headerClass
• footerClass
Oefening 3
• voeg zoekfunctie op achternaam toe
• hoofdletter onafhankelijk
• ‘starting with’ (suggest)
• Tips:
• gebruik <h:dataTable> om de zoekresultaten
te tonen
• gebruik de juiste finder methode in de
personService
Pagina oefening 3
<h:dataTable value="#{handler.persons}" var="row">
<f:facet name="caption">#{messages.persons}</f:facet>
<h:column>
<f:facet name="header">#{messages.personId}</f:facet>
#{row.bsn}
</h:column>
<h:column>
<f:facet name="header">#{messages.personName}</f:facet>
#{row.voornaam} #{row.achternaam}
</h:column>
<h:column>
<f:facet name="header">#{messages.personBirthdate}</f:facet>
<h:outputText value="#{row.geboortedatum}">
<f:convertDateTime type="date" pattern="dd-MM-yyyy"
timeZone="GMT+1"/>
</h:outputText>
</h:column>
<h:column>
<f:facet name="header">#{messages.personGender}</f:facet>
#{row.geslacht}
</h:column>
</h:dataTable>
Bean oefening 3
public String findByLastname() {
LogUtil.println("findByLastname "+persoon.getAchternaam());
persons = personService.findByLastname(persoon.getAchternaam());
return null;
}
public List getPersons() {
return persons;
}
DataTable select row
• gebruik DataModel in plaats van List
• model.setWrappedData( list data )
• row object = model.getRowData()
• plaats handler in sessie scope om dit te
laten werken
Oefening 4
• selecteer een persoon uit de dataTable
• Tips:
• gebruik ListDataModel in plaats van List
• maak de cellen clickable met
<h:commandLink>
• houd een selected persoon bij
• let op de scope van de handler (request is
een probleem)
Bean oefening 4
public class PersonHandler {
private PersonService personService;
private Persoon persoon;
private DataModel persons = new ListDataModel();
public String findByLastname() {
LogUtil.println("findByLastname "+persoon.getAchternaam());
persons.setWrappedData(
personService.findByLastname( persoon.getAchternaam()));
return null;
}
public String selectPerson() {
persoon = (Persoon) persons.getRowData();
return null;
}
public DataModel getPersons() {
return persons;
}
Pagina oefening 4
<h:column>
<f:facet name="header">#{messages.personId}</f:facet>
<h:commandLink value="#{row.bsn}" action="#{handler.selectPerson}"/>
</h:column>
DataTable sorteren
• geen support out of the box
• niet moeilijk zelf te schrijven
• Tomahawk heeft support
• RichFaces ook
Oefening 5
• sorteer op kolom
• Tips:
• keep it simple: commandLink in kolom header
• besteed sortering uit aan service die dan kan
besluiten de DB in te schakelen
• maar houd de service stateless
• dus geef sorteerrichting (ascending,
descending) mee
Bean oefening 5
public class PersonHandler {
private boolean ascBirthdate = true;
public String sortBirthdate() {
ascBirthdate = !ascBirthdate;
persons.setWrappedData(
personService.findByLastnameSortBirthdate(persoon.getAchternaam(),
ascBirthdate));
return null;
}
Pagina oefening 5
<h:column>
<f:facet name="header">
<h:commandLink value="#{messages.personBirthdate}"
action="#{handler.sortBirthdate}"/>
</f:facet>
Verwijder row
• geen support out of the box
• niet moeilijk zelf te schrijven
• meeste oplossingen vervuilen domein
object met boolean veld
• extend ListDataModel is een nette
oplossing
Oefening 6
• verwijder een persoon uit de dataTable
• Tips:
• gebruik h:selectBooleanCheckbox
• gebruik ListDataModelExt
Pagina oefening 6
<h:dataTable value="#{handler.persons}" var="row">
<h:column>
<f:facet name="header">#{messages.delete}</f:facet>
<h:selectBooleanCheckbox value="#{handler.persons.markDeletion}" />
</h:column>
Bean oefening 6
public class PersonHandler {
private ListDataModelExt persons = new ListDataModelExt();
public String delete() {
LogUtil.println("delete selected persons "+persons.getDeletedRows());
persons.setWrappedData(
personService.delete( (List)persons.getWrappedData(),
persons.getDeletedRows()
)
);
return null;
}
ListDataModelExt
public class ListDataModelExt extends ListDataModel{
private ArrayList deletions;
/**
* Geef terug of het huidige record (rowIndex) gemarkeerd is voor
verwijdering.
* @return
*/
public boolean isMarkDeletion() {
if (deletions==null || deletions.size() != super.getRowCount()) {
return false;
}
if (super.getRowCount() <= 0) {
return false;
}
Boolean b = (Boolean) deletions.get(super.getRowIndex());
return b.booleanValue();
}
ListDataModelExt (2)
/**
* Markeer het huidige record (rowIndex) voor verwijdering (true) of juist
* niet (false).
* @param markDeletion
*/
public void setMarkDeletion(boolean markDeletion) {
if (super.getRowCount() <= 0) {
return;
}
if (deletions==null || deletions.size() != super.getRowCount()) {
deletions = new ArrayList(super.getRowCount());
// init
for (int i=0; i < super.getRowCount(); i++) {
deletions.add(Boolean.FALSE);
}
}
deletions.set(super.getRowIndex(), Boolean.valueOf(markDeletion));
}
ListDataModelExt (3)
/**
* Geef een lijst van data objecten terug die gemarkeerd zijn om te
* verwijderen.
* @return
*/
public List getDeletedRows() {
ArrayList list = new ArrayList();
List data = (List) super.getWrappedData();
for (int i=0; i < deletions.size(); i++) {
Boolean b = (Boolean) deletions.get(i);
if (b.booleanValue()) {
list.add(data.get(i));
}
}
return list;
}
Dropdown list
• list data door middel van:
• <f:selectItem itemValue=.. itemLabel=../>
child tags
• of: array/ List van SelectItem objecten
• er is ook een itemDisabled attribute/property
• Belast de database niet onnodig: cache
de statische lijstjes, bijvoorbeeld met
static
• Let dan wel op de getter: geen static
methode!
Oefening 7
• introduceer dropdown list bij man/vrouw
keuze (alleen bij het invulscherm)
• Tips:
• gebruik h:selectOneListbox
Bean oefening 7
public class PersonHandler {
private static List genderOptions;
static {
genderOptions = new ArrayList();
genderOptions.add( new SelectItem(Geslacht.MAN));
genderOptions.add( new SelectItem(Geslacht.VROUW));
}
// niet static !!
public List getGenderOptions() {
return genderOptions;
}
Pagina oefening 7
<td>
<h:selectOneListbox value="#{handler.persoon.geslacht}" size="1">
<f:selectItems value="#{handler.genderOptions}"/>
</h:selectOneListbox>
</td>
Internationalization (i18n)
• aantal aandachtspunten:
• zet locale in faces config
• gebruik alleen tekst uit properties file of een
ander schakelmechanisme
• voorkom zomertijd/ wintertijd problemen
door het zetten van timeZone:
<f:convertDateTime type="date"
pattern="dd-MM-yyyy" timeZone="GMT+1"/>
• gebruik altijd itemValue en itemLabel bij
SelectItem waarbij value taalneutraal is
Oefening 8
• I18n: zorg dat de man/vrouw keuze
rekening houdt met de taal (current
locale)
• Tips:
• gebruik WebUtil.getMessage( key)
Bean oefening 8
public class PersonHandler {
private static List genderOptions;
static {
genderOptions = new ArrayList();
genderOptions.add( new SelectItem(Geslacht.MAN,
WebUtil.getMessage("man")));
genderOptions.add( new SelectItem(Geslacht.VROUW,
WebUtil.getMessage("woman")));
}
…
}
messages.properties:
man
woman
man
vrouw
Facelet functions
• wrapper om een static methode
• aan te roepen met
#{ns-prefix:functienaam
( argument1, argument2) }
• argumenten kunnen literals zijn maar ook
variabelen:
• #{howitsdone:showMessage( ‘hallo’) }
• #{howitsdone:showObject( persoon) }
Oefening 9
• I18n: zorg dat de man/vrouw keuze in de
dataTable ook rekening houdt met de
taal (current locale)
• Tips:
• gebruik facelet functie
#{howitsdone:getMessage( key)}
Pagina oefening 9
<h:column>
<f:facet name="header">#{messages.personGender}</f:facet>
#{howitsdone:getMessage( row.geslacht )}
</h:column>
en dus in messages properties:
MAN
VROUW
man
vrouw
en dan ook elders:
genderOptions.add( new SelectItem(Geslacht.MAN,
WebUtil.getMessage(Geslacht.MAN.toString())));
genderOptions.add( new SelectItem(Geslacht.VROUW,
WebUtil.getMessage(Geslacht.VROUW.toString())));
Taglib facelet function
<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0
"http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
<facelet-taglib>
<namespace>http://howitsdone.nl/taglib</namespace>
<function>
<function-name>getMessage</function-name>
<function-class>nl.howitsdone.example.web.jsf.WebUtil</function-class>
<function-signature>java.lang.String getMessage(java.lang.String)</functionsignature>
</function>
</facelet-taglib>
Facelet tags
• normale tag syntax:
• <ns-prefix:tagnaam/>
• attributen zijn mogelijk maar impliciet
<howitsdone:date days=…/>
• aparte file, bijvoorbeeld date.xhtml:
• <ui:composition>
<… #{days} …/>
</ui:composition>
Facelet tags (2)
• action bindings kun je NIET overdragen
• truc: bean en action apart overdragen:
<howitsdone:link bean=… action=… label=…/>
• <ui:composition>
<h:commandLink action=“#{bean[action]}”
value=“#{label}”/>
…
</ui:composition>
Oefening 10
• Zorg dat het invoeren van de
geboortedatum via drie dropdown lijstjes
gebeurt: dag, maand, jaar
• Tips:
• gebruik facelet tag <howitsdone:date>
Pagina oefening 10
<tr>
<td><h:outputLabel value="#{messages.personBirthdate}" for="birthdate"/></td>
<td>
<howitsdone:date id="birthdate"
days="#{handler.birthdays}"
selectedDay="#{handler.birthday}"
months="#{handler.birthmonths}" selectedMonth="#{handler.birthmonth}"
years="#{handler.birthyears}" selectedYear="#{handler.birthyear}"
/>
</td>
<td/>
</tr>
Tag date
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<div>
<h:selectOneListbox size="1" value="#{selectedDay}" id="#{id}">
<f:selectItems value="#{days}"/>
</h:selectOneListbox>
<h:selectOneListbox size="1" value="#{selectedMonth}" >
<f:selectItems value="#{months}"/>
</h:selectOneListbox>
<h:selectOneListbox size="1" value="#{selectedYear}" >
<f:selectItems value="#{years}"/>
</h:selectOneListbox>
</div>
</ui:composition>
Taglib facelet tag
<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD
Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelettaglib_1_0.dtd">
<facelet-taglib>
<namespace>http://howitsdone.nl/taglib</namespace>
<tag>
<tag-name>date</tag-name>
<source>date.xhtml</source>
</tag>
</facelet-taglib>
Bean oefening 10
private final static List birthyears;
private final static List birthmonths;
private final static List birthdays;
static {
birthdays = new ArrayList();
birthdays.add(new SelectItem(Integer.valueOf(0),WebUtil.getMessage("empty")));
for (int i = 1; i < 32; i++) {
birthdays.add(new SelectItem(Integer.valueOf(i), Integer.toString(i)));
}
birthmonths = new ArrayList();
birthmonths.add(new SelectItem(Integer.valueOf(0),WebUtil.getMessage("empty")));
for (int i=1;i<=12;i++) {
birthmonths.add(new SelectItem(Integer.valueOf(i),
WebUtil.getMessage("month"+i)));
}
Calendar kalender = new GregorianCalendar();
birthyears = new ArrayList();
int year = kalender.get(Calendar.YEAR);
birthyears.add(new SelectItem(Integer.valueOf(0),WebUtil.getMessage("empty")));
for (int i=1900;i <= year; i++) {
birthyears.add(new SelectItem(Integer.valueOf(i),Integer.toString(i)));
}
Bean oefening 10 (2)
// niet static !!
public List getBirthyears() {
return birthyears;
}
public List getBirthmonths() {
return birthmonths;
}
public List getBirthdays() {
return birthdays;
}
Bean oefening 10 (3)
private int birthday;
private int birthmonth;
private int birthyear;
public int getBirthday() {
return birthday;
}
public void setBirthday(int birthday) {
this.birthday = birthday;
}
public int getBirthmonth() {
return birthmonth;
}
public void setBirthmonth(int birthmonth) {
this.birthmonth = birthmonth;
}
public int getBirthyear() {
return birthyear;
}
public void setBirthyear(int birthyear) {
this.birthyear = birthyear;
}
Bean oefening 10 (4)
public String save() {
fillGeboortedatum();
personService.save(persoon);
// let op month -1
private void fillGeboortedatum() {
if (persoon != null) {
Calendar c = new GregorianCalendar(birthyear, birthmonth-1,
birthday);
persoon.setGeboortedatum(c.getTime());
}
}
Bean oefening 10 (5)
public String findByBsn() {
persoon = personService.findByBsn(persoon.getBsn());
showGeboortedatum();
public String selectPerson() {
persoon = (Persoon) persons.getRowData();
showGeboortedatum();
private void showGeboortedatum() {
// init
birthday=0;
birthmonth=0;
birthyear=0;
if (persoon != null && persoon.getGeboortedatum() != null) {
Calendar c = new GregorianCalendar();
c.setTime(persoon.getGeboortedatum());
birthday = c.get(Calendar.DAY_OF_MONTH);
birthmonth = c.get(Calendar.MONTH);
birthyear = c.get(Calendar.YEAR);
}
}
Validatie
• pagina
• attributen op <h:inputXXX en <h:selectXXX
required=“true” requiredMessage=“#{…}”
validator=“#{method expression}”
• child tags in <h:inputXXX en <h:selectXXX
• <f:validateLength>
• <f:validateLongRange>,<f:validateDoubleRange>
• schrijf je eigen validator
• <f:validator validatorId=“
• gebruik een validator van bijv. Tomahawk
• <t:validateRegExpr pattern="[1-9]{1}[0-9]{3}\s?[azA-Z]{2}"/>
Validatie (2)
• JSF validatie fase (fase 3)
• zogauw er een veld invalide is bevonden gaat
lifecycle door naar fase 6
• m.a.w. je komt nooit bij je action methodes
• in onze oefeningen werkt daardoor de
zoekfunctionaliteit niet meer
• oplossing 1: immediate=true op
commandButton, maar… zoekwaarde is
niet overgezet (fase 4) dus werkt niet
• oplossing 2: validatie in code
Validatie (3)
• code
• zelf schrijven in action/actionListener methode
• zie ook WebUtil.java voor helpers
public String save() {
if (birthday==0 || birthmonth==0 || birthyear==0) {
WebUtil.getSetGlobalErrorMessage("birtdayNotComplete");
return null;
}
--------public static String getMessage(String key) {
ELContext elContext= FacesContext.getCurrentInstance().getELContext();
ELResolver elResolver =
FacesContext.getCurrentInstance().getApplication().getELResolver();
Object context = elResolver.getValue(elContext, null, "messages");
return (String) elResolver.getValue(elContext, context, key);
}
public static void getSetGlobalErrorMessage(String key) {
String message = getMessage(key);
if (message != null) {
FacesMessage fMessage = new FacesMessage();
fMessage.setSummary(message);
fMessage.setSeverity(FacesMessage.SEVERITY_ERROR);
FacesContext.getCurrentInstance().addMessage(null, fMessage);
Meldingen tonen
• javax.faces.application.FacesMessage
• severity: fatal, error, warn, info
• summary & detail
• global of gebonden aan component
•
•
facesContext.addMessage(null, facesMessage)
facesContext.addMessage(“mainform:lastname”,
facesMessage)
• alle meldingen: <h:messages>
• alleen de summary (default)
• ook de detail meldingen: showDetail=“true”
• melding per component: <h:message
for=
Oefening 11
• Zorg dat voornaam, achternaam en
geslacht verplichte velden worden
• Zorg dat bsn groter dan 0 is
• Tips:
• gebruik required en validateLongRange
• bekijk de meldingen die het template via
<h:messages> toont
• zorg dat de meldingen gebruikersvriendelijker
worden
Oefening 12
• Zorg dat geboortedatum gevuld is
• Tips:
• check de drie waardes in de handler: save()
• extra: check ook of de datum echt klopt,
bijvoorbeeld accepteer geen 30 feb
• eventueel: plaats validatie in het domein
object en roep het aan vanuit de service in
plaats van de handler; voordeel is dat
validatie ook plaatsvindt als je niet via JSF
binnenkomt (tip: JValidate framework)
Oefening 13
• Maak een eigen validator om te testen of
het bsn nummer een geldig sofinummer is
Download