Assign onclick to entire class - javascript

I'm trying to attach an event handler on all elements with a specific div. I have created a jsfiddle showing an example of my code. Could someone please point me in the right direction?
http://jsfiddle.net/nw4Xs/
var l = document.getElementsByClassName("item").Length;
var foo = function () { alert("foo"); };
for (var i = l - 1; i >= 0; i--) {
document.getElementsByClassName("item")[i].onclick = foo();
}
please no jquery answers
Thanks

I'd suggest (unless you explicitly need to iterate in reverse):
var els = document.getElementsByClassName('item'),
l = els.length,
foo = function () { alert("foo"); };
for (var i = 0; i< l; i++) {
els[i].onclick = foo;
}
JS Fiddle demo.
Problems with your code:
// 'Length' should be 'length':
var l = document.getElementsByClassName("item").Length;
var foo = function () { alert("foo"); };
// since you're not changing the class-name there's no need to
// go in reverse (it's just confusing to read):
for (var i = l - 1; i >= 0; i--) {
// you're re-querying every iteration (plus when you're getting the length), why?
// keeping the parentheses assigns the (non-existent) return value of the function,
// instead of binding the function to the 'click' event:
document.getElementsByClassName("item")[i].onclick = foo();
}
Incidentally, you could instead bind the event-handler to the closest ancestor element that wraps all the elements you want to have an effect when they're clicked (note: use the closest ancestor that exists in the DOM at the time of event-binding, in this case it's the body, because there's no other wrapping elements, in most cases there will be, and the closest should be used to avoid events having to bubble all the way to the 'top'):
var bindTarget = document.body, // use the closest wrapping element
foo = function (e) {
var e = e || window.event,
clicked = e.target || e.srcElement;
if (clicked.className.indexOf('item') > -1) {
alert("foo");
}
};
bindTarget.onclick = foo;
JS Fiddle demo.

The property of a NodeList (what getElementByClassName() returns) that you want is length (lowercase).
To pass a reference to a function, just use its name; don't put parentheses after it. Otherwise you're calling the function and assigning or passing its return value.
var items = document.getElementsByClassName("item");
var l = items.length;
var foo = function () { alert("foo"); };
for (var i = l - 1; i >= 0; i--) {
items[i].onclick = foo;
}

You can call Array.prototype.forEach over the result of document.getElementsByClassName("item")
var foo = function () { alert('foo'); };
Array.prototype.forEach.call(document.getElementsByClassName('item'), function ( item ) {
item.addEventListener('click', foo);
});
Fiddle:
http://jsfiddle.net/nw4Xs/7/

If you are interested in using jQuery, you can write this out a lot simpler. Whilst it's not necessary it may save you a lot of time in the future when you are trying to do harder stuff. At a basic level:
function foo...
$(".item").click(foo);
This will get all DOM elements with a class of "item" and attach foo to the click event.
If you're interested, there is lots of documentation and help with using jQuery.

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");
}

Javascript. Onclick returns always the same object

