views:

44

answers:

2

Hi,

I'm trying to write a simple wrapper for mouse behaviour. This is my current code:

function MouseWrapper() {

  this.mouseDown = 0;  
  this.OnMouseDownEvent = null;
  this.OnMouseUpEvent = null;
  document.body.onmousedown = this.OnMouseDown;  
  document.body.onmouseup = this.OnMouseUp;
}

MouseWrapper.prototype.Subscribe = function (eventName, fn) {

  // Subscribe a function to the event
  if (eventName == 'MouseDown') {
    this.OnMouseDownEvent = fn;
  } else if (eventName == 'MouseUp') {
    this.OnMouseUpEvent = fn;
  } else {    
    alert('Subscribe: Unknown event.'); 
  }  
}  


MouseWrapper.prototype.OnMouseDown = function () {  
  this.mouseDown = 1;  
  // Fire event
  $.dump(this.OnMouseDownEvent);
  if (this.OnMouseDownEvent != null) {
    alert('test');
    this.OnMouseDownEvent();
  }
}

MouseWrapper.prototype.OnMouseUp = function () {

  this.mouseDown = 0;
  // Fire event
  if (this.OnMouseUpEvent != null) {
    this.OnMouseUpEvent();
  }  
}

From what I gathered it seems that in MouseWrapper.prototype.OnMouseUp and MouseWrapper.prototype.OnMouseDown the keyword "this" doesn't mean current instance of MouseWrapper but something else. And it makes sense that it doesn't point to my instance but how to solve the problem?

I want to solve the problem properly I don't want to use something dirty.

My thinking: * use a singleton pattern (mouse is only one after all) * pass somehow my instance to OnMouseDown/Up - how?

Thank you for help!

+1  A: 

In Javascript, unlike most languages, the value of the this keyword is determined when the function is called.

For example:

var dumper = function() { alert(this); };
var str1 = "A";
str1.dump = dumper;
str1.dump();        //Alerts A

var str2 = "B";
str2.dump = str1.dump;
str2.dump();        //Alerts B

When the browser calls your event handler, it calls it in the context of the DOM element, so this isn't what you think it is.

To work around this, you need to use an anonymous method that calls your handlers in the right context.

For example:

var self = this;

document.body.onmousedown = function(e) { return self.OnMouseDown(e); };
document.body.onmouseup = function(e) { return self.OnMouseUp(e); };

Also, you should not handle events like this.

Instead, call attachEvent / addEventListener.

SLaks
`self.OnMouseDown()` ...
nickf
you're not using your shiny new self variable after you set it.
lincolnk
@nickf: I already fixed that.
SLaks
Thank you for explanation! The code works. I'll have a look on the links. I don't work in Javascript very often.
MartyIX
+2  A: 

The above solution is fine but a more re-usable way is to "bind" your method to a context by creating a method that creates that closure for you. For example

Function.bind = function(method, context) {
  return function() {
    return method.apply(context, arguments);
  }
}

Following Slaks's example, you would have:

document.body.onmousedown = Function.bind(this.OnMouseDown,this);
document.body.onmouseup = Function.bind(this.OnMouseUp, this); 

or you could do it to Function.prototype for syntactic sugar.

Function.prototype.bind = function(context) {
  return function() {
    // since this is a prototype method, "this" is the method to be called
    return this.apply(context, arguments);
  }
}

document.body.onmousedown = this.OnMouseDown.bind(this);
document.body.onmouseup = this.OnMouseUp.bind(this); 

You could also bind arguments to be called as well as the context... ask if you're interested.

The singleton approach, though it works, is a hack. It's adding globals instead of properly addressing the issues at hand. It means if you needed the behavior in two places, it wouldn't work.

Juan Mendes