Any web architecture using JavaScript will always have plenty of
AJAX requests to the server. There are many, many, many different ways AJAX calls can be made.
Suppose you're thinking good software engineering and you want to avoid coupling your client code to the AJAX mechanism you are
using? You could use a wrapper / bridge / proxy type object which contains all the APIs to get information
from the Server and encapsulates your AJAX mechanism.
Following the Crockford Module pattern this could pan out something like:
dublintech.myproject.apiBridge = function() { function createEntity(data) { ... // actual AJAX call ... } function readEntity(id) { ... // actual AJAX call ... } that = {} ... that.createEntity = createEntity; that.readEntity = readEntity; return that; };This could be invoked simply as:
apiBridge.createEntity(data);What are the pro's of using the wrapper approach? Well essentially the advantages are all derived from the fact there is a nice separation of concerns: the transport mechanism (doesn't even have to be ajax) is separated from the actual data requests coming from the client. This means:
- If you wish to change Ajax mechanism (i.e. move from one Ajax library to another), it's not a big deal. The impact is limited.
- It's easier to stub out and test.
- It's easier to achieve code reuse. Suppose you want to have consistent error handling for exceptions from Ajax requests, it's much easier to do this if all Ajax request are following the same pattern.
Tell me something more interesting.
Well we all know that you can see the AJAX requests in firebug. But say you wanted another way to log what requests had been invoked on your wrapper. In Java, you could write an Aspect and set it on every method that made an Ajax request. There is no direct equivalent to aspects in JavaScripts. But we can do something similar. Firstly, the function to do the pre and post logging:function wrapPrePostLogic(fn) { return function withLogic() { console.log("before" + fn.name); var res = fn.apply(this, arguments); console.log("after" + fn.name); } }Ok so
wrapPrePostLogic
takes a function, and returns a new function
which wraps around the existing function and puts logging before and after
the invocation of that function. Hold on sec - IE is awkward. It
doesn't support getting the function.name variable. So let's write a helper method
to get function name for any browse and use that instead.
function wrapPrePostLogic(fn) { return function withLogic() { getFnName("before " + getFnName(fn)); var res = fn.apply(this, arguments); getFnName("after " + getFnName(fn)); } } function getFnName(fn) { var toReturn = (fn.name ? fn.name : (fn.toString().match(/function (.+?)\(/)||[,''])[1]); return toReturn; }So now what we need to ensure wrapPrePostLogic() is called for every method. We could do:
that = {} ... that.createEntity = wrapPrePostLogic(createEntity); that.readEntity = wrapPrePostLogic(readEntity); ... return that;But I am smelling code bloat and human error. What would be nicer is to iterate over all functions returned by
that
and wrap them appropriately.
To do that we could do...
that = {} ... for (var prop in that) { if (typeof that[prop] === "function") { that[prop] = wrapPrePostLogic(that[prop]); } } return that;
Not bad. Anything else?
Ok, so say you are you using a UI test framework and wanted to test code coverage of all methods that send / receive data to / from the server. Instead of getting your wrapper function to log to the console you could update a hidden div and then make your test framework check this hidden div.function updateHiddenDiv(methodName) { if (jQuery("#js_methods_invoked").length === 0) { var hiddenDiv = jQuery('Now we can just change our wrapper function to:
function wrapPrePostLogic(fn) {
return function withLogic() {
var res = fn.apply(this, arguments);
updateHiddenDiv(fn.name);
return res;
};
}
In addition, you can find what JavaScript methods have been invoked by opening
a JavaScript console and wacking in:
jQuery(#hiddenDiv).This will return what methods have been invoked. Until the next time, take care of yourselves!