Prerequisites:
- JSF 2.1
- Primefaces 5.2
- Glassfish 3.1
Story:
I've created a p:dialog used for displaying FacesMessages on a p:messages element. This dialog is needed, because the user has to commit specific FacesMessages with an "OK"-Button before proceeding.
Dialog:
<p:outputPanel id="modalMessage">
<p:dialog id="dlgMessageDialog" dynamic="true" style="z-index: 100"
closable="false" widgetVar="wigVarMessageDialog" modal="true"
appendTo="#(body)">
<f:facet name="header">
<h:outputText id="messageDialogHeader"
value="#{messageDialogBean.header}" />
</f:facet>
<p:outputPanel id="modalMessagePanel">
<h:form id="messageForm" enctype="multipart/form-data">
<p:messages id="messages" escape="false" closable="false"
showDetail="true" autoUpdate="true"
for="#{messageDialogBean.messageDialogId}"></p:messages>
<p:spacer height="20px"></p:spacer>
<p:commandButton value="#{msg.btnOk}"
oncomplete="PF('wigVarMessageDialog').hide()" />
</h:form>
</p:outputPanel>
</p:dialog>
</p:outputPanel>
Bean:
#Named("messageDialogBean")
#SessionScoped
public class MessageDialogBean implements Serializable {
private static final long serialVersionUID = 1L;
private final String messageDialogId = "messageDialogId";
private FacesMessage message = new FacesMessage();
private String header = "test";
public void showMessage(final String pHeader, final FacesMessage pMessage) {
if (pMessage != null) {
setHeader(pHeader);
this.message = pMessage;
show();
}
}
public void showWarn(final String pHeader, final String pSummary, final String pDetail) {
setHeader(pHeader);
this.message = new FacesMessage(FacesMessage.SEVERITY_WARN, pSummary, pDetail);
show();
}
public void showInfo(final String pHeader, final String pSummary, final String pDetail) {
setHeader(pHeader);
this.message = new FacesMessage(FacesMessage.SEVERITY_INFO, pSummary, pDetail);
show();
}
public void showError(final String pHeader, final String pSummary, final String pDetail) {
setHeader(pHeader);
this.message = new FacesMessage(FacesMessage.SEVERITY_ERROR, pSummary, pDetail);
show();
}
public void updateDialog() {
RequestContext context = RequestContext.getCurrentInstance();
context.update("mainForm:messageDialogHeader");
}
private void show() {
updateDialog();
RequestContext context = RequestContext.getCurrentInstance();
context.execute("PF('wigVarMessageDialog').show();");
FacesContext.getCurrentInstance().addMessage(this.messageDialogId, this.message);
}
public String getMessageDialogId() {
return this.messageDialogId;
}
public void setHeader(final String pHeader) {
this.header = pHeader;
}
public String getHeader() {
return this.header;
}
public FacesMessage getLastMessage() {
return this.message;
}
}
One of the messages which have to be commited:
this.messageDialogBean.showInfo("Title", "Summary", "Detail");
Problem:
The p:messages element of the dialog does not show the message when the dialog is opened the first time. After opening and hiding it once it shows all further FacesMessages just fine.
Question:
So far i am useing opening and closeing the dialog once when the interface is initialized as a workarround. Does annyone know what causes this problem in the first place and also how to solve it properly?
Thanks for answers
First of all it is not allowed to put a form inside of another, as stated in W3C XHTML specification, "form must not contain other form elements." visit: https://www.w3.org/TR/xhtml1/#prohibitions.
So your dialog should not be inside of the main form, you have to sperate the dialog from the form, your code sould be orginsed like this :
<form id="mainForm" >
<!--your main page-->
</form>
<p:dialog id="dlgMessageDialog" >
<h:form id="messageForm" enctype="multipart/form-data">
<f:facet name="header">
<h:outputText id="messageDialogHeader"
value="#{messageDialogBean.header}" />
</f:facet>
<p:messages id="messages" escape="false" closable="false"
showDetail="true" autoUpdate="true"
for="#{messageDialogBean.messageDialogId}"></p:messages>
<p:spacer height="20px"></p:spacer>
<p:commandButton value="#{msg.btnOk}"
oncomplete="PF('wigVarMessageDialog').hide()" />
</h:form>
</p:dialog>
Another thing, you have to update the whole dialog, so when the dialog is opened the messges is automaticly updated :
context.update("dlgMessageDialog");
Related
I am using JSF 2.2.20. In the xhtml page, there is one input text field and a file upload (advanced mode) . But my problem is, while listener is called for file-upload, the input text is coming as empty. How can I get the input text? Help me out.
xhtml code snippet:
<h:form id="formID" enctype="multipart/form-data" method="post">
<p:growl id="growlId" sticky="true" showDetail="true"><p:autoUpdate /></p:growl>
<p:outputPanel id="DocInfo">
<h:panelGrid id="DocGrid" columns="3" style="margin-bottom:10px" cellpadding="8">
<p:outputLabel for="upload_Doc_Name" value="Document Name :* " />
<p:inputText id="upload_Doc_Name" value="#{uploadDocManagedBean.uploadDocName}" style="font-size:8pt;width:230px" />
<p:outputLabel for="projectUploadDocId" value="Choose File :* " style="font-weight:bold; float:left" />
<p:fileUpload id="projectUploadDocId" value="#{uploadDocManagedBean.uploadedFile}" mode="advanced" dragDropSupport="true"
listener="#{uploadDocManagedBean.fileUploadListener}" />
<br/>
</h:panelGrid>
</p:outputPanel>
</h:form>
The managed bean code snippet:
#ManagedBean(name = "uploadDocManagedBean")
#ViewScoped
public class UploadDocManagedBean implements Serializable {
private static final long serialVersionUID = -1L;
private String uploadDocName;
private UploadedFile uploadedFile;
public String getUploadDocName() {
return uploadDocName;
}
public void setUploadDocName(String uploadDocName) {
this.uploadDocName = uploadDocName;
}
public UploadedFile getUploadedFile() {
return uploadedFile;
}
public void setUploadedFile(UploadedFile uploadedFile) {
this.uploadedFile = uploadedFile;
}
public void fileUploadListener(FileUploadEvent fileUploadEvent) {
try {
uploadedFile = fileUploadEvent.getFile();
String filename = uploadedFile.getFileName();
if (uploadDocName == null || uploadDocName.isEmpty()) {
String msg = "Document Name is empty.";
FacesContext.getCurrentInstance().addMessage("growlId",
new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg));
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
In the managed bean, the uploadDocName is the coming as empty everytime, while this fileUploadListener() method is called.
What am I missing here?
You can define process attribute on fileUpload. To make it simple, try with process="#form", it should work ;) By default, only fileUpload client id e.g #this is sent to the server for processing
More details for fileUpload component, see here: https://primefaces.github.io/primefaces/11_0_0/#/components/fileupload
I'm trying to set a dialog title at runtime (dynamically) but update with #widgetVar expression cannot do the trick. Any ideas?
The first command button renders the dialog with the dynamic title but the second command fail to render the title! Why? Here the example is simplified but the real page is more complex and it is difficult to specify the id of the dialog to render.
<h:form>
<p:commandButton value="Basic" ajax="true" process="#this"
update=":dlg1" oncomplete="PF('widget-dialog').show();"
actionListener="#{dialogView.changeTitle('Dynamic title')}" />
<p:commandButton value="Widget" ajax="true" process="#this"
update="#widgetVar('widget-dialog')"
oncomplete="PF('widget-dialog-1').show();"
actionListener="#{dialogView.changeTitle('Dynamic title')}" />
</h:form>
<p:dialog id="dlg1" header="#{dialogView.title}"
widgetVar="widget-dialog" dynamic="true">
<h:outputText value="Resistance to PrimeFaces is futile!!!" />
</p:dialog>
#ManagedBean(name = "dialogView")
#ViewScoped
public class DialogView {
private String title = null;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public void changeTitle(String title) {
setTitle(title);
}
}
I have a jsf page that has 2 parts: a table displaying a list of records and a dialog to add a new record. I added some validation features but I can't get them to work properly.
I need it to:
1) Validation error not appear the first time dialog shows up
2) If any validation error happens, keep it open and show error messages.
3) If no validation error and back-end executed successfully, update the table.
This is what I have:
<h:body>
<h:form id="form01">
<p:dataTable id="tbl1" value="#{welcomeController.teams}" var="team" >
<p:column headerText="Id">
<h:outputText value="#{team.seq}" />
</p:column>
<p:column headerText="Name">
<h:outputText value="#{team.name}" />
</p:column>
</p:dataTable>
<p:dialog id="teamDialog" closable="false" visible="#{welcomeController.addMode}"
widgetVar="teamDialog_w" modal="true" resizable="false" draggable="true"
header="New Team Detail">
<p:messages />
<p:panelGrid columns="2">
<h:outputText value="Name" />
<p:inputText id="name" value="#{welcomeController.newTeam.name}" />
</p:panelGrid>
<p:commandButton value="Submit" ajax="true" actionListener="#{welcomeController.addNewTeam}"
update=":form01"oncomplete="teamDialog_w.hide(); console.log(args);" />
</p:dialog>
<p:commandButton value="ADD" actionListener="#{welcomeController.startAdd}"
oncomplete="teamDialog_w.show(); console.log(args);" update="teamDialog" />
</h:form>
The bean:
#Named
#ConversationScoped
public class WelcomeController implements Serializable {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private static final long serialVersionUID = 1L;
private List<TeamDto> teams;
#Inject SessionManager sessionMan;
#Inject DatabaseUtil dbCache;
#Inject TeamService teamService;
#Inject Conversation conversation;
private TeamDto newTeam = new TeamDto();
private boolean addMode = false;
public List<TeamDto> getTeams() throws IOException {
if (teams == null || teams.size() == 0) {
teams = teamService.getAll();
}
return teams;
}
public void setTeams(List<TeamDto> teams) {
this.teams = teams;
}
public void reload() {
conversationBegin();
}
public void conversationBegin() {
if (conversation.isTransient()) {
conversation.begin();
}
}
public void conversationEnd() {
if(!conversation.isTransient()){
conversation.end();
}
}
public void startAdd() {
reload();
newTeam = new TeamDto();
addMode = true;
}
public TeamDto getNewTeam() {
return newTeam;
}
public void setNewTeam(TeamDto newTeam) {
this.newTeam = newTeam;
}
public void addNewTeam() throws IOException, ValidatorException {
if (newTeam.getName().isEmpty()) {
sessionMan.addGlobalMessageFatal("INVALID INFO", null);
return;
}
teamService.addTeam(newTeam);
teams.add(newTeam);
newTeam = new TeamDto();
addMode = false;
}
public boolean isAddMode() {
return addMode;
}
public void setAddMode(boolean addMode) {
this.addMode = addMode;
}
}
I have 2 problems here:
1) After I submit an empty string, I expect the dialog to still open (because addMode is true) but it's not. Why is it?
2) If I put the "ADD" button like this:
<p:commandButton value="ADD" actionListener="#{welcomeController.startAdd}" oncomplete="teamDialog_w.show(); console.log(args);" >
<f:ajax render="teamDialog" />
</p:commandButton>
at least when I open the dialog again, I can see the error message. But in my code, I can't see the error message. Why is it so? Aren't they equivalent?
Is there anything wrong with my understanding?
Please help. Thank you very much.
Dialogs should operate with their own form in JSF, this is crucial.
To prevent your dialog from closing on validation you can use in Primefaces :
if (args &&
!args.validationFailed){PF('eventDialog').hide();}
like this:
<p:commandButton value="ADD"
actionListener="#{welcomeController.startAdd()}"
oncomplete="if (args && !args.validationFailed){PF('teamDialog').hide();}"
update="your_dialog_formID:messages, other_updated_IDs" />
here
if (args && !args.validationFailed){PF('teamDialog').hide();}
you can obvoiusly add else clause and do #3 "If no validation error and back-end executed successfully, update the table."
here BalusC gives great explenation why:Keep p:dialog open when a validation error occurs after submit
I am trying to add a PrimeFaces <p:tab> dynamically. While adding the second tab I am getting the following exception:
"java.lang.IllegalStateException: Component ID tab0 has already been found in the view".
How can I solve this?
Here is the view code:
<h:form prependId="false">
<p:tabView id="tabview" dynamic="true" cache="false"
binding="#{testBean.tabView}"
activeIndex="#{testBean.activeTab}" >
<h:commandButton value="Close" action="#{testBean.removeTab}"/>
</p:tabView>
<h:commandButton value="Add Tab" action="#{testBean.addTab}"/>
</h:form>
Here is the bean code:
public String addTab() {
String tabId="tab"+id;
System.out.println("Gen Id: "+tabId);
tab = new Tab();
tab.setTitle("Title: "+tabId);
tab.setId(tabId);
System.out.println("Tab Id: "+tab.getId());
tabView.getChildren().add(id,this.tab);
id++;
return "tabtest.jsf";
}
public String removeTab() {
tabView.getChildren().remove(activeTab);
return "tabtest.jsf";
}
Don't manually create components if everything can just be done in the view. This construct will fail if the bean is in a broader scope than the request scope. See also e.g. Binding attribute causes duplicate component ID found in the view
Follow the showcase example "TabView with Model" which allows you to dynamically populate tabs via a sane model and <p:tabView value="..." var="..."> like as <ui:repeat>/<h:dataTable>.
E.g. this view
<h:form>
<p:tabView value="#{bean.tabs}" var="tab">
<p:tab title="#{tab.title}">
#{tab.content}
<p:commandButton value="Close" action="#{bean.remove(tab)}" update="#form" />
</p:tab>
</p:tabView>
<p:commandButton value="Add Tab" action="#{bean.add}" update="#form" />
</h:form>
with this controller
#ManagedBean
#ViewScoped
public class Bean implements Serializable {
private List<Tab> tabs;
#PostConstruct
public void init() {
tabs = new ArrayList<>();
}
public void add() {
tabs.add(new Tab("tab" + tabs.size(), "some content"));
}
public void remove(Tab tab) {
tabs.remove(tab);
}
public List<Tab> getTabs() {
return tabs;
}
}
and this model
public class Tab {
private String title;
private String content;
public Tab(String title, String content) {
this.title = title;
this.content = content;
}
public String getTitle() {
return title;
}
public String getContent() {
return content;
}
}
This is because same ID is being generated for new tab(The one which ur adding). To avoid this, append a variable to the id as
<p:tabView id="tabview_#{testBean.i}" dynamic="true" cache="false"binding="#{testBean.tabView}"
activeIndex="#{testBean.activeTab}" >
<h:commandButton value="Close" action="#{testBean.removeTab}"/>
</p:tabView>
I am trying to add a PrimeFaces <p:tab> dynamically. While adding the second tab I am getting the following exception:
"java.lang.IllegalStateException: Component ID tab0 has already been found in the view".
How can I solve this?
Here is the view code:
<h:form prependId="false">
<p:tabView id="tabview" dynamic="true" cache="false"
binding="#{testBean.tabView}"
activeIndex="#{testBean.activeTab}" >
<h:commandButton value="Close" action="#{testBean.removeTab}"/>
</p:tabView>
<h:commandButton value="Add Tab" action="#{testBean.addTab}"/>
</h:form>
Here is the bean code:
public String addTab() {
String tabId="tab"+id;
System.out.println("Gen Id: "+tabId);
tab = new Tab();
tab.setTitle("Title: "+tabId);
tab.setId(tabId);
System.out.println("Tab Id: "+tab.getId());
tabView.getChildren().add(id,this.tab);
id++;
return "tabtest.jsf";
}
public String removeTab() {
tabView.getChildren().remove(activeTab);
return "tabtest.jsf";
}
Don't manually create components if everything can just be done in the view. This construct will fail if the bean is in a broader scope than the request scope. See also e.g. Binding attribute causes duplicate component ID found in the view
Follow the showcase example "TabView with Model" which allows you to dynamically populate tabs via a sane model and <p:tabView value="..." var="..."> like as <ui:repeat>/<h:dataTable>.
E.g. this view
<h:form>
<p:tabView value="#{bean.tabs}" var="tab">
<p:tab title="#{tab.title}">
#{tab.content}
<p:commandButton value="Close" action="#{bean.remove(tab)}" update="#form" />
</p:tab>
</p:tabView>
<p:commandButton value="Add Tab" action="#{bean.add}" update="#form" />
</h:form>
with this controller
#ManagedBean
#ViewScoped
public class Bean implements Serializable {
private List<Tab> tabs;
#PostConstruct
public void init() {
tabs = new ArrayList<>();
}
public void add() {
tabs.add(new Tab("tab" + tabs.size(), "some content"));
}
public void remove(Tab tab) {
tabs.remove(tab);
}
public List<Tab> getTabs() {
return tabs;
}
}
and this model
public class Tab {
private String title;
private String content;
public Tab(String title, String content) {
this.title = title;
this.content = content;
}
public String getTitle() {
return title;
}
public String getContent() {
return content;
}
}
This is because same ID is being generated for new tab(The one which ur adding). To avoid this, append a variable to the id as
<p:tabView id="tabview_#{testBean.i}" dynamic="true" cache="false"binding="#{testBean.tabView}"
activeIndex="#{testBean.activeTab}" >
<h:commandButton value="Close" action="#{testBean.removeTab}"/>
</p:tabView>