Dynamically generate ice:commandButton components - jsf

I've been trying a lot of different things that I would think would work like expected. However, they are causing me some frustration. Here's the scoop:
I am using ICEFaces 1.8 components in a Java EE web application. My goal is to render a bunch of ice:commandButtons on the page based on a query to my database. I want these buttons to be able to toggle selections that I will later use for parameters to another query to the database (basically a query front end of sorts for a set of users). I would like the output to look like so:
When I click on a button, I would like the following update to my page:
When I created the buttons on my page statically, as such:
<ice:commandButton id="seasonSEP09" style="background-color: #FFFFFF;" partialSubmit="true" actionListener="#{bean.updateSeasons}" value="2009-2010" />
<ice:commandButton id="seasonSEP08" style="background-color: #FFFFFF;" partialSubmit="true" actionListener="#{bean.updateSeasons}" value="2008-2009" />
<ice:commandButton id="seasonSEP07" style="background-color: #FFFFFF;" partialSubmit="true" actionListener="#{bean.updateSeasons}" value="2007-2008" />
<ice:commandButton id="seasonSEP06" style="background-color: #FFFFFF;" partialSubmit="true" actionListener="#{bean.updateSeasons}" value="2006-2007" />
this works great, and each button works individually as I would expect. My backing bean is updated, the parameters are correctly added in updateSeasons() method, and my output at the end yields the correct records.
However, I know this is not what I want. I don't want to update these anytime another season is entered in the system. Maintainance nightmare, right?
So what I want to do is dynamically generate these ice:commandButton components based on my database table full of Season objects. Here is the Season class I am using:
public class Season
{
String StartMonth;
String Season;
public String getStartMonth()
{
return StartMonth;
}
public void setStartMonth(String startMonth)
{
StartMonth = sweep;
}
public void setSeason(String season)
{
Season = season;
}
public String getSeason()
{
return Season;
}
}
Very straightforward. Two properties, which I'm guaranteed to be unique in the database.
Here is the backing bean I am using:
public class Bean
{
public Bean()
{
defineSeasonsList();
}
public List<HtmlCommandButton> seasonsList;
// seasonsList getter & setter omitted
public List<String> selectedSeasons;
// selectedSeasons getter & setter omitted
private void defineSeasonsList()
{
seasonsList = new ArrayList<HtmlCommandButton>();
selectedSeasons = new ArrayList<String>();
try
{
hibernate.openTransaction();
for(Season season:defineSeasonsListFromDataSource()))
{
HtmlCommandButton button = new HtmlCommandButton();
button.setId("season" + season.getStartMonth());
button.setValue(season.getSeason);
button.setStyle("background-color: #FFFFFF;");
button.setPartialSubmit(true);
seasonsList.add(button);
}
}
catch (Exception e)
{
System.out.println("Error defining seasons list: " + e.getMessage());
}
finally
{
hibernate.commitTransaction();
}
}
public void updateSeasons(ActionEvent ae)
{
HtmlCommandButton selected = (HtmlCommandButton) ae.getComponent();
if(selectedSeasons.contains(selected.getValue().toString()))
{
selectedSeasons.remove(selected.getValue().toString());
selected.setStyle("background: #FFFFFF;");
}
else
{
selectedSeasons.add(selected.getValue().toString());
selected.setStyle("background: #009DD9; color: #FFFFFF;");
}
}
}
OK, so here comes my dilemma(s).
First, I tried to render this markup:
<p>
<ice:panelGroup>
<ice:panelSeries id="seasonsList" value="#{bean.seasonsList}" var="season">
<ice:commandButton binding="#{season}"/>
</ice:panelSeries>
</ice:panelGroup>
</p>
And I get this output:
So, being frustrated and adventurous, I tried to render this markup to achieve my goal:
<p>
<ice:panelGroup>
<ice:panelSeries id="seasonsList" value="#{bean.seasonsList}" var="season">
<ice:commandButton id="#{season.id}" partialSubmit="true" style="background-color: #FFFFFF" value="#{season.value}" actionListener="#{bean.updateSeasons}"/>
</ice:panelSeries>
</ice:panelGroup>
</p>
Which yielded the following stacktrace:
Aug 4, 2009 2:28:11 PM com.sun.faces.lifecycle.Phase doPhase
SEVERE: JSF1054: (Phase ID: RENDER_RESPONSE 6, View ID: /phase1.jspx) Exception thrown during phase execution: javax.faces.event.PhaseEvent[source=com.sun.faces.lifecycle.LifecycleImpl#1a477b7]
Aug 4, 2009 2:28:11 PM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet Persistent Faces Servlet threw exception
java.lang.IllegalArgumentException: #{season.id}
at javax.faces.component.UIComponentBase.validateId(UIComponentBase.java:549)
at javax.faces.component.UIComponentBase.setId(UIComponentBase.java:351)
at javax.faces.webapp.UIComponentTag.createComponent(UIComponentTag.java:219)
at javax.faces.webapp.UIComponentClassicTagBase.createChild(UIComponentClassicTagBase.java:486)
at javax.faces.webapp.UIComponentClassicTagBase.findComponent(UIComponentClassicTagBase.java:670)
at javax.faces.webapp.UIComponentClassicTagBase.doStartTag(UIComponentClassicTagBase.java:1142)
at com.icesoft.faces.component.CommandButtonTag.doStartTag(CommandButtonTag.java:741)
at com.icesoft.faces.webapp.parser.Parser.executeJspLifecycle(Parser.java:204)
at com.icesoft.faces.webapp.parser.Parser.executeJspLifecycle(Parser.java:229)
at com.icesoft.faces.webapp.parser.Parser.executeJspLifecycle(Parser.java:229)
at com.icesoft.faces.webapp.parser.Parser.executeJspLifecycle(Parser.java:229)
at com.icesoft.faces.webapp.parser.Parser.executeJspLifecycle(Parser.java:229)
at com.icesoft.faces.webapp.parser.Parser.executeJspLifecycle(Parser.java:229)
at com.icesoft.faces.webapp.parser.Parser.executeJspLifecycle(Parser.java:229)
at com.icesoft.faces.webapp.parser.Parser.executeJspLifecycle(Parser.java:229)
at com.icesoft.faces.webapp.parser.Parser.executeJspLifecycle(Parser.java:229)
at com.icesoft.faces.webapp.parser.Parser.executeJspLifecycle(Parser.java:229)
at com.icesoft.faces.webapp.parser.Parser.executeJspLifecycle(Parser.java:229)
at com.icesoft.faces.webapp.parser.Parser.executeJspLifecycle(Parser.java:229)
at com.icesoft.faces.webapp.parser.Parser.parse(Parser.java:162)
at com.icesoft.faces.application.D2DViewHandler.renderResponse(D2DViewHandler.java:464)
at com.icesoft.faces.application.D2DViewHandler.renderView(D2DViewHandler.java:153)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:110)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:100)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
at com.icesoft.faces.webapp.http.core.JsfLifecycleExecutor.apply(JsfLifecycleExecutor.java:17)
at com.icesoft.faces.context.View$2$1.respond(View.java:47)
at com.icesoft.faces.webapp.http.servlet.ServletRequestResponse.respondWith(ServletRequestResponse.java:197)
at com.icesoft.faces.webapp.http.servlet.ThreadBlockingAdaptingServlet$ThreadBlockingRequestResponse.respondWith(ThreadBlockingAdaptingServlet.java:36)
at com.icesoft.faces.context.View$2.serve(View.java:72)
at com.icesoft.faces.context.View.servePage(View.java:133)
at com.icesoft.faces.webapp.http.core.SingleViewServer.service(SingleViewServer.java:52)
at com.icesoft.faces.webapp.http.common.ServerProxy.service(ServerProxy.java:11)
at com.icesoft.faces.webapp.http.servlet.MainSessionBoundServlet$4.service(MainSessionBoundServlet.java:114)
at com.icesoft.faces.webapp.http.common.standard.PathDispatcherServer.service(PathDispatcherServer.java:24)
at com.icesoft.faces.webapp.http.servlet.MainSessionBoundServlet.service(MainSessionBoundServlet.java:160)
at com.icesoft.faces.webapp.http.servlet.SessionDispatcher$1.service(SessionDispatcher.java:42)
at com.icesoft.faces.webapp.http.servlet.ThreadBlockingAdaptingServlet.service(ThreadBlockingAdaptingServlet.java:19)
at com.icesoft.faces.webapp.http.servlet.EnvironmentAdaptingServlet.service(EnvironmentAdaptingServlet.java:63)
at com.icesoft.faces.webapp.http.servlet.SessionDispatcher.service(SessionDispatcher.java:62)
at com.icesoft.faces.webapp.http.servlet.PathDispatcher.service(PathDispatcher.java:23)
at com.icesoft.faces.webapp.http.servlet.MainServlet.service(MainServlet.java:153)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:286)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:845)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
at java.lang.Thread.run(Thread.java:619)
Am I trying to do something that I shouldn't be doing?
Is there a better way to accomplish this goal?
If more information is necessary I'd be happy to provide it.
Thanks in advance, my friends.
UPDATE:
So I tried changing the seasonsList collection from List to List and rendering some different markup, like so:
<p>
<ice:panelGroup>
<ice:panelSeries value="#{bean.seasonsList}" var="season">
<ice:commandButton partialSubmit="true" style="background-color: #FFFFFF" value="#{season}" actionListener="#{Phase1EventBean.updateSeasons}"/>
</ice:panelSeries>
</ice:panelGroup>
</p>
And changing the defineSeasonsList() method to:
public void defineNationalSeasonsList()
{
try
{
seasonsList = new ArrayList<String>();
selectedSeasonsList = new ArrayList<String>();
hibernate.openTransaction();
for(UedaNationalDates season:hibernate.getList(new UedaNationalDates(), QueryFactory.getUedaNationalSeasons(hibernate.getHibSession())))
{
nationalSeasonsList.add(season.getSeason());
}
}
catch (Exception e)
{
System.out.println("Error defining nationalMeasurementPeriods: " + e.getMessage());
}
finally
{
hibernate.commitTransaction();
}
}
This actually renders all the buttons I would like to see, and adds them correctly to the selectedSeasonsList in my backing bean when I click on them, and removes them from it when I click again.
However, on the UI, every button appears to be toggled when I click just the one button. For example, when I click on 2009-2010, this is what I see:

<ice:commandButton binding="#{season}"/>
The binding attribute must be bound to a bean property of type UIComponent. It is used where you want the framework to give you a reference to the component in a backing bean or to provide an instance from the backing bean). See section 3.1.5 of the JSF 1.2 spec for more details.
<ice:commandButton id="#{season.id}"
partialSubmit="true"
style="background-color: #FFFFFF"
value="#{season.value}"
actionListener="#{Phase1EventBean.updateSeasons}"/>
The id attribute cannot be dynamic - JSF will take care of ensuring its uniqueness on the client using the clientId (read this for more detail).
EDIT:
However, on the UI, every button appears to be toggled when I click just the one button.
I am guessing that ice:panelSeries does not store the component state of every row as some repeating controls do (e.g. dataTable). Remember, there is only one button instance, even if it is encoded/decoded once per "row".
I've never used ICEfaces, but I suggest binding to beans similar to this:
public class Bean {
private final List<SelectionBean> seasonsList = Arrays.asList(
new SelectionBean("Spring"), new SelectionBean("Summer"),
new SelectionBean("Autumn"), new SelectionBean("Winter"));
public List<SelectionBean> getSeasonsList() { return seasonsList; }
public static class SelectionBean {
private String season;
private boolean selected;
public SelectionBean() {}
public SelectionBean(String season) { this.season = season; }
public String getSeason() { return season; }
public void setSeason(String season) { this.season = season; }
public String toggle() {
System.out.println("toggle " + season);
selected = !selected;
return null;
}
public String getStyle() {
return selected ? "background-color: yellow" : "background-color: blue";
}
}
}
I've pared the logic down to the bare minimum, but hopefully you get how to modify the logic to put the hibernate support back in. Your component would then become something like this:
<ice:panelSeries value="#{bean.seasonsList}" var="item">
<ice:commandButton partialSubmit="true"
style="#{item.style}"
value="#{item.season}"
action="#{item.toggle}"/>
</ice:panelSeries>
So, for each item in your list, all the binding goes back to the one piece of state (a SelectionBean instance) and you don't try to store any non-declarative state on the component itself.
I try to use action over actionListener when I can - it keeps JSF stuff out of the POJOs.

