Single JSF form on a page approach vs several forms? - jsf

Does it makes sense to merge these several hidden forms on a single page into just one single big form?
To submit a particular set of parameters belonging to a particular form process attribute could be used to submit all elements required to be processed.
What are the pro/cons of this single form approach over using several forms?
<span class="hiddenForms">
<h:form>
<h:inputHidden id="selctdChnlType_in" value="#{channelCntlr.type}"/>
<h:inputHidden id="selctdChnlId_in" value="#{channelCntlr.channelId}"/>
<p:remoteCommand name="updateChnlDataPanel" process="#form" actionListener="#{channelCntlr.init()}" update=":channelHeader, :channelDataPanel, :channelSideColumn"/>
</h:form>
<h:form>
<h:inputHidden id="selctdLOBId_in" value="#{lobCntlr.targetLOBId}"/>
<p:remoteCommand name="updateLOBPanel" process="selctdLOBId_in, #this" actionListener="#{lobCntlr.retrieveCurrentLOB()}" update=":lobFullContentPanel" />
</h:form>
<h:form id="lobAction_form" >
<h:inputText id="targetLOBId_in" value="#{lobCntlr.targetLOBId}"/>
<h:inputText id="targetResponseId_in" value="#{lobCntlr.targetResponseOrCommmentId}"/>
<h:inputText id="targetAction_in" value="#{lobCntlr.targetAction}"/>
<p:remoteCommand name="doLOBAction" process="targetLOBId_in, targetAction_in, targetResponseId_in,#this" actionListener="#{lobCntlr.doLOBAction()}"/>
<h:inputText id="targetTopics" value="#{lobCntlr.list}" converter="listConverter"/>
<p:remoteCommand name="suggestAsHotLOB" process="targetLOBId_in, targetTopics, #this" actionListener="#{lobCntlr.addForTryAsHotLOB()}"/>
</h:form>
<h:form id="comment_form" >
<h:inputText id="targetLOBId_in" value="#{lobCntlr.targetLOBId}"/>
<h:inputText id="targetCommentOrResponseId_in" value="#{lobCntlr.targetResponseOrCommmentId}"/>
<h:inputText id="comment_in" value="#{lobCntlr.text_input}" required="true">
<f:validateLength minimum="15" maximum="1000"/>
</h:inputText>
<h:inputText id="previousCommenters_in" value="#{lobCntlr.list}" converter="listConverter"/>
<p:remoteCommand name="addComment" process="#form" actionListener="#{lobCntlr.addUserComment()}" oncomplete="addCommentToPage(args);" />
<p:remoteCommand name="deleteComment" process="targetLOBId_in, targetCommentOrResponseId_in, #this" actionListener="#{lobCntlr.removeUserComment()}" oncomplete="removeFromPage(args);" />
</h:form>
<h:form id="recosForm">
<h:inputText id="startFromRecos_in" value="#{recmdnsCntlr.startFromIndex}"/>
<p:remoteCommand name="fetchAllRecos" actionListener="#{recmdnsCntlr.retrieveAllRecmmndns()}" process="startFromRecos_in,howManyRecos_in,isLocalStorAvailble_in,#this" />
<p:remoteCommand name="fetchFollowiesList" actionListener="#{recmdnsCntlr.fetchAllFollowiesList()}" process="#this" oncomplete="storeFollowiesList(args)"/>
</h:form>
<span id="editsForm" style="display:none">
<form action="javascript:void(0);" class="edits_submitter" >
<p:inputTextarea styleClass="editedText"/>
<input type="submit" value="Save edits"/>
<a class="cancel-edit" href="javascript:void(0)">Cancel</a>
</form>
</span>
</span>

