Failed validation should prevent closing of modal dialog - ajax

I am using Bootsfaces for displaying a modal dialog. Inside the modal dialog there is a field (actually it's b:inputTextarea) and a command button which uses Ajax to submit the data. I want to make sure that the user has entered something to the text field before submitting. If the field is empty then the modal should not be closed and the submit should not happen, if the field is non-empty then the dialog should be closed and the submitting should happen Ajax-style.
Due to an issue with Bootsfaces 1.4.2 I cannot use b:command to do the Ajax-part. I have to use the standard way with h:commandButton and f:ajax.
I am trying to solve my problem like this:
<b:form>
<!-- section that gets updated by ajax-request -->
<b:panel id="updateSection">
<h:outputText value="#{testBean.value1}" />
<b:button value="open dialog" onclick="$('.pseudoClass').modal()" />
</b:panel>
<!-- modal dialog -->
<b:modal title="model" styleClass="pseudoClass" closable="false" closeOnEscape="true">
<b:panel id="modalOutput"><h:inputText value="#{testBean.value1}" /></b:panel>
<f:facet name="footer">
<!-- cancel button -->
<b:button largeScreen="half" value="cancel" dismiss="modal" onclick="return false;" />
<!-- submit button ajax-style -->
<h:commandButton value="submit" action="#{testBean.save()}" onclick="validateModalDialog();">
<f:ajax render="updateSection" execute="#this modalOutput" onevent="closeModalDialog" />
</h:commandButton>
<!-- scripts to close & validate modal dialog -->
<h:outputScript>
function closeModalDialog(data) {
var status = data.status;
if (status === 'complete') { $('.pseudoClass').modal('hide'); }
}
function validateModalDialog() {
alert('i get called before closing dialog');
return false;
}
</h:outputScript>
</f:facet>
</b:modal>
</b:form>
The script function validateModalDialog gets called before the modal dialog gets closed but the dialog gets closed regardless of the return value of the function (true or false).
My knowledge of JavaScript is rather limited. That was one of the reasons I chose to use JSF and Bootsfaces. But I am pretty sure there is a way to do a validation and prevent the dialog from getting closed.
How do I emulate the Bootsfaces functionality (client-side validation of text field in a modal dialog) when using the standard components h:commandButton and f:ajax?
SOLUTION:
To achieve client-side validation preventing the Ajax request from firing and the modal dialog from closing the following change needs to be done compared to my initial code:
<h:commandButton value="submit" action="#{testBean.save()}" onclick="return validateModalDialog();">
<f:ajax render="updateSection" execute="#this modalOutput" onevent="closeModalDialog" />
</h:commandButton>

It indeed looks like
<h:commandButton>
<f:ajax onevent="function(e){if(e.status == 'complete') doSomething();}"/>
</h:commandButton>
is not the equivalent for
<b:commandButton ajax="true" oncomplete="doSomething();"/>
In order to close the modal dialog via javascript from a h:commandButton only if the form has no validation error you have to:
Add the <b:fetchBeanInfos/> component to your form as suggested in this post I originally proposed as duplicate of your question. Also make sure it is updated with each ajax request.
Do not check for data.status == 'complete' but instead data.status == 'success'
Remove the onclick="validateModalDialog();" entirely.
Here is your code changed for server side validation closing the dialog only if the input is not empty (required="true"). Note that also testBean.save() gets invoked only for valid input.
<b:form>
<!-- section that gets updated by ajax-request -->
<b:panel id="updateSection">
<h:outputText value="#{testBean.value1}" />
<b:button value="open dialog" onclick="$('.pseudoClass').modal()" />
</b:panel>
<!-- modal dialog -->
<b:modal title="model" styleClass="pseudoClass" closable="false"
closeOnEscape="true">
<b:panel id="modalOutput">
<h:inputText id="myInput" value="#{testBean.value1}" required="true" />
<h:message for="myInput" />
<b:fetchBeanInfos /> <!-- creates the validationFailed JavaScript variable
and must be within rendered components. -->
</b:panel>
<f:facet name="footer">
<!-- cancel button -->
<b:button largeScreen="half" value="cancel" dismiss="modal"
onclick="return false;" />
<!-- submit button ajax-style -->
<h:commandButton value="submit" action="#{testBean.save()}">
<f:ajax render="updateSection modalOutput"
execute="#this modalOutput" onevent="closeModalDialog" />
</h:commandButton>
<!-- scripts to close & validate modal dialog -->
<h:outputScript>
function closeModalDialog(data) {
var status = data.status;
if (!validationFailed && status === 'success') { $('.pseudoClass').modal('hide'); }
}
</h:outputScript>
</f:facet>
</b:modal>
</b:form>
A little demonstration:
If you want to completely block the ajax form submission on invalid form and do client side validation, you have to modify your onclick in accordance to: JSF ajax request is not fired when combined with JS client-side validation:
<h:commandButton value="submit" action="#{testBean.save()}"
onclick="if(!validateModalDialog()) return false;">
<f:ajax render="updateSection" execute="#this modalOutput" onevent="closeModalDialog" />
</h:commandButton>
Beware client side validation does not prevent users from submitting invalid data by e.g. simply playing around using their browser dev tools.

Related

jsf 2.0 ajax renders the whole page (from button) instead of just rendering a panelgroup [duplicate]

This question already has an answer here:
How make commandButton not fully refresh page? How to use f:ajax?
(1 answer)
Closed 7 years ago.
I have a button that should render only one specific panelGroup (parentWrapper). Instead it re-render the whole page. This is the way it is supposed to work:
The user looks for a excel document and enters the location in the fileupload component
Then the user selects a Customer using the Autocomplete component
Then the user clicks on the AddCustomer button. That executes the CustomerInSchedule (autocomplete) and the action editScheduleController.addCustomerRowFromLookAhead(), both are working fine.
When the user clicks the AddCustomer button, the ajax is supposed to render only the :CustomerScheduleForm:parentWrapper. Instead of that the whole page is rendered. That creates issues in other parts of this page. So the question is: Why is ajax rendering the whole page and not just the panelGroup id ="parentWrapper" ?
<div align="center">
<h:panelGroup id="editScreenDiv" layout="block" style="width:1180px">
<h:panelGrid border="0" columns="2" class="SimpleFormBoundedSection SubRegionBox" columnClasses="InputLabel,InputElement" rowClasses="FirstRow,LastRow" headerClass="SectionHead">
<!-- some stuff -->
<l:fileUpload id="fileupload" value="#{editScheduleController.customerRateScheduleBean.sourceFile}" onchange="file_onchange()" rendered="#{editScheduleController.customerRateScheduleBean.action != 'editSchedule'}" />
<!-- ... some stuff -->
<h:commandButton id="AddCustomer" value="Add" action="#{editScheduleController.addCustomerRowFromLookAhead()}" >
</h:commandButton>
<p:autoComplete id="CustomerInSchedule" required="false" value="#{editScheduleController.customerNumberAndName}" completeMethod="#{editScheduleController.suggestAuthorizedCustomer}" minQueryLength="3" scrollHeight="250" size="65" maxlength="50" forceSelection="true">
For some reason I could not continue adding the xhtml code here. What follows is the rest of the code that belongs to the same page. I have omitted style attributes from the div and the dataTable.
<f:ajax render=":CustomerScheduleForm:parentWrapper" execute="CustomerInSchedule" onevent="clearCustomerInSchedule()"/>
</p:autoComplete>
<h:panelGroup id ="parentWrapper">
<h:panelGroup id ="wrapper">
<div>
<h:dataTable id="customerTable" value="#{editScheduleController.customerRateScheduleBean.listOfCustomersInSchedule}"
var="customerRow" >
<!-- ...some stuff -->
</h:dataTable>
</div>
</h:panelGroup>
</h:panelGrid>
</h:panelGroup>
<!-- ... some stuff -->
Add an update to your commandButton, with the component to update : update=":CustomerScheduleForm:parentWrapper".
Edit : do this accordinaly with esiting commandButton to p: instead of h to use the update and ajax.

Prevent confirmation dialogue from opening when there's a validation error

I have a text area inside a tab of accordion panel which is a description. I am trying to edit a description and saving it. I am validating the text area so that max character shouldn't exceed 1000 character. I am using <p:message> to display validation message. Before the actual save, a confirmation dialogue will be shown to confirm the save.
<p:messages showDetail="true" autoUpdate="true" />
<p:accordionPanel dynamic="true">
<p:tab id="_0" title="description">
<p:inputTextarea styleClass="max" id="editDesc1" widgetVar="txtBox" value="#{testBean.description}"
rows="6" cols="150" validatorMessage="#{msg.AddSystem_validationMsg5}" autoResize="false">
<f:validateLength maximum="1000"></f:validateLength>
</p:inputTextarea>
<p:commandButton value="save" oncomplete="saveDialog.show()"/>
<p:confirmDialog message="#{msg.EditSystem_confirmMsg1}" width="200"
showEffect="explode" hideEffect="explode"
header="Confirm" severity="alert" widgetVar="saveDialog">
<p:commandButton value="#{msg.EditSystem_confirmAnswer1}" action="#{testBean.saveEdit}" process="#this" />
<p:commandButton value="#{msg.EditSystem_confirmAnswer2}" onclick="saveDialog.hide()" type="button" />
If an user enters more than 1000 characters and tries to save it, then the validation message appears for a short time and then the confirmation dialogue pops up, causing the validation message to disappear. How do I prevent the confirmation dialogue from popping up when there is a validation error?
You need to check in oncomplete of the save button if validation hasn't failed. PrimeFaces puts a global args object in the JavaScript scope which in turn has a boolean validationFailed property. You could make use of it:
<p:commandButton value="save" oncomplete="if (args && !args.validationFailed) saveDialog.show()"/>
This way the confirm dialog will only be shown if the validation has not failed.
I think you can use javascript to validate:
<script type="text/javascript">
function test(){
// validation here
if(isValidated){saveDialog.show()}
else alert('exceed ...');
}
</script>
<p:commandButton value="save" onclick="test()"/>

JSF skip Required-Validation without immediate=true

I have multiple forms, where I have mandatory fields and optional fields.
To submit such a form I require the validation on the required-attribute to be executed, which works fine.
To cancel such a form I use the attribute immediate="true" on the p:commandbutton, which makes its action happen during the Apply Request Values-Phase as addressed here: How to skip validation when a specific button is clicked?
However, for large forms I want to provide the user with a Save-Button, so he can proceed later.
For just saving the current state I also want to ignore the validation of the required-attribute. However, using immediate="true" is not working, because then my save method simple saves nothing, because the JSF lifecycle never hits the "UpdateModelValues"-Phase. (Acording to http://www.javacodegeeks.com/2012/01/jsf-and-immediate-attribute-command.html )
So, how to bypass the required-check but not skip half the lifecycle?
Each Button creates an entry inside the Param-List as long as it's member of the form.
So I simple applied a check for the presence of that entry to the "required" parameter:
<h:form id="form" prependId="true">
...
<p:inputText id="someId"
required="#{param['form:save']==null}" ... />
...
<p:commandButton id="save" value="Save" />
<p:commandButton id="submit" value="Submit" />
<p:commandButton id="cancel" value="Cancel" immediate="true" />
</h:form>
When I click "Submit" the param['form:save'] is NULL, which then turns the expression to true so the validation is executed.
When I click "Save" the param['form:save'] is NOT NULL (but empty!), which resolves to false so the validation is ignored. (Or let's say JSF thinks it is not a required field due to the expression beeing evaluated to false)
if you want to skip validation when click on button then easly add parameter to button where you want to skip it. Example:
<p:commandButton value="button1" action="#{bean.action()}" >
<f:param name="skipValidator" value="true"/>
</p:commandButton>
Then in validator you can read this parameter and if it is true then skip it:
#FacesValidator("com.validators.MyValidator")
public class MyValidator implements Validator{
public void validate(FacesContext ct, UIComponent co, Object obj) throws ValidatorException {
if(!continueValidation()){
return;
}
// validation process
}
protected boolean continueValidation() {
String skipValidator= FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("skipValidator");
if (skipValidator != null && skipValidator.equalsIgnoreCase("true")) {
return false;
}
return true;
}
}
This is an excellent question and a very helpful answer. This approach saves a lot of hassle with immediate="true".
I'd like to add this info (but am not allowed to comment yet). Your code examples seem to require JSF 2.0 or above (correct me). If you are like me damned to use JSF 1.1/1.2 then consider these changes/additions:
<h:form id="form">
...
<p:inputText id="someId" required="#{!empty param['save']}" ... />
...
<p:commandButton id="save" value="Save" />
</h:form>
There is no attribute prependId in JSF 1.1
Therefore in the param[...] you must only specify the button id
You are using a syntax ="{true and ...}" that might be a mistake (no #)?
As you can see from your own editing history the "null or not null" logic is not very intuitive :) Thats why I immediately liked the !empty ... version when I stumbled upon it.
An alternative to what others proposed is to use a custom BeanValidator that will validate the form if say, clicked the button with id save. Any other button not implicitly defined to perform validation will not validate but just submit your data available. Find the full nice and clean example here
I use skipValidators for such a case (assuming all validations are skipped). Code from omnifaces
<h:form>
<h:dataTable value="#{bean.items}" var="item">
<h:column>
<h:inputText value="#{item.value}" required="true" />
</h:column>
</h:dataTable>
<h:commandButton value="add new row" action="#{bean.add}">
<o:skipValidators />
</h:commandButton>
<h:commandButton value="save all data" action="#{bean.save}" />
<h:messages />
</h:form>
In my case I didn't find the clientId of the button in the params but I found this
param['javax.faces.source'] = buttonClientId in the requestmap. The value will be the clientId of the clicked button.

How to submit two forms in jsf with Ajax or without Ajax

I have basically two problems.
When I press the button in Form1, it is working fine, but I can't see the messages from FacesContext.
Another problem is in Form2. When I press the button only once, it goes to the server but nothing happens, no submit. But when I press it on second time, it is working fine. There is of course the same problem like in form one, that I can't see the messages from FacesContext. Could you please help and tell me what is causing that or is there another solution for having multiple forms inside one page?
<p:tabView>
<p:tab title="Form1">
<h:form id="form1">
<p:inputText id="txtInput" value="#{controller.selected.defaultLayout}" />
<h:commandButton value="Submit other form" action="#{controller.createMenu()}">
<f:ajax execute="#form" render="#form" />
</h:commandButton>
</h:form>
</p:tab>
<p:tab title="Form2">
<h:form id="form2">
<p:inputText id="txtInput2" value="#{controller.selected.defaultTheme}" />
<h:commandButton value="Submit other form" action="#{controller.createMenu2()}">
<f:ajax execute="#form" render="#form" />
</h:commandButton>
</h:form>
</p:tab>
</p:tabView>
Controller:
public String createMenu() {
Menu menu = current.getMenuMenuId();
try {
//current.getMenuMenuId().setMenuCreated(true);
//getFacade().edit(current);
JsfUtil.addSuccessMessage(ResourceBundle.getBundle("resources/Bundle").getString("MenuCreated"));-----> never visible!
return "";
} catch (Exception e) {
JsfUtil.addErrorMessage(e, ResourceBundle.getBundle("resources/Bundle").getString("PersistenceErrorOccured"));
return null;
}
}
EDIT: I took the outer -tags off and then the page is not working anymore, then I get the error message:
javax.faces.FacesException: <f:ajax> contains an unknown id ':form2' - cannot locate it in the context of the component j_idt77
at com.sun.faces.renderkit.html_basic.AjaxBehaviorRenderer.getResolvedId(AjaxBehaviorRenderer.java:285)
at com.sun.faces.renderkit.html_basic.AjaxBehaviorRenderer.appendIds(AjaxBehaviorRenderer.java:272)
at com.sun.faces.renderkit.html_basic.AjaxBehaviorRenderer.buildAjaxCommand(AjaxBehaviorRenderer.java:214)
at com.sun.faces.renderkit.html_basic.AjaxBehaviorRenderer.getScript(AjaxBehaviorRenderer.java:86)
at javax.faces.component.behavior.ClientBehaviorBase.getScript(ClientBehaviorBase.java:103)
Thank you!
Sami
Nesting forms is not valid html. This will cause unexpected behavior. Remove the outer form and see which of your issues persist.
UPDATE:
Primefaces tabview works without form as well. However if you have input elements and command buttons inside your tabs you need a form. But this is a html requirement and not PF specific. You should remove the outer form only and not the inner forms. Please update your question with your current version.

Rendering other form by ajax causes its view state to be lost, how do I add this back?

I have
<h:form>
<h:commandLink action="#{my_fake_ajax_link}">
<h:outputText value="Link" />
<f:ajax render=":mydiv" />
</h:commandLink>
</h:form>
<h:panelGroup layout="block" id="mydiv">
<h:form>
<h:commandLink action="#{mybean.delete(0)}">
<h:outputText value="Here" />
<f:ajax render="#form" />
</h:commandLink>
</h:form>
</h:panelGroup>
When I click once on "my_fake_ajax_link", then I have to click twice on "delete" link. This is only an example. I don't have this real case. I have multiple forms on a page and I can't just add all of them in a single form.
I checked what the problem is and it is:
When you click on "my_fake_ajax_link", the mydiv refreshs with ajax as it should.
ViewState of the refreshed form on ajax is missing.
How can I add the ViewState? How can I make it work without using only one form? This looks like an JSF bug to me. I don't want to refresh that div automatically with
jQuery("#mydiv").load(document.location.href);
but I will in my worst case possible.
This is a known problem in the auto-included jsf.js library of JSF which handles ajax responses. See also JSF spec issue 790 which is fixed in the upcoming JSF 2.3. In the meanwhile, with JSF 2.0/2.1/2.2, you have to explicitly specify the ID of the other <h:form> inside the render attribtue to trigger proper addition of the view state.
<h:form>
<h:commandLink action="#{my_fake_ajax_link}">
<h:outputText value="Link" />
<f:ajax render=":mydiv :mydivForm" />
</h:commandLink>
</h:form>
<h:panelGroup layout="block" id="mydiv">
<h:form id="mydivForm">
<h:commandLink action="#{mybean.delete(0)}">
<h:outputText value="Here" />
<f:ajax render="#form" />
</h:commandLink>
</h:form>
</h:panelGroup>
No, this does not cause any overhead or markup duplication in the ajax response. Alternatively, use OmniFaces fixviewstate.js.
See also:
Communication in JSF 2.0 - Ajax rendering of content which contains another form
h:commandButton/h:commandLink does not work on first click, works only on second click
Workaround for that:
First you need to set an onevent handler for the button that sends the ajax request:
<h:form id="newComment">
<h:commandButton id="saveNewComment" action="#{postBean.actionSaveNewCommentAjax}" value="#{rb['speichern']}">
<f:ajax execute="#form" render=":commentsBox" onevent="function(data) { fixOtherFormsViewState(data, 'newComment') }" />
</h:commandButton>
</h:form>
Then you need to declare this javascript methods that will take the correct viewState value and add it to all forms which doesn't have it:
<h:outputScript>
function fixOtherFormsViewState(data, goodFormId) {
if (data.status != "success") {
return;
}
var viewState = jQuery("#" + goodFormId + " input[name='javax.faces.ViewState']").val();
jQuery("form:not(:contains(input[name='javax.faces.ViewState']))").each(function (idx, elem) {
var form = jQuery(elem);
var input = jQuery("<input type='hidden' name='javax.faces.ViewState' />").val(viewState);
form.append(input);
});
}
</h:outputScript>

Resources