Related

Trigger listener clicking on ace:textEntry

I'm using JSF 2.0 and I want to invoke a function defined in a Java controller when I click on an ace:textEntry.
I tried in this way:
<ace:textEntry readonly="true" value="#{myController.getValue()}"
onclick="#{myController.myFunc()}"/>
but when my page is open, the click event is called instantly.
So, I tried with:
<ace:textEntry readonly="true" value="#{myController.getValue()}">
<ace:ajax event="click" listener="#{myController.myFunc()}"/>
</ace:textEntry>
but my page is not rendered.
Is there another way to implement this behaviour ?
PS: I can use similar JSF components instead of ace:textEntry too.
First, you do not access getters directly in JSF for value backing - you access the property. Secondly, you should call the listener with the correct signature. To correct your example I would first rewrite the call like this,
<ace:textEntry readonly="true" value="#{myController.value}">
<ace:ajax event="click" listener="#{myController.myFunc}"/>
</ace:textEntry>
Then define MyController, like this;
#Named
#ViewScoped
public class MyController {
private value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public void myFunc(javax.faces.event.AjaxBehaviorEvent event) {
/* Do somethinig here... */
}
}

ClassCastException with Primefaces GMap OverlaySelect event

I want to show some data inside infoWindow of Gmap. the code is as follow:
<p:gmap center="#{mybean.latitude}, #{mybean.longitude}" zoom="15"
type="ROADMAP" model="#{mybean.mymapModel}" >
<p:ajax event="overlaySelect" listener="#{mybean.onMarkerSelect}" />
<p:gmapInfoWindow >
<div class="popup_title prem">Address:</div>
<div class="popup_address">#{mybean.markerAll.street}, #{mybean.markerAll.zipcode} #{mybean.markerAll.city}</div>
</p:gmapInfoWindow>
</p:gmap>
and My ManagedBean
public void onMarkerSelect(OverlaySelectEvent event) {
marker = (Marker) event.getOverlay();
markerAll = (myHelperClass) marker.getData();
}
Everything is working fine in development on my local computer. When the marker is clicked, the data (address) shows up in the infoWindow.
On production I see a lot of ClassCastException like:
FullAjaxExceptionHandler: An exception occurred during processing JSF ajax request. Error page '/error.jsf' will be shown.
java.lang.ClassCastException: java.lang.String cannot be cast
to com.mypackage.util.myHelperClass
at com.mypackage.myBean.onMarkerSelect(myBean.java:416)
...
...
The property data of the org.primefaces.model.map.Marker is of type Object and not String!
What am I doing wrong? And why this works sometime and sometimes not?
I'm using Primefaces 5.3.7
I think it's missing the uppercase letter m - myHelperClass by MyHelperClass
public void onMarkerSelect(OverlaySelectEvent event) {
marker = (Marker) event.getOverlay();
markerAll = (MyHelperClass) marker.getData();
}