A major con to the single, monolithic JSF form control is the sheer volume of data that is (needlessly) sent to the server for processing. Using your existing code. Consider the following. If all the controls in <h:form id="lobAction_form" > and <h:form id="comment_form" > were in a single form, you'd have
<h:inputText id="targetLOBId_in" value="#{lobCntlr.targetLOBId}"/>
<h:inputText id="targetResponseId_in" value="#{lobCntlr.targetResponseOrCommmentId}"/>
<h:inputText id="targetAction_in" value="#{lobCntlr.targetAction}"/>
<p:remoteCommand name="doLOBAction" process="targetLOBId_in, targetAction_in, targetResponseId_in,#this" actionListener="#{lobCntlr.doLOBAction()}"/>
<h:inputText id="targetTopics" value="#{lobCntlr.list}" converter="listConverter"/>
<p:remoteCommand name="suggestAsHotLOB" process="targetLOBId_in, targetTopics, #this" actionListener="#{lobCntlr.addForTryAsHotLOB()}"/>
<h:inputText id="targetLOBId_in" value="#{lobCntlr.targetLOBId}"/>
<h:inputText id="targetCommentOrResponseId_in" value="#{lobCntlr.targetResponseOrCommmentId}"/>
<h:inputText id="comment_in" value="#{lobCntlr.text_input}" required="true">
<f:validateLength minimum="15" maximum="1000"/>
</h:inputText>
<h:inputText id="previousCommenters_in" value="#{lobCntlr.list}" converter="listConverter"/>
<p:remoteCommand name="addComment" process="#form" actionListener="#{lobCntlr.addUserComment()}" oncomplete="addCommentToPage(args);" />
<p:remoteCommand name="deleteComment" process="targetLOBId_in, targetCommentOrResponseId_in, #this" actionListener="#{lobCntlr.removeUserComment()}" oncomplete="removeFromPage(args);" />
</h:form>
For every command action that you initiate in that form, possibly to process 1 input text component, you'd always be sending all 13 components in there to the server anyway. Wasteful and unnecessary. You'll have high volume of client-server communications for small operations and sometimes slow response times. Depending on whatever JSF framework you're using, you might be able to get creative with this situation, selectively processing components and what not, but that is just needless and painful. Clean separation of concerns also comes into play in the presentation layer.
Then there is the problem of validation. More often than not, you will have selected components in a single form that are marked as required and have nothing to do with the rest of the components within that form. You'll most likely be unable to selectively process those components without affecting all the other components on that form.

I see you're using primefaces. You could consider the use of Wizard component. With this component there is a single form with more sections in different tabs. The validation is done in Ajax mode when you go from one tab to the next one. As the partial validation is done with Ajax, only the fields of the tab you are validating, are processed and sent to the server.
It is also useful to split long forms in more readable and user friendly sections.

Related

JSF component is not rendered from my p:commandButton in in a dialog in included page

