views:

113

answers:

4

I have several methods that I need to wrap in new methods in basically the same manner. My first solution doesn't work, and I understand why, but I don't know if there is a simple solution to this problem or if it can't be done the way that I want to do it.

Here's an example. I have objects a-c that have an onClick method. I need to execute some code before the onClick methods. I tried the following:

// objects a-c
a = {onClick : function () { console.log('a'); }};
b = {onClick : function () { console.log('b'); }};
c = {onClick : function () { console.log('c'); }};

// first try
var arr = [a, b, c]
for (var i = 0; i < arr.length; i++) {
    var oldOnClick = arr[i].onClick;
    arr[i].onClick = function () {
     // new code here
     oldOnClick();
    }  
}

// outputs 'c'
// what i want is to maintain a reference to the original method
// so in this case, execute my new code and output 'a'
a.onClick();

This doesn't work because when the new method is called, oldOnClick will point to the method from the last iteration and not the to method when it was assigned.

Is there a simple solution that I'm overlooking?

+2  A: 

What you need is closure:

for(var i=0, l=arr.length; i<l; i++){
 (function(i){
   var oldOnclick = arr[i].onClick; 
   //etc.
 })(i);
}
Pim Jager
ahh .. yes b/c that will execute immediately. thx, i thought there was a simple solution. :)
Keith Bentrup
+1 for closure usage
dfa
+1  A: 

Javascript binding rules are pretty odd. Really, javascript is pretty odd.

I don't know that I'd call this the way to fix it, but by introducing a sub-function you can get introduce another bind and thereby fix this particular problem.

Your (modified for quick-y Chrome hacking) code becomes:

a = {onClick : function () { alert('a'); }};
b = {onClick : function () { alert('b'); }};
c = {onClick : function () { alert('c'); }};

var arr = [a, b, c]
for (var i = 0; i < arr.length; i++) {
    var oldOnClick = arr[i].onClick;
    arr[i].onClick = bind(oldOnClick);
}

a.onClick();
b.onClick();
c.onClick();

function bind(oldFunc)
{
    return function () {
        //New Code
        oldFunc();
    }  
}

The above code throws up three alerts: a, b, c. Anything replacing '//New Code' will be run at the right time.

Kevin Montrose
+1  A: 

did you tried with some AOP framwork for Javascript?

for example using jQuery AOP plugin:

jQuery.aop.before( {target: String, method: 'replace'}, 
  function(regex, newString) { 
    alert("About to replace string '" + this + "' with '" + newString + 
          "' using regEx '" + regex + "'");
  }
);

check also here.

dfa
thanks. i may need AOP framework sometime.
Keith Bentrup
A: 
var a = {onClick : function () { console.log('a'); }};
var b = {onClick : function () { console.log('b'); }};
var c = {onClick : function () { console.log('c'); }};

var arr = [a, b, c];
for (var i = 0; i < arr.length; i++) {
    var oldOnClick = arr[i].onClick;
    arr[i].onClick = wrapHandler(oldOnClick);  
}

function wrapHandler(handler) {
    return function() {
        console.log("New stuff");
        handler();
    }
}

a.onClick(); // outputs "New stuff" then "a"
b.onClick(); // outputs "New stuff" then "b"
b.onClick(); // outputs "New stuff" then "c"
NickFitz