views:

34

answers:

2

I've got a JS object I've made, with a few prototypal functions, and calling them from within the constructor is fine, using this.[function]

But in a later event handler function, this refers to the element, and not the object, and I'm not sure how to resolve this:

It goes through the AddListener fine, the mouse down event triggers, causing enable/disableDragging to work fine. But in that function, "this" is the element, and not the dragger object, so I can't call access the WindowCenter, DraggingBool or Add/RemoveListener events.

From reading up it looks like I might need to implement prototype.bind? But not sure how to rearrange my existing code to do this. Or do I change one of the functions to return a function?

Yes, I know I could do this in jQuery, done it many times, just trying to see if I can get it going with POJS :)

Dragger = function(element, draggingElement) {
    this.Element = element;
    this.DraggingElement = element;
    this.WindowCenter;
    this.DraggingBool = false;

    this.AddListener(this.DraggingElement, "mousedown", this.enableDragging); 
    this.AddListener(this.DraggingElement, "mouseup", this.disableDragging);
}

Dragger.prototype.AddListener = function (element, eventType, listener, bubble) {
    if (typeof (element) != "object" || typeof (listener) != "function")
        return;
    if (typeof (bubble) == "undefined")
        var bubble = false;
    eventType = eventType.toLowerCase();
    if (eventType.substr(0, 2) == "on")
        eventType = eventType.substr(2);
    if (window.addEventListener) { // Mozilla, Netscape, Firefox
        element.addEventListener(eventType, listener, bubble);
    } else if (window.attachEvent) { // IE
        element.attachEvent("on" + eventType, listener);
    }
}

Dragger.prototype.RemoveListener = function (element, eventType, listener) {
    eventType = eventType.toLowerCase();
    if (eventType.substr(0, 2) == "on")
        eventType = eventType.substr(2);
    if (window.addEventListener) { // Mozilla, Netscape, Firefox
        element.removeEventListener(eventType, listener, false);
    } else if (window.attachEvent) { // IE
        element.detachEvent("on" + eventType, listener);
    }
}

Dragger.prototype.enableDragging = function (e) {
    if (!e) var e = window.event;
    //dont move for right click
    if (e.which && e.which == 3)
        return;
    else if (e.button && e.button == 2)
        return;

    this.DraggingBool = true;
    this.WindowCenter = new Point(e.pageX, e.pageY);
    this.AddListener(document.body, "mouseover", this.mouseMoveListener);
}

Dragger.prototype.disableDragging = function () {
    this.DraggingBool = false;
    this.RemoveListener(document.body, "mouseover", this.mouseMoveListener);
}

Dragger.prototype.mouseMoveListener = function (e) {
    if (!this.DraggingBool)
        return;
    if (!e) var e = window.event;
    e.preventDefault();
    var newLoc = new Point(e.pageX, e.pageY);
    var xDif = windowCenter.X - newLoc.X;
    var yDif = windowCenter.Y - newLoc.Y;
    this.Element.style.left = (this.Element.offsetLeft + xDif) + "px";
    this.Element.style.top = (this.Element.offsetTop + yDif) + "px";
    this.WindowCenter = newLoc;
}
+2  A: 

This is because when using addEventListener the execution scope is set to the node, just as if you had used node.onclick = foo. When you use attachEvent in IE, the execution scope becomes the global execution scope, which is the window.

One way to get around this is to use Function.call, or Function.apply to specify the execution scope of your liking. Many libraries will declare a binding function to help with this, such as:

function bind(fn, context) {
  return function() {
    return fn.apply(context, arguments);
  };
}

this.AddListener(this.DraggingElement, "mousedown",
    bind(this.enableDragging, this));

You could also add a context argument to your AddListener function, and do the binding there.

Ecmascript 5 has a built in bind method.

Alex M.
Brilliant! Cheers, much better than my hack of setting the global variable to the instance of the object when it's created, and calling that hardcoded in the functions :)Now I've just got to get it working in IE, as you apparently cant set style.left ¬.¬. But I'll figure it out ^^
Psytronic
A: 
  Function.prototype.bind = function () {
    if (arguments.length < 2 && arguments[0] === undefined) {
     return this;
   }

    var thisObj = this,
    args = Array.prototype.slice.call(arguments),

    obj = args.shift();

    return function () {
     return thisObj.apply(obj, args.concat(Array.prototype.slice.call(arguments)));
    };

  };



  Function.bind = function() {
    var args = Array.prototype.slice.call(arguments);
    return Function.prototype.bind.apply(args.shift(), args);

  }

Dragger = function(element, draggingElement) {
    this.Element = element;
    this.DraggingElement = element;
    this.WindowCenter;
    this.DraggingBool = false;

    this.AddListener(this.DraggingElement, "mousedown", this.enableDragging,this); 
    this.AddListener(this.DraggingElement, "mouseup", this.disableDragging,this);
}

Dragger.prototype.AddListener = function (element, eventType, listener, bubble,dragger_obj) {
    if (typeof (element) != "object" || typeof (listener) != "function")
        return;
    if (typeof (bubble) == "undefined")
        var bubble = false;
    eventType = eventType.toLowerCase();
    if (eventType.substr(0, 2) == "on")
        eventType = eventType.substr(2);
    if (window.addEventListener) { // Mozilla, Netscape, Firefox
        element.addEventListener(eventType, listener.bind(dragger_obj), bubble);
    } else if (window.attachEvent) { // IE
        element.attachEvent("on" + eventType, listener.bind(dragger_obj));
    }
}

Dragger.prototype.RemoveListener = function (element, eventType, listener,dragger_obj) {
    eventType = eventType.toLowerCase();
    if (eventType.substr(0, 2) == "on")
        eventType = eventType.substr(2);
    if (window.addEventListener) { // Mozilla, Netscape, Firefox
        element.removeEventListener(eventType, listener.bind(dragger_obj), false);
    } else if (window.attachEvent) { // IE
        element.detachEvent("on" + eventType, listener.bind(dragger_obj));
    }
}

Dragger.prototype.enableDragging = function (e) {
    if (!e) var e = window.event;
    //dont move for right click
    if (e.which && e.which == 3)
        return;
    else if (e.button && e.button == 2)
        return;

    this.DraggingBool = true;
    this.WindowCenter = new Point(e.pageX, e.pageY);
    this.AddListener(document.body, "mouseover", this.mouseMoveListener,this);
}

Dragger.prototype.disableDragging = function () {
    this.DraggingBool = false;
    this.RemoveListener(document.body, "mouseover", this.mouseMoveListener,this);
}

Dragger.prototype.mouseMoveListener = function (e) {
    if (!this.DraggingBool)
        return;
    if (!e) var e = window.event;
    e.preventDefault();
    var newLoc = new Point(e.pageX, e.pageY);
    var xDif = windowCenter.X - newLoc.X;
    var yDif = windowCenter.Y - newLoc.Y;
    this.Element.style.left = (this.Element.offsetLeft + xDif) + "px";
    this.Element.style.top = (this.Element.offsetTop + yDif) + "px";
    this.WindowCenter = newLoc;
}
NM