My page contains a login header that i include via ui:include. The included page contains a dialog with a p:commandButton. When the user logs in, the include page is refreshed properly according to the #form in the update attribute. I also want to update a component outside the included page, that shall display a button when the user is logged in. The include page refreshes and the name of the logged in user is displayed. But the button in the main page is not shown. It is displayed if I refresh the page though. I can't figure out what I'm doing wrong here. Anyone have any ideas.
The header page also displays the commandLink component properly. But when clicking the logout link, the button in the main page is not removed. Since the commandLink does not use ajax, I assume that a normal page POST is done. Which should reload the whole page. Doesn't this work from a page that have been referenced with ui:include?
The login page is using a session scoped backing bean. The main page is view scoped.
Here's the included xhtml (login.xhtml):
<ui:composition
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<div style="width:100%;font-size:12px;line-height:20px;background-color:#b0d9e6;color:white">
<h:form>
<h:message id="top_msg"></h:message>
<h:panelGrid width="100%" columns="3" columnClasses="none,right1,right1">
<h:outputLink rendered="#{!loginController.loggedIn}" styleClass="text-align:right;" value="javascript:void(0)" onclick="PF('dlg').show();" title="login">
<p:outputLabel>Login</p:outputLabel>
</h:outputLink>
<h:commandLink rendered="#{loginController.loggedIn}" action="#{loginController.logout}" styleClass="text-align:right;" >
<h:outputLabel>Logout</h:outputLabel>
</h:commandLink>
<p:growl id="growl" sticky="true" showDetail="true" life="3000" />
<p:dialog header="Login" widgetVar="dlg" resizable="false">
<h:panelGrid columns="2" cellpadding="5">
<h:outputLabel for="username" value="Username:" />
<p:inputText id="username" value="#{loginController.username}" required="true" label="username" />
<h:outputLabel for="password" value="Password:" />
<p:password id="password" value="#{loginController.password}" required="true" label="password" />
<f:facet name="footer">
<p:commandButton value="Login"
update="#form :createform:createbutton"
actionListener="#{loginController.login}"
oncomplete="handleLoginRequest(xhr, status, args)" >
</p:commandButton>
</f:facet>
</h:panelGrid>
</p:dialog>
</h:panelGrid>
<script type="text/javascript">
function handleLoginRequest(xhr, status, args)
</script>
</h:form>
</div>
</ui:composition>
...
This one is included in the following main page:
...
<ui:include src="login.xhtml" />
<h:form id="createform">
<h:panelGroup id="createbutton" layout="block">
<p:commandButton id="createnew"
ajax="false"
action="#{recepieController.createNewRecipes}"
value="Create new recipe"
rendered="#{recepieController.loggedIn and !recepieController.create and !recepieController.viewOnly and !recepieController.edit}"
accesskey="s">
</p:commandButton>
</h:panelGroup>
</h:form>
You cannot rerender a non-rendered component. If you partially render a button, and the button is not rendered, you cannot call an update on that button, because it does not exist in the DOM.
You have to call the AJAX update on the parent naming container, that is ALWAYS rendered. Thus, update the :createform rather than the button inside. The form is always rendered, no matter what.
I found the issue. In my commandButton "createnew", I used the wrong value to render on.
<p:commandButton id="createnew"
ajax="false"
action="#{recepieController.createNewRecipes}"
value="Create new recipe"
rendered="#{recepieController.loggedIn and !recepieController.create and !recepieController.viewOnly and !recepieController.edit}"
accesskey="s">
</p:commandButton>
It should use my session scoped bean (loginController) to check if the user is logged in. Changing to the following works.
<h:panelGroup id="createbutton">
<p:commandButton id="createnew"
ajax="false"
action="#{recepieController.createNewRecipes}"
value="Create new recipe"
rendered="#{loginController.loggedIn and !recepieController.create and !recepieController.viewOnly and !recepieController.edit}"
accesskey="s">
</p:commandButton>
</h:panelGroup>
Note the difference rendered="#{loginController.login ...} instead of rendered="#{recepieController.loggedIn}"
The recepieController also has a loggedIn attribute which I set, but since the page is not re-posted I guess the value is not changed for the attribute when I login.
However, I believe I tested to use ajax="false" in the p:commandButton for the login dialog which I guess should reset the view scoped version of my loggedIn attribute. I don't fully understand why that didn't work.

URL parameters lost after JSF validation

I have a requirement where I need to display the header and footer based upon the value in the URL parameters. Everything works fine. But the URL params are lost after JSF validations. This happens only when I click submit. Works fine for the ajax validations.
<h:panelGrid columns="1" id="lnamePanel">
<h:inputText tabindex="1" maxlength="50" id="lastName" value="#{registrationBean.last_name}" required="true" requiredMessage="Last Name is a required field" validatorMessage="Not a valid last name">
<f:validateRegex pattern="^[A-Za-z-_.\s]{2,50}$"/>
<f:ajax event="blur" render="lnamePanel" />
</h:inputText>
<h:message for="lastName" display="text" style="color:red;"/>
</h:panelGrid>
<h:commandButton value="Submit" actionListener="#{registrationBean.handleSubmit}" ajax="true"/>
How do I retain the url parameters after JSF validation? In this case, I cannot manipulate the URL inside the backing bean as the backing bean method is not getting called.
Any help would be appreciated.

