Ajax for valueChangeListener - ajax

I'm using the p:ajax listener to handle value change events (because valueChangeListener is launched on form submit):
<p:ajax event="change" listener="#{bean.onNameChanged}"/>
Handle method:
public void onNameChanged(final AjaxBehaviorEvent event)
The problem is, I can't find in AjaxBehaviorEvent nor its class hierarchy the place to read the old value of the input. Neither could I find hint in google, how to get the old value...
How to access the old value in the p:ajax onChange event?

The problem is, I can't find in AjaxBehaviorEvent nor its class hierarchy the place to read the old value of the input. Neither could I find hint in google, how to get the old value...
Use a valueChangeListener.
Unfortunatelly, valueChangeListener is invoked before p:ajax, so I don't have actual data from forms in that method, so in theory I could use valueChangeListener to remember the old value and then wait for p:ajax to process...
Queue the value change event to the invoke application phase.
public void valueChangeListenerMethod(ValueChangeEvent event) {
if (event.getPhaseId() != PhaseId.INVOKE_APPLICATION) {
event.setPhaseId(PhaseId.INVOKE_APPLICATION);
event.queue();
return;
}
// Do your original job here.
// It will only be invoked when current phase ID is INVOKE_APPLICATION.
}

The ValueChangeListener should work this way:
The view:
<h:form>
<h:inputText value="#{sessionBean.hello}"
valueChangeListener="#{sessionBean.valueChangeListener}">
<p:ajax/>
</h:inputText>
</h:form>
The bean:
public void valueChangeListener(ValueChangeEvent e) {
System.out.println("valueChangeListener invoked:"
+ " OLD: " + e.getOldValue()
+ " NEW: " + e.getNewValue());
}
The above code will print if I change the text field from "hello" to "world":
valueChangeListener invoked: OLD: hello NEW: world

You could try the following:
Implement the value change event in your bean
public void processValueChange(ValueChangeEvent e){
//foo the bar
}
Define a valueChangeListener on your selection component
<p:selectOneMenu value="#{yourBean.value}" onchange="submit()" valueChangeListener="{#yourBean.processValueChange}">
The key piece there is the submit() bit that processes the enclosing form on change of the value. You can then getNewValue() and getOldValue() as necessary.
EDIT: Now that I think about it, I see no reason why you cannot leave your setup as-is and simply define the valueChangeListener. It should still be processed during the change event in the <p:ajax/>, in fact, it will be processed before the listener for the ajax event itself.

you can use this:
public void onNameChanged(AjaxBehaviorEvent event)
{
String myVal = (String) ((UIOutput) event.getSource()).getValue();
System.out.println("myVal: " + myVal);
}

Workaround is possible (tested with Primefaces 10):
<p:inputText id="name" value="bean.name">
<p:ajax event="valueChange" update="name"
listener="#{bean.onNameChanged}"
onstart="cfg.ext={params:[{name:'oldValue', value:'#{bean.name}'}]};"/>
</p:inputText>
update="name" is important, to get each time the new value into the javascript event handler.
Bean Method:
public void onNameChanged(final AjaxBehaviorEvent event) {
String oldValue = getFacesContext().getExternalContext().getRequestParameterMap()
.get("oldValue");
//Do with oldValue, whatever you want
}

Related

How to use modifers like Ctrl or Shift with jsf.2.x and <f:ajax>? [duplicate]

