While performing CRUD operations using JSF/PrimeFaces, a common method that resets managed bean fields/properties is generally needed which is to be invoked basically after one such operation is successfully completed so that the fields in the backing bean are reset to their initial (default) value.
Imaginary code :
#Named
#ViewScoped
public class Bean extends LazyDataModel<Entity> implements Serializable {
#Inject
private Service service; // EJB.
// Holds a list of selected rows in a <p:dataTable>.
private List<Entity> selectedValues; // Getter & setter.
private String someField; // Getter & setter.
// Other fields depending upon the business requirement.
public Bean() {}
#PostConstruct
private void init() {
// Do something.
}
#Override
public List<Entity> load(int first, int pageSize, List<SortMeta> multiSortMeta, Map<String, Object> filters) {
setRowCount(service.rowCount());
// Other complex logic as and when required.
return service.getList(first, pageSize, map, filters); // Returns a List<Entity>.
}
// Resets fields to their default value.
public void reset() {
someField = null;
selectedValues = null;
// Reset other fields to their default value.
}
// Add (insert submitted values to the database).
// This method is basically bound to an action(Listener) of <p:commandButton>.
public void submit() {
if (service.insert(someField)) {
// Add a FacesMessge to indicate a success.
reset(); // Calling reset.
} else {
// Add a FacesMessge to indicate a failure.
}
}
// Update the database using submitted values.
public void onRowEdit(RowEditEvent event) {
if (event.getObject() instanceof Entity) {
Entity entity = (Entity) event.getObject();
Entity newEntity = service.update(entity);
if (newEntity != null) {
// Update the model.
// Other things like adding a FacesMessage to indicate a success.
} else {
// Add a FacesMessage to warn against the null entity returned by the service layer.
}
} else {
// Add a FacesMessage to indicate a failure.
}
reset(); // Finally reset the fields to their initial/default value.
}
// Similarly, performing the delete operation also requires to call the reset() method.
}
The submit() method performing "insert" is basically associated with JSF/PrimeFaces command components like <p/h:commandButton> or <p/h:commandLink>. Such as.
<p:inputText value="#{bean.someField}"/>
<p:commandButton value="Submit"
actionListener="#{bean.submit}"
oncomplete="if(args && !args.validationFailed) {updateTable();}"/>
<!-- Updating a p:dataTable in question after the above p:commandButton completes. -->
<p:remoteCommand name="updateTable" update="dataTable" process="#this"/>
The following AJAX events associated with a <p:dataTable> also require to call the reset() method.
<p:ajax event="rowEdit"
onstart="..."
oncomplete="..."
update="..."
listener="#{bean.onRowEdit}"/>
<p:ajax event="rowEditCancel"
onstart="..."
oncomplete="..."
update="..."
listener="#{bean.reset}"/>
<p:ajax event="page"
onstart="..."
oncomplete="..."
update="..."
listener="#{bean.reset}"/>
<p:ajax event="sort"
onstart="..."
oncomplete="..."
update="..."
listener="#{bean.reset}"/>
<p:ajax event="filter"
onstart="..."
oncomplete="..."
update="..."
listener="#{bean.reset}"/>
As can be seen, the reset() method needs to be memorized carefully as it is invoked from several places. The way is somewhat difficult to maintain.
Does there exist a way to invoke such a common method automatically after each POST request performing one of the CRUD operations has finished its job successfully?
JSF doesn't have any tag for this.
Ideally, you'd like to have something like a <f:event type="postInvokeAction" listener="#{bean.reset}"> directly attached to the <p:dataTable>. But that event doesn't exist in JSF 2.2. OmniFaces has one, but it's only supported on UIViewRoot, UIForm, UIInput and UICommand.
The <f:phaseListener> comes close, but it gets attached to UIViewRoot directly, even though you place it inside <p:dataTable>. And, it requires a whole PhaseListener implementation.
The <f:view afterPhase> seems your best bet. You only need some additional checking on the phase ID and the source component.
<p:dataTable binding="#{table}" ...>
<f:view afterPhase="#{bean.reset(table)}" />
<p:ajax ... />
<p:ajax ... />
<p:ajax ... />
<p:ajax ... />
...
</p:dataTable>
public void reset(UIData table) {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() != PhaseId.INVOKE_APPLICATION) {
return;
}
String source = context.getExternalContext().getRequestParameterMap().get("javax.faces.source");
if (!table.getClientId(context).equals(source)) {
return;
}
// Reset logic here.
// ...
}
(binding could if necessary be replaced by a hardcoded table client ID)
I understand that this is awkward. So, for the upcoming OmniFaces 2.2 I have altered the existing InvokeActionEventListener to support this use case too. It now supports being attached on any UIComponent.
<p:dataTable ...>
<f:event type="postInvokeAction" listener="#{bean.reset}" />
<p:ajax ... />
<p:ajax ... />
<p:ajax ... />
<p:ajax ... />
...
</p:dataTable>
public void reset() {
// ...
}
Related
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... */
}
}
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.
I have a lazily loaded <p:dataGrid>. The corresponding XHTML code is as under.
<p:panel id="dataPanel" header="Data">
<p:dataGrid value="#{testManagedBean}" var="row" columns="3" rows="9" pageLinks="10" paginator="true" lazy="true">
<p:column>
<h:outputText id="lblCharge" value="#{row.weight}" converter="#{bigDecimalGeneralOutputConverter}"/><br/>
<p:inputText id="txtCharge" value="#{row.charge}" converter="#{bigDecimalGeneralConverter}"/>
</p:column>
<p:ajax event="page"/>
</p:dataGrid>
<p:commandButton id="btnSubmit" update="dataPanel" actionListener="#{testManagedBean.insert}" value="Save" icon="ui-icon-check"/>
<p:commandButton value="Reset" update="dataPanel" process="#this">
<p:resetInput target="dataPanel" />
</p:commandButton>
</p:panel>
The associated JSF managed bean looks like as show below.
#ManagedBean
#ViewScoped
public final class TestManagedBean extends LazyDataModel<ZoneChargeUtils> implements Serializable
{
#EJB
private final ZoneChargeBeanLocal zoneChargeService=null;
private static final long serialVersionUID = 1L;
#Override
public List<ZoneChargeUtils> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters)
{
setRowCount(zoneChargeService.rowCount(7L).intValue());
return zoneChargeService.getZoneChargeList(7L, first, pageSize);
}
public void insert() {
System.out.println("insert() called.");
}
}
The data grid displays a group of <p:inputText>s that looks something like the following.
A user can modify values held by <p:inputText> as can be seen in the snap shot. The modified values are to be stored into the database, when the given <p:commandButton> (save) immediately below the data grid is pressed.
The associated action listener insert() is invoked, when this <p:commandButton> is pressed but how to get these modified values in that action listener so that they can be sent to the database in question?
This can be done avoiding lazy loading as shown in this answer. This is the exact same scenario but with lazy loading.
How to get the list, List<ZoneChargeUtils> with new / modified values in <p:inputText>, when the given <p:commandButton> is clicked?
The utility class ZoneChargeUtils though completely unnecessary.
public final class ZoneChargeUtils implements Serializable
{
private Long weightId;
private BigDecimal weight;
private BigDecimal charge;
private static final long serialVersionUID = 1L;
//Getters and setters + constructor(s).
}
This is not a persistent entity / POJO. It is used to execute queries with constructor expressions in JPA
The solution to this problem involves keeping two buffers in your #ViewScoped managed bean, one for the whole set of the changed values and other one for the current view values. Moreover, you'll need to call a listener method everytime you switch the page to send your current values to the managed bean:
<p:panel id="dataPanel" header="Data">
<p:dataGrid value="#{testManagedBean}" var="row" columns="3"
rows="9" pageLinks="10" paginator="true" lazy="true">
<p:column>
<h:outputText id="lblCharge" value="#{row.weight}"
converter="#{bigDecimalGeneralOutputConverter}"/><br/>
<p:inputText id="txtCharge" value="#{row.charge}"
converter="#{bigDecimalGeneralConverter}"/>
</p:column>
<p:ajax event="page" listener="#{testManagedBean.pageChanged}"/>
</p:dataGrid>
<p:commandButton id="btnSubmit" update="dataPanel"
action="#{testManagedBean.insert}" value="Save" icon="ui-icon-check"/>
<p:commandButton value="Reset" update="dataPanel" process="#this">
<p:resetInput target="dataPanel" />
</p:commandButton>
</p:panel>
I made couple of changes here. One is adding a listener method to the ajax event for the page changing. The other one is replacing actionListener by action in your p:commandButton. action is the most convenient way to go when performing business actions like yours.
#ManagedBean
#ViewScoped
public final class TestManagedBean extends LazyDataModel<ZoneChargeUtils> implements Serializable
{
#EJB
private final ZoneChargeBeanLocal zoneChargeService=null;
private static final long serialVersionUID = 1L;
private Map<Integer, ZoneChargeUtils> bufferedZones = new HashMap<Integer, ZoneChargeUtils>();
private List<ZoneChargeUtils> currentZones = new ArrayList<ZoneChargeUtils>();
#Override
public List<ZoneChargeUtils> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters)
{
currentZones.clear();
setRowCount(zoneChargeService.rowCount(7L).intValue());
List loadedZones = zoneChargeService.getZoneChargeList(7L, first, pageSize);
for (ZoneChargeUtils zone : loadedZones){
if (bufferedZones.containsKey(zone.getId())){
//The zone has been loaded before in the view. Load the buffered value
//Otherwise, you'll lose it'll be overwritten with DB value when lazy loading
currentZones.add(bufferedZones.get(zone.getId()));
}else{
currentZones.add(zone);
}
}
return currentZones;
}
public void pageChanged(){
//The user has changed the page. Update bufferedZones with the possible new values
for (ZoneChargeUtils zone : currentZones){
bufferedZones.put(zone.getId(), zone);
}
}
//Looks for modified values in the CURRENT page before updating them.
//If value isn't there, return the one passed in the param
private ZoneChargeUtils lookUpInCurrent(ZoneChargeUtils zone){
for (ZoneChargeUtils z : currentZones){
if (zone.getId() == z.getId()){
return z;
}
}
return zone;
}
//Persist the values. Look in the general buffer, keeping in mind it has been updated in the last page change.
//Because of that, we have to look also in the current buffer for changes made in the current page.
public void insert() {
for (ZoneChargeUtils zone : bufferedZones.values()){
zoneChargeService.updateZone(lookUpInCurrent(zone));
}
}
}
For the managed bean, you're interested in keeping two values: the changes happened in the current page and the changes for the whole set of loaded pages. I use a List for the first one and a Map for the second, supposing you already have an Id defined for your ZoneChargeUtils. Steps are following:
When user loads the first page (or any page) the loadData method is called for the current value set. In order of returning the persisted values directly, you check them against the whole buffer. If the value has been loaded before, use the buffered value. Otherwise, present the persisted one.
When user changes the page, update the whole buffer with the current one. That way you are able to keep the modified values.
When user saves the changes, go to the whole buffer (the Map) and iterate over its values. Make an update for each one of the loaded entities.
The associated action listener insert() is invoked, when this
is pressed but how to get these modified values in
that action listener so that they can be sent to the database in
question?
Store the reference of changed 'entities' in a HashMap<Integer, ZoneChargeUtils >. Then execute those changes in insert(), by iterating through that HashMap. You need an ajax value change listener for the <p:inputText> to listen to the changes, and in order to modify/update the state storing HashMap.
It's slightly different that the solution proposed by Xtreme Biker, but the idea is same : Store the state-change in Map.
hy all,
First: i m not ok for do #ManagedBean and #ViewScoped in Class TestManagedBean where this Class extend LazyDataModel
the solution is:
public final class lazyZoneChargeUtils extends LazyDataModel<ZoneChargeUtils> implements Serializable{
#Override
public List<ZoneChargeUtils> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters)
{
currentZones.clear();
setRowCount(zoneChargeService.rowCount(7L).intValue());
List loadedZones = zoneChargeService.getZoneChargeList(7L, first, pageSize);
for (ZoneChargeUtils zone : loadedZones){
if (bufferedZones.containsKey(zone.getId())){
//The zone has been loaded before in the view. Load the buffered value
currentZones.add(bufferedZones.get(zone.getId()));
}else{
currentZones.add(zone);
}
}
return currentZones;
}
}
and :
#ManagedBean
#ViewScoped
public class TestManagedBean {
//all variable
private LazyDataModel<ZoneChargeUtils> listZoneChargeUtils;
#PostConstruct
public void init() {
//init listZoneChargeUtils
this.listZoneChargeUtils = new lazyZoneChargeUtils();
}
public void onEdit(RowEditEvent event) {
ZoneChargeUtils var = (ZoneChargeUtils) event.getObject();
//do your code and save object in DataBase
}
public void onCancel(RowEditEvent event) {
ZoneChargeUtils var = (ZoneChargeUtils) event.getObject();
FacesMessage msg = new FacesMessage("Message : ", var.getName());
FacesContext.getCurrentInstance().addMessage(null, msg);
}
/**
* #return the listZoneChargeUtils
*
* #author asghaier
*
* Created on 12/mag/2014
*/
public LazyDataModel<ZoneChargeUtils> getListZoneChargeUtils() {
return listZoneChargeUtils;
}
/**
* #param listZoneChargeUtils the listZoneChargeUtils to set
*
* #author asghaier
*
* Created on 12/mag/2014
*/
public void setListZoneChargeUtils(LazyDataModel<ZoneChargeUtils> listZoneChargeUtils) {
this.listZoneChargeUtils = listZoneChargeUtils;
}
//all getter and setter
}
and now in your page do this:
<p:panel id="dataPanel" header="Data">
<p:dataGrid value="#{testManagedBean.listZoneChargeUtils}" var="row" columns="3" rows="9" pageLinks="10" paginator="true" lazy="true" editable="true">
<p:ajax event="rowEdit" listener="#{testManagedBean.onEdit}" />
<p:ajax event="rowEditCancel" listener="#{testManagedBean.onCancel}" />
<p:column>
<h:outputText id="lblCharge" value="#{row.weight}" converter="#{bigDecimalGeneralOutputConverter}"/><br/>
<p:cellEditor>
<f:facet name="output">
<h:outputText value="#{row.charge}" />
</f:facet>
<f:facet name="input">
<p:inputText value="#{row.charge}" style="width:100%" converter="#{bigDecimalGeneralConverter}"/>
</f:facet>
</p:cellEditor>
</p:column>
<p:column >
<p:rowEditor />
</p:column>
</p:dataGrid>
this is in general haw you must do, ( i dont have see good all code).
resume:
1- do class extends LazyDataModel<...> and dont put any #ManagedBean or #ViewScoped
2- do class #ManagedBeanand and #ViewScoped and in the class creat variable in type
LazyDataModel<...> listData, implement getter and setter for this variabile, and init the variable listData in methode init() where is #PostConstruct
3- add editable="true" to your dataTable
4- in the class creat in step 2 add method onCancel & onEdit , in the methode onEdit do your code Java and save the object in database.
5- all is ok :)
In this case, the following approach using the getWrappedData() method of LazyDataModel<T> works as expected.
#ManagedBean
#ViewScoped
public final class TestManagedBean extends LazyDataModel<ZoneChargeUtils> implements Serializable
{
#EJB
private final ZoneChargeBeanLocal zoneChargeService=null;
private static final long serialVersionUID = 1L;
#Override
public List<ZoneChargeUtils> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters)
{
setRowCount(zoneChargeService.rowCount(7L).intValue());
return zoneChargeService.getZoneChargeList(7L, first, pageSize);
}
public void insert() {
//Returns the lazily loaded list with the modified values completely appropriate to the page size.
//Do whatever you want to do with the lazily loaded list now.
List<ZoneChargeUtils> zoneChargeUtils = (List<ZoneChargeUtils>) getWrappedData();
for(ZoneChargeUtils utils:zoneChargeUtils) {
System.out.println(utils.getWeightId()+" : "+utils.getWeight()+" : "+utils.getCharge());
}
}
}
The getWrappedData() method returns the list with modified values in <p:inputText> according to the page size defined, hence, eliminating the need of extraneous code to get the associated lazily loaded list back in the listener method.
I'm wondering what the best practices are to pass data (an object) between two ViewScoped beans.
They need to be view scoped because of the problem that's brilliantly explained here (to put it short: In both views I'm using a h:commandLink from within a h:dataTable which requires the data model to still be present when submitting).
My problem now is that clicking the link also navigates to a new view, so using the following code, my object gets passed but the DetailViewController instance gets killed right after that and a new one is created when the view changes (as you would expect).
View:
<h:dataTable value="#{searchController.dataModel}" var="item">
...
<h:column>
<f:facet name="header">Action</f:facet>
<h:commandLink id="open" value="open" action="#{searchController.showDetail(item)}" />
</h:column>
</h:dataTable>
Bean:
#ManagedBean
#ViewScoped
public class SearchController {
#ManagedProperty(value="#{detailViewController}")
private DetailViewController detailViewController;
// getters, setters, etc. ...
public String showDetail(Item i) {
detailViewController.setItem(i);
return "view_detail.xhtml";
}
}
How would you solve this? I thought about putting the object inside Flash: FacesContext.getExternalContext.getFlash()... Is there an easier or more elegant solution?
You can use view parameters. (See How do you pass view parameters when navigating from an action in JSF2?)
Typically, your method return the url with query parameters:
public String showDetail(Item i) {
return "view_detail.xhtml?id="+i.getId();
}
And in your view_detail.xhtml file, you add a f:viewParam tag evaluating to on of your bean field:
<f:metadata>
<f:viewParam name="id" value="#{myBean.id}" />
</f:metadata>
Then from your backing bean, you use that field to get your Item instance in your #postConstruct method.
If you don't use the f:viewparam tag, you can also fetch the request parameters to obtain the id.
private String id;
private Item item;
#PostConstruct
public void init() {
if (id != null) {
item = fetchItem(id);
} else {
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
Map<String, String> requestParameterMap = externalContext.getRequestParameterMap();
if (requestParameters.containsKey("id")) {
id = requestParameters.get("id");
item = fetchItem(id);
} else {
throw new WebServiceException("No item id in request parameters");
}
}
}
I have a JSF page which renders a text field depending on the value of a drop down using primefaces ajax listner. The dynamic rendering is done fine. but the problem is once I submit the form the the bound value of that textfield doesn't get bound instead it is shown as null.
this is the part of my JSF only the necessary fields are included here
<h:panelGroup id="textPanel" >
<h:form id="main" prependId="false">
<h:outputText value="WorkFlow ID:" />
<h:selectOneMenu id="workFlows" value="#{workFlowSelectionController.selectedWorkFlowId}" >
<p:ajax event="change" listener="#{workFlowSelectionController.dropDownChange}" update="textPanel"/>
<f:selectItems value="#{workFlowSelectionController.allActiveworkFlows}"/>
</h:selectOneMenu>
<p:inputText value="#{workFlowSelectionController.texField}" rendered="#{workFlowSelectionController.textfieldVisibility}"/>
<p:commandButton ajax="false" value="Next" action="#{workFlowSelectionController.addWorkFlowselectionDetails}"/>
</h:form>
</h:panelGroup>
this is my managed bean
#ManagedBean
#RequestScoped
public class WorkFlowSelectionController {
private boolean textfieldVisibility = false;
private String texField;
public void dropDownChange() {
logger.info("WorkFlowSelectionController.dropDownChange() entered");
if (selectedWorkFlowId != null) {
if (selectedWorkFlowId.equals("-1")) {
textfieldVisibility = true;
operationListStatus = false;
} else {
textfieldVisibility = false;
operationListStatus = true;
}
} else {
textfieldVisibility = false;
operationListStatus = true;
}
public void addWorkFlowselectionDetails() throws CloneNotSupportedException {
System.out.println("Selected Value of Text Field is" + texField);
}
public String getTexField() {
return texField;
}
public void setTexField(String texField) {
this.texField = texField;
}
}
i haven't included the dropdown code of the backing bean. i just need an idea of what i am doing wrong here if i remove the rendered attribute of the textfield it works fine.
thank you
Put the bean in the view scope instead of request scope. A request scoped is recreated on every single HTTP request. The boolean property will default to false again whenever you submit the form, so the submitted value won't be processed then.
#ManagedBean
#ViewScoped
public class WorkFlowSelectionController {
//
}
A view scoped bean will live as long as you're (ajax-) interacting with the same view by returning null or void from action(listener) methods.