Recreating jQuery's ajaxStart and ajaxComplete functionality - javascript

I'm trying to reproduce jQuery's functions ajaxComplete and ajaxStart without jQuery so that they could be used in any environment with no library dependencies (it's a special use case). These functions allow for an event listener to be called before and after any ajax request. In my example, I call them preAjaxListener and postAjaxListener.
I'm trying to accomplish it by hooking into the XMLHttpRequest object and overwriting/decorating open and send. Yes, I know this is dirty.
XMLHttpRequest.prototype.open = (function(orig){
return function(a,b,c){
this._HREF = b; // store target url
return orig.apply(this, arguments); // call original 'open' function
};
})(XMLHttpRequest.prototype.open);
XMLHttpRequest.prototype.send = (function(orig){
return function(){
var xhr = this;
_core._fireAjaxEvents('pre', xhr._HREF); // preAjaxListener fires
var rsc = xhr.onreadystatechange || function(){}; // store the original onreadystatechange if it exists
xhr.onreadystatechange = function(){ // overwrite with custom function
try {
if (xhr.readyState == 4){
_core._fireAjaxEvents('post', xhr._HREF); // postAjaxListneer should fire
this.onreadystatechange = rsc;
}
} catch (e){ }
return rsc.apply(this, arguments); // call original readystatechange function
};
return orig.apply(this, arguments); // call original 'send' function
};
})(XMLHttpRequest.prototype.send);
I do not want to write wrapper functions to make ajax requests. I want to be able to hook into any ajax request made by any library (or with vanilla js) on the page.
So far, only the preAjaxListener function works. I can't seem to figure out why, but it seems that onreadystatechange is never being called. Any guidance would be greatly appreciated.
Working demo: http://jsfiddle.net/_nderscore/QTQ5s/

Using .onreadystatechange wasn't working because I was testing with jQuery and jQuery's ajax methods manipulate and removes the onreadystatechange property.
However, adding an event listener for loadend works just fine everywhere but IE. For IE, I set up an interval instead - not the optimal solution, but it works for my needs. I only intended this script to work on IE8+ and modern browsers.
XMLHttpRequest.prototype.send = (function(orig){
return function(){
_core._fireAjaxEvents('pre', this._HREF);
if (!/MSIE/.test(navigator.userAgent)){
this.addEventListener("loadend", function(){
_core._fireAjaxEvents('post', this._HREF);
}, false);
} else {
var xhr = this,
waiter = setInterval(function(){
if(xhr.readyState && xhr.readyState == 4){
_core._fireAjaxEvents('post', xhr._HREF);
clearInterval(waiter);
}
}, 50);
}
return orig.apply(this, arguments);
};
})(XMLHttpRequest.prototype.send);

Related

Javascript addEventListener not working with for loop

Edit: Topic has been marked as duplicate. I just wanted to say that im not happy with the answers of the other post. The accepted answer uses a not so nice way of dealing with such problem. Apart from that I made it clear that I read all those posts before and have difficulties understanding that particular problem area in javascript.
I dont get this at all. I really do a lot of reading, especially about closures but I dont get this...im on it for hours. Why does the following code fail to work? Im absolutely lost now and I need someone who could really point out how I can overcome situations like these where my index fails to be recognized within my event function.
How does this work? My addEventListener just wont work since the index is only recognized outside of it.
var lastResponse;
var alpha = document.getElementById('alpha');
var httpRequest;
function initAjax(url) {
httpRequest = new XMLHttpRequest();
httpRequest.open('POST', url, true);
httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
httpRequest.send();
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState === 4 && httpRequest.status === 200) {
var response = httpRequest.responseText;
document.getElementById("omega").innerHTML = response;
fn();
}
}
}
alpha.addEventListener("click", function() {
initAjax("test.php");
});
var x = document.getElementById("omega").children;
function fn() {
for(var i=0; i < x.length; i++){
console.log(x.length);
document.getElementById("omega").addEventListener("click", function(event){
hello(i, event);
}, false);
}
}
function hello(index, event){
console.log(event.currentTarget.children[index]);
}
Updated code
Ajax gets content with their respective divs
When request is completed javascript injects the returned data from the server to omega.
Omega has children.
Now I want to click on one of those children and get their index.
As this question marked as duplicate before #blessenm adds his answer, I'm just rewriting my answer so that his elegant solution will be visible to someone looking in this question in the future. I've posted different answer before the question was closed, so I'm still able to edit my answer. All credits to #blessenm:
function start() {
omega.addEventListener("click", function(evt) {
alert([].indexOf.call(evt.currentTarget.children, evt.target));
}, false);
}
[].indexOf is shortcut of Array.prototype.indexOf function. The first argument you're passing is the context, the second argument is the item you're searching in that array. DOM elements array-like objects but are not actual arrays therefore they don't have the array methods in Array.prototype - that's why you cannot use alert(evt.currentTarget.children.indexOf(evt.target)). To do array operations on a DOM we use the call method of Array prototype.

Calling a function from within itself