I have a JSF ajax keyup event linked to an event listner in a backing bean.
The code in the JSF file is like below.
<h:inputText id="txtDescription" value="#{institutionController.current.description}" disabled="#{institutionController.modifyControlDisable}" >
<f:ajax event="keyup" listener="#{institutionController.changeDetailsEvent}" />
</h:inputText>
The code in the backing bean is like below.
public void changeDetailsEvent(AjaxBehaviorEvent event) {
}
I want to achieve different logic depending on the key presses, like shown is pseudocode below.
public void changeDetailsEvent(AjaxBehaviorEvent event) {
If (event.key = Key.enter) {
do something;
} else if (event.key = Key.Escape) {
so something else;
} else {
do nothing;
}
}
Can someone please tell me how this is done in the backing bean?
The AjaxBehaviorEvent doesn't contain any information about the JavaScript event object. You need to pass the desired information along yourself. This can be achieved by a hidden input field whose value is to be prefilled by JavaScript. For example,
<h:inputText value="#{bean.input}" onkeyup="document.getElementById('#{keyCode.clientId}').value=event.keyCode">
<f:ajax event="keyup" execute="#this keyCode" listener="#{bean.listener}" />
</h:inputText>
<h:inputHidden id="keyCode" binding="#{keyCode}" value="#{bean.keyCode}" />
(please note that the id of the hidden field is included in execute so that it get submitted along on the ajax request, please also note that the binding is used to be able to dynamically obtain the generated client ID in document.getElementById() in order to set the key code value, you could alternatively also hardcode the client ID if it's fixed)
with
private String input;
private int keyCode;
public void listener() {
switch (keyCode) {
case 13:
// Enter key was pressed.
break;
case 27:
// Escape key was pressed.
break;
default:
// Other key was pressed.
break;
}
}
You can find an overview of all valid keyCode values in the Mozilla DOM reference.

When to use valueChangeListener or f:ajax listener?

What's the difference between the following two pieces of code - with regards to listener placement?
<h:selectOneMenu ...>
<f:selectItems ... />
<f:ajax listener="#{bean.listener}" />
</h:selectOneMenu>
and
<h:selectOneMenu ... valueChangeListener="#{bean.listener}">
<f:selectItems ... />
</h:selectOneMenu>
The valueChangeListener will only be invoked when the form is submitted and the submitted value is different from the initial value. It's thus not invoked when only the HTML DOM change event is fired. If you would like to submit the form during the HTML DOM change event, then you'd need to add another <f:ajax/> without a listener(!) to the input component. It will cause a form submit which processes only the current component (as in execute="#this").
<h:selectOneMenu value="#{bean.value}" valueChangeListener="#{bean.changeListener}">
<f:selectItems ... />
<f:ajax />
</h:selectOneMenu>
When using <f:ajax listener> instead of valueChangeListener, it would by default executed during the HTML DOM change event already. Inside UICommand components and input components representing a checkbox or radiobutton, it would be by default executed during the HTML DOM click event only.
<h:selectOneMenu value="#{bean.value}">
<f:selectItems ... />
<f:ajax listener="#{bean.ajaxListener}" />
</h:selectOneMenu>
Another major difference is that the valueChangeListener method is invoked during the end of the PROCESS_VALIDATIONS phase. At that moment, the submitted value is not been updated in the model yet. So you cannot get it by just accessing the bean property which is bound to the input component's value. You need to get it by ValueChangeEvent#getNewValue(). The old value is by the way also available by ValueChangeEvent#getOldValue().
public void changeListener(ValueChangeEvent event) {
Object oldValue = event.getOldValue();
Object newValue = event.getNewValue();
// ...
}
The <f:ajax listener> method is invoked during INVOKE_APPLICATION phase. At that moment, the submitted value is already been updated in the model. You can just get it by directly accessing the bean property which is bound to the input component's value.
private Object value; // +getter+setter.
public void ajaxListener(AjaxBehaviorEvent event) {
System.out.println(value); // Look, (new) value is already set.
}
Also, if you would need to update another property based on the submitted value, then it would fail when you're using valueChangeListener as the updated property can be overridden by the submitted value during the subsequent UPDATE_MODEL_VALUES phase. That's exactly why you see in old JSF 1.x applications/tutorials/resources that a valueChangeListener is in such construct been used in combination with immediate="true" and FacesContext#renderResponse() to prevent that from happening. After all, using the valueChangeListener to execute business actions has actually always been a hack/workaround.
Summarized: Use the valueChangeListener only if you need to intercept on the actual value change itself. I.e. you're actually interested in both the old and the new value (e.g. to log them).
public void changeListener(ValueChangeEvent event) {
changeLogger.log(event.getOldValue(), event.getNewValue());
}
Use the <f:ajax listener> only if you need to execute a business action on the newly changed value. I.e. you're actually interested in only the new value (e.g. to populate a second dropdown).
public void ajaxListener(AjaxBehaviorEvent event) {
selectItemsOfSecondDropdown = populateItBasedOn(selectedValueOfFirstDropdown);
}
If you're actually also interested in the old value while executing a business action, then fall back to valueChangeListener, but queue it to the INVOKE_APPLICATION phase.
public void changeListener(ValueChangeEvent event) {
if (event.getPhaseId() != PhaseId.INVOKE_APPLICATION) {
event.setPhaseId(PhaseId.INVOKE_APPLICATION);
event.queue();
return;
}
Object oldValue = event.getOldValue();
Object newValue = event.getNewValue();
System.out.println(newValue.equals(value)); // true
// ...
}
for the first fragment (ajax listener attribute):
The "listener" attribute of an ajax tag is a method that is called on the server side every time the ajax function happens on the client side. For instance, you could use this attribute to specify a server side function to call every time the user pressed a key
but the second fragment (valueChangeListener) :
The ValueChangeListener will only be called when the form is submitted, not when the value of the input is changed
*you might like to view this handy answer

JSF2 Composite component link using ajax

Here is my (simplified) issue :
I've got a page that is using 2 composite components of mine :
- CCSelection
- CCDisplay
In CCSelection, I have a list of values, each one has got a h:commandLink onto.
When clicking on a link, the CCDiaplay component is refreshed using the selected value.
To do this, CCSelection exposes a method attribute that is directly linked on each h:commandLink. The value is given to the method using f:attribute.
In the page backing bean, I've got a method (that is given to CCSelection as an attribute), that sets a member.
CCDisplay gets this value though an cc:attribute via the pages's member's getter.
It works !
Now, I want to ajaxize this behaviour.
I tryed to put an f:ajax for each h:commandLink in CCSelection... but if I put #form or #all in the render attribute, nothing is rendered (but the setter methods are called). If I put the id of the UIComponent (of the Page) to render, I get a nullpointerexception saying that a property is not defined for NamingContainer in CCDisplay. Quite strange because I didn't change anything inside CCDisplay !
I think the solution is to put the f:ajax not inside CCSelection but in Page.
So there may be 2 ways to achieve this :
- CCSelection raises an event f:ajax can manage... but how ?
- Using cc:clientBehaviour for CCSelection. But is it possible to target more that 1 component (I've got many h:commandLink, but I want only 1 event).
- Other ways ?
Here is a pseudo code
page.xhtml
<myComp:ccSelection actionMethod="#{pageBean.select}"
render="#{clientIDHelper.clientId['display']}" />
<h:panelGroup id="diplay" binding="#{clientIDHelper.bindings['display']}">
<myComp:ccDisplay value="#{pageBean.value}" />
</h:panelGroup>
To recover the full clientid of the panel containing the ccDiaplay composite component, I use a clientIDMap technic described here.
PageBean.java
private String _value;
public String getValue() { return _value; }
public void setValue(String value) [ _value = value; }
public void select(String value) {
setValue(value);
}
ccSelection.xhtml
<cc:interface>
<cc:attribute method-signature="void selectAction(String)"
name="actionMethod" />
<cc:attribute name="render" />
</cc:interface>
<cc:implementation>
<t:dataTable value="#{cc.values}"
var="val"
...
>
<h:column>
<t:commandLink actionListener="#{cc.selectionValueListener}"
<f:ajax render="#{cc.attrs.render}" />
<f:attribute name="value"
value="#{val}" />
</t:commandLink>
</h:column>
</t:dataTable>
</cc:implementation>
ccSelection.java
public void selectionValueListener() {
// recover the attribute value
String value = event.getComponent().getAttributes().get("value");
// call the callback method of the page
FacesContext context = FacesContext.getCurrentInstance();
MethodExpression method = (MethodExpression) this.getAttributes().get("actionMethod");
if (method != null)
method.invoke(context.getELContext(), new Object[] {value});
}
I don't think ccDisplay is interressting.
So, if I don't put the f:ajax tag, it works.
When I put the f:ajax with the render pointing to the clientId passed in param, I get an error while loading the page.
If I change the render for #form or #all, the pageBean.select method is called, but ccDisplay is not refreshed.
I think i see a little error in page.xhtml.
See when you created the component cc:display you said:
<cc:attribute method-signature="void selectAction(String)" name="actionMethod" />
That means that a parameter is needed.
But when you call it in page.xhtml you do this:
<myComp:ccSelection actionMethod="#{pageBean.select}"...
And its backing bean method is:
public void select(String value) {
setValue(value);
}
As you see the backing bean is correct, but when you call the component in the page, there is no parameter being passed to the bean, and at the end the value is never set.
I think that might be one of the reasons.
To fix it i think you should pass the value some how:
<myComp:ccSelection actionMethod="#{pageBean.select(???Selected value
???)}"...
OK. It is solved... but I don't like it very much.
You'll think I'm a fool : I solved the problem by removing the <![CDATA surrounding my scripts !
I've already found some issue using CDATA. I don't know if this is a MyFaces bug or something I do the wrong way like putting many h:outputScript blocks with CDATA in composite components but with CDATA, I get errors or not working. Just removing it, it works !

<f:validator disabled flag not working

I've created a custom validator for my project, it simply checks the select ones value and 'validates' the value is not '0'. We have a standard (I'm sure not uncommon) of manually setting the first value of our selectOneMenu compents to:
<f:selectItem itemValue="0"
itemLabel="-- Select One --"/>
Which works fine, but then makes the component always pass the required check. So this validator simply treats this value as if there was no selection made.
SelectOneMenu example:
<h:selectOneMenu id="eligibility"
value="#{reg.eligibility}"
required="#{reg.fieldsRequired}">
<f:selectItem itemValue="0"
itemLabel="-- Select One --"/>
<f:selectItems value="#{reg.eligibilityList}" />
<f:validator validatorId="selectOneValidator"
disabled="#{!reg.fieldsRequired}"/>
Custom Validator:
#FacesValidator("selectOneValidator")
public class SelectOneValidator implements Validator {
#Override
public void validate(FacesContext context, UIComponent uiComponent, Object o) throws ValidatorException {
String val = null;
if (uiComponent instanceof HtmlSelectOneMenu) {
HtmlSelectOneMenu oneMenu = (HtmlSelectOneMenu) uiComponent;
if (oneMenu.isRequired() && !oneMenu.isDisabled()) {
if (o instanceof String) {
val = (String) o;
} else if (o instanceof Number) {
val = String.valueOf(o);
}
if ("0".equals(val)) {
FacesMessage msg = new FacesMessage();
msg.setSummary("Please select a value from the list.");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(msg);
}
}
}
}
}
This has worked just fine thus far, however, the current use-case I'm running into trouble.
My page has multiple SelectOneMenus one of which toggles the required and disabled attributes via an ajax call for the page. I'm not having any issues with the required="#{reg.fieldsRequired}", however; the disabled="#{!reg.fieldsRequired}" attribute on my custom validator does not seem to make a difference. I'm just thinking out loud, but when the page first loads the #{reg.fieldsRequired} expression is false. If I then change the SelectOneMenu to set this boolean value to true, then press the submit button, the disabled attribute doesn't seem to have been set. I wondered if this is simply a ajax issue and that all of components simply needed to be re-renderd so I added the #form in my ajax call: <a4j:ajax render="#form" listener="#{reg.saveActionChanged}"/> in hopes that would fix the problem, but no difference in the behavior.
Environment:
JSF 2.0.3
Tomcat 6.0.14
ajax call being made with RichFaces 4.0
Any help is most appreciated!
Sorry, I can't reproduce your problem with JSF 2.1.3. The <f:validator disabled> works as expected.
However, as a completely different alternative, you can in this particular case just make use of the standard required validator. You only need to set the item value to #{null} instead of 0.
<f:selectItem itemValue="#{null}" itemLabel="-- Select One --"/>
This way you don't need the custom validator. The message can be set as requiredMessage attribute of the input component.
<h:selectOneMenu requiredMessage="Please select a value from the list.">

