PrimeFaces disable submit on pressing enter key.
I’m, running PrimeFaces 5.1 running on WildFly 8.2 Final.
I have dialog, with two inputNumbers and two buttons. And the first inputNumber does some calculation on ajax blur event. Next to it is button which does some calculation in bean. And the problem is that when users press enter while focus is in inputNumber the button’s action gets fired and it’s really annoying. Is there a way to disable submitting with enter key on dialog?
Here is small xhtml dialog which can simulate my behavior:
<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:p="http://primefaces.org/ui"
xmlns:pe="http://primefaces.org/ui/extensions" >
<p:dialog id="id_example" header="Test dialog"
widgetVar="exampleDialog" modal="true" closable="true" >
<h:form id="id_example_form">
<p:panelGrid columns="3" styleClass="noBorders">
<h:outputText value="Input 1:" />
<pe:inputNumber id="Input1" value="#{exampleBean.number1}">
<p:ajax event="blur" update="valueInput1" />
</pe:inputNumber>
<p:commandButton value="Check something else" action="#{exampleBean.checkForUsername()}"
update=":growl_form" />
<h:outputText value="Input 1:" />
<p:inputText id="valueInput1" value="#{exampleBean.number1}" />
<p:commandButton value="Save" action="#{exampleBean.save()}" oncomplete="PF('exampleDialog').hide();"
update=":growl_form" />
</p:panelGrid>
</h:form>
</p:dialog>
</ui:composition>
And the bean:
package si.pucko.beans;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import javax.faces.view.ViewScoped;
import javax.inject.Named;
import si.pucko.util.Util;
#Named(value = "exampleBean")
#ViewScoped
public class ExampleBean implements Serializable {
private static final long serialVersionUID = 1L;
private BigDecimal number1;
public ExampleBean() {
number1 = new BigDecimal(BigInteger.ONE);
}
public BigDecimal getNumber1() {
return number1;
}
public void setNumber1(BigDecimal number1) {
this.number1 = number1;
}
public void checkForUsername() {
Util.ShowWarning("Just testing");
}
public void save() {
Util.ShowWarning("Saved");
}
}
The catch is i can't disable enter key with:
<h:form onkeypress="if (event.keyCode == 13) { return false; }">
Because client asked for hotkeys support and enter is used for submiting forms, recalculation some other values in some cases etc...
I think you use JavaScript to capture the enter key press and do nothing.
<h:form onkeypress="if (event.keyCode == 13) { return false; }">
Ref: https://stackoverflow.com/a/5486046/201891
return false; cancels an event across browsers if called at the end of an event handler attribute in the HTML. This behaviour is not formally specified anywhere as far as I know.
Ref: https://stackoverflow.com/a/1648854/201891
Update
It sounds like you want to disable the Enter key only when focus is in a particular field. You can write a Javascript method for that too and bind it to onkeypress. Write a Javascript method something like "if the enter key was pressed and the focus is in this field, return false; otherwise, return true".
As the answer referenced by Nimnio says, this is specific to HTML and browsers.
I consider this behavior to be inappropriate when using PrimeFaces.
I prefer to disable it globally, for all forms like this:
$('form').off('keypress.disableAutoSubmitOnEnter').on('keypress.disableAutoSubmitOnEnter', function(event) {
if (event.which === $.ui.keyCode.ENTER && $(event.target).is(':input:not(textarea,:button,:submit,:reset)')) {
event.preventDefault();
}
});
The target check allows the other default behaviors to work, like adding a line break in a textarea by pressing Enter.
To take into account new ajaxically added forms you'll need to call the above script after every AJAX request. There are multiple ways to do that, such as a <script> in a p:outputPanel autoUpdate="true", or calling a function in a p:ajaxStatus's oncomplete callback.
If this solution is not appropriate for some reason then consider the more localized one:
<h:form onsubmit="return false;">
Returning false here disables the non-AJAX default submit.
Putting a dummy/hidden button before the one you want to stop works best for me
<p:commandButton style="visibility: hidden;"/> <!-- dummy hidden button to stop keyPress from firirng submit -->
I used following solution:
<h:form onsubmit="return false;">
This prevents form submit. It works well only in case if you have ajax only behavior on this form.
It is default browser behavior to hunt a form for jQuery(":submit") elements and trigger the first listed on the form, when the enter key is pressed.
this will look strange on debugger, because if you have a function such as onclick="handle(event);".
You go to the text field, hit enter, and you will see an "artifical" onclick event with being passed to your primary submit action for that form.
The surest way to be in-control of what happens, I would say, is not by means of onkeypress as explained above. I found that to not work in all cases. On soame cases the form onkeypress simply does not get triggered, and you do not have then the opportunity to return flase; / event.preventDefault();. I am not 100% sure of all cases that justfied the onkeypress not getting triggered, but I suspect framework code preventing event bubbling in some instances.
Ultimately, what is really happening is your form is being submitted by your browser default behavior on ENTER withing input text field. It is not the bubling of the event to the form that submits the form. That is the flaw of trying to handle the event on the form and preventing default behavior there. That might just be too late.
And this is very easy to verify. If you tune youe inpu text and onkeypress always to event.preventDefault(). (A) you kill the textbox no text gets written on the texbox (B) The event still bubles up, but the browser does not submit the form anyway.
So the most important element of all is: where is the browser aiming at when it acts with the default behavior?
So what you can decide instead is that what you will take control of where the browser unleashes its default behavior. You can direct it to a dummy button.
This is the one and only one mechanism I have found to be really reliable to be in control of what happens when you hit enter on a text field withing a form is to have on that form a well known artifical button, e.g.:
<input type="submit" onclick"return false;" style="border:none; background-color: #myAppBrackgroundColor;" />
You get the idea, to make sure that the submit event is being routed by the browser to a button under your control.
You have to be careful, IE in particular is quirky.
If not for IE, you could simply style your button as display: none.
Because of IE, the browser will route the default submit action to a visible submit button, if one exists.
Therefore, your button cannot be in display none. You have to make it logically visible, but phsysically invisible. For that you supress the border and give it an appropriate background for your application.
This method is 100% reliable.
onkeydown and onchange()
<h:form id="inputform">
<p:inputText onkeydown="if (event.keyCode === 13) {onchange();return false;}" >
<p:ajax update="updatearea" />
</p:inputText>
</h:form>
Related
I have implemented a web application which is a one-page-design. Which basically loads a single page then updates with AJAX the central content. The code is the following:
<h:body>
<pe:layout id="page" fullPage="true">
<!-- West -->
<pe:layoutPane id="west" position="west" >
<f:facet name="header">Main Menu</f:facet>
<h:form id="form1">
<p:panelMenu id="panelMenu">
<p:submenu label="Persons">
<p:menuitem value="Person List" update=":centerpanel"
actionListener="#{layout.setAll('formPersonList.xhtml', 'Person List')}">
</p:menuitem>
</p:submenu>
</p:panelMenu>
</h:form>
</pe:layoutPane>
<!-- Center -->
<pe:layoutPane id="content" position="center">
<h:panelGroup id="centerpanel" layout="block">
<ui:include id="include" src="#{layout.navigation}" />
</h:panelGroup>
</pe:layoutPane>
</pe:layout>
</h:body>
This basically works, but I want to enable browser navigation as well. For example like: http://ticketmonster-jdf.rhcloud.com/ with the hashtags on the url. So using the back/forward button I can go to the equivalent option.
Any idea how to do this?
I have created a blog post explaining how to get this to work using jQuery BBQ, based on your question.
With jQuery BBQ you can keep track of state, history and allow bookmarking while dynamically modifying the page via AJAX and/or DHTML.. just click the links, use your browser's back and next buttons, reload the page..
First we should include jQuery BBQ.
<h:outputScript library="js" name="jquery.ba-bbq.min.js" />
Now consider we have the menu (with all our nav rules)
<p:submenu label="Meat">
<p:menuitem outcome="/meat/steak.xhtml" value="Steak" />
<p:menuitem outcome="/meat/burger.xhtml" value="Burger" />
<p:menuitem outcome="/meat/chicken.xhtml" value="Chicken" />
<p:menuitem outcome="/meat/kebab.xhtml" value="Kebab" />
</p:submenu>
Then the centered content
<pe:layoutPane id="content" position="center">
<h:panelGroup id="centerpanel" layout="block">
<ui:include id="include" src="#{mainBean.currentNav}" />
</h:panelGroup>
</pe:layoutPane>
the include reflects the currentNav clicked.
now define a remoteCommand inside the form
<p:remoteCommand name="updateNav"
actionListener="#{mainBean.updateNav()}"
update=":centerpanel"/>
This remoteCommand will update our currentNav based on the hashtag.
Create your JS file or include the following code into the document ready
$(document).ready(function() {
$('.ui-menuitem-link').click(function(event) {
event.preventDefault();
var currentNav = $(this).attr('href').
substr($(this).attr('href').indexOf("/faces") + 6)
window.location.hash = '#' + currentNav;
})
$(window).bind('hashchange', function(e) {
var url = $.param.fragment();
updateNav([{name: 'currentNav', value: url}]); //remoteCommand Call
})
$(window).trigger('hashchange');
});
Basically first we handle our clicks on the menu items, setting the hash of the window.
then we define an event of the hashchange of the window, using the help of jQuery BBQ.
ManagedBean
public void updateNav() {
FacesContext context = FacesContext.getCurrentInstance();
Map map = context.getExternalContext().getRequestParameterMap();
currentNav = (String) map.get("currentNav");
}
For a complete code, please see my newly post created for the question.
Primefaces Hash Navigation Using jQuery BBQ
And also the example is available on github.
Hope it helps.
If I understand well your question, you want to navigate with back/forward button. You can do this with LinkedList in your backing bean:
private LinkedList<String> historyForBackPage= new LinkedList<String>();
public void setLastBackPage(String navigationCase) {
historyForBackPage.push(navigationCase);
if (historyForBackPage.size() > yourMaxSize) {
historyForBackPage.pollLast();
}
}
public String getLastBackPage() {
return historyForBackPage.pop();
}
and always add last page when you call layout.setAll method. The simple commandButton call getLastBackPage() method. Before try it, please configure navigation case in faces-config.xml.
If your case cannot work navigation case, because you work only one XHTML, than you could add back/foward page name for your bean and render your page. May be simple JavaScript call onclick="window.history.go(-1); return false;" it could be usefull in your case. I don't know. Please try it!
In my answer I focused only to back button, but I think you can adapt foward button in same way.
By the way, the Breadcrumb is nice PrimeFaces feature.
If you want to catch browser back clicking action, you can use one JavaScript or Ajax script.
For Ajax script please check this answer https://stackoverflow.com/a/10050826/1047582.
I have set up a basic testcase in which I'm experience some (to me) weird behaviour. When using the setup below, the typed value in the editor will only be visible by h:outputText on the second submit. E.g.
Change value in editor to "myvalue"
Send Ajax-request
h:outputText shows "test" (default value from bean)
Change value in editor to "anothervalue"
Send Ajax-request
h:outputText shows "myvalue"
Send Ajax-request
h:outputText shows "anothervalue"
Note: there's a custom composite, please ask for code if needed (it simply creates textarea for TinyMCE and loads .js file from below)
index.xhtml
<h:body>
<h:form>
<mh:editor id="tinymceEditor"
value="#{bean.value}" />
<h:commandButton value="Ajax">
<f:ajax execute="tinymceEditor"
render="show" />
</h:commandButton>
<h:outputText id="show" value="#{bean.value}" />
</h:form>
</h:body>
jsfhandler.js -> included on header in custom composite mh:editor
jsf.ajax.addOnEvent(function(data) {
switch(data.status) {
case "begin":
tinyMCE.execCommand('mceRemoveControl',true,"tinymceEditor");
tinyMCE.triggerSave();
break;
case "complete":
tinyMCE.execCommand('mceAddControl',true,"tinymceEditor");
break;
case "success":
break;
}
});
Bean.java
#Named
#RequestScoped
public class Bean {
private String value = "test";
}
The JSF ajax begin event is too late in order to take changes in form data into account. The ajax request is already prepared based on form data before this event.
Effectively, the sequence is as follows:
User enters input (and leaves field).
HTML DOM "change" event is triggered on input field.
User clicks submit button.
HTML DOM "click" event is triggered on submit button.
JSF prepares ajax request.
JSF ajax "begin" event is triggered on ajax request.
JSF sends ajax request.
...
Basically, you should be doing tinyMCE.triggerSave() during the HTML DOM "click" event.
<h:commandButton ... onclick="tinyMCE.triggerSave()">
Or, better, during the HTML DOM "change" event of the tinyMCE textarea.
I am trying to have an h:inputText switch out with a different one when an h:commandButton is clicked. To do this, I am trying to tie an f:ajax command with the h:commandButton, with the listener setting a value on the bean (deciding which one to display), and re-rendering the area.
I have tried using listener on the f:ajax, actionListener on the h:commandButton, action on the h:commandButton with execute on the f:ajax. Nothing worked. The mothed I am trying to call is not being called at all - there is no println (see what follows).
The panelGroup is being re-rendered, which is why I need the onevent attribute that re-attaches some JavaScript hint text based on the title (I had an earlier question involving this).
The method I am trying to call:
public void morePressed(AjaxBehaviorEvent e) {
easySearch = !easySearch;
System.out.println("Made it!");
}
The JSF segment that is not working (note the last h:commandButton is trying to re-render the panelGroup):
<h:form>
<h:panelGroup id="switchSearchTexts">
<h:inputText accesskey="s" alt="Search" id="searchBoxPeople" title="Search Plebeians" valueChangeListener="#{peopleBean.simplePersonQuery}" size="25" rendered="#{peopleBean.easySearch}">
<f:ajax render="peopleDataTable" event="keyup" />
</h:inputText>
<h:inputText accesskey="s" alt="Search First Name" id="searchBoxFN" title="Search First Name" size="25" rendered="#{!peopleBean.easySearch}">
<f:ajax render="peopleDataTable" event="keyup" />
</h:inputText>
</h:panelGroup>
<div id="expandBox">
<h:inputText id="searchBoxLN" alt="Search Last Name" styleClass="hideToggle" title="Search Last Name" size="25" />
<h:inputText id="searchBoxAddress" alt="Search Address" styleClass="hideToggle" title="Search Address" size="25" />
</div>
<h:commandButton type="button" styleClass="moreButtonAsText" id="moreButtonAsText" value="▸More">
<f:ajax listener="#{peopleBean.morePressed}" render="switchSearchTexts" event="click" onevent="callFunctionAjaxRequest" />
</h:commandButton>
This is the JavaScript (jQuery) that I attach to the More button on pageload. I give it not because I think it could help, but I don't know if this could interfere with the ajax listener in any way:
$(function() {
textboxHint();
$('input[id$="moreButtonAsText"]').toggle(function(e) {
$(this).prop('value', '\u25c2Less');
$(".hideToggle").show(300);
}
, function () {
$(this).prop('value', '\u25b8More');
$(".hideToggle").hide(100);
$('input[id$="searchBoxAddress"]').prop('value', function() {
return $(this).prop('title');
});
$('input[id$="searchBoxAddress"]').blur();
});
});
I have no idea. As I said, I have tried actionListener on h:commandButton with various appraoches there, as well as various approaches to the listener on the ajax. Can anybody see why the listener does not work?
Update:
What I ended up doing, before having an answer, is converting everything to display and hide based on JavaScript, with stuff I needed hidden if they didn't have javascript initially hidden, etc.
However I need the f:ajax elsewhere, now.
The solution is to take out event="click" on the h:commandButton. I still do now know why this was causing it to break, but hey, whatever works.
I had an issue like this. It turned out that an inputText somewhere had a value="#{something.something}" where the property wasn't settable. The exception wasn't being reported anywhere. I had to put a breakpoint on Exception and all subclasses to find it.
Do you really have a function named callFunctionAjaxRequest in your js code? cause if not it may cause the button not to work (because its being referenced to a not existing function) ,
look at the firebug for a possible errors...
try this version of your command button (event="click" can be removed too... and self execute too)
<h:commandButton type="button" styleClass="moreButtonAsText" id="moreButtonAsText" value="More">
<f:ajax listener="#{peopleBean.morePressed}" render="switchSearchTexts" />
</h:commandButton>
Another thing , in your ajax calls of the upper input texts you got reference to searchBoxPeople twice (instead to searchBoxFN in the second f:ajax), fix it cause otherwise when working with searchBoxFN its f:ajax will try to execute an element that its not being rendered ... (can cause serious issues...)
p.s prependId="false" in your h:form will simplify your selectors in jQuery...
The issue is that the managed bean needs to be set up with the right signature event as an input param. Through lots of testing, I was trying to use the same class taking an AjaxBehaviorEvent. As in the same example on the previous forum.
when I declared an ActionListener event (compliant with the button jsf action), the bean is executed!
I had the same problem and followed your example to help me exactly. I simply (20 hrs) fixed this by including the following in my bean:
The first one now gets fired!
public void actionListener(ActionEvent actionEvent) {
// Add event code here...
System.out.println("Made it!");
}
public void morePressed(AjaxBehaviorEvent e) {
System.out.println("Made it!");
}
Minimal example dialog:
<p:dialog header="Test Dialog"
widgetVar="testDialog">
<h:form>
<p:inputText value="#{mbean.someValue}"/>
<p:commandButton value="Save"
onsuccess="testDialog.hide()"
actionListener="#{mbean.saveMethod}"/>
</h:form>
</p:dialog>
What I want to be able to do is have the mbean.saveMethod somehow prevent the dialog from closing if there was some problem and only output a message through growl. This is a case where a validator won't help because there's no way to tell if someValue is valid until a save is submitted to a back end server. Currently I do this using the visible attribute and point it to a boolean field in mbean. That works but it makes the user interface slower because popping up or down the dialog requires hitting the server.
The onsuccess runs if ajax request itself was successful (i.e. there's no network error, uncaught exception, etc), not if action method was successfully invoked.
Given a <p:dialog widgetVar="yourWidgetVarName">, you could remove the onsuccess and replace it by PrimeFaces RequestContext#execute() inside saveMethod():
if (success) {
RequestContext.getCurrentInstance().execute("PF('yourWidgetVarName').hide()");
}
Note: PF() was introduced in PrimeFaces 4.0. In older PrimeFaces versions, you need yourWidgetVarName.hide() instead.
If you prefer to not clutter the controller with view-specific scripts, you could use oncomplete instead which offers an args object which has a boolean validationFailed property:
<p:commandButton ...
oncomplete="if (args && !args.validationFailed) PF('yourWidgetVarName').hide()" />
The if (args) check is necessary because it may be absent when an ajax error has occurred and thus cause a new JS error when you try to get validationFailed from it; the & instead of & is mandatory for the reason explained in this answer, refactor if necessary to a JS function which you invoke like oncomplete="hideDialogOnSuccess(args, 'yourWidgetVarName')" as shown in Keep <p:dialog> open when validation has failed.
If there is however no validation error and the action method is successfully triggered, and you would still like to keep the dialog open because of e.g. an exception in the service method call, then you can manually trigger validationFailed to true from inside backing bean action method by explicitly invoking FacesContext#validationFailed(). E.g.
FacesContext.getCurrentInstance().validationFailed();
Using the oncomplete attribute from your command button and really simple script will help you a lot.
Your dialog and command button would be something similar to this:
<p:dialog widgetVar="dialog">
<h:form id="dialogView">
<p:commandButton id="saveButton" icon="ui-icon-disk"
value="#{ui['action.save']}"
update=":dataList :dialogView"
actionListener="#{mbean.save()}"
oncomplete="handleDialogSubmit(xhr, status, args)" />
</h:form>
</p:dialog>
An the script would be something like this:
<script type="text/javascript">
function handleDialogSubmit(xhr, status, args) {
if (args.validationFailed) {
dialog.show();
} else {
dialog.hide();
}
}
</script>
I've just googled up this solution. Basically the idea is to use actionListener instead of button's action, and in backing bean you add callback parameter which will be then check in button's oncomplete method. Sample partial code:
JSF first:
<p:commandButton actionListener="#{myBean.doAction}"
oncomplete="if (!args.validationFailed && args.saved) schedulesDialog.hide();" />
Backing bean:
public void doAction(ActionEvent actionEvent) {
// do your stuff here...
if (ok) {
RequestContext.getCurrentInstance().addCallbackParam("saved", true);
} else {
RequestContext.getCurrentInstance().addCallbackParam("saved", false);
}
}
Hope this helps someone :)
I use this solution:
JSF code:
<p:dialog ... widgetVar="dlgModify" ... >
...
<p:commandButton value="Save" update="#form" actionListener="#{AdminMB.saveTable}" />
<p:commandButton value="Cancel" oncomplete="PF('dlgModify').hide();"/>
Backing bean code:
public void saveTable() {
RequestContext rc = RequestContext.getCurrentInstance();
rc.execute("PF('dlgModify').hide()");
}
I believe this is the cleanest solution.
Doing this you don't need to change your buttons code.
This solution overrides the hide function prototype.
$(document).ready(function() {
PrimeFaces.widget.Dialog.prototype.originalHide = PrimeFaces.widget.Dialog.prototype.hide; // keep a reference to the original hide()
PrimeFaces.widget.Dialog.prototype.hide = function() {
var ajaxResponseArgs = arguments.callee.caller.arguments[2]; // accesses oncomplete arguments
if (ajaxResponseArgs && ajaxResponseArgs.validationFailed) {
return; // on validation error, prevent closing
}
this.originalHide();
};
});
This way, you can keep your code like:
<p:commandButton value="Save" oncomplete="videoDetalheDialogJS.hide();"
actionListener="#{videoBean.saveVideo(video)}" />
The easiest solution is to not have any "widget.hide", neither in onclick, neither in oncomplete. Remove the hide functions and just put
visible="#{facesContext.validationFailed}"
for the dialog tag
I wonder if it is possible for me to freeze or disable the entire update form? I have an input h:form with a check box in it. when users check the box, I would like to freeze or disable the entire form so that disallow users from changing inputs.
Thanks, and I am using JSF, Spring Web Flow, Facelets, and Trinidad.
You would want to use javascript to set all the form inputs to disabled when the user checks the checkbox. Something like:
document.getElementById('id').disabled = true;
You would do this for each input element where 'id' is the ID of that element.
If you want to disable only certain inputs, It is a good idea to enumerate them:
function OptCheckBox(chkd) {
if (chkd == 'y') {
document.frm.input1.disabled = true;
document.frm.input2.disabled = true;
}
}
You cannot disable an entire form at once. You really need to disable each of the input elements. There are basically two ways to achieve this.
First way is to use Javascript to submit the form to the server when the checkbox is clicked, so that you can use JSF component's disabled attribute to disable the elements. Here's a basic example:
<h:form>
<h:selectBooleanCheckbox value="#{bean.freeze}" onclick="submit()" />
<h:inputText value="#{bean.value1}" disabled="#{bean.freeze}" />
<h:inputText value="#{bean.value2}" disabled="#{bean.freeze}" />
<h:inputText value="#{bean.value3}" disabled="#{bean.freeze}" />
</h:form>
Here #{bean.freeze} should point to a boolean property.
Second way is to write a Javascript function for this. This does not require a form submit and saves you from one HTTP request-response cycle, which is better for user experience.
<h:form>
<h:selectBooleanCheckbox onclick="disableForm(this)" />
<h:inputText value="#{bean.value1}" />
<h:inputText value="#{bean.value2}" />
<h:inputText value="#{bean.value3}" />
</h:form>
The JS function disableForm() is basically simple. Just pass the checkbox in as function argument by this so that you can get the parent form by checkboxElement.form and then get all form elements by form.elements. You only need to make sure that you don't disable the checkbox itself, so that you could re-enable the form again :)
function disableForm(checkboxElement) {
var elements = checkboxElement.form.elements;
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
if (element != checkboxElement) {
element.disabled = checkbox.checked;
}
}
}
No need to know the ID's beforehand and this makes the JS code generic and reuseable.