Note: This question uses jQuery but the question has nothing to do with jQuery!
Okay so I have this object:
var box = new BigBox();
This object has a method named Serialize():
box.AddToPage();
Here is the method AddToPage():
function AddToPage()
{
$('#some_item').html("<div id='box' onclick='this.OnClick()'></div>");
}
The problem above is the this.OnClick() (which obviously does not work). I need the onclick handler to invoke a member of the BigBox class. How can I do this?
How can an object refer to itself in an event handler?
You should attach the handler using jQuery:
function AddToPage()
{
var self = this;
$('#some_item').empty().append(
$("<div id='box'></div>")
.click(function() { self.OnClick(someParameter); })
);
}
In order to force the event handler to be called on the context of your object (and to pass parameters), you need to add an anonymous function that calls the handler correctly. Otherwise, the this keyword in the handler will refer to the DOM element.
Don't add event handlers with inline code.
function AddToPage()
{
$('#some_item').html("<div id='box'></div>");
$('#box').click(this.OnClick);
}
EDIT:
Another way (avoids the extra select):
function AddToPage()
{
var div = $('<div id="box"></div>'); // probably don't need ID anymore..
div.click(this.OnClick);
$('#some_item').append(div);
}
EDIT (in response to "how to pass parameters");
I'm not sure what params you want to pass, but..
function AddToPage()
{
var self = this, div = $('<div></div>');
div.click(function (eventObj) {
self.OnClick(eventObj, your, params, here);
});
$('#some_item').append(div);
}
In jQuery 1.4 you could use a proxy.
BigBox.prototype.AddToPage= function () {
var div= $('<div>', {id: box});
div.click(jQuery.proxy(this, 'OnClick');
div.appendTo('#some_item');
}
You can also use a manual closure:
var that= this;
div.click(function(event) { that.OnClick(event); });
Or, most simply of all, but requiring some help to implement in browsers that don't yet support it (it's an ECMAScript Fifth Edition feature):
div.click(this.OnClick.bind(this));
If you are using jQuery, then you can separate your code from your markup (the old seperation of concerns thing) like this
$(document).ready(function() {
var box = new BigBox();
$('#box').click(function() {
box.serialize();
});
});
You only need to add the click handler once for all divs with id of box. And because the click is an anonymous function, it gets the scope of the function it is placed in and therefore access to the box instance.
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've used the jQuery Boilerplate template as starting point for a jQuery plug-in. This template provides a set up where this represents the plug-in instance and gives access to properties and methods:
init: function() {
$(this.element).css({borderColor: "red"});
this.drawMarker([100, 200]);
},
drawMarker: function(coordinates) {
if (this.settings.isAbsolute) {
// ...
}
}
Now I need to handle some mouse clicks and it's all getting really confusing because callback functions redefine the this variable to represent the clicked event so, in order to access the plugin stuff, I came up with this ugly workaround:
this.container.on("click", "." + this.settings.markerClass,
{plugin: this}, this.removeMarker);
... and:
removeMarker: function(event){
var plugin = event.data.plugin;
var marker = $(this);
if (plugin.settings.isAbsolute) {
// ...
}
}
Is this actually what I'm supposed to do or I'm overlooking a most straightforward approach?
One possibility is to use the jQuery.proxy() function (added on 1.4) to force a given context inside event handlers:
this.$container.on("click", "." + this.settings.markerClass,
$.proxy(this.removeMarker, this));
Then, the stuff you need can be reached as follows:
Plugin properties/methods: this
Clicked element: event.target (on delegated events, it's the precise element the user clicked on; the one we normally want)
removeMarker: function(event){
var $marker = $(event.target);
if (this.settings.isAbsolute) {
// ...
}
}
This technique is courtesy of Patrick Evans.
If you need to access privately scoped variables (using functions that are therefore by definition not on the plugins prototype) just create an additional variable that aliases the plugin object:
var plugin = this;
this.container.on('click', function() {
// use plugin here
...
});
If the callback function in question is on the prototype, you can access the object within the callback thus:
var plugin = $(element).data('plugin_' + pluginName);
When I assign the event handler without parameters, it works: http://jsfiddle.net/mUj43/
function show(){
alert('work');
}
var myButton = document.createElement("input");
myButton.type="button";
myButton.value="click";
myButton.onclick=show;
var where = document.getElementById("where");
where.appendChild(myButton);
but if I pass parameters, it doesn't work: http://jsfiddle.net/mUj43/1/
myButton.onclick = show('test');
How can I use function with parameters in dynamically created elements?
You can't do that, you could use partial application by creating a new function and then attach that as event handler:
myButton.onclick=show.bind( myButton, 'test');
http://jsfiddle.net/mUj43/2/
Docs (which I recommend you read because this function is useful for many other things as well) and compatibility information: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
You'll have to create your own closure:
myButton.onclick = function () {
show.call(this, 'test');
};
You could also use #Esailija's bind method, but this one has deeper browser support.
try:
myButton.onclick = function(){show("test");}
or :
myButton.onclick = function(){ show.call( this, "test");}
if you want to retain the element object context inside the show function
That's because when you add events you need a function reference.
In your first example, show is a reference to a function.
In your second example, show('test') is a call to the function show, which returns nothing, and nothing isn't a function reference.
That's why when you load the page, it alerts "work" (the function is called), but when you click the button no function is called.
Then, you need a function.
You can declare it:
myButton.onclick=f;
function f(){
show('test')
}
Or you can use an anonymous one:
myButton.onclick=function(){
show('test')
}
I'm working with jQuery and trying to apply some basic Javascript OOP principles to a set of functions that control hover behavior. However, I can't figure out how to get the "this" keyword to refer to the instance of the object I'm creating. My sample code is:
var zoomin = new Object();
zoomin = function() {
// Constructor goes here
};
zoomin.prototype = {
hoverOn: function() {
this.hoverReset();
// More logic here using jQuery's $(this)...
},
hoverReset: function() {
// Some logic here.
}
};
// Create new instance of zoomin and apply event handler to matching classes.
var my_zoomin = new zoomin();
$(".some_class").hover(my_zoomin.hoverOn, function() { return null; });
The problematic line in the above code is the call to this.hoverReset() inside the hoverOn() function. Since this now refers to element that was hovered on, it does not work as intended. I would basically like to call the function hoverReset() for that instance of the object (my_zoomin).
Is there any way to do this?
Only assigning a function to a property of an object does not associated this inside the function with the object. It is the way how you call the function.
By calling
.hover(my_zoomin.hoverOn,...)
you are only passing the function. It will not "remember" to which object it belonged. What you can do is to pass an anonymous function and call hoverOn inside:
.hover(function(){ my_zoomin.hoverOn(); },...)
This will make the this inside hoverOn refer to my_zoomin. So the call to this.hoverReset() will work. However, inside hoverOn, you will not have a reference to the jQuery object created by the selector.
One solution would be to pass the selected elements as parameter:
var zoomin = function() {
// Constructor goes here
};
zoomin.prototype = {
hoverOn: function($ele) {
this.hoverReset($ele);
// More logic here using jQuery's $ele...
},
hoverReset: function($ele) {
// Some logic here.
}
};
var my_zoomin = new zoomin();
$(".some_class").hover(function() {
my_zoomin.hoverOn($(this)); // pass $(this) to the method
}, function() {
return null;
});
As a next step, you could consider making a jQuery plugin.
You can "bind" the event handler to the object (see Mootools bind code for example).
You can pass the object as a parameter in the anonymous function and use that instead of this in the event handler
As for 1, you add the bind method to function
bind: function(bind){
var self = this,
args = (arguments.length > 1) ? Array.slice(arguments, 1) : null;
return function(){
if (!args && !arguments.length) return self.call(bind);
if (args && arguments.length) return self.apply(bind, args.concat(Array.from(arguments)));
return self.apply(bind, args || arguments);
};
}
Not sure though how well it will interact with JQ stuff.
please see my answers to these questions:
where is my "this"?
why is "this" not this?
this confusion comes up all the time.
when you pass a function in as a callback, it's invoked as a standalone function, so its "this" becomes the global object.
"bind" is a native part of ecmascript 5, and is part of the function prototype. If you go to the end of my second answer up there, you get a link to the mozilla website, which has a "compatibility" version of the bind function. Use use myfunction.bind(myobject), and it'll use the native function if it's available, or the JS function if it is not.
The following doesn't work... (at least not in Firefox: document.getElementById('linkid').click() is not a function)
<script type="text/javascript">
function doOnClick() {
document.getElementById('linkid').click();
//Should alert('/testlocation');
}
</script>
<a id="linkid" href="/testlocation" onclick="alert(this.href);">Testlink</a>
You need to apply the event handler in the context of that element:
var elem = document.getElementById("linkid");
if (typeof elem.onclick == "function") {
elem.onclick.apply(elem);
}
Otherwise this would reference the context the above code is executed in.
The best way to solve this is to use Vanilla JS, but if you are already using jQuery, there´s a very easy solution:
<script type="text/javascript">
function doOnClick() {
$('#linkid').click();
}
</script>
<a id="linkid" href="/testlocation" onclick="alert(this.href);">Testlink</a>
Tested in IE8-10, Chrome, Firefox.
To trigger an event you basically just call the event handler for that
element. Slight change from your code.
var a = document.getElementById("element");
var evnt = a["onclick"];
if (typeof(evnt) == "function") {
evnt.call(a);
}
Granted, OP stated very similarly that this didn't work, but it did for me. Based on the notes in my source, it seems it was implemented around the time, or after, OP's post. Perhaps it's more standard now.
document.getElementsByName('MyElementsName')[0].click();
In my case, my button didn't have an ID. If your element has an id, preferably use the following (untested).
document.getElementById('MyElementsId').click();
I originally tried this method and it didn't work. After Googling I came back and realized my element was by name, and didn't have an ID. Double check you're calling the right attribute.
Source: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click
$("#linkid").trigger("click");
Old thread, but the question is still relevant, so...
(1) The example in your question now DOES work in Firefox. However in addition to calling the event handler (which displays an alert), it ALSO clicks on the link, causing navigation (once the alert is dismissed).
(2) To JUST call the event handler (without triggering navigation) merely replace:
document.getElementById('linkid').click();
with
document.getElementById('linkid').onclick();
Have a look at the handleEvent method
https://developer.mozilla.org/en-US/docs/Web/API/EventListener
"Raw" Javascript:
function MyObj() {
this.abc = "ABC";
}
MyObj.prototype.handleEvent = function(e) {
console.log("caught event: "+e.type);
console.log(this.abc);
}
var myObj = new MyObj();
document.querySelector("#myElement").addEventListener('click', myObj);
Now click on your element (with id "myElement") and it should print the following in the console:
caught event: click
ABC
This allows you to have an object method as event handler, and have access to all the object properties in that method.
You can't just pass a method of an object to addEventListener directly (like that: element.addEventListener('click',myObj.myMethod);) and expect myMethod to act as if I was normally called on the object. I am guessing that any function passed to addEventListener is somehow copied instead of being referenced. For example, if you pass an event listener function reference to addEventListener (in the form of a variable) then unset this reference, the event listener is still executed when events are caught.
Another (less elegant) workaround to pass a method as event listener and stil this and still have access to object properties within the event listener would be something like that:
// see above for definition of MyObj
var myObj = new MyObj();
document.querySelector("#myElement").addEventListener('click', myObj.handleEvent.bind(myObj));
If you're using this purely to reference the function in the onclick attribute, this seems like a very bad idea. Inline events are a bad idea in general.
I would suggest the following:
function addEvent(elm, evType, fn, useCapture) {
if (elm.addEventListener) {
elm.addEventListener(evType, fn, useCapture);
return true;
}
else if (elm.attachEvent) {
var r = elm.attachEvent('on' + evType, fn);
return r;
}
else {
elm['on' + evType] = fn;
}
}
handler = function(){
showHref(el);
}
showHref = function(el) {
alert(el.href);
}
var el = document.getElementById('linkid');
addEvent(el, 'click', handler);
If you want to call the same function from other javascript code, simulating a click to call the function is not the best way. Consider:
function doOnClick() {
showHref(document.getElementById('linkid'));
}
In general I would recommend against calling the event handlers 'manually'.
It's unclear what gets executed because of multiple registered
listeners
Danger to get into a recursive and infinite event-loop (click A
triggering Click B, triggering click A, etc.)
Redundant updates to the DOM
Hard to distinguish actual changes in the view caused by the user from changes made as initialisation code (which should be run only once).
Better is to figure out what exactly you want to have happen, put that in a function and call that manually AND register it as event listener.