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