Jsf/Primefaces Ajax Updates Breaks Jquery Event Listener Function Bindings

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:

  1. Use the oncomplete attribute of the PrimeFaces command button:

    oncomplete="applyChangeHandler()"
  2. Use <h:outputScript target="body"> instead of $(document).ready(),

    <h:outputScript id="applyChangeHandler" target="body">
    applyChangeHandler();
    </h:outputScript>

    and reference it in update attribute:

    update=":applyChangeHandler"
  3. 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>
  4. 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 any keydown listener. The document ready event is not fired during ajax requests. Your ready handler is never re-invoked. The keydown listener is never re-attached. To the enduser, it then indeed seemingly "stops functioning".

The solution on this particular case should now be obvious: explicitly re-attach the 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

I 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.

You can use jsf.ajax.addOnEvent() to apply it to all <f:ajax> requests. See also How to re-execute javascript function after form reRender?


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?

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?

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.


Is jQuery ajax a viable alternative to f:ajax?

It's doable. There are even open source examples available, like PrimeFaces which is built all around jQuery and jQuery UI.

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



Leave a reply



Submit