Assigning onmouse events with a loop to a div array - Javascript - javascript

I'm trying to assign onmouseover - onmouseout events to an array of divs with a loop.
I created the divs through a loop as well using a function parameter createDivs(x), x being number of divs and a bunch of this.property to assign styles.
Everything is working as expected, but assigning the mouse events through a loop with the divArray.Length object.
The script is the following:
Making the divs:
containers : {
create : function(containerCount){
var cArray = [this.c1Color,this.c2Color,this.c3Color];
var aCounter = 0;
divArray = [];
for (var i = 1; i <= containerCount; i++){
var c = document.createElement("div");
c.id = ("container"+i);
c.style.width = "100%";
c.style.height = (this.height) + "px";
c.style.backgroundColor = (cArray[aCounter]);
aCounter++;
document.body.appendChild(c);
divArray.push(c);
}
}
},
Assigning the Events:
events : {
on : function () {
var z = 1;
for (var i = 0; i < divArray.length; i++){
var cont = ("container" + z);
document.getElementById(divArray[i].id).onmouseover = function(){
gala.animate.openAnimation(cont);
}
document.getElementById(divArray[i].id).onmouseout = function(){
gala.animate.shrinkAnimation(cont);
}
console.log(cont);
z++;
}
}
The console show the array sort through the number of divs as expected, and the cont variable ++ increase to assign the id. However at the end, the event listeners are only applied to the last element of the array.
Btw the cont variable is just a placeholder for a parameter that passes too the animation method so it knows what div to animate, meaning animat.openAnimation(cont) cont = div name.

Looks like you need a new scope to keep the value of the cont variable constant inside the event handlers. I replaced the cont variable as it didn't really seem neccessary
events : {
on : function () {
for (var j = 0; j < divArray.length; j++){
(function(i) {
divArray[i].onmouseover = function(){
gala.animate.openAnimation("container" + (i+1));
}
divArray[i].onmouseout = function(){
gala.animate.shrinkAnimation("container" + (i+1));
}
})(j);
}
}

Related

.addEventListener - long list

I found few answer on my issue but probably I'm not so experienced to processes it to my case.
I have list of items generated to .html
<div id="grid">
by JavaScript
var div_block = "";
for (i = 0; i < 70; i++) {
div_block = div_block + '<div id="c' + i + '" class="card"></div>';
}
document.getElementById("grid").innerHTML = div_block;
for (i = 0; i < 70; i++) {
'var c' + i + ' = document.getElementById(c' + i + ');'
}
and it works fine.
I want to chose .addEventListner method to chose proper element but for 70 and more elements code:
c0.addEventListener("click", function () {revealCard(0);});
c1.addEventListener("click", function () {revealCard(1);});
...
cn.addEventListener("click", function () {revealCard(n);});
is huge and not elegant. Method I've tried didn't work
for (i = 0; i < 70; i++) {
'c'+i+'.addEventListener("click", function() { revealCard('+i+'); });'
}
How to build working addEventListener() for this?
Thanks a lot.
The problem you are facing can be solved by using the card class that you add on each of your card. Then, to refer to the right card, you can use the keyword this, which in the context of an addEventListener will refer to whichever DOM element received the click. You also won't need to generate a unique Id for each one of your div, which I think is a big plus.
Your code would look like this:
let div_block = "";
for (i = 0; i < 70; i++) {
div_block = div_block + '<div class="card"></div>';
}
const cards = querySelectorAll(".card");
cards.forEach(card => {
card.addEventListener("click", revealCard)
})
function revealCard(){
// Here, `this` refers to the card that was clicked
// So you can do what you want with it
console.log(this);
}
Slight modification to brk's answer, using a single event listener on the parent that will trigger for the events on the children
var div_block = "";
for (let i = 0; i < 5; i++) {
div_block += `<div data-attr="${i}" id="c${i}" class="card">Hello</div>`;
}
var grid = document.getElementById("grid");
grid.innerHTML = div_block;
grid.addEventListener('click', function (e) {
if (e.target.getAttribute('class') === 'card') {
revealCard(e.target.dataset.attr);
}
});
function revealCard(num) {
console.log(num)
}
<div id='grid'></div>
You can use dataset, that is while creating the dom add a dataset property.
Then use querySelectorAll to get all the div with class card and iterate over it to add event using addEventListener. On click of the element get the dataset value and pass to revealCard function
var div_block = "";
for (let i = 0; i < 5; i++) {
div_block += `<div data-attr="${i}" id="c${i}" class="card">Hello</div>`;
}
document.getElementById("grid").innerHTML = div_block;
document.querySelectorAll('.card').forEach(function(item) {
item.addEventListener('click', function(e) {
revealCard(item.dataset.attr)
})
})
function revealCard(num) {
console.log(num)
}
<div id='grid'></div>
There are multiple ways to do this, but I wouldn't use IDs and I wouldn't bind X event listeners. I would use event delegation and data-* attributes:
Build your list of elements:
const grid = document.getElementById("grid");
for (var i = 0; i < 70; i++) {
const child = document.createElement('div');
child.className = 'card';
child.dataset.index = i;
grid.appendChild(child);
}
Add an event listener to the grid element:
grid.addEventListener('click', function(event) {
let target = event.target;
// traverse up if clicked inside element
while (target.className !== 'card') {
target = target.parentNode;
}
if (target) {
revealCard(target.dataset.index);
}
});
const grid = document.getElementById("grid");
for (var i = 0; i < 70; i++) {
const child = document.createElement('div');
child.className = 'card';
child.dataset.index = i;
child.innerText = `Card ${i}`;
grid.appendChild(child);
}
grid.addEventListener('click', function(event) {
let target = event.target;
// traverse up if clicked inside element
while (target.className !== 'card') {
target = target.parentNode;
}
if (target) {
revealCard(target.dataset.index);
}
});
function revealCard(i) {
console.log(`Card ${i} revealed`);
}
<div id="grid"></div>
I found probably the easiest (shortest) solution:
for(var i=0;i<boardSize;i++){
document.getElementById('c'+i).addEventListener("click",function(){
revealCard(this.id.substring(1));
});
}
What do you thing?

having trouble with adding an event "onmouseover" to a span [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
Can someone tell me what I am doing wrong? I've spent an entire day troubleshooting this but I am getting nowhere... I want to add the event "onmouseover" to my span elements. However when I implement the code below, nothing happens. I did a bit of googling and I think it might be a variable scope problem?? Im not too sure... Any help is appreciated!
<!DOCTYPE html>
<html>
<head>
<title>Fixing bugs in JS</title>
<script src="question1.js" type="text/javascript"></script>
<head>
<body>
<div id="output"></div>
</body>
<html>
var NUMBERS = 100;
function go()
{
var out = document.getElementById("output");
for (var i = 1; i < NUMBERS+1; i++) {
var span_one = document.createElement("span");
span_one.id = "span" + i;
span_one.innerHTML = "" + i;
out.appendChild(span_one);
if (isPrime(i) === true) { // where i is a prime number (3, 5, 7..etc)
span_one.style.backgroundColor = "red";
span_one.onmouseover = function() {
hover("span"+i, "yellow", "150%")
};
span_one.onmouseout = function() {
hover("span"+i, "red", "100%") // whatever color in this line always overrides previous set color...
};
}
function hover(id, color, size) {
var span = document.getElementById(id);
span.style.backgroundColor = color;
span.style.fontSize = size;
}
function etc() {
...
}
window.onload=go;
There's really no need to (a) give the elements an id (b) to use the i counter for anything other than the loop of creating them.
Here's an alternative.
function newEl(tag){return document.createElement(tag)}
function byId(id){return document.getElementById(id)}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
var i, n = 100;
var outputContainer = byId('output');
for (i=1; i<=n; i++)
{
var span = newEl('span');
//span.id = 'span_' + i;
span.textContent = i;
outputContainer.appendChild(span);
if ( i%2 == 1) // isOdd
{
span.addEventListener('mouseover', onSpanMouseOver, false);
span.addEventListener('mouseout', onSpanMouseOut, false);
}
}
}
function onSpanMouseOver(evt)
{
this.style.backgroundColor = 'yellow';
this.style.fontSize = '150%';
}
function onSpanMouseOut(evt)
{
this.style.backgroundColor = 'red';
this.style.fontSize = '100%';
}
<div id='output'></div>
Your issue is that you have closures around your i variable.
Closures occur whenever you nest a function within another function. Where the code runs unpredictably is when the nested function uses a variable from an ancestor function and the nested function has a longer lifetime than the ancestor in question.
Here, your mouseover and mouseout functions rely on i from the parent function go. Since the mouseover and mouseout functions are being attached to DOM elements and those DOM elements are going to remain in memory until the page is unloaded, those functions will have a longer lifetime than go. This means that the i variable that go declared can't go out of scope when go completes and that both of the mouse functions will SHARE the same value of i. The value that i has by the time a human comes along and moves the mouse is the LAST value it had when the loop ended.
Closures can be challenging at first, but you can read a bit more about them here.
Changing var i to let i on your loop solves that because let introduces block scope for each iteration of the loop.
Also, I saw that you were missing two closing curly braces that were causing errors. I added my own isPrime() function. See comments for locations:
window.onload=go;
const NUMBERS = 100;
function go(){
var out = document.getElementById("output");
// Using let instead of var avoids a closure by making sure
// that each looping number exists in the block scope of the
// loop and upon each iteration a new variable is created.
for (let i = 1; i < NUMBERS+1; i++) {
var span = document.createElement("span");
span.id = "span" + i;
span.innerHTML = i + "<br>";
out.appendChild(span);
if (isPrime(i)) { // where i is a prime number (2, 3, 5, 7..etc)
span.style.backgroundColor = "red";
// If you use the i variable in nested functions, you will create a
// closure around it and both the mouseover and mouseout functions will
// share the last known value of i. Each function must get its own copy
// of i.
span.onmouseover = function() {
hover("span" + i, "yellow", "150%")
};
span.onmouseout = function() {
// whatever color in this line always overrides previous set color...
hover("span" + i, "red", "100%")
};
} // <-- Missing
} // <-- Missing
}
function isPrime(value) {
for(var i = 2; i < value; i++) {
if(value % i === 0) {
return false;
}
}
return value > 1;
}
function hover(id, color, size) {
var span = document.getElementById(id);
span.style.backgroundColor = color;
span.style.fontSize = size;
console.log(id, span);
}
<div id="output"></div>
Here's a working example:
http://jsbin.com/zixeno/edit?js,console,output
The problem is exactly what enhzflep said. One solution is to to move the "addSpan" logic out of the for loop and into a function.
var NUMBERS = 100;
function go() {
var out = document.getElementById("output");
for (var i = 1; i < NUMBERS+1; i++) {
addSpan(i);
}
function hover(id, color, size) {
var span = document.getElementById(id);
span.style.backgroundColor = color;
span.style.fontSize = size;
}
function addSpan(i) {
var span_one = document.createElement("span");
span_one.id = "span" + i;
span_one.innerHTML = "" + i;
out.appendChild(span_one);
if (isPrime(i) === true) {
span_one.style.backgroundColor = "red";
span_one.onmouseover = function() {
hover("span"+i, "yellow", "150%")
};
span_one.onmouseout = function() {
hover("span"+i, "red", "100%");
};
}
}
}
The problem is with the variable i, its a common issue with closures. For more information you can have a look at MDN closures and go to the section Creating closures in loops: A common mistake. To come over this issue, change the var in for loop to let. This will help you retain the scope and thus fixing the issue.
var NUMBERS = 100;
function go() {
var out = document.getElementById("output");
for (let i = 1; i < NUMBERS+1; i++) {
let span_one = document.createElement("span");
span_one.id = "span" + i;
span_one.innerHTML = "" + i;
out.appendChild(span_one);
if (isPrime(i) === true) { // if a number is a prime then run this func
span_one.style.backgroundColor = "red";
span_one.onmouseover = function() {
hover("span"+i, "yellow", "150%")
};
span_one.onmouseout = function() {
hover("span"+i, "red", "100%") // whatever color in this line always overrides previous set color...
};
}
function hover(id, color, size) {
var span = document.getElementById(id);
span.style.backgroundColor = color;
span.style.fontSize = size;
}
//Added my custom function as it was not provided
function isPrime(i){
return i%2 != 0;
}
}
}
window.onload = go;
<div id="output"></div>

Flipping a div by class name

I'm trying to flip a div on mouseover, I found a few pages really useful.
followed this to set ID and then according to this one I need to add the mouseover property in the HTML but it's not easy as the ID.
Here is my code so far:
var abcElements = document.querySelectorAll('.builder_row_cover');
for (var i = 0; i < abcElements.length; i++)
abcElements[i].id = 'abc-';
var oHover = document.getElementById("abc-");
oHover.setAttribute("onmousehover", "flip()");
var k = 0;
function flip() {
var j = document.getElementById("abc-");
k += 180;
j.style.transform = "rotatey(" + k + "deg)";
j.style.transitionDuration = "0.5s"
}
I'm just starting, I have tried with setting attribute, but no way to see the mouseover in the HTML, any suggestion?
First, the name of the event is onmouseover.
Giving multiple elements the same ID won't work. document.getElementById() will just return the first element with that ID, not the one that the mouse is over.
You don't need to use the ID at all. You can use this in the event handler to refer to the target of the event.
var abcElements = document.querySelectorAll('.builder_row_cover');
for (var i = 0; i < abcElements.length; i++) {
abcElements[i].addEventListener('mouseover', flip);
}
var k = 0;
function flip()
k += 180;
this.style.transform = "rotatey(" + k + "deg)";
this.style.transitionDuration = "0.5s";
}

I'm trying to make a div width expand once clicking on another div

Im trying to make a div expanded once you click on another div. In my case I'm try to make div with some text in it expand when the image is clicked. A link to my JSFiddle - https://jsfiddle.net/txoyuvqn/3/
My javascript that I am using looks like.
var divs = document.getElementsByClassName('image');
var whattochange = document.getElementsByClassName('text');
for (var i = 0; i < divs.length; i++)
divs[i].addEventListener("click", function () {
for (var i = 0; i < whattochange.length; i++) {
whattochange[i].style.width = '500px'
whattochange[i].style.transition = 'all 1s'
whattochange[i].style.backgroundColor = 'red'
}
}, false);
However when I click on the class called image it effects all the Text classes, i know it's because were changing the css to all of the text divs, however is there a way to make it only effect the correlating div? Or am I going about creating this in the wrong way?
getElementsByClassName returns an array, not a single element.
divs is an array, and you are correctly using a for loop and the index indicator [i] after your variable name divs.
You need a similar for loop for whattochange.
var divs = document.getElementsByClassName('image');
var whattochange = document.getElementsByClassName('text');
for (var i = 0; i < divs.length; i++)
divs[i].addEventListener("click", function () {
for (var i = 0; i < whattochange.length; i++) {
whattochange[i].style.width = '800px';
whattochange[i].style.transition = 'all 1s';
whattochange[i].style.backgroundColor = 'red';
}
}, false);
There may be a better way, but you could do it like this:
var divs = document.getElementsByClassName('image');
var whattochange = document.getElementsByClassName('text');
for (var i = 0; i < divs.length; i++)
{
divs[i].addEventListener("click", function()
{
var w = document.getElementById(this.id.replace('img', 'text'));
w.style.width = '800px'
w.style.transition = 'all 1s'
w.style.backgroundColor = 'red'
});
whattochange[i].id = 'text' + i;
divs[i].id = 'img' + i;
}
See the fiddle
Javascript
var divs = document.getElementsByClassName('image');
var whattochange = document.getElementsByClassName('text');
for (var i = 0; i < divs.length; i++) {
divs[i].addEventListener("click", function () {
for (var i = 0; i < whattochange.length; i++) {
whattochange[i].style.width = '800px';
whattochange[i].style.transition = 'all 1s';
whattochange[i].style.backgroundColor = 'red';
}
}, false);
}
You have to be sure that the elements exist if your JavaScript code depends on them. The reason why your fiddle didnt work was because, you was not loading the script after the body has finished loading.
In your code, One way of achieving this is by putting the <script> tag at the end of the body like this:
<html>
<head></head>
<body>
<script type="text/javascript">
// code here
</script>
</body>
You can also put all your code in a function for the window.onload event or use jQuery.

How do I change an html cell's color on click, if the table is generated dynamically?

function drawTable() {
var table = document.getElementById("myTable");
var input = prompt("Insert height (in number of cells)");
var a = +input;
var input2 = prompt("Insert width (in number of cells)");
var b = +input2;
for (var i = 0; i < a; i++) {
var row = table.insertRow(i);
for (var j = 0; j < b; j++) {
var cell = row.insertCell(j);
};
};
};
I can't for the life of me figure out how to add a onClick event that would change the color of a cell. Do I create a new function in JavaScript and add an onClick event to the table element? That's what I did, but it doesn't seem to work.
function changeColor() {
var td = document.getElementsById("myTable").getElementsByTagName("td");
td.style.backgroundColor = "red";
}
for (var j = 0; j < b; j++) {
var cell = row.insertCell(j);
cell.addEventListener("click", changeColor.bind(cell), false);
};
function changeColor(e) {
this.style.backgroundColor = "red";
}
Should do the trick. Every cell gets an onclick handler set in the for loop. Bind passes the reference of the cell to the changeColor function. The function can address the cell by using this.
For some situations, the answer suggested by Mouser work well. But if consider a situation taking your example of table creation based on number of rows and columns, then adding eventlistener to each cell doesn't sound a good approach. Suppose at initial user requested for 10X10 table. At that moment,
eventlistener is added to each cell.
But what if at some later point of time, more rows/columns are added dynamically. In that situation, only thing you will left with is to add event listeners.
Better approach is to understand the term
Event Delegation
In this approach, you add event listener to parent and just listen to event bubbled up(default behavior) by the child elements.In that case you dont have to be worry about dynamically created cells and adding event listeners to those.
You can take a look on working sample with Event Delegation approach on your code at below link:
http://jsfiddle.net/zL690Ljb/1/
function drawTable() {
var table = document.getElementById("myTable");
table.addEventListener("click", changeColor);
var input = prompt("Insert height (in number of cells)");
var a = +input;
var input2 = prompt("Insert width (in number of cells)");
var b = +input2;
for (var i = 0; i < a; i++) {
var row = table.insertRow(i);
for (var j = 0; j < b; j++) {
var cell = row.insertCell(j);
//cell.addEventListener("click", changeColor.bind(cell), false);
};
};
};
function changeColor(event) {
if (event.target && event.target.tagName && (!event.target.tagName.match(/table|th|tr|tbody|thead/i)) )
{
var element = event.target;
while (element && element.parentElement)
{
if(element.tagName=='TD'){
element.style.backgroundColor = "red";
break;
}
else
{
element = element.parentElement;
}
}
}
}
drawTable();
I hope Mouser will agree on this. Thanks!
Inside the for you can add the onclick event for each cell:
for (var i = 0; i < a; i++) {
var row = table.insertRow(i);
for (var j = 0; j < b; j++) {
var cell = row.insertCell(j);
cell.onclick = function(){
changeColor(this);
}
};
};
Then the changeColor will be as following:
function changeColor(cell) {
cell.style.backgroundColor = "red";
}

Categories