Intercepting JavaScript methods, Ruby-style

Ruby’s open classes and modules, along with alias_method, make it really easy to add functionality to existing methods. Take this example from my plugin AttrLocked:

class ActionView::Helpers::InstanceTag
  def tag_with_attribute_locking(name, options = nil)
    options = (options || {}).update("disabled" => attribute_locked?)
    tag_without_attribute_locking(name, options)
  end
  alias_method(:tag_without_attribute_locking, :tag)
  alias_method(:tag, :tag_with_attribute_locking)
end

All that does is intercept any calls to InstanceTag#tag and add on an option to disable the form field if required. The original tag method is copied to tag_without_attribute_locking, then tag is re-aliased to call your interception.

(If you are using Rails 1.2 or later, you can use alias_method_chain(:tag, :attribute_locking) in place of the two alias_method calls. I use alias_method in my plugins for backwards compatibility.)

JavaScript has open objects too, and it allows you to reference methods using strings, and it lets you pass functions around like regular variables. So, you could write this:

function intercept(object, method, chain, interception) {
  object[method + '_without_' + chain] = object[method];
  object[method] = interception;
}

which then lets you intercept any method on an object. Let’s say I want to log every call to Event.observe:

intercept(Event, 'observe', 'logging', function(element, ... ) {
  console.log('Event.observe: ' + element);
  Event.observe_without_logging(element, ... );
});

This works great for extending third-party libraries you’re using if you can’t/don’t want to modify their source code.

If you've enjoyed this article, you might enjoy my recently published book JavaScript Testing Recipes. It's full of simple techniques for writing modular, maintainable JavaScript apps in the browser and on the server.