Can't detach javascript event after it fires - javascript

I'm using this structure to create one-time click events:
function structure() {
this.elements = document.getElementsByClassName('className');
this.numElements = this.elements.length;
for(var i = 0; i < this.numElements; i++) {
this.elements[i].addEventListener('click', this.elementClicked.bind(this));
}
}
The handler of those events is implemented as follows:
structure.prototype.elementClicked = function(e) {
// ... processing event
for(var i = 0; i < this.numElements; i++) {
this.elements[i].removeEventListener('click', arguments.callee);
}
};
The idea is to fire the handler once if any of the registered elements gets clicked, and then unregister the event from each of those elements
Unfortunately the handler still gets fired everytime I click on one of the registered items
I'm aware anonymous functions can't be used to reference the same object, but specifying arguments.callee or the entire name of the referenced function still didn't help the cause

An alternative is to make your objects implement the EventListener interface. You can do this by adding a handleEvent method to the .prototype of the constructor, and then passing the object itself in place of the event handler.
function Structure() {
this.elements = document.getElementsByClassName('className');
this.numElements = this.elements.length;
for(var i = 0; i < this.numElements; i++) { // v-- pass the object
this.elements[i].addEventListener('click', this);
}
}
// Implement the interface; gets invoked when an event occurs
Structure.prototype.handleEvent = function(e) {
// Used a switch statement in anticipation of other event types
switch (e.type) {
case "click":
this.elementClicked(e);
break;
}
};
Structure.prototype.elementClicked = function(e) {
// ... processing event
for(var i = 0; i < this.numElements; i++) { // v-- pass the object
this.elements[i].removeEventListener('click', this);
}
};
Now there's no longer any need to use .bind(). Instead the value of this in handleEvent will be the bound object. You can still get the element to which the handler was bound via e.currentTarget.

