Is it possible to turn a raw XMLHttpRequest or XDomainRequest into a jQuery Deferred object (or wrap it in a jQuery Deferred object)?
When I say raw, I mean the object is created directly using new XMLHttpRequest() or new XDomainRequest(), not using jQuery's convenience functions ($.ajax, $.get, etc.)
I am trying to do this because in my page, there is an XMLHttpRequest (or XDomainRequest) that is created manually before jQuery is loaded, and then I want to turn it into a Deferred object and use in a $.when call once jQuery is loaded:
// raw XMLHttpRequest created here, with onreadystatechange attached.
window.my_request = new XMLHttpRequest();
// ...
window.my_request.send();
// Load jQuery, using head.js or require.js
head.js(
'//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js',
'//mydomain/my-script.js'
);
// Inside my-script.js, I want to turn window.my_request into
// a jQuery Deferred object and use it in a $.when call.
You can probably do something like this to give you a promise which is a very crude emulation of a jQuery.ajax promise.
window.my_request = new XMLHttpRequest();
// ...
window.my_request.send();
// Load jQuery, using head.js or require.js
head.js(
'//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js',
'//mydomain/my-script.js'
);
// ...
var dfrd = $.Deferred();
window.my_request.onreadystatechange = function() {
if (window.my_request.readyState==4) {
if (window.my_request.status==200) {
//or possibly
//if (window.my_request.status >= 200 && window.my_request.status < 300 || window.my_request.status === 304) {
dfrd.resolve(window.my_request.responseText, window.my_request.statusText, window.my_request);
}
else (
dfrd.reject(window.my_request, window.my_request.statusText);
)
}
}
window.my_request_promise = dfrd.promise();
Well I've just looked at the source and unfortunately they haven't modularized the process of wrapping an XMLHttpRequest object and the process seems to be encapsulated within the $.ajax function itself.
Have a look at the source starting at line 428 to see how they are wrapping it.
You will have to wrap it yourself unfortunately.
Note: I would rather have a look at the reasons you need an XMLHttpRequest object to be instanciated before jQuery gets loaded to make sure it isin't simply a design issue.
Related
I am new to Javascript, and I am creating a chrome extension where I am trying to inject some HTMLs into the existing public website,
My current code is something like this,
if (document.readyState ==='complete'){
inject_html();
}
However, on the current website, when a button is pressed, ajax is processed and new HTML DOM is loaded for the same url, so the script doesn't run.
Is there a way to listen to whenever ajax is finished processing? What is the best way in this case, can an expert help me?
This is rather hacky, but you can override any property (and thus interpose any method call) of the XMLHttpRequest prototype, as well as the function/constructor itself.
The most convenient thing to do in your case is probably to hook the XMLHttpRequest function itself, and simply add an event listener there:
var originalXHR = XMLHttpRequest;
XMLHttpRequest = function()
{
var ret = new originalXHR();
ret.addEventListener('load', function()
{
console.log('Ajax request finished!');
});
return ret;
};
The control flow here goes like this:
Call original function
Modify return value
Return modified value
This takes advantage of the fact that if in JavaScript a constructor returns a value, that value replaces the object created with new.
In action:
// Interface code
var field = document.querySelector('input');
document.querySelector('button').addEventListener('click', function()
{
var xhr = new XMLHttpRequest();
// crossorigin.me because we need a CORS proxy for most external URLs
xhr.open("GET", 'https://crossorigin.me/' + field.value);
xhr.send(null);
});
// Hooking code
var originalXHR = XMLHttpRequest;
XMLHttpRequest = function()
{
var ret = new originalXHR();
ret.addEventListener('load', function(ev)
{
// var xhr = ev.target;
console.log('Ajax request finished!');
});
return ret;
};
<!-- Demo page: -->
URL: <input type="text" value="https://google.com"><br>
<button>Load via XHR</button>
Note that this only gives you a method to detect when an XHR has finished loading, and not control over its return value. From within the function that does console.log, you do however have access to the XHR itself, via ev.target.
You could also create hooking points to modify the loaded content before it reaches the code on the page, but that would require a couple more hooks, because listeners can be added in more than one way (.onload, .onreadystatechange, .addEventListener, etc), but it would be doable if necessary.
I thought this would be easy b.c. Backbone uses jQuery and has a single access point I thought I could just do this:
Backbone.ajax = function() {
var xhr = Backbone.$.ajax.apply(Backbone.$, arguments);
// xhr.addEventListener("loadend", loadEnd);
return xhr;
};
but for some reason I can not load an event listener to the xhr object as I normally do.
I get an error stating that addEventListener is not a function.
See jqXHR:
The jQuery XMLHttpRequest (jqXHR) object returned by $.ajax() as of jQuery 1.5 is a superset of the browser's native XMLHttpRequest object.
It is not exactly the native XMLHttpRequest, so it might not behave exactly like the native one. There is no guarantee that it exposes methods like addEventListener unless it's documented. If you want to set something globally it's safer to use jquery.ajaxsetup
Imagine I have these three functions:
$( document ).ajaxSuccess(function( event, xhr, settings ) {
console.log('ajaxSuccess fired');
});
function doCall1 () {
var xhr = new XMLHttpRequest();
xhr.open('get', 'someurl.com', true);
xhr.send();
}
function doCall2 (){
$.get('someurl.com');
}
When I call doCall1, ajaxSuccess does not fire. However, when I call doCall2, ajaxSuccess does fire.
I understand this is related to the fact jQuery is in fact using a callback function to various methods of $.ajax() rather than looking at onreadystatechange / readyState of the XMLHttpRequest (correct me if I'm off the mark), but I still don't completely understand.
Can someone give me a quick explanation or direct me to some docs that'd get me the rest of the way to getting this?
As others have noted, ajaxSuccess and ajaxComplete are non-native events added by jQuery. If for some reason you wanted interop between jQuery and vanilla js, you could dispatch the events yourself (with the caveat that browser support for CustomEvent is limited):
var event = new CustomEvent("ajaxComplete");
document.dispatchEvent(event);
Thats because the ajaxSuccess and ajaxComplete methods are part of jQuery and they are only triggered by the jQuery functions.
XMLHttpRequest is a native javascript object and its not related with jQuery.
The code for triggering ajaxSuccess/ajaxComplete are written in the end of $.ajax(), $.get(), $.post(), etc. The above events are related to jQuery and the $.ajax(), $.get(), $.post() functions are jQuery functions.
The XHR is native JavaScript and is no way related to jQuery.
the one thing is the jQuery object having deferrers and the other one a native JavaScript object, where you have to attach a callback yourself and may forward it to the ajaxSuccess of jQuery (jQ wraps the native XMLRequest object and does that).
Will it be a good or a bad practice to override all ajax call in my Web App's JavaScript code with an "invisible" Cache layer?
It'd be something like (pseudo-code):
$.prototype.ajax = function(...) {
if( requested URL can be found in localStorage )
return dataFromLocalStorage; // as Deferred
data = invoke original $.ajax;
localStorage.setItem(key, data);
return data; // as Deferred
}
I can think of one pro: no refactoring is needed for existing ajax calls, and one con: future developers will be unaware of this functionality, as it disguises itself as a regular ajax call.
What do you think? Will it be a good practice or should I avoid it?
No, it is not a good idea to override the default behavior of $.ajax like this. Other code on your page including jQuery plugins might depend on the default behavior.
It is likely at some point you might want to get the freshest version of some data, if $.ajax always caches to localStorage there will be no way to do that. This could also lead to future debugging headaches when someone working on the code (including you) can't figure out why their AJAX calls keep returning stale data.
It would be much better to just implement the caching in a separate function. That way when you see a call to something like ajaxWithCaching it will be obvious that something more that a plain AJAX call is going on.
I wouldn't be overriding the default behavior of anything! If you change the way $.ajax works, what will you do when you're using a lightbox plugin (or literally anything) which relies on the normal functionality of $.ajax?
It would backfire terribly upon you.
Instead, create your own function which performs the caching, and uses $.ajax normally.
var cache={}; // Your Cache
function getArticle(id,callback){ // Your caching ajax-wrapping function (requires a callback)
// Require the arguments
if (arguments.length<2) throw "getArticle requires id and callback arguments";
// If the article is cached, pass it to the callback and return
var cachedArticle = id in cache
? cache[id]
: null;
if (cachedArticle) return callback(cachedArticle,true); // cached=true
// If that article isn't in the cache, perform an ajax call to get it
$.ajax({
url: "article.php",
success: function(data,textStatus,jqXHR){
cache[id] = data; // Cache it!
callback(data,false); // Pass it to the callback // cached=false
}
});
return true; // reporting that we launched an ajax request
}
Then to use it, you've got a pretty robust little function.
var ajaxWasUsed = getArticle(8, function(articleData,wasCached){
console.log("Got Article #8 "+(wasCached?"(from cache)":"(fresh ajax)")+":",articleData);
});
So I was reading through the code on Malsup's twitter plugin and I noticed he'd written his own method to handle jsonp but with timeouts and errors. I can only assume the built in jQuery method 'getJSON' doesn't have this functionality even though it clearly works fine.
So, should I continue to use Malsups version in my projects where I'm making JSONP requests or just stick with jQuery's method. I have emailed Malsup and Paul Irish to ask about why it was necessary to write this but I didn't hear back. Can't blame 'em really:)
$.getJSONP = function(s){
s.dataType = 'jsonp';
$.ajax(s);
// figure out what the callback fn is
var $script = $(document.getElementsByTagName('head')[0].firstChild);
var url = $script.attr('src') || '';
var cb = (url.match(/callback=(\w+)/) || [])[1];
if (!cb)
return; // bail
var t = 0, cbFn = window[cb];
$script[0].onerror = function(e){
$script.remove();
handleError(s, {}, "error", e);
clearTimeout(t);
};
if (!s.timeout)
return;
window[cb] = function(json){
clearTimeout(t);
cbFn(json);
cbFn = null;
};
t = setTimeout(function(){
$script.remove();
handleError(s, {}, "timeout");
if (cbFn)
window[cb] = function(){
};
}, s.timeout);
function handleError(s, o, msg, e){
// support jquery versions before and after 1.4.3
($.ajax.handleError || $.handleError)(s, o, msg, e);
}
};
If it's JSONP, you can use
$.getJSON(url + "&callback=?", [args]);
to get JSONP and call a function when it loads. The &callback=? query lets jQuery generate a random callback function in the global scope to respond to the JSONP call.
From the jQuery docs for getJSON(...)
Important: As of jQuery 1.4, if the
JSON file contains a syntax error, the
request will usually fail silently.
Avoid frequent hand-editing of JSON
data for this reason. JSON is a
data-interchange format with syntax
rules that are stricter than those of
JavaScript's object literal notation.
For example, all strings represented
in JSON, whether they are properties
or values, must be enclosed in
double-quotes. For details on the JSON
format, see http://json.org/.
Presumably, silent failure was not something they liked, hence the plugin. In your case, if you're making JSONP requests and find yourself using the onError, or onTimeout methods, then keep the plugin. I've not had to use JSONP in any real capacity, but I would assume that error handling is always nice to have. In the link to the jQuery docs, there is good discussion on this towards the end of the comments
JQuery HowTo has a post specifically about using jQuery, JSONP, and Twitter.
Twitter JSON(P) API URL:
http://twitter.com/status/user_timeline/USERNAME.json?count=10&callback=JSONPcallbackFunction
Here is a code to use with jQuery’s
$.getJSON() function:
http://twitter.com/status/user_timeline/USERNAME.json?count=10&callback=?
We have put ? (question mark) for
callback function name so jQuery could
replace it with a dynamic one that it
has created.