I'm trying to intercept all AJAX calls in order to check if that AJAX response contains specific error code that I send as JSON from my PHP script (codes: ACCESS_DENIED, SYSTEM_ERROR, NOT_FOUND).
I know one can do something like this:
$('.log').ajaxSuccess(function(e, xhr, settings) {
});
But - does this work only if "ajaxSuccess" event bubble up to .log div? Am I correct? Can I achieve what I want by binding "ajaxSuccess" event to document?
$(document).ajaxSuccess(function(e, xhr, settings) {
});
I can do this in either jQuery or raw JavaScript.
If you're using jQuery, $.ajaxSuccess is a good option, but here's a generic option that will intercept XHR calls from all frameworks (I've tested it with ExtJS and jQuery - it should work even if multiple frameworks are loaded concurrently). It's been tested to work with IE8, Chrome and Firefox.
(function(XHR) {
"use strict";
var open = XHR.prototype.open;
var send = XHR.prototype.send;
XHR.prototype.open = function(method, url, async, user, pass) {
this._url = url;
open.call(this, method, url, async, user, pass);
};
XHR.prototype.send = function(data) {
var self = this;
var oldOnReadyStateChange;
var url = this._url;
function onReadyStateChange() {
if(self.readyState == 4 /* complete */) {
/* This is where you can put code that you want to execute post-complete*/
/* URL is kept in this._url */
}
if(oldOnReadyStateChange) {
oldOnReadyStateChange();
}
}
/* Set xhr.noIntercept to true to disable the interceptor for a particular call */
if(!this.noIntercept) {
if(this.addEventListener) {
this.addEventListener("readystatechange", onReadyStateChange, false);
} else {
oldOnReadyStateChange = this.onreadystatechange;
this.onreadystatechange = onReadyStateChange;
}
}
send.call(this, data);
}
})(XMLHttpRequest);
I've posted a more specific example on github which intercepts AJAX calls and posts the AJAX call durations back to the server for statistical analysis.
From http://api.jquery.com/ajaxSuccess/ :
Whenever an Ajax request completes successfully, jQuery triggers the ajaxSuccess event. Any and all handlers that have been registered with the .ajaxSuccess() method are executed at this time.
So the selector doesn't define the position where you are "catching" the event (because, honestly, ajax event by its nature doesn't start from a DOM element), but rather defines a scope to which the handling will be defaulted (i.e. this will poitn to that/those element(s)).
In summary - it should be exactly what you wish for
The best way, which I found https://lowrey.me/intercept-2/
const intercept = (urlmatch, callback) => {
let send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
this.addEventListener('readystatechange', function() {
if (this.responseURL.includes(urlmatch) && this.readyState === 4) {
callback(this);
}
}, false);
send.apply(this, arguments);
};
};
Try using Mockjax.js http://code.appendto.com/plugins/jquery-mockjax
It lets you hijack AJAX calls to the server and mock the location.
Related
I just implemented ag-grid, but found that IE9 crashes when using cellTemplates with angular compiled templates inside.
Did any of you encounter this and maybe found a workaround?
How to reproduce:
Head here (http://www.ag-grid.com/angular-grid-cell-template/index.php) with IE, and from DevTools, select IE9.
It will crash because of the angular compiled templates. Not sure what I can do about it.
(I also opened an issue on GitHub on this: https://github.com/ceolter/ag-grid/issues/521 )
EDIT:
Debugged it, there's an infinite loop because an update to an array from one method, is not visible to another method somehow...
The infinite loop is:
getTemplate, (wait in line until the call ends), call ends, template added to cache, run callback, callback doesn't see the template in templateCache, creates another callback, adds it to the queue, and so on.
(code from ag-grid below).
// returns the template if it is loaded, or null if it is not loaded
// but will call the callback when it is loaded
TemplateService.prototype.getTemplate = function (url, callback) {
var templateFromCache = this.templateCache[url];
if (templateFromCache) {
return templateFromCache;
}
var callbackList = this.waitingCallbacks[url];
var that = this;
if (!callbackList) {
// first time this was called, so need a new list for callbacks
callbackList = [];
this.waitingCallbacks[url] = callbackList;
// and also need to do the http request
var client = new XMLHttpRequest();
client.onload = function () {
that.handleHttpResult(this, url);
};
client.open("GET", url);
client.send();
}
// add this callback
if (callback) {
callbackList.push(callback);
}
// caller needs to wait for template to load, so return null
return null;
};
TemplateService.prototype.handleHttpResult = function (httpResult, url) {
if (httpResult.status !== 200 || httpResult.response === null) {
console.warn('Unable to get template error ' + httpResult.status + ' - ' + url);
return;
}
// response success, so process it
this.templateCache[url] = httpResult.response;
// inform all listeners that this is now in the cache
var callbacks = this.waitingCallbacks[url];
for (var i = 0; i < callbacks.length; i++) {
var callback = callbacks[i];
// we could pass the callback the response, however we know the client of this code
// is the cell renderer, and it passes the 'cellRefresh' method in as the callback
// which doesn't take any parameters.
callback();
}
if (this.$scope) {
var that = this;
setTimeout(function () {
that.$scope.$apply();
}, 0);
}
};
return TemplateService;
})();
I eventually found the issue.
In IE9, the template is on responseText inside the response.
In IE10+ and all other browsers it's on response.
So in order to fix it, in the above code, instead of:
// response success, so process it
this.templateCache[url] = httpResult.response;
I added:
// response success, so process it
//in IE9 the response is in - responseText
this.templateCache[url] = httpResult.response || httpResult.responseText;
For future reference, adding the answer here.
Had nothing to do with Angular. :)
UPDATE:
https://github.com/ceolter/ag-grid/issues/521
Code got into the repo :)
Thanks Niall Crosby (ceolter).
I have a page and I would like to disable all AJAX requests with jQuery.
Do you have any ideas? And if it is possible?
if (false) {
//disable all ajax requests
}
If all of your ajax requests are being sent through jQuery ajax methods (including helper methods), you can do this with beforeSend.
window.ajaxEnabled = true;
$.ajaxSetup({
beforeSend: function(){
return window.ajaxEnabled;
}
});
$.post("http://www.google.com"); // throws an error
window.ajaxEnabled = false;
$.post("http://www.google.com"); // doesn't throw an error
http://jsfiddle.net/k2T95/3
And here's one that will block all, regardless of what javascript library is sending it, also based on a global flag. Doesn't affect XDomainRequest obj though
(function (xhr) {
var nativeSend = xhr.prototype.send;
window.ajaxEnabled = true;
xhr.prototype.send = function () {
if (window.ajaxEnabled) {
nativeSend.apply(this, arguments);
}
};
}(window.XMLHttpRequest || window.ActiveXObject));
http://jsfiddle.net/k2T95/4/
EDIT: There are better ways, depending your specific case which i don't know
Try: {test it for cross browser support, i didn't do it}
XMLHttpRequest.prototype.send = function(){};
Not only for requests done in jQuery
If you want to re-enable it:
var oSend = XMLHttpRequest.prototype.send; // keep reference
XMLHttpRequest.prototype.send = function(){};
Then call:
XMLHttpRequest.prototype.send = oSend; // get back reference to prototype method
How would go about monkey patching the XMLHTTPRequest's onreadystatechange function. I'm trying to add a function that would be called when every ajax request made from a page come back.
I know this sounds like a terrible idea, but the use case is quite peculiar. I want to use a certain SDK with a console (jqconsole) but show status and results from ajax calls within the console without modifying the external SDK.
I've looked at this post which had great info, but nothing on monkey patching the callback which seem to exceed my JavaScript skills.
P.S Can't use jQuery since it only supports ajax calls made from jQuery not from XMLHTTPRequests directly which is the case here.
To monkey-patch XMLHttpRequests, you need to know how an AJAX request is generally constructed:
Constructor invocation
Preparation the request (setRequestHeader(), open())
Sending the request (.send).
General-purpose patch
(function(xhr) {
function banana(xhrInstance) { // Example
console.log('Monkey RS: ' + xhrInstance.readyState);
}
// Capture request before any network activity occurs:
var send = xhr.send;
xhr.send = function(data) {
var rsc = this.onreadystatechange;
if (rsc) {
// "onreadystatechange" exists. Monkey-patch it
this.onreadystatechange = function() {
banana(this);
return rsc.apply(this, arguments);
};
}
return send.apply(this, arguments);
};
})(XMLHttpRequest.prototype);
The previous assumed that onreadystatechange was assigned to the onreadystatechange handler. For simplicity, I didn't include the code for other events, such as onload. Also, I did not account for events added using addEventListener.
The previous patch runs for all requests. But what if you want to limit the patch to a specific request only? A request with a certain URL or async flag and a specific request body?
Conditional monkey-patch
Example: Intercepting all POST requests whose request body contains "TEST"
(function(xhr) {
function banana(xhrInstance) { // Example
console.log('Monkey RS: ' + xhrInstance.readyState);
}
//
var open = xhr.open;
xhr.open = function(method, url, async) {
// Test if method is POST
if (/^POST$/i.test(method)) {
var send = this.send;
this.send = function(data) {
// Test if request body contains "TEST"
if (typeof data === 'string' && data.indexOf('TEST') >= 0) {
var rsc = this.onreadystatechange;
if (rsc) {
// Apply monkey-patch
this.onreadystatechange = function() {
banana(this);
return rsc.apply(this, arguments);
};
}
}
return send.apply(this, arguments);
};
}
return open.apply(this, arguments);
};
})(XMLHttpRequest.prototype);
The main techniques used is the transparent rewrite using...
var original = xhr.method;
xhr.method = function(){
/*...*/;
return original.apply(this, arguments);
};
My examples are very basic, and can be extended to meet your exact wishes. That's up to you, however.
Assuming you can ignore IE...
//Totally untested code, typed at the SO <textarea>... but the concept *should* work, let me know if it doesn't.
var OldXMLRequest = XMLHttpRequest;
// Create a new instance
function XMLHttpRequest() {
var ajax = new OldXMLRequest();
// save old function
var f = ajax.onreadystatechange;
ajax.onreadystatechange = function() {
console.log("Whatever!");
f(); // Call the old function
}
return ajax;
}
you can learn from Ajax-hook written by chinese!
it is a advanced js to enable Monkey patch XMLHTTPRequest
I have a javascript code on my website, there is a variable:
var remoteJsonVar;
On the other hand there is a json file on a remote website
https://graph.facebook.com/?ids=http://www.stackoverflow.com
I need to set the variable remoteJsonVar to this remote jason data.
I am sure that it is very simple, but I can't find the solution.
A small working example would be nice.
Because you're trying to get the data from a different origin, if you want to do this entirely client-side, you'd use JSON-P rather than just JSON because of the Same Origin Policy. Facebook supports this if you just add a callback parameter to your query string, e.g.:
https://graph.facebook.com/?ids=http://www.stackoverflow.com?callback=foo
Then you define a function in your script (at global scope) which has the name you give in that callback parameter, like this:
function foo(data) {
remoteJsonVar = data;
}
You trigger it by creating a script element and setting the src to the desired URL, e.g.:
var script = document.createElement('script');
script.src = "https://graph.facebook.com/?ids=http://www.stackoverflow.com?callback=foo";
document.documentElement.appendChild(script);
Note that the call to your function will be asynchronous.
Now, since you may want to have more than one outstanding request, and you probably don't want to leave that callback lying around when you're done, you may want to be a bit more sophisticated and create a random callback name, etc. Here's a complete example:
Live copy | Live source
(function() {
// Your variable; if you prefer, it could be a global,
// but I try to avoid globals where I can
var responseJsonVar;
// Hook up the button
hookEvent(document.getElementById("theButton"),
"click",
function() {
var callbackName, script;
// Get a random name for our callback
callbackName = "foo" + new Date().getTime() + Math.floor(Math.random() * 10000);
// Create it
window[callbackName] = function(data) {
responseJsonVar = data;
display("Got the data, <code>shares = " +
data["http://www.stackoverflow.com"].shares +
"</code>");
// Remove our callback (`delete` with `window` properties
// fails on some versions of IE, so we fall back to setting
// the property to `undefined` if that happens)
try {
delete window[callbackName];
}
catch (e) {
window[callbackName] = undefined;
}
}
// Do the JSONP request
script = document.createElement('script');
script.src = "https://graph.facebook.com/?ids=http://www.stackoverflow.com&callback=" + callbackName;
document.documentElement.appendChild(script);
display("Request started");
});
// === Basic utility functions
function display(msg) {
var p = document.createElement('p');
p.innerHTML = msg;
document.body.appendChild(p);
}
function hookEvent(element, eventName, handler) {
// Very quick-and-dirty, recommend using a proper library,
// this is just for the purposes of the example.
if (typeof element.addEventListener !== "undefined") {
element.addEventListener(eventName, handler, false);
}
else if (typeof element.attachEvent !== "undefined") {
element.attachEvent("on" + eventName, function(event) {
return handler(event || window.event);
});
}
else {
throw "Browser not supported.";
}
}
})();
Note that when you use JSONP, you're putting a lot of trust in the site at the other end. Technically, JSONP isn't JSON at all, it's giving the remote site the opportunity to run code on your page. If you trust the other end, great, but just remember the potential for abuse.
You haven't mentioned using any libraries, so I haven't used any above, but I would recommend looking at a good JavaScript library like jQuery, Prototype, YUI, Closure, or any of several others. A lot of the code above has already been written for you with a good library. For instance, here's the above using jQuery:
Live copy | Live source
jQuery(function($) {
// Your variable
var responseJsonVar;
$("#theButton").click(function() {
display("Sending request");
$.get("https://graph.facebook.com/?ids=http://www.stackoverflow.com&callback=?",
function(data) {
responseJsonVar = data;
display("Got the data, <code>shares = " +
data["http://www.stackoverflow.com"].shares +
"</code>");
},
"jsonp");
});
function display(msg) {
$("<p>").html(msg).appendTo(document.body);
}
});
I'm trying to intercept all AJAX calls in order to check if that AJAX response contains specific error code that I send as JSON from my PHP script (codes: ACCESS_DENIED, SYSTEM_ERROR, NOT_FOUND).
I know one can do something like this:
$('.log').ajaxSuccess(function(e, xhr, settings) {
});
But - does this work only if "ajaxSuccess" event bubble up to .log div? Am I correct? Can I achieve what I want by binding "ajaxSuccess" event to document?
$(document).ajaxSuccess(function(e, xhr, settings) {
});
I can do this in either jQuery or raw JavaScript.
If you're using jQuery, $.ajaxSuccess is a good option, but here's a generic option that will intercept XHR calls from all frameworks (I've tested it with ExtJS and jQuery - it should work even if multiple frameworks are loaded concurrently). It's been tested to work with IE8, Chrome and Firefox.
(function(XHR) {
"use strict";
var open = XHR.prototype.open;
var send = XHR.prototype.send;
XHR.prototype.open = function(method, url, async, user, pass) {
this._url = url;
open.call(this, method, url, async, user, pass);
};
XHR.prototype.send = function(data) {
var self = this;
var oldOnReadyStateChange;
var url = this._url;
function onReadyStateChange() {
if(self.readyState == 4 /* complete */) {
/* This is where you can put code that you want to execute post-complete*/
/* URL is kept in this._url */
}
if(oldOnReadyStateChange) {
oldOnReadyStateChange();
}
}
/* Set xhr.noIntercept to true to disable the interceptor for a particular call */
if(!this.noIntercept) {
if(this.addEventListener) {
this.addEventListener("readystatechange", onReadyStateChange, false);
} else {
oldOnReadyStateChange = this.onreadystatechange;
this.onreadystatechange = onReadyStateChange;
}
}
send.call(this, data);
}
})(XMLHttpRequest);
I've posted a more specific example on github which intercepts AJAX calls and posts the AJAX call durations back to the server for statistical analysis.
From http://api.jquery.com/ajaxSuccess/ :
Whenever an Ajax request completes successfully, jQuery triggers the ajaxSuccess event. Any and all handlers that have been registered with the .ajaxSuccess() method are executed at this time.
So the selector doesn't define the position where you are "catching" the event (because, honestly, ajax event by its nature doesn't start from a DOM element), but rather defines a scope to which the handling will be defaulted (i.e. this will poitn to that/those element(s)).
In summary - it should be exactly what you wish for
The best way, which I found https://lowrey.me/intercept-2/
const intercept = (urlmatch, callback) => {
let send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
this.addEventListener('readystatechange', function() {
if (this.responseURL.includes(urlmatch) && this.readyState === 4) {
callback(this);
}
}, false);
send.apply(this, arguments);
};
};
Try using Mockjax.js http://code.appendto.com/plugins/jquery-mockjax
It lets you hijack AJAX calls to the server and mock the location.