How do I know which element was clicked? - javascript

I'm attempting the surprisingly difficult task of finding out which element was clicked. I have these functions from Head First AJAX:
function getActivatedObject(e) {
var obj;
if (!e) {
obj = window.event.srcElement;
} else if (e.srcElement) {
obj = e.srcElement;
} else {
obj = e.target;
}
return obj;
}
function addEventHandler(obj, eventName, handler) {
if (document.attachEvent) {
obj.attachEvent("on" + eventName, handler);
} else if (document.addEventListener) {
obj.addEventListener(eventName, handler, false);
}
}
And my code:
mainPane = document.getElementById("mainDiv");
contactPane = document.getElementById("contactDiv");
addEventHandler(mainPane, "click", openFunction);
addEventHandler(contactPane, "click", openFunction);
function openFunction(e) {
var me = getActivatedObject(e);
//Some other stuff
}
Unfortunately, the me variable sometimes refers to the div, but it sometimes refers to the image inside the div. Even though the image has no onclick function or any other kind of event! So how can I get the div that triggered the event?

You're dealing with Event bubbling.
Basically, your click on a child element bubbles up through all of that child's parents; if those parents have a click handler bound to them, it will execute.
In pseudocode: you can see what element your current target is, and if it's not a DIV, you know that you need to look for that element's parent.
Using your example, it would look something like this (simplified a bit for your example; in reality, you'd need something much more robust):
function getActivatedObject(e) {
var obj;
if (!e) {
obj = window.event.srcElement;
} else if (e.srcElement) {
obj = e.srcElement;
} else {
obj = e.target;
}
// Images are direct children of our DIV. If our source element was an img, we should
// fetch its parent
if(obj.tagName.toLowerCase() === "img"){
obj = obj.parentNode;
}
return obj;
}
Note that this will work for your example only; in the real world, you would likely need to iterate back up the DOM tree, comparing parentNodes with the elements you are interested in until you've found what you're looking for.
Most JavaScript libraries abstract this away for you (jQuery, for example, sets a currentTarget property of all Events it dispatches that refers to the element you need, encapsulating all the traversal work for you). Makes it easier, but it's not too terrible a job to write that yourself and I'd argue against including the overhead of an entire JavaScript library if that's all you need to do. :-)
EDIT: Totally destroyed my formatting! D'oh!

Related

How do I remove event listener whilst maintaining/passing 'e' variable [duplicate]

I have an object that has methods in it. These methods are put into the object inside an anonymous function. It looks like this:
var t = {};
window.document.addEventListener("keydown", function(e) {
t.scroll = function(x, y) {
window.scrollBy(x, y);
};
t.scrollTo = function(x, y) {
window.scrollTo(x, y);
};
});
(there is a lot more code, but this is enough to show the problem)
Now I want to stop the event listener in some cases. Therefore I am trying to do a removeEventListener but I can't figure out how to do this. I have read in other questions that it is not possible to call removeEventListener on anonymous functions, but is this also the case in this situation?
I have a method in t created inside the anonymous function and therefore I thought it was possible. Looks like this:
t.disable = function() {
window.document.removeEventListener("keydown", this, false);
}
Why can't I do this?
Is there any other (good) way to do this?
Bonus info; this only has to work in Safari, hence the missing IE support.
You can name the function passed and use the name in the removeEventListener. as in:
button.addEventListener('click', function eventHandler() {
///this will execute only once
alert('only once!');
this.removeEventListener('click', eventHandler);
});
EDIT:
This will not work if you are working in strict mode ("use strict";)
EDIT 2:
arguments.callee is now deprecated (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments/callee)
I believe that is the point of an anonymous function, it lacks a name or a way to reference it.
If I were you I would just create a named function, or put it in a variable so you have a reference to it.
var t = {};
var handler = function(e) {
t.scroll = function(x, y) {
window.scrollBy(x, y);
};
t.scrollTo = function(x, y) {
window.scrollTo(x, y);
};
};
window.document.addEventListener("keydown", handler);
You can then remove it by
window.document.removeEventListener("keydown", handler);
A version of Otto Nascarella's solution that works in strict mode is:
button.addEventListener('click', function handler() {
///this will execute only once
alert('only once!');
this.removeEventListener('click', handler);
});
in modern browsers you can do the following...
button.addEventListener( 'click', () => {
alert( 'only once!' );
}, { once: true } );
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters
window.document.removeEventListener("keydown", getEventListeners(window.document.keydown[0].listener));
May be several anonymous functions, keydown1
Warning: only works in Chrome Dev Tools & cannot be used in code: link
There's a new way to do this that is supported by the latest versions of most popular browsers with the exception of Safari.
Check caniuse for updated support.
Update: Now also supported by Sefari (version 15^).
We can add an option to addEventListner called signal and assign a signal from an AbortController on which you can later call the abort() method.
Here is an example.
We create an AbortController:
const controller = new AbortController();
Then we create the eventListner and pass in the option signal:
document.addEventListener('scroll',()=>{
// do something
},{signal: controller.signal})
And then to remove the eventListner at a later time, we call:
controller.abort()
This is not ideal as it removes all, but might work for your needs:
z = document.querySelector('video');
z.parentNode.replaceChild(z.cloneNode(1), z);
Cloning a node copies all of its attributes and their values, including
intrinsic (in–line) listeners. It does not copy event listeners added using
addEventListener()
Node.cloneNode()
A not so anonymous option
element.funky = function() {
console.log("Click!");
};
element.funky.type = "click";
element.funky.capt = false;
element.addEventListener(element.funky.type, element.funky, element.funky.capt);
// blah blah blah
element.removeEventListener(element.funky.type, element.funky, element.funky.capt);
Since receiving feedback from Andy (quite right, but as with many examples, I wished to show a contextual expansion of the idea), here's a less complicated exposition:
<script id="konami" type="text/javascript" async>
var konami = {
ptrn: "38,38,40,40,37,39,37,39,66,65",
kl: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
};
document.body.addEventListener( "keyup", function knm ( evt ) {
konami.kl = konami.kl.slice( -9 );
konami.kl.push( evt.keyCode );
if ( konami.ptrn === konami.kl.join() ) {
evt.target.removeEventListener( "keyup", knm, false );
/* Although at this point we wish to remove a listener
we could easily have had multiple "keyup" listeners
each triggering different functions, so we MUST
say which function we no longer wish to trigger
rather than which listener we wish to remove.
Normal scoping will apply to where we can mention this function
and thus, where we can remove the listener set to trigger it. */
document.body.classList.add( "konami" );
}
}, false );
document.body.removeChild( document.getElementById( "konami" ) );
</script>
This allows an effectively anonymous function structure, avoids the use of the practically deprecated callee, and allows easy removal.
Incidentally: The removal of the script element immediately after setting the listener is a cute trick for hiding code one would prefer wasn't starkly obvious to prying eyes (would spoil the surprise ;-)
So the method (more simply) is:
element.addEventListener( action, function name () {
doSomething();
element.removeEventListener( action, name, capture );
}, capture );
To give a more up-to-date approach to this:
//one-time fire
element.addEventListener('mousedown', {
handleEvent: function (evt) {
element.removeEventListener(evt.type, this, false);
}
}, false);
JavaScript: addEventListener
method registers the specified listener on the EventTarget(Element|document|Window) it's called on.
EventTarget.addEventListener(event_type, handler_function, Bubbling|Capturing);
Mouse, Keyboard events Example test in WebConsole:
var keyboard = function(e) {
console.log('Key_Down Code : ' + e.keyCode);
};
var mouseSimple = function(e) {
var element = e.srcElement || e.target;
var tagName = element.tagName || element.relatedTarget;
console.log('Mouse Over TagName : ' + tagName);
};
var mouseComplex = function(e) {
console.log('Mouse Click Code : ' + e.button);
}
window.document.addEventListener('keydown', keyboard, false);
window.document.addEventListener('mouseover', mouseSimple, false);
window.document.addEventListener('click', mouseComplex, false);
removeEventListener
method removes the event listener previously registered with EventTarget.addEventListener().
window.document.removeEventListener('keydown', keyboard, false);
window.document.removeEventListener('mouseover', mouseSimple, false);
window.document.removeEventListener('click', mouseComplex, false);
caniuse
I have stumbled across the same problem and this was the best solution I could get:
/*Adding the event listener (the 'mousemove' event, in this specific case)*/
element.onmousemove = function(event) {
/*do your stuff*/
};
/*Removing the event listener*/
element.onmousemove = null;
Please keep in mind I have only tested this for the window element and for the 'mousemove' event, so there could be some problems with this approach.
Possibly not the best solution in terms of what you are asking. I have still not determined an efficient method for removing anonymous function declared inline with the event listener invocation.
I personally use a variable to store the <target> and declare the function outside of the event listener invocation eg:
const target = document.querySelector('<identifier>');
function myFunc(event) {
function code;
}
target.addEventListener('click', myFunc);
Then to remove the listener:
target.removeEventListener('click', myFunc);
Not the top recommendation you will receive but to remove anonymous functions the only solution I have found useful is to remove then replace the HTML element. I am sure there must be a better vanilla JS method but I haven't seen it yet.
I know this is a fairly old thread, but thought I might put in my two cents for those who find it useful.
The script (apologies about the uncreative method names):
window.Listener = {
_Active: [],
remove: function(attached, on, callback, capture){
for(var i = 0; i < this._Active.length; i++){
var current = this._Active[i];
if(current[0] === attached && current[1] === on && current[2] === callback){
attached.removeEventListener(on, callback, (capture || false));
return this._Active.splice(i, 1);
}
}
}, removeAtIndex(i){
if(this._Active[i]){
var remove = this._Active[i];
var attached = remove[0], on = remove[1], callback = remove[2];
attached.removeEventListener(on, callback, false);
return this._Active.splice(i, 1);
}
}, purge: function(){
for(var i = 0; i < this._Active.length; i++){
var current = this._Active[i];
current[0].removeEventListener(current[1], current[2]);
this._Active.splice(i, 1);
}
}, declare: function(attached, on, callback, capture){
attached.addEventListener(on, callback, (capture || false));
if(this._Active.push([attached, on, callback])){
return this._Active.length - 1;
}
}
};
And you can use it like so:
// declare a new onclick listener attached to the document
var clickListener = Listener.declare(document, "click" function(e){
// on click, remove the listener and log the clicked element
console.log(e.target);
Listener.removeAtIndex(clickListener);
});
// completely remove all active listeners
// (at least, ones declared via the Listener object)
Listener.purge();
// works exactly like removeEventListener
Listener.remove(element, on, callback);
I just experienced similiar problem with copy-protection wordpress plugin. The code was:
function disableSelection(target){
if (typeof target.onselectstart!="undefined") //For IE
target.onselectstart=function(){return false}
else if (typeof target.style.MozUserSelect!="undefined") //For Firefox
target.style.MozUserSelect="none"
else //All other route (For Opera)
target.onmousedown=function(){return false}
target.style.cursor = "default"
}
And then it was initiated by loosely put
<script type="text/javascript">disableSelection(document.body)</script>.
I came around this simply by attaching other annonymous function to this event:
document.body.onselectstart = function() { return true; };
Set anonymous listener:
document.getElementById('ID').addEventListener('click', () => { alert('Hi'); });
Remove anonymous listener:
document.getElementById('ID').removeEventListener('click',getEventListeners(document.getElementById('ID')).click[0].listener)
Using the AbortController, neat and clean
Attaching EventListener
const el = document.getElementById('ID')
const controller = new AbortController;
el.addEventListener('click',() => {
console.log("Clicked")
},{signal: controller.signal})
when you want to remove the event listener
controller.abort()
Another alternative workaround to achieve this is adding an empty event handler and preventing event propagation.
Let's assume you need to remove mouseleave event handler from an element which has #specific-div id, that is added with an anonymous function, and you can't use removeEventListener() since you don't have a function name.
You can add another event handler to that element and use event.stopImmediatePropagation(), for being sure this event handler works before existing ones you should pass the third parameter (useCapture) as true.
The final code should look like the below:
document.getElementById("specific-div")
.addEventListener("mouseleave", function(event) {
event.stopImmediatePropagation()
}, true);
This could help for some specific cases that you can't prefer cloneNode() method.
window.document.onkeydown = function(){};

Am I missing something basic, or is there a bug with Chrome? [duplicate]

I've created a Javascript object via prototyping. I'm trying to render a table dynamically. While the rendering part is simple and works fine, I also need to handle certain client side events for the dynamically rendered table. That, also is easy. Where I'm having issues is with the "this" reference inside of the function that handles the event. Instead of "this" references the object, it's referencing the element that raised the event.
See code. The problematic area is in ticketTable.prototype.handleCellClick = function():
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
}
ticketTable.prototype.render = function(element)
{
var tbl = document.createElement("table");
for ( var i = 0; i < this.tickets.length; i++ )
{
// create row and cells
var row = document.createElement("tr");
var cell1 = document.createElement("td");
var cell2 = document.createElement("td");
// add text to the cells
cell1.appendChild(document.createTextNode(i));
cell2.appendChild(document.createTextNode(this.tickets[i]));
// handle clicks to the first cell.
// FYI, this only works in FF, need a little more code for IE
cell1.addEventListener("click", this.handleCellClick, false);
// add cells to row
row.appendChild(cell1);
row.appendChild(cell2);
// add row to table
tbl.appendChild(row);
}
// Add table to the page
element.appendChild(tbl);
}
ticketTable.prototype.handleCellClick = function()
{
// PROBLEM!!! in the context of this function,
// when used to handle an event,
// "this" is the element that triggered the event.
// this works fine
alert(this.innerHTML);
// this does not. I can't seem to figure out the syntax to access the array in the object.
alert(this.tickets.length);
}
You can use bind which lets you specify the value that should be used as this for all calls to a given function.
var Something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as this is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as this is the binded Something object
};
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}
A problem in the example above is that you cannot remove the listener with bind. Another solution is using a special function called handleEvent to catch any events:
var Something = function(element) {
this.name = 'Something Good';
this.handleEvent = function(event) {
console.log(this.name); // 'Something Good', as this is the Something object
switch(event.type) {
case 'click':
// some code here...
break;
case 'dblclick':
// some code here...
break;
}
};
// Note that the listeners in this case are this, not this.handleEvent
element.addEventListener('click', this, false);
element.addEventListener('dblclick', this, false);
// You can properly remove the listners
element.removeEventListener('click', this, false);
element.removeEventListener('dblclick', this, false);
}
Like always mdn is the best :). I just copy pasted the part than answer this question.
You need to "bind" handler to your instance.
var _this = this;
function onClickBound(e) {
_this.handleCellClick.call(cell1, e || window.event);
}
if (cell1.addEventListener) {
cell1.addEventListener("click", onClickBound, false);
}
else if (cell1.attachEvent) {
cell1.attachEvent("onclick", onClickBound);
}
Note that event handler here normalizes event object (passed as a first argument) and invokes handleCellClick in a proper context (i.e. referring to an element that was attached event listener to).
Also note that context normalization here (i.e. setting proper this in event handler) creates a circular reference between function used as event handler (onClickBound) and an element object (cell1). In some versions of IE (6 and 7) this can, and probably will, result in a memory leak. This leak in essence is browser failing to release memory on page refresh due to circular reference existing between native and host object.
To circumvent it, you would need to either a) drop this normalization; b) employ alternative (and more complex) normalization strategy; c) "clean up" existing event listeners on page unload, i.e. by using removeEventListener, detachEvent and elements nulling (which unfortunately would render browsers' fast history navigation useless).
You could also find a JS library that takes care of this. Most of them (e.g.: jQuery, Prototype.js, YUI, etc.) usually handle cleanups as described in (c).
Also, one more way is to use the EventListener Interface (from DOM2 !! Wondering why no one mentioned it, considering it is the neatest way and meant for just such a situation.)
I.e, instead of a passing a callback function, You pass an object which implements EventListener Interface. Simply put, it just means you should have a property in the object called "handleEvent" , which points to the event handler function. The main difference here is, inside the function, this will refer to the object passed to the addEventListener. That is, this.theTicketTable will be the object instance in the belowCode. To understand what I mean, look at the modified code carefully:
ticketTable.prototype.render = function(element) {
...
var self = this;
/*
* Notice that Instead of a function, we pass an object.
* It has "handleEvent" property/key. You can add other
* objects inside the object. The whole object will become
* "this" when the function gets called.
*/
cell1.addEventListener('click', {
handleEvent:this.handleCellClick,
theTicketTable:this
}, false);
...
};
// note the "event" parameter added.
ticketTable.prototype.handleCellClick = function(event)
{
/*
* "this" does not always refer to the event target element.
* It is a bad practice to use 'this' to refer to event targets
* inside event handlers. Always use event.target or some property
* from 'event' object passed as parameter by the DOM engine.
*/
alert(event.target.innerHTML);
// "this" now points to the object we passed to addEventListener. So:
alert(this.theTicketTable.tickets.length);
}
This arrow syntax works for me:
document.addEventListener('click', (event) => {
// do stuff with event
// do stuff with this
});
this will be the parent context and not the document context.
With ES6, you can use an arrow function as that will use lexical scoping[0] which allows you to avoid having to use bind or self = this:
var something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // 'Something Good'
};
element.addEventListener('click', () => this.onclick1());
}
[0] https://medium.freecodecamp.org/learn-es6-the-dope-way-part-ii-arrow-functions-and-the-this-keyword-381ac7a32881
According to https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener ,
my_element.addEventListener('click', (e) => {
console.log(this.className) // WARNING: `this` is not `my_element`
console.log(e.currentTarget === this) // logs `false`
})
so if you use the arrow functions you can go safe beacause they do not have their own this context.
I know this is an older post, but you can also simply assign the context to a variable self, throw your function in an anonymous function that invokes your function with .call(self) and passes in the context.
ticketTable.prototype.render = function(element) {
...
var self = this;
cell1.addEventListener('click', function(evt) { self.handleCellClick.call(self, evt) }, false);
...
};
This works better than the "accepted answer" because the context doesn't need to be assigned a variable for the entire class or global, rather it's neatly tucked away within the same method that listens for the event.
What about
...
cell1.addEventListener("click", this.handleCellClick.bind(this));
...
ticketTable.prototype.handleCellClick = function(e)
{
alert(e.currentTarget.innerHTML);
alert(this.tickets.length);
}
e.currentTarget points to the target which is bound to the "click event" (to the element that raised the event) while
bind(this) preserves the outerscope value of this inside the click event function.
If you want to get an exact target clicked, use e.target instead.
Heavily influenced by kamathln and gagarine's answer I thought I might tackle this.
I was thinking you could probably gain a bit more freedom if you put handeCellClick in a callback list and use an object using the EventListener interface on the event to trigger the callback list methods with the correct this.
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
// the callback array of methods to be run when
// event is triggered
this._callbacks = {handleCellClick:[this._handleCellClick]};
// assigned eventListenerInterface to one of this
// objects properties
this.handleCellClick = new eventListenerInterface(this,'handleCellClick');
}
//set when eventListenerInterface is instantiated
function eventListenerInterface(parent, callback_type)
{
this.parent = parent;
this.callback_type = callback_type;
}
//run when event is triggered
eventListenerInterface.prototype.handleEvent(evt)
{
for ( var i = 0; i < this.parent._callbacks[this.callback_type].length; i++ ) {
//run the callback method here, with this.parent as
//this and evt as the first argument to the method
this.parent._callbacks[this.callback_type][i].call(this.parent, evt);
}
}
ticketTable.prototype.render = function(element)
{
/* your code*/
{
/* your code*/
//the way the event is attached looks the same
cell1.addEventListener("click", this.handleCellClick, false);
/* your code*/
}
/* your code*/
}
//handleCellClick renamed to _handleCellClick
//and added evt attribute
ticketTable.prototype._handleCellClick = function(evt)
{
// this shouldn't work
alert(this.innerHTML);
// this however might work
alert(evt.target.innerHTML);
// this should work
alert(this.tickets.length);
}
The MDN explanation gives what to me is a neater solution further down.
In this example you store the result of the bind() call, which you can then use to unregister the handler later.
const Something = function(element) {
// |this| is a newly created object
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as |this| is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as |this| is bound to newly created object
};
// bind causes a fixed `this` context to be assigned to onclick2
this.onclick2 = this.onclick2.bind(this);
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2, false); // Trick
}
const s = new Something(document.body);
In the posters example you would want to bind the handler function in the constructor:
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
this.handleCellClick = this.handleCellClick.bind(this); // Note, this means that our handleCellClick is specific to our instance, we aren't directly referencing the prototype any more.
}
ticketTable.prototype.render = function(element)
{
var tbl = document.createElement("table");
for ( var i = 0; i < this.tickets.length; i++ )
{
// create row and cells
var row = document.createElement("tr");
var cell1 = document.createElement("td");
var cell2 = document.createElement("td");
// add text to the cells
cell1.appendChild(document.createTextNode(i));
cell2.appendChild(document.createTextNode(this.tickets[i]));
// handle clicks to the first cell.
// FYI, this only works in FF, need a little more code for IE
this.handleCellClick = this.handleCellClick.bind(this); // Note, this means that our handleCellClick is specific to our instance, we aren't directly referencing the prototype any more.
cell1.addEventListener("click", this.handleCellClick, false);
// We could now unregister ourselves at some point in the future with:
cell1.removeEventListener("click", this.handleCellClick);
// add cells to row
row.appendChild(cell1);
row.appendChild(cell2);
// add row to table
tbl.appendChild(row);
}
// Add table to the page
element.appendChild(tbl);
}
ticketTable.prototype.handleCellClick = function()
{
// PROBLEM!!! in the context of this function,
// when used to handle an event,
// "this" is the element that triggered the event.
// this works fine
alert(this.innerHTML);
// this does not. I can't seem to figure out the syntax to access the array in the object.
alert(this.tickets.length);
}