Each time you call...
this.elements[i].addEventListener('click', this.elementClicked.bind(this));
... bind creates another instance of a method. It uses this.elementClicked, true, but otherwise is a completely different function. That's why you won't drop it with remoteEventListener called on this.elementClicked.
What's the workarounds? One possible option - passing { once: true } as addEventListener param - has been given in the comments, but it's not supported by IE and Edge (and most likely won't be supported by the Safari you encounter in the nearest future). Here's another approach:
function Structure() {
this.elements = document.getElementsByClassName('className');
this.numElements = this.elements.length;
// reassign a bound method onto instance:
this.elementClicked = this.elementClicked.bind(this);
for(var i = 0; i < this.numElements; i++) {
this.elements[i].addEventListener('click', this.elementClicked);
}
}
Structure.prototype.elementClicked = function(e) {
// ... processing event
for(var i = 0; i < this.numElements; i++) {
this.elements[i].removeEventListener('click', this.elementClicked);
}
};
Now you create a bound elementClicked method for each instance of structure object, having its context set permanently.

Related

Event listener with anonymous function (typeError , toggle undefined)

I am new to Javascript.
I have two identical tables laid side by side. I would like to have a mirror effect. case 1 works fine with no anonymous function.
However, there seems to be some problem in my case 2 Javascript code which is vital for my project
CASE 1
var table1td = document.querySelectorAll("#table1 td");
var table2td = document.querySelectorAll("#table2 td");
for(var i=0; i<table2td.length; i++)
{
table2td[i].addEventListener("click",_click);
}
function _click() {
this.classList.toggle("_changecolor");
}
CASE 2
var table1td = document.querySelectorAll("#table1 td");
var table2td = document.querySelectorAll("#table2 td");
for(var i=0; i<table2td.length; i++)
{
table2td[i].addEventListener("click", function(){_click(i)});
}
function _click(index) {
this.classList.toggle("_changecolor");
table2td[9-index].classList.toggle("_changecolor");
}
(no changes in HTML,CSS code)
There are two problems with your code:
You can't use this inside _click because it won't be your element. The element that fired the event will be bound to the anonymous function passed to addEventListener, not to _click (which, depeneding on the rest of your code, will either be bound to undefined or the global object window).
You can fix that by explicitly setting the this value when you call _click from within the anonymous function using Function#call or Function#apply:
for(var i = 0; i < table2td.length; i++) {
table2td[i].addEventListener("click", function() {
_click.call(this); // passing the current this (of the anonymous function) to _click as we invoke it
});
}
function _click() {
this.classList.toggle("_changecolor"); // using this is OK
}
You can't use the indexes due to this famous problem.
A quick fix will be to use let (which respects the block scope) instead of var (which doesn't):
for(let i = 0; i < table2td.length; i++) { // using let instead of var
table2td[i].addEventListener("click", function() { _click(i); });
}
function _click(index) { // using index is also OK
table2td[index].classList.toggle("_changecolor");
}

Do I need a parameter in this function?

In this homework assignment, I'm having issues with this part of the problem.
window.onload=setup;
function setup()
{
var questions = document.querySelectorAll('ol li');
for (var i= 0; i < questions.length ; i++)
{
questions[i].id = i + "phrases";
questions[i].onmousedown = showEnglish;
//questions[i].onmouseup = showFrench;
questions[i].style.cursor = "pointer";
}
}
function showEnglish()
{
var phraseNumber = parseInt(question[i].id)
document.getElementById(phraseNumber).innerHTML = english[phraseNumber];
english[phraseNumber].style.font = "italic";
english[phraseNumber].style.Color = "rgb(191,22,31)";
}
a) Using the id property of the list item experiencing the mousedown event, extract the index number with the the parseInt() function and store that value in the phraseNumber variable.
I get an error, saying questions is not defined in the showenglish().
Am I supposed to be referencing another object?
You need to pass the question as a parameter:
for(i=0;i<question.length;i++){
let a=i;//important for scoping
question[a].onmousedown=function(){
showEnglish(question[a]);
}
}
function showEnglish(question){
document.getElementById(question.id).style.font="italic";
...
}
(Note: this answer contains ES6. Do not use it in real productional environment. The let a=i; defines that a is kept for being used inside of the listener, while i will always be question.length, because the event is probably clicked after the loop occured...)
Alternatively, the event listener binds this as the clicked element:
question[i].addEventListener("click",showEnglish,false);
function showEnglish(){
document.getElementById(this.id).style.font="italic";
...
}
The mousedown event is raised when the user presses the mouse button. Look at the documentation for the mousedown event.
Your event handler function will be passed an Event object, which has a target property, which is a reference to the element that the mouse clicked on.
You can access this inside your event handler function with event.target.
window.onload = setup;
function setup() {
var questions = document.querySelectorAll('ol li');
for (var i = 0; i < questions.length; i++) {
questions[i].id = i + "phrases";
questions[i].onmousedown = showEnglish;
//questions[i].onmouseup = showFrench;
questions[i].style.cursor = "pointer";
}
}
function showEnglish(event) {
var phraseNumber = parseInt(event.target.id);
// etc
};

Pass "this" implicitly through inline event listener

var fn = (function() {
return {
'init': function(className) {
// Access <a> tag here and apply className
}
};
}());
Link
In the above code snippet, would it be possible to implicitly pass this to fn.init?
I realize that I can change the declaration of fn.init to function(className, el) and use onmouseover="fn.init('myClass', this)" to access the element, but I am just curious if it would be possible without passing this in the inline event listener.
You can use call as well (I realize that you said you didn't want to pass this in the inline event listener, but this is probably the only way). The only other way is to use addEventListener and not use an inline event.
var fn = (function() {
return {
'init': function(className) {
// Notice that I can use "this" in this function
// Which refers to the <a> element
this.className = className;
}
};
}());
.myClass {
color: red;
}
Link
If you wanted to add an event to every <a> element, you would do this:
var elems = document.getElementsByTagName("a");
for (var i = 0; i < elems.length; ++i) {
elems[i].addEventListener("click", function() {
fn.init.call(this, 'myClass');
}, false);
}
Or if you are using only modern browsers (no IE)
var elems = document.getElementsByTagName("a");
for (var i = 0; i < elems.length; ++i) {
elems[i].addEventListener("click", fn.init.bind(elems[i], 'myClass'), false);
}
No. You get a new value of this each time you call a function.
fn.init('myClass') is a different function call to theElement.onmouseover(event).
You could use addEventListener to bind the event handler instead of using intrinsic event attributes.

How to get onclick for an HTML object?

Using vanilla JS I would like to know if would be possible to see the property onclick on a HTML object (div)
for (var name in element) {
if(name == "onclick"){
// do smt
}
}
Instead of enumerating properties of element, you can immediately retrieve the onclick property of element with:
var clickHandler = element.onclick;
Events nowadays are bound with addEventListener (and attachEvent in old IE), which allow for multiple handlers per event type. Setting the onevent property only allows for one handler, can be overwritten, and normally isn't the way to bind handlers in a web page.
Unfortunately, you are not able to retrieve any listeners bound with addEventListener (and attachEvent), without writing a wrapper function that tracks them...for example:
var events = [];
function addEvent(element, eventName, callback) {
element.addEventListener(eventName, callback, false);
var found = false;
for (var i = 0; i < events.length; i++) {
if (events[i].el === element) {
found = true;
events[i].list.push(callback);
break;
}
}
if (!found) {
events.push({
el: element,
list: [callback]
});
}
}
function viewEvents(element) {
for (var i = 0; i < events.length; i++) {
if (events[i].el === element) {
return events[i].list;
}
}
return null;
}
And you'd use it like:
var div = document.getElementById("some_id");
addEvent(div, "click", function () {
console.log("whatever");
});
console.log(viewEvents(div));
(of course, you'd need a wrapper for removeEventListener that removes handlers from events too)

Handling onclick events

I have a list which contains links . I am using this code to access them:
function initAll() {
var allLinks = document.getElementById("nav").getElementsByTagName("a");
for (var i=0; i< allLinks.length; i++) {
allLinks[i].onmouseover = showPreview;
allLinks[i].onmouseout = function() {
document.getElementById("previewWin").style.visibility = "hidden";
allLinks[i].onclick=mainProcess;
}
}
}
function mainProcess(evt){
alert(this.value);
false;
}
This is not the exact code, what I am trying to do is that I need to identify link is clicked and perform some function on the basis of link clicked. I don't know where code needs to be modified... Page is giving error on the allLinks[i].onclick=mainProcess(this); line.
Now the problem is that I don't know how I should handle all the three events?
1) You're setting the onclick property of each of the links to be the value returned by mainProcess() - which always returns false. So, in effect, you're writing allLinks[i].onclick = false;
2) When you define an event handler directly, the argument that gets passed to it when the event fires, is the event object - not the element it was fired on.
To figure out the element, you can either look in the event object, or (since the handler has been added to the element itself) simply use this, as that will refer to the link element
for (var i = 0; i < allLinks.length; i++) {
allLinks[i].onclick = mainProcess;
}
function mainProcess(event) {
{
alert(this.value);
return false;
}
You do need to pass this to mainProcess(link). As stated in http://www.quirksmode.org/js/events_tradmod.html "No parentheses!" and "this" chapters. Check it out, there's an example there too. Should be everything you need.
Try changing to this:
for (var i = 0; i < allLinks.length; i++) {
allLinks[i].onclick = mainProcess;
}
function mainProcess(event) {
{
alert(this.value);
return false;
}

Categories