how to override a returned nested method in javascript?

535 Views Asked by At

Say I'm using a library with the code that looks like below:

(function($)
{
    function Library(el, options)
    {
        return new Library.prototype.init(el, options);
    }
    Library.fn = $.Library.prototype = {
        init: function(el, options) {
            this.$elm.on('keydown.library', $.proxy(this.keydown.init, this));
        }
        keydown: function() {
            return {
                init: function(e) {
                    ... somecode
                },
                checkStuff: function(arg1, arg2) {
                    ...someCode
                }
            }
        };
    }
})(jQuery);

It has a plugin system that provides access to this where this is an Object {init: function, keydown: function...}. I want to override the keydown.init function. Normally I could see using something like _.wrap to do it:

somefunc = _.wrap(somefuc, function(oldfunc, args) {
     donewstuff();
     oldfunc.call(this.args);
});

but that doesn't seem to work on the returned nested method e.g.:

this.keydown.init = _.wrap(this.keydown.init, function(oldfunc, args) {
     donewstuff();
     oldfunc.call(this.args);
});

The question might be answered on here but I don't really know the right words to use to describe this style of coding so its hard to search. Bonus points if you let me know if it is even correct to call it a nested returned method?

3

There are 3 best solutions below

4
On BEST ANSWER

You will need to decorate (read: wrap) the keydown function so that you can wrap the init method of the object it returns:

somefunc.keydown = _.wrap(somefunc.keydown, function(orig) {
    var module = orig(); // it doesn't seem to take arguments or rely on `this` context
    module.init = _.wrap(module.init, function(orig, e) {
         donewstuff();
         return orig.call(this, e);
    });
    return module;
});
2
On

The problem is that your method is run out of context.

You need to set its this context (use .bind() for this)

somefunc.init = _.wrap(somefuc.init.bind(somefunc), function(oldfunc, args) {
     donewstuff();
     oldfunc.call(this.args);
});
2
On

This pattern is called a module. The best thing you can do here is cache the method you want to override and call the cached method inside your override:

somefunc._init = somefunc.init;
somefunc.init = function () {
    doStuff();
    this._init();
};

I checked _.wrap and it does the same thing, what your missing as pointed out by another answer is you're losing the context of somefunc. In order to prevent that you can do:

somefunc.init = _.wrap(_.bind(somefunc.init, somefunc), function (oldRef, args) {
    doStuff();
    oldRef.call(this.args);
});