JSF/PrimeFaces ajax updates breaks jQuery event listener function bindings
As to the cause of your problem, the ajax request will update the HTML DOM tree with new HTML elements from the ajax response. Those new HTML elements do —obviously— not have the jQuery event handler function attached. However, the $(document).ready()
isn't re-executed on ajax requests. You need to manually re-execute it.
This can be achieved in various ways. The simplest way is to use $(document).on(event, selector, function)
instead of $(selector).on(event, function)
. This is tied to the document and the given functionRef
is always invoked when the given eventName
is triggered on an element matching the given selector
. So you never need to explicitly re-execute the function by JSF means.
$(document).on("change", ":input", function() {
console.log("From change event on any input: " + this.id);
});
The alternative way is to explicitly re-execute the function yourself on complete of ajax request. This would be the only way when you're actually interested in immediately execute the function during the
ready
/load
event (e.g. to directly apply some plugin specific behavior/look'n'feel, such as date pickers). First, you need to refactor the $(document).ready()
job into a reusable function as follows:$(document).ready(function(){
applyChangeHandler();
});
function applyChangeHandler() {
$(":input").on("change", function() {
console.log("From applyChangeHandler: " + this.id);
});
}
(note that I removed and simplified your completely unnecessary $.each()
approach)Then, choose one of the following ways to re-execute it on complete of ajax request:
Use the
oncomplete
attribute of the PrimeFaces command button:oncomplete="applyChangeHandler()"
Use
<h:outputScript target="body">
instead of$(document).ready()
,
and reference it in<h:outputScript id="applyChangeHandler" target="body">
applyChangeHandler();
</h:outputScript>update
attribute:update=":applyChangeHandler"
Use
<p:outputPanel autoUpdate="true">
to auto update it on every ajax request:<p:outputPanel autoUpdate="true">
<h:outputScript id="applyChangeHandler">
applyChangeHandler();
</h:outputScript>
</p:outputPanel>Use OmniFaces
<o:onloadScript>
instead of$(document).ready()
,<h:outputScript>
and all on em.<o:onloadScript>applyChangeHandler();</o:onloadScript>
JavaScript/jQuery event listeners do not work after JSF component is updated via Ajax
The flow is as follows:
- Browser retrieves HTML output.
- Browser populates HTML DOM tree based on HTML markup.
- When finished, browser triggers HTML DOM
ready
event. - jQuery's
$(document).ready()
function handlers are all invoked. - Yours finds an element in DOM by ID and attaches a
keydown
listener to it. - During user interaction, an ajax request is fired and the HTML DOM tree is updated with new HTML elements delivered by the ajax response.
- Among others, exactly that element having the
keydown
listener is removed from HTML DOM tree and replaced by a fresh new element without anykeydown
listener. The documentready
event is not fired during ajax requests. Your ready handler is never re-invoked. Thekeydown
listener is never re-attached. To the enduser, it then indeed seemingly "stops functioning".
keydown
listener on complete of ajax call. Most straightforward would be to extract the job of attaching the keydown
listener into a reusable function and fire it as follows:function applyKeydownOnTableFilter() {
// ...
}
$(document).ready(applyKeydownOnTableFilter);
So that you can just do a:<p:commandButton ... oncomplete="applyKeydownOnTableFilter()" />
But this is quite tedious to repeat for every single ajax command/listener and not very maintenance friendly. Better is to approach it differently: use jQuery's $.on()
instead. Replace$(document).ready(function() {
$("#form\\:dataTable\\:id\\:filter").keydown(function(event) {
// ...
});
});
by$(document).on("keydown", "#form\\:dataTable\\:id\\:filter", function(event) {
// ...
});
This way the keydown
listener isn't actually attached to the element of interest. Instead, thanks to the event bubbling feature of JavaScript, the keydown event will ultimately reach the $(document)
— which is always present and usually not changed during JSF ajax requests. Once reached, the $(document).on()
is triggered, which will then determine the source of the event and check it if matches the given selector and if so, then invoke the function. This all without the need to attach the keydown
listener to the physical element and thus not sensitive to whether the element is removed/replaced in the HTML DOM tree.See also:
- JSF/PrimeFaces ajax updates breaks jQuery event listener function bindings
By the way, do you also see how much similarities there are between HTML DOM tree and JSF component tree?
capture event when partial update happens for a element
An in my opinion very nice 'generic' solution is overriding a PrimeFaces javascript ajax function. If you check out the source (may it always be with you) you can see that one function is always called for each elemement that needs to be updated. If you override that and AFTER calling the original function, do your own 'thing'. In this example (used for real by us), the elements that are changed on a page are highlighted.
var originalPrimeFacesAjaxUtilsUpdateElement = PrimeFaces.ajax.Utils.updateElement;
PrimeFaces.ajax.Utils.updateElement = function(id, content, xhr) {
//call the original function first that does the basic updating
originalPrimeFacesAjaxUtilsUpdateElement.apply(this, arguments);
// Do your work here...
if (id.contains("panel1")) {
$(PrimeFaces.escapeClientId(id)).effect("highlight", {}, 3000);
//console.log(id);
}
};
JSF Ajax's listener is reached before the event itslef
That's fully by specification. Action listeners are called before actions during invoke action phase. If you need to execute a business action and/or to navigate, do it in action
. If you need to listen on the action event so that you can if necessary do some preprocessing, use listener
.
In your particular case, it look like you don't need the action listener at all. Just remove it and move the job into the action
method. The event
attribute is by the way superfluous. It already defaults to action
. Just remove it. This works equally good:
<h:commandButton image="#{CodeBean.imgSrc}" action="#{CodeBean.clickImg}">
<f:ajax render="@this" />
</h:commandButton>
See also:
- Differences between action and actionListener
jQuery applying css classes after f:ajax
You can useI have now played around and found that you can add onevent to f:ajax and force the main jQuery function to be called again. But this doesn't feel very elegant and I need to add it to all f:ajax tags.
jsf.ajax.addOnEvent()
to apply it to all <f:ajax>
requests. See also How to re-execute javascript function after form reRender?It indeed indicates a design/thinking problem. For instance, why don't you just use those jQuery selectors instead of the CSS classes you intend to add? To add CSS classes to certain elements, you had to select them using a CSS selector anyway, right? What's wrong with using exactly that CSS selector for the final purpose?Does it still make sense to apply style classes in document.ready()? Or is it bad practise in this instance? Should I simply add the classes to the elements?
In Facelets perspective, you can consider creating tagfiles to reduce code boilerplate. You can wrap <h:inputText styleClass="some-specific-class">
in a /WEB-INF/tags/inputText.xhtml
and keep using <my:inputText>
instead.
It's doable. There are even open source examples available, like PrimeFaces which is built all around jQuery and jQuery UI.Is jQuery ajax a viable alternative to f:ajax?
Javascript doesnt work after updating component
Thanks to Hatem Alimam, I was able to solve the problem :-*
I bound the script to the scrollPanel instead of to the datatable itself. This works:
$(document).ready(function() {
$('#accordion\\:duoDlgForm2\\:scrollPanelID').on('click','tr',function() {
var $item = $(this).closest("tr").find("td:nth-child(1)").text().trim();
$("#accordion\\:duoDlgForm2\\:rt1Selected").append($item);
});
});
JSF 2 ajax behavior event listener not firing on a Date
It's not fired because a conversion error has occurred due to invalid date format. The submitted value is converted everytime before the bean listener method is hit. Add a <h:message>
/<h:messages>
and include its ID in <f:ajax render>
. You'll then see it.
A String
value of for example 2
can impossibly represent a valid Date
object when parsed with the pattern dd-MMM-yyyy
.
You'll really need to keep it a String
if you want to achieve the functional requirement this way. As a completely different alternative, you could consider to do it entirely at the JavaScript side without sending JSF ajax requests.
Related Topics
Waiting for Image to Load in JavaScript
Javascript: Do Primitive Strings Have Methods
React-Router Getting This.Props.Location in Child Components
Convert JavaScript Object or Array to JSON for Ajax Data
How to Stop Events Bubbling in Jquery
Create Regexps on the Fly Using String Variables
Rotate Object on Specific Axis Anywhere in Three.Js - Including Outside of Mesh
How to Keep an JavaScript Object/Array Ordered While Also Maintaining Key Lookups
Remove HTML Comments with Regex, in JavaScript
JavaScript Toisostring() Ignores Timezone Offset
How to Know When All Promises Are Resolved in a Dynamic "Iterable" Parameter
Why Does If("String") Evaluate "String" as True But If ("String"==True) Does Not
JavaScript Loop Variable Scope
JavaScript - Generating All Combinations of Elements in a Single Array (In Pairs)
JavaScript Date Timezone Issue