views:

1166

answers:

5

I know I could do this with closures (var self = this) if object was a function...

<a href="#" id="x">click here</a>

<script type="text/javascript">
    var object = {
        y : 1,

        handle_click : function (e) {
            alert('handling click');

            //want to access y here

            return false;
        },

        load : function () {
            document.getElementById('x').onclick = this.handle_click;
        }
    };

    object.load();
</script>
+1  A: 

So, the event handler part wires up just fine (I tested it myself) but, as your comment indicates, you have no access to the "y" property of the object you just defined.

This works:

var object = { 
  y : 1, 
  handle_click : function (e) {
    alert('handling click');

    //want to access y here 
    alert(this.y); 

    return false; 
  }, 
  load : function () { 
    var that = this; 
    document.getElementById('x').onclick = function(e) {
      that.handle_click(e); // pass-through the event object
    }; 
  } 
}; 
object.load();

There are other ways of doing this too, but this works.

Jason Bunting
Jason, you can change alert(y); to alert(this.y);, and the results are the same - you're already calling the handler in the context of object, no need to pass in y as an argument.
Shog9
Yeah, I see that. I need to refrain from posting when I am under the influence of pain killers. :P
Jason Bunting
Updated to reflect that...
Jason Bunting
+1  A: 

The simplest way to bind the call to handle_click to the object it is defined in would be something like this:

        var self=this;
        document.getElementById('x').onclick = 
           function(e) { return self.handle_click(e) };

If you need to pass in parameters or want to make the code look cleaner (for instance, if you're setting up a lot of similar event handlers), you could use a currying technique to achieve the same:

bind : function(fn)
{
   var self = this;
   // copy arguments into local array
   var args = Array.prototype.slice.call(arguments, 0); 
   // returned function replaces first argument with event arg,
   // calls fn with composite arguments
   return function(e) { args[0] = e; return fn.apply(self, args); };
},

...

        document.getElementById('x').onclick = this.bind(this.handle_click, 
           "this parameter is passed to handle_click()",
           "as is this one");
Shog9
The problem isn't the setting of the onclick handler, that works fine. The problem is that when handle_click executes, there is no access to the value of the "y" variable.
Jason Bunting
@Jason: what's wrong with this.y ?
Shog9
It doesn't work - I tested it and it comes back undefined because of how the event handler works, "this" becomes the element that fired the click event (at least it does in FF3).
Jason Bunting
Ok, maybe i'm misunderstanding the problem? The code sw234 posted works fine - but as you noted, there's no access to 'y' from within 'handle_click'. I thought he was asking how to bind the handler such that it *would* provide access to 'y' via this...
Shog9
Well, maybe I am misunderstanding the problem as well - there are multiple ways of dealing with this, yours works as well.
Jason Bunting
IMHO more complicated than needed.
some
@some: the first method presented is three lines, about as simple as you can get. The second method would likely only be used if a great deal of binding were necessary.
Shog9
@Shog: Disregard my last comment. Now when I reread your answer I realize that I misread your answer. I guess I shouldn't try to answer when I'm in a hurry. Voted you up.
some
A: 

Thanks for the responses so far.

Obviously, I'd want access to other variables of object than just y (if there were more). What's the best way to do this? Pass 'that' instead of 'that.y' to the function?

Both of the techniques presented will give you access to all properties on `object` via `this`. Pick one and have fun... :)
Shog9
A: 

I see how to do it with Jason's latest one. Any way to do it without the anonymous function?

You're stuck with either a closure or a global variable. Why would you turn your nose up at a closure? They are one of the best features of JavaScript...
Shog9
A: 

The problem is that you need a link to your object. The children of an object don't know who their parents are, because they can be hosted by any parent or by no parent at all:

var myobject1={ func1:function(){alert(1);} }
var myobject2={ func2: myobject1.func1 }
var func3 = myobject1.func1;

It is the exact same anonymous function that is assigned to myobject1.func1, myobject2.func2 and to the variable func3. There is no way that it can know what object it hosted by, since it, as you can see, can be hosted by several objects at once.

The normal way to solve that problem is to use a closure in the objects constructor. If you don't want to use that you have to give the link to the object in some other way.

If you want to assign the event handler in html, you can use this:

<button onclick="myobject.handle_click.call(myobject,this);"/>

When the handle_click is called, "this" will refer to myobject, and your first parameter will be a link to the object that fired the event. Note that you have to enter the name of the object as the first parameter to "call".

If you want to assign it in javscript you have to give the link in another way. If you have this object:

var myobject = {
  y:1;
  handle_click:function() {
    var myself = arguments.callee.myself; // Get the link to the object
    alert(myself.y);
  }
}

Assign a link to the object and set the onlick event on your element:

myobject.handle_click.myself = myobject; // set the link
element.onclick = myobject.handle_click;

With the technique above the function always have a link to the object that you have specified. Even if you assign it to a variable or another object, it still have the handle to your first object.

With your example you can use this:

var myobject = {
    y : 1,
    handle_click : function (e) {
     var myself = arguments.callee.myself;
     alert('handling click ' + myself.y);
     return false;
    },

    load : function () {
     this.handle_click.myself = this;
     document.getElementById('x').onclick = this.handle_click;
    }
};

myobject.load();

The main drawback with the method above is that you have to assign the link to every function that needs it.

var myobject = new function () {
    var myself = this;
    myself.y = 2;
    myself.handle_click=function(e) {
     alert('handling click ' + myself.y);
     return false;
    };
    myself.load = function () {
     document.getElementById('btn2').onclick = myself.handle_click;
    }
}();

With the closure above, every function has access to itself, regardless of how it was called.

some