CommandLink not Working on a Lazy Loaded Primefaces Datascroller

I'm having an issue with lazy loading a Primefaces Datascroller component.
I have a jsf page that should display 10 events on page load. If the user wants to see more he/she can click the more button to load and display the 10 next events. For each of the event rows, there is a link that can be used to display the event's details.
<h:form id="mainForm" >
<p:dataScroller value="#{backing.lazyModel}" var="event" lazy="true" chunkSize="10" rowIndexVar="index">
#{event.name}
<p:commandLink class="view-trigger"
value="View Event Details"
actionListener="#{backing.initViewEventDetails(index, event)}"/>
<f:facet name="loader">
<p:outputPanel
visible="#{backing.lazyModel.rowCount gt 10}"
rendered="#{backing.lazyModel.rowCount gt 10}">
<p:commandLink value="More" />
</p:outputPanel>
</f:facet>
</p:dataScroller>
</h:form>
The initial search works fine, that is, when I click the view event details link, my backing bean is invoked and I see that the index and event received correspond to the row I clicked on.
However, once I load the next chunk, which consists of 1 extra event, the page displays 11 events but clicking a view event details link sends the proper index but does not send the proper event. For example, if I click on event at index 0, I get the event at index 10, if I click on event at index 1 my backing bean is not invoked.
It looks like the datascroller forgets about the last 10 events when I click on the more button but my lazy data model still remembers.
The backing bean:
#ManagedBean(name="backing")
#ViewScoped
public class DataScrollerBacking implements Serializable {
private static final long serialVersionUID = 4012320411042043677L;
private static final Logger LOGGER = Logger.getLogger(DataScrollerBacking.class);
#ManagedProperty("#{settings.dataSource}")
private String dataSource;
private WebEventDAO webEventDAO;
private LazyDataModel<Event> lazyModel;
#PostConstruct
public void init() {
webEventDAO = CommonDAOFactory.getInstance(dataSource).getWebEventDAO();
search();
}
public void search() {
DateTime start = new DateTime(2014, 1, 1, 0, 0 ,0);
final Date startDate = start.toDate();
final Date endDate = start.plus(Years.ONE.toPeriod()).minus(Seconds.ONE.toPeriod()).toDate();
lazyModel = new LazyDataModel<Event>() {
private static final long serialVersionUID = 1231902031619933635L;
private LinkedHashSet<Event> eventCache; // Ordered set of all retrieved events so far.
// I'm using a set because the load method is called twice on page load (any idea why???) and I don't want duplicates in my cache.
#Override
public List<Event> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
List<Event> events = new ArrayList<Event>(10);
try {
if(eventCache == null){
int count = webEventDAO.getSearchByPeriodRaceTypeAndRaceStatusForCompanyCount(Collections.singletonList(1), startDate, endDate, null, null);
this.setRowCount(count);
eventCache = new LinkedHashSet<Event>(count);
}
events = webEventDAO.searchByPeriodRaceTypeAndRaceStatusForCompany(Collections.singletonList(1), startDate, endDate, null, null, true, first, pageSize);
eventCache.addAll(events);
} catch (DAOException e) {
LOGGER.error("An error occurred while retrieving events.", e);
}
return events;
}
};
}
public void initViewEventDetails(Integer index, Event event){
LOGGER.info("index=" + index + " eventname=" + event.getName());
}
public String getDataSource() {
return dataSource;
}
public void setDataSource(String dataSource) {
this.dataSource = dataSource;
}
public LazyDataModel<Event> getLazyModel() {
return lazyModel;
}
public void setLazyModel(LazyDataModel<Event> lazyModel) {
this.lazyModel = lazyModel;
}}
Since the page displays the proper information and the index received is always valid, my current workaround is to go fetch the Event in the lazy data model by index.
However, I would like to understand why the received event is not the one I clicked on.
Am I doing something wrong or this is just how the scroller is implemented?
Running on Mojarra 2.2, Tomcat 7, Primefaces 5, Omnifaces 1.8
I found a good explanation about the behavior of request scope in this link http://www.theserverside.com/news/thread.tss?thread_id=44186
If you are using ManagedBeans in request scope, you get problems with
CommandLinks inside DataTables. DataTables are one thing I really like
about JSF, and CommandLinks often come in handy as well. But when you
put a CommandLink inside a DataTable, e. g., to select the entry of
the row in which the CommandLink is, you get bitten. That is, if you
want ManagedBeans with request scope. The action which should be
triggered by the CommandLink is never triggered, the page is simply
rendered again. The reason for this behaviour is that the DataTable
modifies the id of the CommandLink during renderering, but the
CommandLink does not know that it was rendered with a different id.
During the decoding of the request which was triggered by clicking the
CommandLink, the ComandLinkRenderer looks at a hidden form parameter.
If the value of that form parameter equals the id of the CommandLink,
an action is queued. If not, nothing is done. Since the DataTable
changes the ids, the value of the hidden form parameter does not match
the id of the CommandLink.
Based on above context, you need to change the scope annotations from #ViewScoped to
#SessionScope, and your problem will be solved automatically. It seems to be a better solution than write additional code, unless you need to keep the #ViewScopped
A workaround would be to use PrimeFaces remote command, passing arguments with rc([name: 'paramName', value: someParamValue]). These arguments should be available using #{param['paramName']} EL expression
Example:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:body>
<ui:composition>
<p:dataTable id="#{id}" widgetVar="#{id}"
value="#{requestCache.getLazy(id, () -> dataSource)}" var="rpo"
selectionMode="single" selection="#{selection}"
lazy="true" paginator="true" rows="#{pageSizeController.pageSize}"
pageLinks="10" paginatorPosition="top"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
currentPageReportTemplate="(#{label.Page} {currentPage} #{label.of} {totalPages}, #{label.Row} {startRecord} - {endRecord} #{label.of} {totalRecords})"
rowsPerPageTemplate="5 10 20 30 40 50 100"
scrollable="true" scrollHeight="#{empty scrollHeight ? 300 : scrollHeight}"
resizableColumns="true" emptyMessage="#{label.Table_is_empty}">
<p:column>
<h:outputText value="#{rpo.decreeSequence.code}" />
<p:commandLink id="displayAdditionalInfoCommandLink" type="link" style="float: right; text-decoration: none"
onclick="displayAdditionalInfo([{name: 'refundPaymentOrderId', value: #{rpo.id}}])"
title="#{label.Additional_information}">
<h:outputLabel for="displayAdditionalInfoCommandLink" styleClass="fa fa-info-circle"
onmouseover="jQuery(this).addClass('fa-lg').css('cursor', 'pointer')"
onmouseout="jQuery(this).removeClass('fa-lg')"/>
</p:commandLink>
</p:column>
</p:dataTable>
<p:remoteCommand name="displayAdditionalInfo" process="#this" update="#parent">
<f:setPropertyActionListener target="#{refundPaymentOrderCache.refundPaymentOrder}"
value="#{refundPaymentOrderRepo.find(requestCache.toLong(param['refundPaymentOrderId']))}" />
<f:actionListener binding="#{dialog.displayInputForm('RPO_ADDITIONAL_INFO')}" />
</p:remoteCommand>
</ui:composition>
</h:body>
</html>
I finally had time to spend on this issue and I found a workaround. It's a hack so maybe the proper solution would be to use a different component or create my own.
It seems like Primefaces DataScroller limitation that occurs when using the DataScroller with a LazyDataModel. It would seem that the component was not designed to do this.
To avoid this issue, I implemented my own lazy loading where the same list instance is returned in addition to the newly added elements.
Here is my previous example modified to implement this new lazy loading pattern:
The html page:
<h:form id="mainForm" >
<p:dataScroller value="#{backing.events}" var="event" rowIndexVar="index">
#{event.name}
<p:commandLink class="view-trigger"
value="View Event Details"
action="#{backing.initViewEventDetails(index, event)}"/>
<f:facet name="loader"><h:outputText value=""/></f:facet>
</p:dataScroller>
<p:commandLink value="More" process="#form" update="#form"
action="#{backing.loadMore()}"
visible="#{backing.totalCount gt backing.events.size()}"
rendered="#{backing.totalCount gt backing.events.size()}"/>
</h:form>
The DataScroller no longer has lazy="true", chunkSize="10", uses a list called events as the value and declares an empty loader facet (to avoid auto-load more when the bottom of the list is reached). I used a commandLink that calls backing.loadMore() and updates the form to replace the loader facet.
The backing bean:
#Named("backing")
#ViewScoped
public class DataScrollerBacking implements Serializable {
private static final long serialVersionUID = 4012320411042043677L;
private static final Logger LOGGER = Logger.getLogger(DataScrollerBacking.class);
private static final Integer CHUNK_SIZE = 10;
#DataSource
#Inject
private String dataSource;
private WebEventDAO webEventDAO;
private List<Event> events;
private Integer totalCount;
private Date startDate;
private Date endDate;
#PostConstruct
public void init() {
webEventDAO = CommonDAOFactory.getInstance(dataSource).getWebEventDAO();
search();
}
public void search() {
DateTime start = new DateTime(2014, 1, 1, 0, 0 ,0);
startDate = start.toDate();
endDate = start.plus(Years.ONE.toPeriod()).minus(Seconds.ONE.toPeriod()).toDate();
try {
totalCount = webEventDAO.getSearchByPeriodRaceTypeAndRaceStatusForCompanyCount(Collections.singletonList(1), startDate, endDate, null, null);
events = new ArrayList<Event>(totalCount);
loadMore();
} catch (DAOException e) {
LOGGER.error("An error occurred while retrieving events.", e);
}
}
public void loadMore() {
List<Event> newEvents = new ArrayList<Event>(CHUNK_SIZE);
try {
newEvents = webEventDAO.searchByPeriodRaceTypeAndRaceStatusForCompany(Collections.singletonList(1), startDate, endDate, null, null, true, events.size(), CHUNK_SIZE);
events.addAll(newEvents);
} catch (DAOException e) {
LOGGER.error("An error occurred while retrieving events.", e);
}
}
public void initViewEventDetails(Integer index, Event event){
LOGGER.info("index=" + index + " eventname=" + event.getName());
}
public String getDataSource() {
return dataSource;
}
public void setDataSource(String dataSource) {
this.dataSource = dataSource;
}
public List<Event> getEvents() {
return events;
}
public void setEvents(List<Event> events) {
this.events = events;
}
public Integer getTotalCount() {
return totalCount;
}
public void setTotalCount(Integer totalCount) {
this.totalCount = totalCount;
}}
In the backing bean, the search method counts the total number of events, saves that information and calls loadMore() to load the first 10 events in the events list.
When the more button is clicked, loadMore() is called again and the next 10 events are appended at the end of events list.
Now when I click on newly loaded elements, the commandLink invokes the backing bean with the correct value.