Value remains in form field after it is cleared in bean in JSF2

In my JSF2 application, I have "Clear" button, which is supposed to clear all the fields. However, it doesn't always work.
My page fragment:
<h:form id="bi">
<h:inputText value="#{bean.entity.firstname}" />
<h:inputText value="#{bean.entity.surname}" />
<h:commandButton value="Clear" immediate="true" action="#{bean.clear}">
<f:ajax render="bi" />
</h:commandButton>
<h:commandButton value="Submit" action="#{bean.submit}" />
</h:form>
And clear() method in my bean:
public void clear() {
entity = new Entity();
}
If I enter values in the fields, and click "Clear", everything is cleared as expected. However, consider such scenario:
1. Enter value only in one field (both are required by JSR303 annotations on entity).
2. Click "Submit". Error message appears.
3. Click "Clear".
Entered value remains. Why is it not cleared?
Moreover, if I clear it by hand, and click "Clear", it returns to the field. I checked that it comes to the browser in partial response after clicking "Clear" button. I suspect it has something to do with view state.
Moreover, if I add validator="#{bean.validate}" to the field, it enter this validation. Even if button has immediate="true" attribute. Why? Shouldn't immediate button ommit validation?
You've run into a more or less well-known issue regarding updating components for which validation has already happened.
This post is rather old, but still relevant: http://ishabalov.blogspot.com/2007/08/sad-story-about-uiinput.html
There is a community created solution for A4J in JSF 1.2 posted here: http://community.jboss.org/thread/8446?start=15&tstart=0
But unfortunately, this doesn't work directly in JSF 2.0 and in your case it wouldn't work at all since it's A4J specific. Nevertheless it might be a source of inspiration.
Basically you need to walk the component tree and clear its state. The neatest thing is to clear exactly the state of the components that you are going to re-render. But you might take the brute-force approach and just clear all if your particular application or page can tolerate that.
I wound up having to avoid submit or action to get the form to clear properly. I used actionListener with a void bean method instead.
But then I faced the problem of conditionally needing navigation which is usually done with a String method from action. I used ExternalContext.redirect() to accomplish that which I learned from the following:
JSF PostConstruct Exception Handling - Redirect
JSF navigation redirect to previous page
my page code:
<p:commandButton value="Login" update=":loginForm"
actionListener="#{loginBean.login}"/>
my bean code:
public void login() {
RtsLDAPAD laLdap = new RtsLDAPAD();
boolean lbAuthenticated = false;
try
{
lbAuthenticated = laLdap.login(userName, password);
System.out.println(
"The Result is " + lbAuthenticated + " for " + userName);
}
catch (Exception aeRTSEx)
{
aeRTSEx.printStackTrace();
}
if (lbAuthenticated) {
try {
FacesContext.getCurrentInstance().getExternalContext().redirect("taskform.jsf");
} catch (IOException e) {
e.printStackTrace();
}
} else {
FacesContext facesContext = FacesContext.getCurrentInstance();
facesContext.addMessage(null,
new FacesMessage("Login failed for " + userName + "."));
UIViewRoot uiViewRoot = facesContext.getViewRoot();
HtmlInputText inputText = null;
Password pwd = null;
inputText = (HtmlInputText) uiViewRoot.findComponent("loginForm:username");
inputText.setSubmittedValue(null);
inputText.setValue(null);
inputText.setLocalValueSet(false);
inputText.setValid(true);
pwd = (Password) uiViewRoot.findComponent("loginForm:password");
pwd.setSubmittedValue(null);
pwd.setValue(null);
pwd.setLocalValueSet(false);
pwd.setValid(true);
userName = null;
password = null;
}
}

Resources