How to find delegated event listeners?

Normally to get listeners on that DOM node I am using
$('selector').data('events');
However this does not show event listeners that are being add via delegation, e.g
$(document).on('click', 'selector', handlerFunction)
One obvious way is to traverse up the DOM tree and look if any of parents are delegating events to element at hand, by concurrently calling $('selector').parent().data('events') until no parent can be found, however this does not strike me as very efficient or standard way of doing things, and I think of it this sort of problem is too common not to have a better solution.
How to find all the event listeners including delegated ones?
ATM I am using functions below, not to elegant - but saves me quite some time.
var getAllEventListeners = function (options) {
if (options.internalArr == undefined)
options.internalArr = [];
if (options.elements.data('events') != undefined) {
options.internalArr.push({
elements: options.elements,
events: options.elements.data('events')
});
}
if (options.elements.parent().length != 0) {
getAllEventListeners({
elements: options.elements.parent(),
internalArr: options.internalArr
});
}
}
var findAllListeners = function (selector) {
var opt = {
elements: $(selector),
internalArr: []
};
getAllEventListeners(opt);
return opt.internalArr;
}

how does $(this) work when nothing is selected

I just got into caching my jquery objects but not sure how to properly do it when using (this).
Also the only time I know how to use (this) is when its inside a click object or function so like:
$(".trigger").click(function () {
if ($(this).hasClass('toggle')) {
$(this).closest("td").next().find(".clueless > div").slideUp();
$(this).removeClass('toggle');
} else {
$(this).closest("td").next().find(".clueless > div").slideDown();
$(this).addClass('toggle');
}
});
so if I wanted to cache the $(this).closest("td").next().find(".clueless > div") the only thing I could think would be:
var $something = $(".trigger").find(this).closest("td").next().find(".clueless > div");
I will not completely re-iterate how the this keyword works, but there's an exhaustive explanation here.
In JS when the default this behaviour is not altered
Keeping things simple, to know the object to which the this keyword refers to you can simply look at the left-side of the . in a function invocation.
For example, in myObj.someFunction(), the this keyword within someFunction will point to myObj (that is unless the function has been bound using Function.prototype.bind).
If the function is not invoked on an object, such as someFunction(), then this will point to the global object which is window in browsers.
This is also the case within anonymous functions that are passed around, except for addEventListener, which will make sure that the this value within the handler is the object to which the handler was attached.
E.g.
setTimeout(function () { this; /*window*/ }, 10);
document.addEventListener('click', function (e) {
e.target; //the clicked DOM element
this; //the document
});
When this is altered by the API
Using Function.prototype.call or Function.prototype.apply, it is possible to specify the object to which this will point to during a function execution.
Some libraries (e.g. jQuery) are taking advantage of that feature to make this point to an object that is more intuitive, rather than the global object.
E.g.
$('#someEl').on('click', function (e) {
this; //the DOM element that was clicked (not the jQuery wrapper)
});
When this is altered in such way by the library, there is no other way than looking at the library's documentation to see what this will be.
We can read from jQuery event docs that:
In addition to the event object, the event handling function also has
access to the DOM element that the handler was bound to via the
keyword this.
Rewriting your function
Now, here's how you could re-write your function:
$(".trigger").click(function () {
var $this = $(this).toggleClass('toggle'),
$elementToSlide = $this.closest("td").next().find(".clueless > div"),
isToggled = !$this.hasClass('toggle'),
slideBehavior = isToggled? 'slideUp' : 'slideDown';
$elementToSlide[slideBehavior]();
});
Not sure exactly what you're trying to achieve but you can cache $(this) whenever it has context.
$(".trigger").click(function () {
var $trigger = $(this);
if ($trigger.hasClass('toggle')) {
$trigger.closest("td").next().find(".clueless > div").slideUp();
$trigger.removeClass('toggle');
} else {
$trigger.closest("td").next().find(".clueless > div").slideDown();
$trigger.addClass('toggle');
}
});
If you want to cache $(this).closest("td").next().find(".clueless > div") then
$(".trigger").click(function () {
var el = $(this).closest("td").next().find(".clueless > div");
if ($(this).hasClass('toggle')) {
el.slideUp();
$(this).removeClass('toggle');
} else {
el.slideDown();
$(this).addClass('toggle');
}
});
I assume this is what you are going for, it would make sense to cache $(this) since you are using it multiple times
var $this = $(this);
would do it