I have several elements inside a container. All their events are handled inside a function events().
On click of a button inside the container, I replace the container's html with the response from an AJAX request.
function events() {
$(".btn").on("click", function() {
...
});
$(".txt").on("keyup", function() {
...
});
$(".btn2").on("click", function() {
var xmlhttp;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
}
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {
$(".container").html(xmlhttp.responseText);
events(); // ***
}
}
url = 'handlers/gethtml.ashx';
xmlhttp.open("GET", url, true);
xmlhttp.send(null);
});
}
Now, I need to rebind all the controls with events so after changing the html $(".container").html(xmlhttp.responseText); I call the function within itself because the new html contains the same controls with the same events.
Does this cause stack overflow? I have some performance issues so i was wondering if this maybe the cause. Also, is there any workaround, maybe a more elegant way to do this?
Try using event delegation, you don't need to bind the event again
$(".container").on("click",".btn2", function() {...});
http://api.jquery.com/on/
http://learn.jquery.com/events/event-delegation/
Use event delegation, then no need to rebind the events since it will handle dynamic element as well
You could further shorten the code by using jquery to load the url contents
function events() {
var ct = $(".container");
ct.on("click", ".btn", function() {
});
ct.on("keyup", ".txt", function() {
});
ct.on("click", ".btn2", function() {
ct.load('handlers/gethtml.ashx')
}
Just call events(). It is in the scope!
There are a couple of things you need to be aware of, though:
Elements already on there will have two click handlers on the second run - the old, and the new. To get rid of them, use unbind to unbind all click events, for example.
You may want to split up your event creation function further.

How can I call a JavaScript function after every a4j AJAX response?

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.
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
Since you are using RichFaces you can simply use this:
<a:status id="globalStatus" onstart="onRequestStart()" onstop="onRequestEnd()" />
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.
I think this is what you are looking for: Using Global Ajax Handlers In jQuery

js jquery forward saved event

I´m holding back some of my native click events on hyperlinks, to check if the result page holds a content.
Im saving the jquery event object and after some checkings, i want to let the event go its natural way.
Capture Event
Check for Contents
If contents available
forward event as it was fired
else
do nothin
At this moment, I just saving the "href" property and want to set it to the document.location.href if true comes back.
Now, the question: Is there a better way to forward/reraise the existing event, than setting the href to the document.location.href?
Using document.location.href would be fine, and also seems like the simplest option to me.
But just for the sake of exploring other options, you could also have js click the link for you if it's deemed as safe. For example, something like this.
$('a').click(function() {
if( !$(this).is('.content-verified') ) {
var self = this;
// Do your content checking here, with the callback for verified good
// content being $(self).has_good_content();
return false;
}
return true;
});
// Callback for good content
// should be something like this:
$.fn.has_good_content = function() {
return $(this).each(function() {
$(self).addClass('content-verified');
$(self).click();
});
};
This sounds like a job for the jQuery Deferred object. New in jQuery 1.5+
function done() {
var dfd = $.Deferred(),
timeout;
timeout = setInterval(function() {
if (contents available) {
clearInterval(timeout);
return dfd.resolve();
}
}, 50);
return dfd.promise();
}
$('#my-link').bind('click', function(e) {
e.preventDefault();
var $this = $(this);
$.when(done())
.then(function(o) {
//unbind the click event that prevents the default action and click on the link afterward
$this.unbind('click').click();
});
});
So what is happening is it will wait for the resolve/success state from the done function. You are telling your click event to wait because done is using the Deferred object and has promised to return something.
I have put a setInterval to check every 50 mili seconds if the contents have loaded then I resolve the Deferred object therefore the then in click event will be called.
You can pass an object to dfd.resolve(); like this dfd.resolve({ test: true });. Then the o argument in then will have o.test .
I have used Deferred several times and I really liked it.
Hope this helps

How to override JavaScript function from a Firefox extension?

I am trying to intercept calls to document.write for all pages. Setting up the interception inside the page by injecting a script like
function overrideDocWrite() {
alert("Override called");
document.write = function(w) {
return function(s) {
alert("special dom");
w.call(this, wrapString(s));
};
}(document.write);
alert("Override finished");
}
Is easy and works, but I would like my extension to setup the interception for each document object from inside the extension. I couldn't find a way to do this. I tried to listen for the "load" event and set up the interception there but it also fails. How do I hook calls to doc.write from an extension?
I made some progress:
var myExtension = {
init: function() {
var appcontent = document.getElementById("appcontent"); // browser
if (appcontent)
appcontent.addEventListener("DOMContentLoaded", myExtension.onPageLoad,
true);
},
onPageLoad: function(aEvent) {
var doc = aEvent.originalTarget; // doc is document that triggered "onload" event
// do something with the loaded page.
// doc.location is a Location object (see below for a link).
// You can use it to make your code executed on certain pages only.
alert("Override called");
alert(doc);
alert(doc.write);
alert(doc.wrappedJSObject);
alert(doc.wrappedJSObject.write);
doc.wrappedJSObject.write = function(w) {
return function(s) {
alert("special dom");
w.call(this, "(" + s + ")");
};
}(doc.write);
alert("Override finished");
}
}
This seem to work, but DOMContentLoaded is the wrong event for the job, because it is fired too late! Is there an earlier event to listen to?
Ressurection of the question ! I got the answer. Here is a sample code :
const os = Components.classes["#mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
os.addObserver({
observe : function(aWindow, aTopic, aData) {
if (aWindow instanceof Ci.nsIDOMWindow && aTopic == 'content-document-global-created') {
aWindow.wrappedJSObject.myfunction = function(){
// Do some stuff . . .
}
}
}
}, 'content-document-global-created', false);
The same goes for document with the event document-element-inserted as of gecko 2.0 .
JavaScript uses a prototypical inheritance system, instead of having classes, objects have prototypes. Prototypes are real objects that are used as a reference to other objects for inheritance of methods and attributes.
The best strategy would be to override the method write in the prototype of "document" (which for the HTML document is HTMLDocument). This should effectively wrap the method for all instances of "document" inside the pages loaded in the browser since they all use the same prototype.
Instead of
document.write = function() { ... }
try something like this:
HTMLDocument.prototype.write= function() { ... }
UPDATE: It does not seem to be as easy as I initially thought, this does not seem to work at first try.

Categories

Resources