Input fields hold previous values only if validation failed

I came up with a strange problem. I tried to isolate the problem so following is my simplified code.
public class MyBean {
private List<Data> dataList;
Data selectedData;
public MyBean() {
dataList = new ArrayList<Data>();
dataList.add(new Data("John", 16));
dataList.add(new Data("William", 25));
}
public List<Data> getDataList() {
return dataList;
}
public void edit(Data data) {
selectedData = data;
}
public void newData() {
selectedData = new Data(null, null);
}
public Data getSelectedData() {
return selectedData;
}
public class Data {
String name;
Integer age;
Data(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
}
xhtml:
<rich:modalPanel id="pop">
<h:form>
Name: <h:inputText value="#{myBean.selectedData.name}" required="true" id="txtName"/><br/>
Age : <h:inputText value="#{myBean.selectedData.age}" required="true" id="txtAge"/>
<a4j:commandButton value="Save"/>
<a4j:commandButton value="Close" onclick="Richfaces.hideModalPanel('pop');return false;"/>
<br/>
<rich:message for="txtName"/><br/>
<rich:message for="txtAge"/>
</h:form>
</rich:modalPanel>
<h:form>
<rich:dataTable value="#{myBean.dataList}" var="data">
<rich:column>#{data.name}</rich:column>
<rich:column>
<a4j:commandLink value="Edit" action="#{myBean.edit(data)}" reRender="pop" oncomplete="Richfaces.showModalPanel('pop')"/>
</rich:column>
</rich:dataTable>
<a4j:commandButton value="New" action="#{myBean.newData()}" reRender="pop" oncomplete="Richfaces.showModalPanel('pop')"/>
</h:form>
This is the path to error:
Load the page
Click the "Edit" link in first row(popup displays)
In popup, clear the "Age" field and click "Save".(Required message shown)
Click cancel(without filling "Age" field)
Click second link.
Now it shows irrelevant data(previous data). - This is the problem
Even when I click "New" button it shows incorrect data.
This happens only if a validation is failed in the popup.
Is there a solution for this?
This problem is in JSF 2 also recognized and explained in detail in the following answer: How can I populate a text field using PrimeFaces AJAX after validation errors occur? If you were using JSF 2, you could have used OmniFaces' ResetInputAjaxActionListener or PrimeFaces' <p:resetInput> or resetValues="true" for this.
To the point, you need to clear the state of the EditableValueHolder component when it's about to be ajax-rendered, but which isn't included in the ajax-execute. To clear the state, in JSF 2 you would have used the convenience method resetValue() for this, but this isn't available in JSF 1.2 and you need to invoke the 4 individual methods setValue(null), setSubmittedValue(null), setLocalValueSet(false), setValid(true) to clear the state.
To figure out which components are to be ajax-rendered, but aren't been ajax-executed, in JSF 2 you would have used PartialViewContext methods for this, but this is not available in JSF 1.2 which hasn't standardized ajax yet. You'd need to fiddle with RichFaces specific ajax API in order to figure that. I can't tell that from top of head, so here's a kickoff example assuming that you already know the components which needs to be cleared. Imagine that the form in your popup has id="popForm" and the name input field has id="nameInput", here's how you could clear it inside the newData() method:
UIInput nameInput = (UIInput) context.getViewRoot().findComponent("popForm:nameInput");
nameInput.setValue(null);
nameInput.setSubmittedValue(null);
nameInput.setLocalValueSet(false);
nameInput.setValid(true);
do one thing on cancel action set all popup values null. now in your next click all values set to be default.
or on click set all previous values null. and set all respective values after that.
I had the same problem. if you are using Primefaces, the solution is as simple as putting resetValues="true" on your p:commandLink or p:commandButton that loads the selected item.
After validation failed if you want to remain same as input data which you have pass as submission parameter, then set value attribute as your form bean name as mention below i.e.
<input type="text" id="fname" path="fname" value="${myFormBean.fname}"/>

ApplicationScoped Bean is null on rich:tab's actionListener-method

I'm using JSF 2.0 and Richfaces 3.3.2.
I'm having an issue on one of my pages.
First of all I have a main page which contains a rich:tabPanel with several rich:tabs, no problem there switching between them.
On the first tab of my page I have another tabPanel with some other tabs.
They all have an actionListener which will call the ViewScoped ManagedBean to edit some Data, which will be displayed on the clicked tab. The method to edit the data calls a method on an ApplicationScoped Bean which is correctly injected (It works in other tabs). When switching a tab on the second layer and the action-method is called, the ApplicationScoped Bean is null.
This only happens when I switch tabs (so it does not happen on the first tab, which is displayed by default) so I think it's related to the actionListener, but i can't figure out why. Unfortunately I can't provide the actual code but this is the rough structure of my page.
mainPage.xhtml
...
<rich:tabPanel>
<rich:tab>
<ui:include src="tabs/tab1.xhtml" />
</rich:tab>
otherTabs
</rich:tabPanel>
...
tab1.xhtml
...
<rich:tabPanel>
<rich:tab actionListener="#{viewScopedBean.method}">
<ui:include src="subtabs/subtab1.xhtml" />
</rich:tab>
<rich:tab actionListener="#{viewScopedBean.method2}">
<ui:include src="subtabs/subtab2.xhtml" />
</rich:tab>
</rich:tabPanel>
ViewScopedBean.java
#ManagedBean
#ViewScoped
public class ViewScopedBean {
#ManagedProperty(value="#{applicationBean}")
private ApplicationBean applicationBean;
private Data data;
public void init() {
...
data = applicationBean.retrieveData();
...
}
public void method(ActionEvent e) {
...
data = applicationBean.retrieveData();
...
}
public void method2(ActionEvent e) {
...
data = applicationBean.retrieveData();
...
}
// getters/setters
}
ApplicationBean.java
#ManagedBean
#ApplicationScoped
public class applicationBean {
public Data retrieveData() {
...
}
}
When clicking subtab2 I get a NullPointerException at the line data = applicationBean.getData(), so the applicationBean is null. This happens everytime on every tab I'm switching to, but never in the init() method, so the applicationBean should not be null.
I could not find any solution or hints on this problem and hope someone has an idea.
Thanks in advance
regards

Resources