<h:commandButton> is not refreshing page after actioned

hi we are using along with a4j tag.
here we are retrieving data from database after a click of button. even though the data is available in server, it will not display over view. After manual refresh of web page will lead to data diplay.
here is code snippet
.... some code here
<rich:tab id="menu5" label="Recall">
<ui:include src="/pages/mctrans/reCallMcifTrans.xhtml" />
</rich:tab>
reCallMcifTrans.xhtml contains below code
<h:commandButton type="button" id="reCallbutton1" value=" Search "
styleClass="commandExButton">
<a4j:support event="onclick" id="ajsf12"
oncomplete="javascript:alert('Search Completed');javascript:document.body.style.cursor='default';"
action="#{mcifRecallTransBean.reCallSearch}" reRender="reCallgrid1" />
</h:commandButton>
It looks like you're working with RichFaces 3.3. So, you don't need a <h:commandButton with <a4j:support> because you can use <a4j:commandButton> that already does this. You can refactor your code to this:
<a4j:commandButton type="button" id="reCallbutton1" value="Search"
styleClass="commandExButton"
action="#{mcifRecallTransBean.reCallSearch}"
reRender="reCallgrid1"
oncomplete="javascript:alert('Search Completed');javascript:document.body.style.cursor='default';" />
Make sure your reCallgrid1 component is available in the same <h:form> of the <a4j:commandButton>.
Since you also want to add a Wait while searching the data behavior when the button is clicked, you can use <a4j:status> along with the <a4j:commandButton> as shown in the <a4j:status> demo. Here's a basic example:
<a4j:commandButton type="button" id="reCallbutton1" value="Search"
styleClass="commandExButton"
action="#{mcifRecallTransBean.reCallSearch}"
reRender="reCallgrid1" />
<!-- Note that there's no oncomplete in this case -->
<a4j:status for="reCallbutton1">
<f:facet name="start">
<h:graphicImage value="/res/images/wait.gif"/>
</f:facet>
</a4j:status>
At last but not least, you should switch your managed bean to request scope and use RichFaces powerful <a4j:keepAlive> in order to simulate JSF 2 #ViewScoped. You can even use it in form of annotation on your managed bean (no additional configuration):
#KeepAlive
public class McifRecallTransBean {
//managed bean code here...
}
When you are using request parameters inside the bean, you need to pass them again with your action :
<h:commandButton type="button" id="reCallbutton1" value="Search" styleClass="commandExButton">
<a4j:support event="onclick" id="ajsf12" oncomplete="javascript:alert('Search Completed');javascript:document.body.style.cursor='default';" action="#{mcifRecallTransBean.reCallSearch}" reRender="reCallgrid1" />
<f:param name="param1" value="#{param['param1']}" />
<f:param name="param2" value="#{param['param2']}" />
</h:commandButton>

How to invoke a command button without validating all required inputs?