I have got 16 divs of the same class name in html document in the following fashion
<div class="game-selection-tab">
<h2>30m + 15s</h2>
</div>
<div class="game-selection-tab">
<h2>60m + 0s</h2>
</div>
<div class="game-selection-tab">
<h2>Custom</h2>
</div>
I want to create onclick method that returns h2 text content from particular div. I tried to solve this by using following javascript code.
var selectionTabs = document.getElementsByClassName("game-selection-tab");
for(var i = 0; i < selectionTabs.length; i++){
var tab = selectionTabs[i];
var content = tab.getElementsByTagName("h2");
tab.onclick = function(){
console.log(content[0].textContent);
}
}
The problem is: no matter which div i click, program always returns h2 text content from the last div(in this example "custom").
Try this
var selectionTabs = document.getElementsByClassName("game-selection-tab");
for(var i = 0; i < selectionTabs.length; i++){
(function (index) {
var tab = selectionTabs[index];
var content = tab.getElementsByTagName("h2");
tab.onclick = function(){
console.log(content[0].textContent);
}
})(i);
}
The thing is by the time your event attaches to the actual DOM element the for loop execution is complete and the value of i is the max value that it can reach. Hence, isolating the same in a function like this works. The function stores the value of i or in this case index as the original value that you expect.
Replace
var i = 0
by
let i = 0
and you're done.
A detailed explanation is here.
I'll quote my answer for your understanding below.
Cause of the problem: lack of understanding scope
Check this example to understand the problem:
var creates function scope
var funcs = []
for (var i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i)
})
}
funcs.forEach(function(func) {
func()
})
While you might expect this forEach loop to result in number 0 to 9 being printed, instead you get ten times 10. The cause of this is the variable i being declared using var keyword, which creates a function scope that leads to each function in funcs holding a reference to the same i variable. At the time the forEach loop is executed, the previous for-loop has ended and i holds 10 (9++ from the last iteration).
Compare how ES6's let, which creates block scope instead of function scope, behaves in this regard:
let (ES6 or officially ES2015) creates block scope:
var funcs = []
for (let i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i)
})
}
funcs.forEach(function(func) {
func()
})
Because let creates block scope, each iteration of the for loop has its "own" variable i.
ES5 solution using an IIFE wrapper
If you need an ES5 solution, an IIFE (immediately invoked function expression) wrapper would be the way to go:
var funcs = []
for (var i = 0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value)
}
}(i)))
}
funcs.forEach(function(func) {
func()
})
Here, i is passed as a parameter to each function which stores its own copy value.
The same is true for for..in loops:
var funcs = [],
obj = {
first: "first",
last: "last",
always: "always"
}
for (var key in obj) {
funcs.push(function() {
console.log(key)
})
}
funcs.forEach(function(func) { // outputs: "always", "always", "always"
func()
})
Again, all functions in funcs hold the reference to the same key because var key creates a function scope that lives outside of the for..in loop. And again, let produces the result you'd probably rather expect:
var funcs = [],
obj = {
first: "first",
last: "last",
always: "always"
}
for (let key in obj) {
funcs.push(function() {
console.log(key)
})
}
funcs.forEach(function(func) {
func()
})
Also compare the excellent (!) book
Nicholas C. Zakas: "Understanding ES6", no starch press, p. 8-9.
from which the examples were taken.
It's showing always the same value because you are setting content outside of the onclick function. After the for loop, content points to the last h2.
Move the content definition inside the onclick function.
tab.onclick = function(){
var content = this.getElementsByTagName("h2");
console.log(content[0].textContent);
}
Working fiddle
Can you try the solution below.
var selectionTabs = document.getElementsByClassName("game-selection-tab");
Object.keys(selectionTabs).forEach((data, index) => {
var context = selectionTabs[data].getElementsByTagName("h2")[0].textContent;
selectionTabs[data].onclick = function () {
console.log(context)
}
})
Try this simple solution:
var els = document.getElementsByClassName('game-selection-tab');
var index = 0;
function getText() {
alert(this.innerText || this.textContent);
}
for (; index < els.length; index++) {
els[index].onclick = getText;
}
<div class="game-selection-tab">
<h2>30m + 15s</h2>
</div>
<div class="game-selection-tab">
<h2>60m + 0s</h2>
</div>
<div class="game-selection-tab">
<h2>Custom</h2>
</div>
Assuming that you don't wanna change your HTML to include an "onclick" event on each h2, this code might help you:
document.addEventListener('click', function(e) {
e = e || window.event;
var target = e.target || e.srcElement,
text = target.textContent || text.innerText;
console.log(text);
}, false);
EDIT
If you want to be more specific and get the content from only your h2's, you could use this:
h2s = document.getElementsByTagName('h2');
for (var i = 0; i < h2s.length; i++) {
h2s[i].addEventListener('click', redirect, false);
}
function redirect(e) {
var target = e.target || e.srcElement;
var text = target.textContent || text.innerText;
console.log(text);
}

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
};

How to move functions outside a loop

