SitePoint Sponsor

User Tag List

Results 1 to 12 of 12
  1. #1
    SitePoint Zealot soapergem's Avatar
    Join Date
    Mar 2005
    Location
    Madison, WI
    Posts
    165
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Event handlers and Class members

    So I've got a bit of code that doesn't work, and I would like to know 1) why it doesn't work, and 2) how to fix it. I've created a new class called A that has one member variable, data. I also have a class function that serves as an onclick event handler for any object on the page I choose. I assumed that because this defined as a class function using prototype, that it would have access to all the class members its particular instantiation of the class. Sadly, though, it seems I was mistaken. I just have this event handler alert two variables: the type of its target node, and the data variable. And unfortunately, the latter of those just comes back reporting undefined rather than whatever value I set it to. Help! How do I get this event handler to remember the class object that instantiated it?

    HTML Code:
    <html>
    <head>
    <script type="text/javascript">
    function A(data, id) {
    	this.data = data;
    	var obj = document.getElementById(id);
    	if ( obj ) {
    		if ( obj.addEventListener ) {
    			obj.addEventListener('click', this.mouseClick, false);
    		} else if ( obj.attachEvent ) {
    			obj.attachEvent('onclick', this.mouseClick);
    		} else {
    			obj['onclick'] = this.mouseClick;
    		}
    	}
    }
    A.prototype.mouseClick = function(evt) {
    	evt = evt || window.event;
    	evt.targetNode = evt.srcElement || evt.target;
    	
    	window.alert(evt.targetNode.tagName);
    	window.alert(this.data);
    }
    window.onload = function() {
    	a = new A('this is a test', 'test');
    }
    </script>
    </head>
    <body>
    <p><button id="test">Click me</button></p>
    <p>This should alert "BUTTON," followed by "this is a test." Instead, it alerts "BUTTON," followed by "undefined."</p>
    </body>
    </html>
    // useless crap about my relationships, philosophy,
    // theology, music and programming projects:

    my $blog = 'http://gordon-myers.com/';

  2. #2
    SitePoint Evangelist
    Join Date
    Jul 2007
    Posts
    345
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    The problem is the use of 'this'.

    You instantiate an A object and set its data property. However, when the button is clicked, 'this' will refer to the button, not the A object. this.data will refer to the data property of the button, which doesn't exist.

    In the constructor code, you could assign the data passed in to your obj instead.
    Code:
    // assigns data to the A object
    // but 'this' will refer to something else in an event handler
    this.data = data
    
    // assign data to the obj instead
    obj.data = data

  3. #3
    SitePoint Zealot soapergem's Avatar
    Join Date
    Mar 2005
    Location
    Madison, WI
    Posts
    165
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    That's all good and fine for very simple uses like this, when you only need to store strings. But what about more complex uses, like if I wanted to call further class methods from within the onclick handler? Is there some way to store a reference to the class object?
    // useless crap about my relationships, philosophy,
    // theology, music and programming projects:

    my $blog = 'http://gordon-myers.com/';

  4. #4
    SitePoint Evangelist
    Join Date
    Jul 2007
    Posts
    345
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    How about attaching a reference back to the class object to the element object?
    Code:
    obj.A = this;
    Then in the event handler you could use
    Code:
    this.A.method();

  5. #5
    SitePoint Zealot soapergem's Avatar
    Join Date
    Mar 2005
    Location
    Madison, WI
    Posts
    165
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks for your help! That effectively solves everything. The design of it all still seems a bit peculiar, with having to explicitly pass a class reference within the same class, but when life gives you lemons, I suppose. Here's the update code, which is working now.

    HTML Code:
    <html>
    <head>
    <script type="text/javascript">
    function A(data, id) {
    	this.data = data;
    	var obj = document.getElementById(id);
    	if ( obj ) {
    		obj.A = this;
    		if ( obj.addEventListener ) {
    			obj.addEventListener('click', this.mouseClick, false);
    		} else if ( obj.attachEvent ) {
    			obj.attachEvent('onclick', this.mouseClick);
    		} else {
    			obj['onclick'] = this.mouseClick;
    		}
    	}
    }
    A.prototype.mouseClick = function(evt) {
    	evt = evt || window.event;
    	evt.targetNode = evt.srcElement || evt.target;
    	
    	window.alert(evt.targetNode.tagName);
    	window.alert(evt.targetNode.A.data);
    }
    window.onload = function() {
    	a = new A('this is a test', 'test');
    }
    </script>
    </head>
    <body>
    <p><button id="test">Click me</button></p>
    </body>
    </html>
    // useless crap about my relationships, philosophy,
    // theology, music and programming projects:

    my $blog = 'http://gordon-myers.com/';

  6. #6
    SitePoint Evangelist
    Join Date
    Jul 2007
    Posts
    345
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    JavaScript: Lemon Fresh and Bitter Lemons.

    It is a bit strange, but also outrageously flexible. The solution above seems quite simple but there'll probably be many others.

    It's good to see 'evt.targetNode' replacing 'this' in the code above. Safe.

  7. #7
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Create a closure:
    Code:
    var self = this;
    obj['onclick'] = function() {
      self.mouseClick();
    }
    The problem by the way, is that in Javascript functions are variables. You can pass them around freely, like any other variable. This means that a function does not belong to a specific class/object. The variable this is kind of magical. It's automatically bound to an object, depending on how the function was invoked. For example:
    Code:
    var fn = function () {
      alert(this);
    }
    var obj = {}
    obj.foo = fn;
    The variable fn points to a function. The object obj has a member foo, which happens to point to the same function. Now, if you call fn directly:
    Code:
    fn();
    Then this won't be rebound. (It will then stay the default, which is the global context, window).
    But if you call the same function, through an assigned member:
    Code:
    obj.fn();
    then this is bound to that object (Eg. obj).

    The opposite is true too. You can take a function, which is assigned as a member and assign it to a variable. If you call that variable, there is no link back to the object it came from. This is no different from other types of variables. If you assigned an integer as a member variable, and then assigned it to someplace else, you wouldn't expect it to "remember" where it came from. Objects in Javascript are simply containers, which can happen to contain functions, as well as other variables.

    Hope that helps a bit.

  8. #8
    SitePoint Evangelist
    Join Date
    Jul 2007
    Posts
    345
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    1) By assigning 'this' to 'self' we use our own variable name rather than relying on 'this' which can change depending on context.
    2) When the 'click' event is fired, the outer function is called. In that function, 'this' will refer to the object the event handler was attached to (or maybe, sometimes, the window?).
    3) Inside the function we call 'self.mouseclick()'. By calling it as a method of self, inside mouseclick(), 'this' will refer to 'self'.

    Does that sound about right?

    What about the event object. If we're expecting it as an argument should we call self.mouseclick(evt)?

  9. #9
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    The reason why it works, is that variables are lexically scoped in javascript. I could have used any name other than self, but it's a common convention to use that.

    this is magical. It changes, depending on the calling context. When the event fires, it invokes the function attached, but it doesn't bind this. In other words, the anonymous function is invoked, but the value of this is undefined (Actually, it's bound to window). The variable self however, retains its value. Thus, calling self.mouseClick() will invoke that function and bind this to the object, referred in self.

    Quote Originally Posted by r51 View Post
    What about the event object. If we're expecting it as an argument should we call self.mouseclick(evt)?
    You'll have to pass the argument along then:
    Code:
    var self = this;
    obj['onclick'] = function(e) {
      self.mouseClick(e);
    }
    Or more generic:
    Code:
    function bind(fn, self) {
      if (typeof(fn) == "string") {
        fn = self[fn];
      }
      return function() {
        return fn.apply(self, arguments);
      }
    }
    and then:
    Code:
    obj['onclick'] = bind("mouseClick", this);
    Yeah, I know -- More magic. arguments is a super-secret feature of Javascript, and all functions have a function apply() which invokes the function (Confused yet?).
    Last edited by kyberfabrikken; May 6, 2008 at 13:36. Reason: Edited the code snippet for bind()

  10. #10
    SitePoint Evangelist
    Join Date
    Jul 2007
    Posts
    345
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Lemon fresh!

    Just one question. In your generic code, have you chosen the argument name 'self' for a good reason? Is it meant to echo your earlier use of 'self' or are you playing with us?

  11. #11
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by r51 View Post
    Lemon fresh!
    Indeed. Javascript is one of the coolest languages I know. It's just so misunderstood.

    Quote Originally Posted by r51 View Post
    Just one question. In your generic code, have you chosen the argument name 'self' for a good reason? Is it meant to echo your earlier use of 'self' or are you playing with us?
    It doesn't matter what name it has -- I just picked it to show the correlation to the earlier code.
    My fault. I edited the code before posting, and it ended up more confusing. Should have read:
    Code:
    function bind(fn, self) {
      if (typeof(fn) == "string") {
        fn = self[fn];
      }
      return function() {
        return fn.apply(self, arguments);
      }
    }
    ... but it should still work though.

  12. #12
    SitePoint Evangelist
    Join Date
    Jul 2007
    Posts
    345
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Okay, that's great. Thanks.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •