I have a JSF 2 application that has two pages, one to list students and one to show details of a given student. The listing page has a link to the details page in each row of the students table, that opens a new tab in browser to show those details, when clicked.
Now the requirements changed to no more show details in a new tab, but in a modal dialog in the listing page.
My idea is to simply embed the details page content in the modal dialog so the listing page will not get too big and hard to maintain. Here start my doubts. After some research I changed the link in each row of the listing to the following button:
<p:commandButton value="Details" type="button"
onclick="PF('dialog-details').show()">
</p:commandButton>
The dialog is declared as follows:
<p:dialog widgetVar="dialog-details" header="Details" modal="true" width="95%">
<ui:include src="student_details.xhtml">
<ui:param name="id" value="#{student.id}"/>
</ui:include>
</p:dialog>
Finally, the details page was changed to be something like this:
<ui:composition
xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui" xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<f:metadata>
<f:viewParam name="id" value="#{studentBean.id}" />
</f:metadata>
<h1 class="title ui-widget-header ui-corner-all">Details of #{studentBean.bean.name} / #{studentBean.bean.number}</h1>
</ui:composition>
When I click the button, the dialog really shows and the content is the details page. I see the following content in the dialog:
Details of /
No errors at all, but the data that should be shown, isn't. A breakpoint was set in StudentBean.setId() (this method loads a property named bean with the Student instance corresponding to the passed id) but it is never hit.
After some time thinking about it, I came to understand why it does not work. The parameter passed to the details page is student.id, but student is the name used as the var in the <p:datatable/> that show all the students, so student is not valid in <p:dialog/> which is outside the <p:datatable/>.
So, what I need is a way to show the dialog using the id of the corresponding student in a given row. Ideally, I would like an ajax call here, so the details would loaded only when neded.
Any ideas?
The button should be an ajax button which sets the currently iterated entity in the bean, and then updates the dialog's content, and finally shows it. The dialog should just reference that entity in the bean and update the list and table on save. It's very important that dialog is placed outside the main form and that it has its own form.
Here's a kickoff example:
<h:form id="master">
<p:dataTable value="#{bean.entities}" var="entity">
<p:column>#{entity.property1}</p:column>
<p:column>#{entity.property2}</p:column>
<p:column>#{entity.property3}</p:column>
...
<p:column>
<p:commandButton value="View" action="#{bean.setEntity(entity)}"
update=":detail" oncomplete="PF('detail').show()" />
</p:column>
</p:dataTable>
</h:form>
<p:dialog id="detail" widgetVar="detail">
<h:form>
<p:inputText value="#{bean.entity.property1}" />
<p:inputText value="#{bean.entity.property2}" />
<p:inputText value="#{bean.entity.property3}" />
...
<p:button value="Close" onclick="PF('detail').hide(); return false" />
<p:commandButton value="Save" action="#{bean.save}"
update=":master" oncomplete="if(!args.validationFailed) PF('detail').hide()" />
</h:form>
</p:dialog>
With this inside a #ViewScoped bean:
private List<Entity> entities; // +getter
private Entity entity; // +getter+setter
#EJB
private EntityService entityService;
#PostConstruct
public void load() {
entities = entityService.list();
entity = null;
}
public void save() {
entityService.save(entity);
load();
}
See also:
Creating master-detail pages for entities, how to link them and which bean scope to choose
Creating master-detail table and dialog, how to reuse same dialog for create and edit
Keep p:dialog open when a validation error occurs after submit
Difference between rendered and visible attributes of <p:dialog>
How to display dialog only on complete of a successful form submit
Related
I have a 'big' project, big table and a lot of relations... So in my form, I have several tabs, some dialogs, and a lot of PrimeFaces commandButtons to manage all the CRUD with Ajax requests.
The xhtml structure is something like that:
<ui:define name="content">
<h:body>
<p:growl id="growl"/>
<h:form id="formCars" enctype="multipart/form-data">
<p:tabView id="formTabView" scrollable="true" >
<p:tab title="Tab1">
<ui:include src="tabOne/tabOne.xhtml" />
</p:tab>
<p:tab title="Tab2">...</p:tab>
...
<p:tab title="Tab8">...</p:tab>
</p:tabView>
<br />
<div align="center">
<p:commandButton id="cmdSave" value="Save"
action="#{controllerMB.save}" icon="ui-icon-disk"
update="#form :growl" process="#form #this" validateClient="true" />
</div>
</h:form>
<p:dialog header="Car Color" id="modalCarColor" widgetVar="modalCarColor" appendToBody="true" modal="true" height="500" width="700" dynamic="true" closeOnEscape="true" >
<p:ajax event="close" listener="#{controllerMB.closeModalCarColor}" update="#this,:formCarColor"/>
<h:form id="formCarColor">
<ui:include src="tabTwo/carColor/carColor.xhtml" />
</h:form>
</p:dialog>
<p:dialog header="Car Size" ...>
<p:ajax event="close" .../>
<h:form id="formCarColor">...</h:form>
</p:dialog>
</h:body>
</ui:define>
And it works flawlessly...
The problem is when I try to reload the page...
I manage to keep the main object on the flash by using
FacesContext.getCurrentInstance().getExternalContext().getFlash().keep(key)
on the #PostConstruct of the #ViewScoped controller.
If I access the page and dont touch the ajax buttons, I can reload the page without a problem... But, if I do touch anything that causes and ajax request, the JSF Flash Scopre loses the main object, therefore I can't reload the page.
My controller is built this way:
#ManagedBean(name="controllerMB")
#ViewScoped
public class ControllerMB implements Serializable{
// attributes
// ----------
#PostConstruct
public void init(){
if(FacesContext.getCurrentInstance().getExternalContext().getFlash().containsKey("car")) {
car = (Car) FacesContext.getCurrentInstance().getExternalContext().getFlash().get("car");
FacesContext.getCurrentInstance().getExternalContext().getFlash().keep("car");
}
if(car==null) {
redirect_back();
return;
}
// attributes initialization and form preparation
// -----
}
// methods
// ----------
}
The "car" as main object is just an example.
Is there any solution or workaround for this?
I want to keep the state after an 'accidental reload' but, it is not working with Flash.
Also, is it possible to capture/handle the reload event from browser (maybe with js)? I mean, then I could process the form in my main object before reload.
I'm using PrimeFaces 5.3, JSF 2, Maven, Tomcat 7 and Java 7 in my project.
Thank you all in advance.
Edit 1: After trying the solution mentioned by the user NightEagle, it almost worked... I modified the code:
<ui:composition ....>
<f:metadata>
<f:event type="preRenderView" listener="#{controllerMB.preRender}"/>
</f:metadata>
<ui:define name="title">...</ui:define>
<ui:define name="header">...</ui:define>
<ui:define name="content">...</ui:define>
</ui:composition>
and
public void preRender()
{
System.out.print("PRE-RENDER ");
if(FacesContext.getCurrentInstance().getExternalContext().getFlash().containsKey("car")) {
System.out.println("KEEP");
FacesContext.getCurrentInstance().getExternalContext().getFlash().keep("car");
} else {
System.out.println("PUT");
FacesContext.getCurrentInstance().getExternalContext().getFlash().put("car",car);
}
}
The weird thing is, the first ajax call is good, after the second... it starts popping the JSF1095 error.
The output after some dialogs openings:
PRE-RENDER PUT
PRE-RENDER KEEP
PRE-RENDER KEEP
Jul 14, 2017 11:43:59 AM com.sun.faces.context.flash.ELFlash setCookie
WARNING: JSF1095: The response was already committed by the time we tried to set the outgoing cookie for the flash. Any values stored to the flash will not be available on the next request.
PRE-RENDER PUT
Jul 14, 2017 11:44:00 AM com.sun.faces.context.flash.ELFlash setCookie
WARNING: JSF1095: The response was already committed by the time we tried to set the outgoing cookie for the flash. Any values stored to the flash will not be available on the next request.
PRE-RENDER PUT
Jul 14, 2017 11:44:04 AM com.sun.faces.context.flash.ELFlash setCookie
WARNING: JSF1095: The response was already committed by the time we tried to set the outgoing cookie for the flash. Any values stored to the flash will not be available on the next request.
Thank you all in advance, still finding a solution.
I think that i know how to resolve it (it helped me in my project)
<f:metadata>
<f:event type="preRenderView" listener="#{myBean.preRender}"/>
</f:metadata>
backed bean:
public class MyBean
{
#PostConstruct
public void init(){
if(FacesContext.getCurrentInstance().getExternalContext().getFlash().containsKey("car")) {
car = (Car) FacesContext.getCurrentInstance().getExternalContext().getFlash().get("car");
FacesContext.getCurrentInstance().getExternalContext().getFlash().keep("car");
}
}
public void preRender()
{
if(FacesContext.getCurrentInstance().getExternalContext().getFlash().containsKey("car")) {
FacesContext.getCurrentInstance().getExternalContext().getFlash().keep("car");
}
}
}
"The preRenderView event is invoked on every HTTP request (yes, this also includes ajax requests!)." (c) https://stackoverflow.com/a/9844616/3163475
My dialog of type "okCancel" does not trigger any event on the server when the ok button is clicked.
The cancel button is working fine, just hiding the popup.
I'm using Oracle ADF Faces 12.1.3 and JBoss seam 2.3 for injecting beans.
Here is the popup and dialog code :
<af:form id="mainForm">
<af:panelStretchLayout id="psl1">
<!-- page layout -->
</af:panelStretchLayout>
<af:popup id="myPopup"
binding="#{bkBean.myPopup}"
contentDelivery="lazyUncached">
<af:dialog id="myDialog" closeIconVisible="true"
modal="true" okVisible="true" cancelVisible="true"
title="Import from server to #{bkBean.file.name}"
type="okCancel"
dialogListener="#{bkBean.doSomething}">
<af:panelFormLayout id="pfl1">
<!-- Several input text here -->
</af:panelFormLayout>
</af:dialog>
</af:popup>
</af:form>
Here is the code for the dialogListener. I set a debug point at the if statement:
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
#Name("exportImportServer")
#Scope(ScopeType.SESSION)
public class ExportImportServerBean implements Cleanable {
public void importFromServer(DialogEvent event) {
if (event.getOutcome() == DialogEvent.Outcome.ok) {
}
}
}
The exportImportServer backing bean is in session scope.
If I toggle a break point in the first code line inside the exportImportServer backing bean importFromServer method it does not stop inside. This method does not get called. It seems that nothing happens (even I have no errors) except that the main form is submitted.
In debug mode after a click on the ok button I'm able to stop inside a PhaseListener beforePhase(PhaseEvent phaseEvent) method. But I don't really know if the backing bean method should be called after the PhaseListener beforePhase(PhaseEvent phaseEvent) method.
I'm searching for any hint on how could I debug it ?
Is there any javascript called when Ok button is clicked on dialog ?
UPDATE: it seems that the problem comes from the fact that the popup is called from a region of the page and defined in another page (the main page containing the region) as seen in the code below :
Inside main.xthml:
<af:document>
<af:form id="mainForm">
<af:panelStretchLayout id="psl1" topHeight="auto" bottomHeight="0">
<f:facet name="center">
<af:region id="myRegion" showHeader="never"
value="#{main.regionModel}" />
</f:facet>
</af:panelStretchLayout>
</af:form>
<af:popup id="myPopup"
binding="#{bkBean.myPopup}"
contentDelivery="lazyUncached">
<af:dialog id="myDialog" closeIconVisible="true"
modal="true" okVisible="true" cancelVisible="true"
title="Import from server to #{bkBean.file.name}"
type="okCancel"
dialogListener="#{bkBean.doSomething}">
<af:panelFormLayout id="pfl1">
<!-- Several input text here -->
</af:panelFormLayout>
</af:dialog>
</af:popup>
</af:document>
Inside myRegion.xhtml:
<?xml version='1.0' encoding='UTF-8'?>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:trh="http://myfaces.apache.org/trinidad/html"
xmlns:tr="http://myfaces.apache.org/trinidad"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
<af:tree ...>
</af:tree>
<!-- Contextual Menu -->
<af:popup id="pp1" contentDelivery="lazyUncached">
<af:menu>
<af:commandMenuItem text="Do something" immediate="true"
rendered="#{bkBean2.isRendered}" icon="icon.png" actionListener="#{bkBean2.showMyPopup}" />
</af:menu>
</af:popup>
</ui:composition>
It works if I define the popup inside myRegion.xhtml but not if I define the popup inside main.xhtml.
Thank you for any hints on how to make it work with the popup defined inside main.xhtml ?
You have two options:
Try mapping your bean in your adfc-config.xml instead of using annotations.
Leave annotations in place and make sure you use enable CDI in your ADF application as below:
http://www.jobinesh.com/2014/08/enabling-cdi-in-adf-applications.html
Worth mentioning that CDI (annotation-based beans) is not being used very often with ADF - at least there is little info about people following CDI approach - so you might be walking on uncharted territory .
I have a form above which captures Contract records and displays them in a datatable which has a commandlink "Edit" tag. When I click “Edit” I would like the form populated with this contract data but with the Contract No field disabled. I’m trying to do this disabling in an Ajax onEvent tag and it is working (ie the disabling). However, the fields are not being populated/displayed in the form when the ajax is being used. If I remove it, everything is fine only that the Contract No will b editable.
These are my edit tags.
<h:commandLink id="editLink" value="#{bundle.ListUnitEditLink}" >
<f:ajax onevent="disablePK" listener="#{contractManager.updateContract}" />
</h:commandLink>
This is my backing bean.
public String updateContract() {
System.out.println("Now in UPDATECONTRACT method and serious debugging");
current = (Contract) items.getRowData();
this.newContractId=current.getContractid();
this.newContractDesc=current.getContractdesc();
this.newContractDt=current.getContractdt();
this.newContractAmt=current.getContractamt();
this.newContractStrtDt=current.getContractstrtdt();
this.newExpDuration=current.getExpduration();
this.newCtdBy=current.getCtdby();
this.newCtdOn=current.getCtdon();
this.lstUpdBy=current.getLstupdby();
this.lstUpdOn=current.getLstupdon();
return "contracts";
}
The properties in the bean are being given correct values but they are not appearing in the form to be edited.
I sorted out my problem by adding render=#all in the ajax tag
<h:commandLink id="editLink" value="#{bundle.ListUnitEditLink}"
actionListener="#{contractManager.updateContract}">
<f:ajax onevent="disablePK" render="#all" />
</h:commandLink>
I have a dataTable with a column named DELETE (it is a link) which has an listener . When I click it the first time (click 1) it deletes the row (as expected), but when I try it with another row after that nothing happened (click 2). In fact wherever I click next nothing happen. I should click another time (click 3) to get it work. I don't want that.
Attention: The "delete()" method in my_user is not reached after "click 2".
Here is the code for the column:
<h:column>
<f:facet name="header">#{lng.del}</f:facet>
<h:form>
<h:commandLink action="#">
<f:ajax event="click" listener="#{my_user.delete}" render="#all" />
<h:graphicImage name="delete.png" library="images" styleClass="tableIcon" />
</h:commandLink>
</h:form>
</h:column>
You've multiple forms inside the table. When you re-render another form from inside a form by ajax, then its view state will get lost and hence the 1st click will fail. This click however takes care that the form gets the view state back, so the 2nd click works.
Technically you need to re-render only the content of the other form, but this isn't possible in this particular use case. Better put the <h:form> outside the <h:dataTable> so that you have a single form with a shared view state.
<h:form>
<h:dataTable>
...
</h:dataTable>
</h:form>
If your page contains another forms as well, I'd suggest to render only the current form instead of all, otherwise any actions on those forms will fail as well.
<f:ajax event="click" listener="#{my_user.delete}" render="#form" />
I'm trying to display/not display a primefaces datatable using the update tag in primefaces.
The table will render correctly when I reload (F5) the page but then I will lose all the data I had imputed into the form. I was looking around for an ajax solution but I have been unable to find any so far.
My code:
<h:form id="adminForm">
<h:selectOneMenu id="adminTypeMenu" value="#{adminBean.orgType}">
<f:selectItem itemValue="" itemLabel="- Select One -"/>
<p:ajax actionListener="#{adminBean.updateOrgType}" update="adminForm" />
</h:selectOneMenu>
<p:dataTable id="adminMacTable" value="#{adminBean.currentArray}" var="currentOrg"
rendered="#{adminBean.Type eq 'bco'}" selection="#{adminBean.selectedMac}"
emptyMessage="No Records Found">
...
</p:dataTable>
</h:form>
The Currently implementation displays the dataTable but erases any form inputs I had. Is there a way to render the datatable without clearing out my form values?
Update: Just tried the same code without and used but got the same result, still trying other methods!
Try this:
<h:form id="adminForm" prependId="false">
<h:selectOneMenu id="adminTypeMenu" value="#{adminBean.orgType}">
<f:selectItem itemValue="" itemLabel="- Select One -"/>
<p:ajax actionListener="#{adminBean.updateOrgType}" update="adminMacOutput" />
</h:selectOneMenu>
<p:outputPanel id="adminMacOutput">
<p:dataTable id="adminMacTable" value="#{adminBean.currentArray}" var="currentOrg"
rendered="#{adminBean.Type eq 'bco'}" selection="#{adminBean.selectedMac}"
emptyMessage="No Records Found">
...
</p:dataTable>
</p:outputPanel>
</h:form>
If that doesn't work, try putting the datatable inside a separate form and update this second form.
I'm guessing that AdminBean might be in request scope in which case your ajax event will update it but then it gets recreated when you press F5. If so then ViewScoped will help:
#ManagedBean(name = "adminBean")
#ViewScoped
public class AdminBean {
}