JSHint is screaming that functions should be declared outside the loop, I'm just confused on how to do this? The specific part: self.onchange = function () {...}
Here's the loop:
for ( var j = 0; j < checkz.length; j++ ) {
var self = checkz[j];
self.onchange = function () {
for ( var z = 0; z < psswrd.length; z++ ) {
psswrd[z].type = self.checked ? 'text' : 'password';
}
};
}
When I move it outside and assign it, the function breaks as 'self' becomes undefined. Any advice appreciated.
In this case, you just need one function:
for ( var j = 0; j < checkz.length; j++ ) {
var self = checkz[j];
self.onchange = changeFunction;
// Or replace the above two lines with:
// checkz[j].onchange = changeFunction;
// ...if you don't need `self` for anything else.
}
function changeFunction() {
for ( var z = 0; z < psswrd.length; z++ ) {
psswrd[z].type = this.checked ? 'text' : 'password';
// ^^^^--- note this changed from `self` to `this`
}
}
You needed that self => this change anyway, because as it was originally, all of the functions would have referred to the same value of self. When you create a function, it has an enduring reference to the variables in context where it's created, not a copy of their values when it's created. (More: Closures are not complicated) In this case, we can use this because within an event handler hooked up that way (and most ways), this will be the element the event handler was hooked up to.
Now in the general case, where sometimes you need to refer to something that's changing in the loop and you don't happen to have a replacement handy for it, you'd typically use a builder function that returns the function to use, like this:
for ( var j = 0; j < checkz.length; j++ ) {
var self = checkz[j];
self.onchange = buildChangeFunction(j);
}
function buildChangeFunction(jarg) {
return function() {
// Use jarg in here...
};
}
That way, the function we assign to onchange closes over the argument to buildChangeFunction, not j, and that argumennt doesn't change.
But again, you don't need that here, the first solution above is all you need.

Javascript reference in loop: "Uncaught TypeError: Cannot read property 'value' of undefined"

I tried debugging my code for like a few hour but I got nothing out of it. The issue is that it makes absolutely no sense on why it reports an error every time I tried to use document.forms[0][i] (i as the iterator) in the event listener but "this" satisfies the code.
//broken
var addListeners = function() {
var i;
var formFields = document.forms[0];
var formSubmit = formFields["submit"];
for (i = 0; i < formFields.length; i++) {
if (formFields[i] != formSubmit) {
formFields[i].onblur = (function () {
checkNonEmpty(formFields[i]);
});
}
}
};
//works
var addListeners = function() {
var i;
var formFields = document.forms[0];
var formSubmit = formFields["submit"];
for (i = 0; i < formFields.length; i++) {
if (formFields[i] != formSubmit) {
formFields[i].onblur = (function () {
checkNonEmpty(this);
});
}
}
};
Wouldn't "this" refer to document.forms[0][i]?... formFields references to document.forms[0]. However the exact same code (with "this" where formFields[i] is at) works just fine.
Here is the demo: http://jsfiddle.net/PbHwy/
Cranio's answer already contains the root of the matter. To get rid of this you can either include formFields[i] by using closures
var blurCallbackGenerator = function(element){
return function () {
checkNonEmpty(element);
};
};
formFields[i].onblur = blurCallbackGenerator(formFields[i]);
/* // dense version:
formFields[i].onblur = (function(element){
return function () {
checkNonEmpty(element);
};
})(formFields[i]);
*/
or simply using this.
See also:
MDN: Creating closures in loops: A common mistake
Because you define formFields in a scope outside (or better, different than) the event listener. When the event listener is called, it is called not in the addListeners function where you define formFields, but "independently", so the reference is lost and its value is undefined (but this works because it is not dependent on that scope).
The problem is that the variable i (referred to in each of your handlers) is the exact same variable in each of them, which by the time the loop has finished has value formFields.length+1 and is therefore wrong for all of them. Try this instead [note: the below used to say something VERY WRONG before I edited it -- thanks to Zeta for pointing out my mistake]:
var addListeners = function() {
var i;
var formFields = document.forms[0];
var formSubmit = formFields["submit"];
for (i = 0; i < formFields.length; i++) {
if (formFields[i] != formSubmit) {
formFields[i].onblur = (function(j) {
return (function () {
checkNonEmpty(formFields[j]);
})(i);
});
}
}
};
and you'll find it works (unless there's another bug that I haven't noticed).
If you can afford to support only Javascript 1.7 and above, you can instead write your old code but make your for look like this: for (let i=0; i<formFields.length; i++). But you quite possibly can't.

Categories