4

I am working on a web app using JSF w/Seam. I want to be able to call a JavaScript function after every ajax response. I'm looking for a way to do this without putting an oncomplete attribute on every commandLink/commandButton on every page.

I think there's a way to set up a servlet filter (interceptor? I get the terms confused) to inject the JS call into each response. I'm going to look into that. In the meantime, if anyone has any other suggestions, I'm all ears.

EDIT: I think the jQuery ajaxSuccess method might be the way to go here, but I'm not sure how to actually use it. I can't get anything to register. I basically want to add code to get any and all ajax requests from any source to call my JavaScript method on success. Can anyone show me the proper way to do this? I've tried a number of ways to do this, including adding jQuery("*").ajaxSuccess(function(){myFunction();}); to the bottom of my template xhtml file.

iandisme
  • 6,346
  • 6
  • 44
  • 63

4 Answers4

5

Rewritten answer: see original answer in revision history

You could override the default send method of XMLHttpRequest with one that hijacks the readystatechange handler:

(function () 
{ 
    var xhrSend = XMLHttpRequest.prototype.send; 
    XMLHttpRequest.prototype.send = function  () 
     { 
        var handler = this.onreadystatechange; 
        this.onreadystatechange = function () 
        { 
            if (handler) {
                if (handler.handleEvent) handler.handleEvent.apply(xhr, arguments);
                else handler.apply(xhr, arguments);
            }
            if (this.readyState == 4) 
            { 
                // your oncomplete function here 
                this.onreadystatechange = handler; 
             } 
         }; 
        xhrSend.apply(this, arguments); 
    }; 
})(); 

Edit: The above function doesn't work with jQuery requests, and so potentially it could fail with other libraries as well. The revision below addresses the issue with a setTimeout hack to delay the code that overrides the handler. Of course, with jQuery, you can just use the .ajaxSuccess() global handler, but for other libraries with similar behavior, this would be useful.

(function() {
    function globalHandler() {
        if (this.readyState == 4) {
            // your oncomplete code here
        }
    }
    var xhrSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function() {
        var xhr = this;
        if (xhr.addEventListener) {
            xhr.removeEventListener("readystatechange", globalHandler);
            xhr.addEventListener("readystatechange", globalHandler, false);
        }
        else {
            function readyStateChange() {
                if (handler) {
                    if (handler.handleEvent)
                        handler.handleEvent.apply(xhr, arguments);
                    else
                        handler.apply(xhr, arguments);
                }
                globalHandler.apply(xhr, arguments);
                setReadyStateChange();
            }
            function setReadyStateChange() {
                setTimeout(function() {
                    if (xhr.onreadystatechange != readyStateChange) {
                        handler = xhr.onreadystatechange;
                        xhr.onreadystatechange = readyStateChange;
                    }
                }, 1);
            }
            var handler;
            setReadyStateChange();
        }
        xhrSend.apply(xhr, arguments);
    };
})();

http://jsfiddle.net/gilly3/FuacA/5/
I tested this in IE7-9, and the latest versions of Chrome and FF

Community
  • 1
  • 1
gilly3
  • 87,962
  • 25
  • 144
  • 176
  • I tried that as well. Still no effect. Could the problem be that my ajax requests aren't done through jQuery? – iandisme Nov 02 '11 at 21:39
  • @iandisme - YES!! Absolutely! That's your problem. `.ajaxSuccess` is a *jQuery event*. If you make a regular XMLHttpRequest outside of jQuery, jQuery has no way of knowing about it. – gilly3 Nov 02 '11 at 22:19
  • @iandisme - I think Martin's answer is probably the best solution for you, since you are using a4j for your AJAX requests. But, if you can't get his to work, take a look at my rewritten answer here. – gilly3 Nov 02 '11 at 23:20
  • I gave you a +1 for this because this code is pretty sexy. Going right after the prototype of a built-in object, I mean. Unfortunately, adding this code causes all my ajax requests to fail. – iandisme Nov 04 '11 at 14:18
  • Let me be more clear: The ajax requests succeed, but the areas of the page that should be re-rendered aren't being re-rendered. – iandisme Nov 04 '11 at 14:23
  • @iandisme - I'm not sure what's going on with a4j, but with jQuery, I notice it removes our `onreadystatechange` handler. I had to work around that by reapplying our readystatechange handler after 1ms. The simple approach is to use `addEventListener` for those browsers that support it. See my updated answer. – gilly3 Nov 04 '11 at 17:25
  • Actually, I was just able to get the code in your previous example to work. The line `handler.apply(this, arguments);` wasn't working because handler isn't a function. I changed it to `handler.handleEvent.apply(this, arguments);` and got the desired result. We'll see if that has any bad consequences, but for now, that did the trick. Thanks a ton! – iandisme Nov 04 '11 at 17:38
  • If you go ahead and change your answer back to the last revision and add the `.handleEvent` bit, I'll accept your answer and award the bounty. That way others can benefit from your help, too. – iandisme Nov 07 '11 at 13:33
  • I had to look up `handler.handleEvent`... I'd never heard of that before. It doesn't seem to work in IE7. I'll update the code to take advantage of `handleEvent` when possible. – gilly3 Nov 07 '11 at 21:55
3

Since you are using RichFaces you can simply use this:

<a:status id="globalStatus" onstart="onRequestStart()" onstop="onRequestEnd()" />
Martin Frey
  • 10,025
  • 4
  • 25
  • 30
  • This isn't working for me. Going by the documentation, there is a "for" attribute that points to an a4j output region that I would need to specify. Simply adding this to the main form in my template document is not doing the trick. +1 for introducing me to that tag though. – iandisme Nov 02 '11 at 18:02
  • Just out of my memory: i think this is working for me because we have a global ajax queue defined. If you have such a queue then this should work. Else you can define the queue name in the for attribute or also the id of the ajax button for example. I would check out the live demo of richfaces for this. – Martin Frey Nov 02 '11 at 20:41
  • I use the global ajax queue to avoid the famous concurrent call to conversation issue so since you do not have one defined yet I wonder if you dont use conversations or solved it differently? – Martin Frey Nov 02 '11 at 20:48
2

Using a4j:status should work, but it has to be inside an h:form tag:

<h:form id="randomForm" styleClass="edit">
        <a:status id="stateStatus"
         onstart="Richfaces.showModalPanel('waitBx'),document.getElementById('randomForm:search').disabled=true;"
         onstop="Richfaces.hideModalPanel('waitBx'),document.getElementById('randomForm:search').disabled=false;"
        styleClass="message" >
</a:status>

...... way more code  
</form> 

After every ajax call this pops up a wait picture and disables the search button.

Interestingly enough, at least in our code, this doesn't work for anything in a nested a4j:region.

gebuh
  • 797
  • 14
  • 40
1

I think this is what you are looking for: Using Global Ajax Handlers In jQuery

PseudoNinja
  • 2,846
  • 1
  • 27
  • 37
  • Can't get this to work either. jQuery("*").bind("ajaxSuccess", function(){myFunction();}); and a host of other stuff doesn't work at all. – iandisme Nov 02 '11 at 18:35
  • The link doesnt work (at least today[10 Jul. 2013] the website is not available). – matt Jul 10 '13 at 13:47