Why does an onclick property set with setAttribute fail to work in IE?

Ran into this problem today, posting in case someone else has the same issue.
var execBtn = document.createElement('input');
execBtn.setAttribute("type", "button");
execBtn.setAttribute("id", "execBtn");
execBtn.setAttribute("value", "Execute");
execBtn.setAttribute("onclick", "runCommand();");
Turns out to get IE to run an onclick on a dynamically generated element, we can't use setAttribute. Instead, we need to set the onclick property on the object with an anonymous function wrapping the code we want to run.
execBtn.onclick = function() { runCommand() };
BAD IDEAS:
You can do
execBtn.setAttribute("onclick", function() { runCommand() });
but it will break in IE in non-standards mode according to #scunliffe.
You can't do this at all
execBtn.setAttribute("onclick", runCommand() );
because it executes immediately, and sets the result of runCommand() to be the onClick attribute value, nor can you do
execBtn.setAttribute("onclick", runCommand);
to make this work in both FF and IE you must write both ways:
button_element.setAttribute('onclick','doSomething();'); // for FF
button_element.onclick = function() {doSomething();}; // for IE
thanks to this post.
UPDATE:
This is to demonstrate that sometimes it is necessary to use setAttribute! This method works if you need to take the original onclick attribute from the HTML and add it to the onclick event, so that it doesn't get overridden:
// get old onclick attribute
var onclick = button_element.getAttribute("onclick");
// if onclick is not a function, it's not IE7, so use setAttribute
if(typeof(onclick) != "function") {
button_element.setAttribute('onclick','doSomething();' + onclick); // for FF,IE8,Chrome
// if onclick is a function, use the IE7 method and call onclick() in the anonymous function
} else {
button_element.onclick = function() {
doSomething();
onclick();
}; // for IE7
}
works great!
using both ways seem to be unnecessary now:
execBtn.onclick = function() { runCommand() };
apparently works in every current browser.
tested in current Firefox, IE, Safari, Opera, Chrome on Windows; Firefox
and Epiphany on Ubuntu; not tested on Mac or mobile systems.
Craig: I'd try "document.getElementById(ID).type='password';
Has anyone checked the "AddEventListener" approach with different engines?
There is a LARGE collection of attributes you can't set in IE using .setAttribute() which includes every inline event handler.
See here for details:
http://webbugtrack.blogspot.com/2007/08/bug-242-setattribute-doesnt-always-work.html
This is an amazing function for cross-browser compatible event binding.
Got it from http://js.isite.net.au/snippets/addevent
With it you can just do Events.addEvent(element, event, function); and be worry free!
For example: (http://jsfiddle.net/Zxeka/)
function hello() {
alert('Hello');
}
var button = document.createElement('input');
button.value = "Hello";
button.type = "button";
Events.addEvent(input_0, "click", hello);
document.body.appendChild(button);
Here's the function:
// We create a function which is called immediately,
// returning the actual function object. This allows us to
// work in a separate scope and only return the functions
// we require.
var Events = (function() {
// For DOM2-compliant browsers.
function addEventW3C(el, ev, f) {
// Since IE only supports bubbling, for
// compatibility we can't use capturing here.
return el.addEventListener(ev, f, false);
}
function removeEventW3C(el, ev, f) {
el.removeEventListener(ev, f, false);
}
// The function as required by IE.
function addEventIE(el, ev, f) {
// This is to work around a bug in IE whereby the
// current element doesn't get passed as context.
// We pass it via closure instead and set it as the
// context using call().
// This needs to be stored for removeEvent().
// We also store the original wrapped function as a
// property, _w.
((el._evts = el._evts || [])[el._evts.length]
= function(e) { return f.call(el, e); })._w = f;
// We prepend "on" to the event name.
return el.attachEvent("on" + ev,
el._evts[el._evts.length - 1]);
}
function removeEventIE(el, ev, f) {
for (var evts = el._evts || [], i = evts.length; i--; )
if (evts[i]._w === f)
el.detachEvent("on" + ev, evts.splice(i, 1)[0]);
}
// A handler to call all events we've registered
// on an element for legacy browsers.
function addEventLegacyHandler(e) {
var evts = this._evts[e.type];
for (var i = 0; i < evts.length; ++i)
if (!evts[i].call(this, e || event))
return false;
}
// For older browsers. We basically reimplement
// attachEvent().
function addEventLegacy(el, ev, f) {
if (!el._evts)
el._evts = {};
if (!el._evts[ev])
el._evts[ev] = [];
el._evts[ev].push(f);
return true;
}
function removeEventLegacy(el, ev, f) {
// Loop through the handlers for this event type
// and remove them if they match f.
for (var evts = el._evts[ev] || [], i = evts.length; i--; )
if (evts[i] === f)
evts.splice(i, 1);
}
// Select the appropriate functions based on what's
// available on the window object and return them.
return window.addEventListener
? {addEvent: addEventW3C, removeEvent: removeEventW3C}
: window.attachEvent
? {addEvent: addEventIE, removeEvent: removeEventIE}
: {addEvent: addEventLegacy, removeEvent: removeEventLegacy};
})();
If you don't want to use such a big function, this should work for almost all browsers, including IE:
if (el.addEventListener) {
el.addEventListener('click', function, false);
} else if (el.attachEvent) {
el.attachEvent('onclick', function);
}
In response to Craig's question. You're going to have to make a new element and copy over the attributes of the old element. This function should do the job: (source)
function changeInputType(oldObject, oType) {
var newObject = document.createElement('input');
newObject.type = oType;
if(oldObject.size) newObject.size = oldObject.size;
if(oldObject.value) newObject.value = oldObject.value;
if(oldObject.name) newObject.name = oldObject.name;
if(oldObject.id) newObject.id = oldObject.id;
if(oldObject.className) newObject.className = oldObject.className;
oldObject.parentNode.replaceChild(newObject,oldObject);
return newObject;
}
Or you could use jQuery and avoid all those issues:
var execBtn = $("<input>", {
type: "button",
id: "execBtn",
value: "Execute"
})
.click(runCommand);
jQuery will take care of all the cross-browser issues as well.
Actually, as far as I know, dynamically created inline event-handlers DO work perfectly within Internet Explorer 8 when created with the x.setAttribute() command; you just have to position them properly within your JavaScript code. I stumbled across the solution to your problem (and mine) here.
When I moved all of my statements containing x.appendChild() to their correct positions (i.e., immediately following the last setAttribute command within their groups), I found that EVERY single setAttribute worked in IE8 as it was supposed to, including all form input attributes (including "name" and "type" attributes, as well as my "onclick" event-handlers).
I found this quite remarkable, since all I got in IE before I did this was garbage rendered across the screen, and one error after another. In addition, I found that every setAttribute still worked within the other browsers as well, so if you just remember this simple coding-practice, you'll be good to go in most cases.
However, this solution won't work if you have to change any attributes on the fly, since they cannot be changed in IE once their HTML element has been appended to the DOM; in this case, I would imagine that one would have to delete the element from the DOM, and then recreate it and its attributes (in the correct order, of course!) for them to work properly, and not throw any errors.
Write the function inline, and the interpreter is smart enough to know you're writing a function. Do it like this, and it assumes it's just a string (which it technically is).
function CheckBrowser(){
if(navigator.userAgent.match(/Android/i)!=null||
navigator.userAgent.match(/BlackBerry/i)!=null||
navigator.userAgent.match(/iPhone|iPad|iPod/i)!=null||
navigator.userAgent.match(/Nokia/i)!=null||
navigator.userAgent.match(/Opera M/i)!=null||
navigator.userAgent.match(/Chrome/i)!=null)
{
return 'OTHER';
}else{
return 'IE';
}
}
function AddButt(i){
var new_butt = document.createElement('input');
new_butt.setAttribute('type','button');
new_butt.setAttribute('value','Delete Item');
new_butt.setAttribute('id', 'answer_del_'+i);
if(CheckBrowser()=='IE'){
new_butt.setAttribute("onclick", function() { DelElemAnswer(i) });
}else{
new_butt.setAttribute('onclick','javascript:DelElemAnswer('+i+');');
}
}
In some cases the examples listed here didn't work out for me in Internet Explorer.
Since you have to set the property with a method like this (without brackets)
HtmlElement.onclick = myMethod;
it won't work if you have to pass an object-name or even parameters. For the Internet Explorer you should create a new object in runtime:
HtmlElement.onclick = new Function('myMethod(' + someParameter + ')');
Works also on other browsers.
Did you try:
execBtn.setAttribute("onclick", function() { runCommand() });
Not relevant to the onclick issue, but also related:
For html attributes whose name collide with javascript reserved words, an alternate name is chosen, eg. <div class=''>, but div.className, or <label for='...'>, but label.htmlFor.
In reasonable browsers, this doesn't affect setAttribute. So in gecko and webkit you'd call div.setAttribute('class', 'foo'), but in IE you have to use the javascript property name instead, so div.setAttribute('className', 'foo').
Have you considered an event listener rather than setting the attribute? Among other things, it lets you pass parameters, which was a problem I ran into when trying to do this. You still have to do it twice for IE and Mozilla:
function makeEvent(element, callback, param, event) {
function local() {
return callback(param);
}
if (element.addEventListener) {
//Mozilla
element.addEventListener(event,local,false);
} else if (element.attachEvent) {
//IE
element.attachEvent("on"+event,local);
}
}
makeEvent(execBtn, alert, "hey buddy, what's up?", "click");
Just let event be a name like "click" or "mouseover".
I did this to get around it and move on, in my case I'm not using an 'input' element, instead I use an image, when I tried setting the "onclick" attribute for this image I experienced the same problem, so I tried wrapping the image with an "a" element and making the reference point to the function like this.
var rowIndex = 1;
var linkDeleter = document.createElement('a');
linkDeleter.setAttribute('href', "javascript:function(" + rowIndex + ");");
var imgDeleter = document.createElement('img');
imgDeleter.setAttribute('alt', "Delete");
imgDeleter.setAttribute('src', "Imagenes/DeleteHS.png");
imgDeleter.setAttribute('border', "0");
linkDeleter.appendChild(imgDeleter);

Categories