Monkey Patching
There are plenty of moments as a developer when you do something so clever that you just have to crack a little smile and be proud of yourself for a bit. When I'm writing JavaScript code many of these moments come in the form of monkey patches, something that is only really possible in dynamic languages. Monkey patching is any process where you override some functionality in another library without modifying the library. I recently implemented a monkey patch that I was rather proud of.
I was working with a JavaScript library known as colorbox. It's a JQuery plugin that allows you to open up a custom modal window on the page. I was using colorbox to display a form to the user so they could enter information. Rather than implement a save button in the popup, colorbox exposes a closing event that I can handle. This way as soon as the user closes the dialog I can fire a save action to save the user's input to the server.
I implemented the closing event successfully and began to test the save functionality. There was a problem. My code needed to access the elements in the form for the user data, but the event exposed by colorbox was non-blocking. In other words, colorbox was firing the closing event and then immediately continuing with the close operation. This resulted in a race condition between my save code and colorbox destroying the form elements in the dialog. Most of the time the elements would be destroyed before my code could retrieve the user's data. This was obviously not ideal.
The colorbox API also exposed a close()
function in case you needed to close the dialog from your own code. The obvious solution would have been to just create my own save/close button that saves the user input and then fires this close()
function. I tried doing this and immediately encountered a snag. The colorbox window contains its own close button as well as the ability to click anywhere outside the dialog to close it. Both of those functions called the colorbox close()
function internally! That means even if I implement my own button I'd now have to use JQuery to hide colorbox's own close button; I'd also have to disable the ability for the user to click outside the window to close the dialog. I didn't really like these solutions.
I thought about doing all sorts of crazy workarounds with hidden fields and keydown events, etc. Then I remembered that this was JavaScript, and being a dynamic language has some distinct advantages.
The Monkey Patch
The close()
function exposed by colorbox was the same close()
function that it called internally when the user clicked the close button or clicked outside the colorbox window. Because of this the solution was so ridiculously simple!
// Store the original close() function in a variable.
var cboxClose = $.colorbox.close;
// Override colorbox's close() function with my own.
$.colorbox.close = function () {
var userData = // get user input;
// Save user's data to server.
$.ajax({
url: '/saveData',
data: userData
}).done(function () {
// Call original colorbox close() function.
cboxClose();
});
}
I successfully wrapped colorbox's own close()
function with my own. Now whenever the close()
function is invoked (internally or externally) my code will save the user's data before colorbox's real close functionality begins. Pretty nifty eh? It takes a while to get used to the cool tricks in a dynamic language. Even when you have a pretty good handle on things it can be easy to forget some of the simple yet powerful things.