I'm now trying to register a mouse event listener on a canvas element.
As my JS application object initializes, it binds mouse events to a custom "MouseController" class:
this.canvas.addEventListener('mousedown',this.input0.btnDown, false);
"this" is the application object being initialized and canvas is a reference to the basic HTML5 canvas on my page.
The controller is a simple class like:
function MouseController(eventHandler) {
this.handler = eventHandler;
this.contact = false;
this.coordX = 0;
this.coordY = 0;
}
MouseController.prototype.btnDown = function(event) {
// Faulty line ???
this.updateCoordinates(event);
}
MouseController.prototype.updateHIDCoordinates = function (event) {
// code to set coordX and coordY of the controller instance
// Again, I can't access the object from here as "this" refers to the Canvas
}
As the mouse is clicked, the console logs "result of expression this.updateCoordinates' is not a function". A previous discussion taught me about the reference "this" being lost, ending up being bound to, in this case, the Canvas item.
So I'm looking for a way to call "btnDown" like I would in java, meaning that it executes as part of an object and thus has access to the variables of that object.
The only solution I found was a singleton... No good :( I'm sure there's a clean solution...
Please help :-)
Thanks!
J.
Either create a closure:
var controller = this.input0;
this.canvas.addEventListener('mousedown', function(event){
controller.btnDown(event);
}, false);
or use ECMAScript 5's .bind() [MDN]:
this.canvas.addEventListener('mousedown', this.input0.btnDown.bind(this.input0), false);
Also note that your other method is called updateHIDCoordinates, not updateCoordinates.
Related
How do I add an event handler inside a class with a class-method as the callback?
<div id="test">move over here</div>
<script>
oClass = new CClass();
function CClass()
{
this.m_s = "hello :-/";
this.OnEvent = OnEvent;
with(this)
{
var r = document.getElementById("test");
r.addEventListener('mouseover', this.OnEvent); // this does NOT work :-/
}
function OnEvent()
{
alert(this); // this will be the HTML div-element
alert(this.m_s); // will be undefined :-()
}
}
</script>
Yes I know some quirks to make it work but what would be the intended way when these event handlers were introduced ??? I again have the bitter feeling, that no-one truly lives OOP :-(
Here for you to play: https://jsfiddle.net/sepfsvyo/1/
The this inside the event listener callback will be the element that fired the event. If you want the this to be the instance of your class, then either:
Bind the function to the class instance:
Using Function.prototype.bind, will create a new function that its this value will always be what you specify it to be (the class instance):
r.addEventListener('mouseover', this.OnEvent.bind(this));
// ^^^^^^^^^^^
Wrap the function inside an anonymous function:
var that = this;
r.addEventListener('mouseover', function(ev) { that.OnEvent(ev); });
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
or use an arrow function (so no need for that):
r.addEventListener('mouseover', ev => this.OnEvent(ev));
// ^^^^^^^^^^^^^^^^^^^^^^
Note: As mentioned in a comment bellow, both of the above methods pass a different function to addEventListener (the one with bind create a new function, and the anounimous function is obviously !== this.OnEvent). If you are going to remove the event listener later, you'll have to store a reference to the function:
var reference;
r.addEventListener('mouseover', reference = this.OnEvent.bind(this));
// ^^^^^^^^^^^^
or:
var reference;
var that = this;
r.addEventListener('mouseover', reference = function(ev) { that.OnEvent(ev); });
// ^^^^^^^^^^^^
then you can remove the event listener like:
r.removeEventListener('mouseover', reference);
You can actually return the object as an EventListener callback, this way JS will search for an handleEvent method in the class and execute accordingly :
var myInstance = new myClass;
myInstance.addEventListener("mousedown",myInstance);
// To remove the event you can follow the same pattern
myInstance.removeEventListener("mousedown",myInstance);
You have to construct your class this way :
class myClass {
constructor(){
// Whatever this is supposed to do.
// You can also add events listener within the class this way :
this.addEventListener("mousedown",this);
}
mouseDownEvent(e)(){
// Some action related to the mouse down event (e)
console.log(e.target);
}
mouseMoveEvent(e)(){
// Some action related to the mouse move event (e)
}
mouseUpEvent(e)(){
// Some action related to the mouse up event (e)
}
handleEvent(e) {
switch(e.type) {
case "mousedown":
this.mouseDownEvent(e);
break;
case "mousemove":
this.mouseMoveEvent(e);
break;
case "mouseup":
this.mouseUpEvent(e);
break;
}
}
}
Sources :
https://medium.com/#WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38
https://www.thecssninja.com/javascript/handleevent
https://metafizzy.co/blog/this-in-event-listeners/
I find this method clearer, also while declaring events inside the class this is pretty explicit.
Hope I helped someone.
The answer from #ibrahimmahrir does the job, but I wanted to consolidate a few points.
As many JavaScript developers struggle to understand, the this keyword is a moving target. In traditional OOP languages, an object method is exclusive to the object, so this is defined as the object to which it is attached.
JavaScript functions are more promiscuous, and can be attached to multiple objects. In JavaScript, this refers to the object which is currently invoking the function, not necessarily the one to which it was originally attached.
For an Event Handler function, the invoking object is the element to which it is attached, not the original object; thus this refers to the element. The usual safe method is to store a reference to the original object in a different variable, often called that:
oClass = new CClass();
function CClass() {
var that = this; // a reference to the original object
this.m_s = "hello :-/";
this.OnEvent = OnEvent;
var r = document.getElementById("test");
r.addEventListener('click', this.OnEvent);
function OnEvent() {
alert(that); // this is now the object
alert(that.m_s); // this works
}
}
The comments above are my updated comments. I have also removed the with statement which wasn’t contributing much and which is seriously discouraged.
Oh, and I have changed the event to click to make it easier to test.
While we’re on the confusion with this, it is not necessarily the element which started things off. Suppose we now include a span:
<div id="test">click <span>over</span> here</div>
Clicking on the span will trigger the event listener, even though the you didn’t actually click on the div to which it is attached. In this case the event is bubbled from the span to the div.
Here this refers only to the div element with the event listener. If you want to reference the span, you will need event.target:
function OnEvent(event) { // include event parameter
alert(this); // the element attached
alert(event.target); // the element clicked
alert(that); // this is now the object
alert(that.m_s); // this works
}
Here is an updated fiddle: https://jsfiddle.net/osk083xv/
I have a shared property across all the instances of my constructor:
function Me() { }
Me.prototype.window = {};
I'd like to update it's content on window resize, but I'd like to do it only once for each resize event, no matter how many instances of my constructor have been created.
Logically, if I define the eventListener as below, in the initialization of my instances, it will be fired multiple times
function Me() {
window.addEventListener('resize', function() {
this.window = window.outerWidth;
}.bind(this));
}
var a = new Me();
var b = new Me();
// on resize, the callback is called twice
How can I do it?
How can I do it?
Have a flag that indicates whether to bind the event handler or not. Then, in the constructor you only need to check the flag:
if (bindHandler) {
// ... bind handler
bindHandler = false;
}
How / where you store the flag is up to you.
Thought I'd put back all the answers I gave to show how OP has not provided all pertinent information up front. Also, the answer he finally came up with and marked correct was one I offered and he shot down.
First, I offered what is probably the most simple solution:
Put the following completely outside of the constructor function. When the window is resized, the shared property is updated - - for all instances.
window.addEventListener('resize', function() {
Me.prototype.window = window.outerWidth;
};
The OP then added new information that this would not be good because if no instances of Me existed, the callback would still be registered on the prototype.
I then offered this which interestingly, he marked as the answer when someone else posted it.
Your next solution would have to be to track whether there are any instances of Me before the event listener is registered. That would mean that you'd need to keep track of whether any instances exist:
// Global variabel to track if Me instances exist
var meInstances = false
var me1 = new Me();
meInstances = true;
if(meInstances){
window.addEventListener('resize', function() {
Me.prototype.window = window.outerWidth;
};
}
But, when I posted it, the response was: "you are completely making useless all the classes, constructors and stuff like that logic. There's not isolation, you are adding a lot of code for nothing and the solution is not robust." In fact, the OP then came up with his own solution that uses an array to store the instances and then the length of the array can be checked to see if there are any. I was actually going to suggest that, but went with the Boolean flag because the OP kept saying he wanted simple.
So, I offered this:
What about:
function Me() {
// This code only runs when instance is being made
// This instance is registers a callback
window.addEventListener('resize', function() {
// Callback modifies single Prototype that all instances share
Me.prototype.window = window.outerWidth;
});
}
Me.prototype.window = {};
Now, I don't fully endorse this as a good solution, but from all the constraints the OP kept adding after each suggestion, this seemed to be a last resort. But, again was rejected.
Adding here the solution I've used in the final code.
I've added a destroy method for completeness:
function Me() {
this.init();
}
Me.prototype.window = 0;
// function used to update this.window
Me.prototype.onResizeBound = null;
Me.prototype.onResize = function() {
Me.prototype.window = window.outerWidth;
};
// if there are no instances of Me, initialize the event listener
// then, add this instance to the instances list
Me.prototype.init = function() {
if (this.instances === 0) {
Me.prototype.onResizeBound = this.onResize.bind(this);
window.addEventListener('resize', Me.prototype.onResizeBound);
}
this.instances++;
};
// remove this instance to the instances list
// if there are no more instances in the list, remove the event listener
Me.prototype.destroy = function() {
this.instances--;
if (this.instances === 0) {
window.removeEventListener('resize', Me.prototype.onResizeBound);
}
};
Me.prototype.instances = 0;
Example:
var a = new Me(); // added event listener
var b = new Me(); // not added event listener since one is already set
b.destroy(); // not removed event listener since one instance is still there
a.destroy(); // removed event listener since there are not other instances
Usually I develop on Java, and now I am studying JavaScript/HTML5 Canvas things. And I get a strange situation from Java developer's point of view.
There's a html5 canvas object on the html page, and I want to track the mouse click events on this canvas.
I declared class GameBoard and initialized its properties:
function GameBoard() {
// defining a property for GameBoard class instance
this.myProperty = 'some text here';
// getting canvas element
this.boardCanvas = document.getElementById("myCanvas");
// setting the mouse click event listener
this.boardCanvas.addEventListener("mousedown", this.handleMouseClick, false);
}
and there's a class method to handle mouse click events:
GameBoard.prototype.handleMouseClick = function(event) {
alert(this.myProperty);
}
handleMouseClick() will display undefined because this in handleMouseClick() method refers to the HTML5 Canvas instance (boardCanvas).
My question: how can I refer the current GameBoard class instance inside of handleMouseClick method to get myProperty field value defined in the class constructor?
What I am doing wrong here?
Thank you.
One of the common conventions is to use an alias to this, usually with a variable named self:
function GameBoard() {
// defining alias
var self = this;
this.myProperty = 'some text here';
this.boardCanvas = document.getElementById("myCanvas");
this.handleMouseClick = function()
{
// using alias
alert(self.myProperty);
};
this.boardCanvas.addEventListener("mousedown", this.handleMouseClick, false);
}
However, since you're defining the method on the prototype here, you can either use bind (as proposed by #Alexander) or try this:
var self = this;
this.boardCanvas.addEventListener("mousedown", function(e)
{
// calling the function with 'self/this' context
self.handleMouseClick(e);
}, false);
(Thanks to #Alexander for his contributions)
You can use bind in order to set this for function
this.boardCanvas.addEventListener("mousedown", this.handleMouseClick.bind(this), false);
Example: http://jsbin.com/vutugi/1/
Is there anyway to select a particular instantiated object of a HTML5 Canvas ("this" pointer) after a mouse click from inside that particular canvas? Keep in mind that "Canvas" method I have shown is a non-standard function that I made up.
function Canvas(element) {
this.canvas = document.createElement("canvas");
this.context = this.canvas.getContext('2d');
this.canvas.id = "display";
var style = {"width" : "500",
"height" : "500"};
for(var index in style) {
this.canvas[index] = style[index];
}
element == undefined ? document.body.appendChild(this.canvas) : element.appendChild(this.canvas);
this.canvas.addEventListener("click", this.click, false);
}
then in a prototyped function I have...
Canvas.prototype.click = function(e) {
this.mouse = [e.clientX, e.clientY];
return this.of(this.mouse);
}
Error:
this.of(this.mouse) doesn't exist however I have a prototype for this function (later on) in my code.
The problem is the way you set the onclick handler:
this.canvas.addEventListener("click", this.click, false);
^^^^^^^^^^
When you add an event listener, you pass the reference, in memory, of the handler. The context of that function (this pointer) is not bounded. So the this pointer in your case will be the canvas element. That's why you've got the following error:
Object #< HTMLCanvasElement > has no method 'of'
To solve the problem you can use the bind function, introduced in Javascript 1.8.5, to bind the context. (Function.prototype.bind reference)
this.canvas.addEventListener("click", this.click.bind(this), false);
See DEMO.
Just a suggestion, but try moving the definition for of() before that of click(). The order of functions is not always significant in JS, but I think it may be for prototypes. I may be wrong (I haven't checked) but it will only take a moment to test.
Using the method .attachEvent() in IE, how can I reference the caller object (the element that triggered the event) with this? In normal browsers, using .addEventListener, the var this points to the element, while in IE it points to the window object.
I need it to work with the following code:
var element = //the element, doesn't matter how it is obtained
element.addAnEvent = function(name, funct){
if(element.addEventListener) // Works in NORMAL browsers...
else if(element.attachEvent){
element.attachEvent("on"+name, funct);
//where the value of "this" in funct should point to "element"
}
}
I just made that code up, it's not exactly the same as my code, but if it works with it then it works with me!
From this quirksmode article with regard to attachEvent:
Events always bubble, no capturing possibility.
The event handling function is referenced, not copied, so the this keyword always refers to the window and is completely useless.
The result of these two weaknesses is that when an event bubbles up it is impossible to know which HTML element currently handles the event. I explain this problem more fully on the Event order page.
Since the Microsoft event adding model is only supported by Explorer 5 and higher on Windows, it cannot be used for cross–browser scripts. But even for Explorer–on–Windows only applications it’s best not to use it, since the bubbling problem can be quite nasty in complex applications.
I haven't tested it, but a possible workaround may be to wrap the handler in an anonymous function that calls your handler via funct.call().
else if(element.attachEvent){
element.attachEvent("on"+name, function(){ funct.call( element ) });
}
My apologies for the untested solution. I don't like doing that, but can't easily get to IE right now.
If you're in IE, you can get the "caller" object, as you call it, by accessing window.event.srcElement within the event handler function. This object is normally referred to as the event target or source.
var funct = function(event) {
if ( !event ) {
event = window.event;
}
var callerElement = event.target || event.srcElement;
// Do stuff
};
This code is untested, but should set you off in the right direction.
Bind it. Well, you can't use Function.prototype.bind that gets added in javascript 1.8.5, but you can use closure and Function.prototype.apply.
var element = //the element, doesn't matter how it is obtained
element.addAnEvent = function(name, funct){
if(element.addEventListener) // Works in NORMAL browsers...
//...
else if(element.attachEvent){
element.attachEvent("on"+name, function() {
//call funct with 'this' == 'element'
return funct.apply(element, arguments);
});
}
}
I think it would be better to extend the Element object , through the prototype chain, instead of adding your method to each element you want to add events to (works with all browsers)..
Element.prototype.addAnEvent = function(name, funct){
if(this.addEventListener) // Works in NORMAL browsers...
{
this.addEventListener(name,funct, false);
}
else if(this.attachEvent){
var _this = this;
this.attachEvent("on"+name, function(){funct.apply(_this);});
//where the value of "this" in funct should point to "element"
}
};
This way it will work for all your current and future elements (and you only have to run it once).