Sometimes when working with JavaScript libraries and widgets, you may want to extend or augment the behavior of certain functions without breaking the existing functionality or completely extending the object. Todays article will explore the Function Hijacking Pattern, which can be used to augment a function with your own logic.
How to do it…
The following is a simple example using the hijack pattern to augment a function:
var obj = { // static object // add function, supports adding 2 values add: function(x, y) { return x + y; } }; // move the existing function to another variable name obj.__add = obj.add; // override the existing function with your own, to support adding 2 or 3 values obj.myFunction = function(x, y, z) { var val = obj.__add(x, y); return z ? val + z : val; }
The following code segment hijacks the on
function of YAHOO.util.Event
, in order to ensure there is only one mousemove
listener per page, no matter how many listener functions are applied:
// create a local variable to reference static object var Event = YAHOO.util.Event; // move the existing function to another variable name Event.__on = Event.on; // array of callback functions Event.__mouseMoveCallbacks = []; // replace the existing function with your own Event.on = function(elem, eventName, fn, data, ctx) { var args = arguments; if (mousemove== eventName && document == elem) { Event.__mouseMoveCallbacks.push({ ctx: true === ctx ? data : ctx, data: data, func: fn }); // when not first callback, dont continue if (1 < Event.__mouseMoveCallbacks.length) { return; } // replace the callback function args[2] = function(e) { var args = [e], o, i, j; for (i=0, j=Event.__mouseMoveCallbacks.length; i<j; i+=1) { o = Event.__mouseMoveCallbacks[i]; args[1] = o.data; o.func.apply(o.scope || this, args); } } } YAHOO.util.Event.__on.apply(this, args); };
This can also be useful when working with OOP objects in JavaScript to hijack functions on an objects
prototype
:
var MyObject = function() {}; MyObject.prototype.add = function(x,y) { return x + y; }; // now hijack the add function MyObject.prototype.__add = MyObject.prototype.add; MyObject.prototype.add = function(x, y, z) { var val = this.__add(x, y); return z ? val + z : val; }; // now when you instantiate MyObject, each instance will have your addfunction myObjectInst = new MyObject(); myObjectInst.add(1,2,3);
Putting this to practical use, suppose you want to hijack the initializer
function of the Widget component in YUI 3 to add additional features:
YUI().use(widget, function(Y) { Y.Lang.augmentObject(Y.Widget.prototype, { __idPrefix: null, __initializer: Y.Widget.prototype.initializer; initializer: function() { var id, bb = this.get(boundingBox); // does the boundingBox have an id attribute if (bb) { id = this.get(id); } // otherwise, fetch the widgets ID if (! id) { id = this.get(id); } // update the id attribute of the boundingBox if (bb) { bb.set(id, id); } this.__idPrefix = id + "_"; this.__initializer.apply(this, arguments); }, // fetch nodes from widget, using boundingBox id as prefix for all widget related elements getWidgetNode: function(name) { return Y.one(this._idPrefix + name); } }, true); });
How it works …
The is no fancy magic at work here, this pattern simply takes an existing function reference and maps it to a new reference. The original function is then overwritten with one that is more useful, which does something new before executing the original function. In the general example, a simpleadd
that takes two parameters and adds them, is replaced by a new function that can take an optional third parameter.The hijack of the YAHOO.util.Event.on
function is a bit more complicated. As it requires an extra variable, __mouseMoveCallbacks
, to store the event callback objects. Additionally, the hijacking function has to parse some arguments so the event handler and callbacks behave the same way they would if executed by YUI. Lastly, as it is written, there is no way to remove these listeners. The YAHOO.util.Event.removeListener
function would also need to be hijacked to remove the callback function from the __mouseMoveCallbacks
array.
The Y.Widget.initializer
function hijack is something useful, if following the coding pattern, where all id
attributes inside of the widget, use the widget id
as a prefix. Meaning, if the widget id
is myWidget
, then a button inside the widget would have an id
like myWidget_myButton
. During the initializer
function the id
of the boundingBox
is stored as _idPrefix
. This allows the shortcut method getWidgetNode
to return elements by only needing the second part of the id
, so getWidgetNode("myButton")
would return the button instance. This is a pattern we use at Mint.com to simplify complex id
namespaces.