I'm trying to make a redirection from index.xhtml to registerFirstTime.xhtml.
The code in the page index.xhtml is:
<h:form id="content" style="margin-top: 1em;">
<p:panel id="panel" header="LOGIN">
<p:messages id="msgs" globalOnly="true"/>
<h:panelGrid columns="3">
<h:outputLabel value="e-mail" />
<p:inputText id="name" required="true" value="#{UserBean.email}"
requiredMessage="Required: e-mail" display="icon">
</p:inputText>
<p:message id="msgName" for="name"/>
<h:outputLabel value="Password" />
<p:password id="password" required="true" value="#{UserBean.password}"
requiredMessage="Required: Password" display="icon" feedback="false">
</p:password>
<p:message id="msgPassword" for="password"/>
</h:panelGrid>
<h:panelGrid columns="2">
<p:commandButton value="Login" actionListener="#{UserBean.validate}" update="msgNombre, msgPassword, msgs"/>
<h:commandButton value="Register for the first time" action="register"/>
</h:panelGrid>
</p:panel>
</h:form>
While, the content of the redirection, in faces-config.xml, is:
<navigation-rule>
<from-view-id>index.xhtml</from-view-id>
<navigation-case>
<from-outcome>register</from-outcome>
<to-view-id>registerFirstTime.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
Until the input name and password are filled, you can't do the redirect.
How I can get do the redirection of the record without taking into account the required fields? Thanks! =)
Add immediate="true" to the command button which should skip processing of all input field which do not have the immediate="true" attribute.
<h:commandButton value="Register for the first time" action="register" immediate="true" />
Unrelated to the concrete problem, please note that you're technically not sending a redirect here. This basically sends a forward, i.e. the URL in the browser address bar remains the URL of the login page. You need to add <redirect/> to the <navigation-case>. Also please note that navigation cases are an leftover of JSF 1.x. Since JSF 2.x you can perform "implicit navigation" whereby you just specify the view ID as outcome.
<h:commandButton value="Register for the first time" action="registerFirstTime?faces-redirect=true" immediate="true" />
This way you can get rid of the navigation case altogether.
#BalusC solution should do the trick, as always. But I'd like to point out a couple of things since you seem to be using Primefaces. Both are unrelated to the question but might help you out at some point.
First, you could use implicit navigation (introduced in JSF2). That way you wouldn't need to define all the navigation rules in your faces-config.xml file (I work on an old JSF 1.2 project and hate the need to define the navigation roles for everything). Here's how you'd do it:
<h:commandButton value="Register for the first time" action="registerFirstTime.xhtml?faces-redirect=true"/>
The faces-redirect parameter forces a redirect instead of a forward, in case you need that.
Also, say you want to properly process some values, but not all of them. In that case, you can use the process attribute of p:commandButton or p:ajax. For example
<h:commandButton value="Register for the first time" action="registerFirstTime.xhtml?faces-redirect=true" process="#this, name"/>
This would make JSF process only the button (#this) and the component with id="name" (your e-mail field). Again, it probably doesn't apply to this question, but it's something I use often.

form within form: skip validation of parent form

<h:form prependId="false" id="parentForm">
...
<h:form prependId="false" id="commentForm">
...
add comment
</h:form>
save
</h:form>
Doesn't work...
Without the inner form the parent's elements get validated when I just want to add a comment.
"add comment" should just validate the comment and when "save" is clicked the parent should be validated.
Nesting forms is illegal in HTML, so also in JSF since all it does is just generating HTML. You need to put them next to each other.
If you have multiple buttons in the same form of which you'd like to skip certain validation on certain button press, then add immediate="true" to the button in question. This way all input fields which do not have immediate="true" will be skipped.
See also:
What is the immediate attribute used for?
Update: OK, you want two physically separate forms inside a single form. If splitting the "God Form" in multiple forms with each its own responsibility is not an option, then there are several ways to go around this:
If you don't use Ajax and you just have a required="true" on an input element which you actually want to make non-required when you press a certain button, then do:
<h:form>
...
<h:commandButton value="Submit form but but do not validate comment" />
...
<h:inputTextarea id="comment" required="#{not empty param[foo.clientId]}" immediate="true" />
<h:commandButton binding="#{foo}" value="Submit and validate comment" immediate="true" />
</h:form>
If you actually use Ajax, then just specify the execute region in execute attribute.
<h:form>
<h:panelGroup id="other">
....
<h:commandButton value="Submit form but but do not validate comment">
<f:ajax execute="other" render="other" />
</h:commandButton>
</h:panelGroup>
<h:panelGroup id="comments">
<h:inputTextarea required="#{not empty param[foo.clientId]}" />
<h:commandButton value="Submit and validate comment by ajax">
<f:ajax execute="comments" render="comments" />
</h:commandButton>
</h:panelGroup>
</h:form>

Resources