In this function, it should give the menu items (li's) an specific background (png) out of an array. However; it doesn't. It gives all the li's the background called color 'blue' :(.
Do you see the problem?
//Gives the menu items a style when hovering over it, in the sequence of the setting in the variable 'backgrounds', on top.
var backgrounds = ["blue", "green", "pink", "purple"];
function MenuColorChange() {
for (var i = 0; i <= 10 ; i++) {
document.getElementById("custom-menu-item-id-" + (i + 1)).onmouseover = function() {
this.style.backgroundImage= "url(images/" + backgrounds[(i % backgrounds.length)] + ".png)";
}
document.getElementById("custom-menu-item-id-" + (i + 1)).onmouseout = function() {
this.style.background = 'none';
MenuActiveColor();
}
}
}
Html:
<ul>
<li id="custom-menu-item-id-1">
<a href="#">
Home
</a>
</li>
/* And 3 li's more... */
</ul>
The function you use for onmouseover is a closuse of the outer function, in the time it is executed all onmouseover handlers have the save value of i, to achieve what you seen to want do:
//Gives the menu items a style when hovering over it, in the sequence of the setting in the variable 'backgrounds', on top.
var backgrounds = ["blue", "green", "pink", "purple"];
function MenuColorChange() {
for (var i = 0; i <= 10 ; i++) {
document.getElementById("custom-menu-item-id-" + (i + 1)).onmouseover = (function(valueOfI){ return function() {
this.style.backgroundImage= "url(images/" + backgrounds[(valueOfI % backgrounds.length)] + ".png)";
}; })(i);
document.getElementById("custom-menu-item-id-" + (i + 1)).onmouseout = function() {
this.style.background = 'none';
MenuActiveColor();
}
}
}
This surprises me. I would expect it to make all the backgrounds pink. The reason this happens is because by the time you actually hover over any of your <li> elements, i will be 10, and 10 % 4 = 2. Index #2 of your array is 'pink'.
To ensure that i is the value you want when the mouseover and mouseout events are fired, wrap them in a function.
function MenuColorChange() {
for (var i = 0; i <= 10 ; i++) {
(function(i) {
document.getElementById("custom-menu-item-id-" + (i + 1)).onmouseover = function() {
this.style.backgroundImage = "url(images/" + backgrounds[(i % backgrounds.length)] + ".png)";
}
document.getElementById("custom-menu-item-id-" + (i + 1)).onmouseout = function() {
this.style.background = 'none';
MenuActiveColor();
}
}(i));
}
}
Here is an explanation that may help: variables-in-anonymous-functions
Related
so here is the code i have so far, now i want to remove the button whenever the color is at the end of the array but i dont get it to work i tried different things with an if statement like this:
if(color.length){
document.getElementById("button").remove();
}
and with "removechild" but none of these works does anyone have an solution?
var color = ["green", "red", "black"];
function page() {
document.body.style.backgroundColor = "grey";
//style page
createButtons(10);
}
page();
function onbuttonclicked(a) {
var Amount = document.getElementById("button" + a);
var click = Amount.getAttribute('Color');
var change = color.indexOf(click);
Amount.setAttribute('style', 'background-color:' + color[change + 1]);
Amount.setAttribute('Color', color[change + 1]);
if(color.length){
document.getElementById("button").remove();
}
}
function set_onclick(amount) {
for (var a = 1; a < (amount + 1); a++) {
document.getElementById("button" + a).setAttribute("onclick", "onbuttonclicked(" + a + ")");
}
}
function createButtons(amount) {
for (var a = 1; a <(amount + 1); a++) {
var button = document.createElement("button");
button.id = "button" + a;
button.innerHTML = "button " + a;
button.setAttribute('Color', color[0]);
button.setAttribute('style', 'background-color:' + color[0]);
container.appendChild(button);
}
set_onclick(amount);
}
so for example i have a few green buttons when you click on the buttons the color changes a few times, the last color is black if the button is black and you click on it then i want to hide the buttons so you dont see it anymore
In order to check if the background color is the same as the last color, simply compare the elements (you named it Amount) background color with the last element in the array. And you also have to put the if statement BEFORE changing the color.
if (Amount.style.backgroundColor == color[color.length-1]) {
Amount.remove();
However you should re-think about your naming since it can get a little confusing.
For example: Amount is definetly the wrong name since it refers to the clicked element. The parameter you pass (a) is the amount.
If you have any questions feel free to ask them.
let color = ["green", "red", "purple", "blue", "black"];
function page() {
document.body.style.backgroundColor = "grey";
//style page
createButtons(30);
}
page();
function onbuttonclicked(a) {
var Amount = document.getElementById("button" + a);
var click = Amount.getAttribute('Color');
var change = color.indexOf(click);
if (Amount.style.backgroundColor == color[color.length - 1]) {
Amount.remove();
} else {
Amount.setAttribute('style', 'background-color:' + color[change + 1]);
Amount.setAttribute('Color', color[change + 1]);
}
}
function set_onclick(amount) {
for (var a = 1; a < (amount + 1); a++) {
document.getElementById("button" + a).setAttribute("onclick", "onbuttonclicked(" + a + ")");
}
}
function createButtons(amount) {
for (var a = 1; a < (amount + 1); a++) {
var button = document.createElement("button");
button.id = "button" + a;
button.innerHTML = "button " + a;
button.setAttribute('Color', color[0]);
button.setAttribute('style', 'background-color:' + color[0]);
document.getElementById("container").appendChild(button);
}
set_onclick(amount);
}
<div id="container">
</div>
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>
I need to add two colors to this code; red to even numbers and blue to odd...
now I tried to use some module to check which are the odds and even...but without luck..
<head>
<script>
for(var i=0;i<=9;i++)
{
for(var j=1;j<=10;j++)
{
if(i*10+j<10)
document.write(" "+" ");
if((i*10+j)%7==0)
{
//document.write("<b>");
document.write(i*10+j+" ");
//document.write("</b>");
}
else
document.write(i*10+j+" ");
if(j==10)
document.write("<br/>");
}
}
</script>
</head>
<body>
</body>
I've added an answer that you might find useful going forward.
Slightly smaller code footprint
It doesn't have nested loops
It doesn't rely on document.write. Instead it uses string concatenation to build up the HTML and then adds that string to the document. This is more efficient.
It calls a separate function to build the span using a joined array.
JavaScript
var div = document.getElementById('out');
var out = '';
var getSpan = function (i) {
return ['<span class="', (i % 2 === 0 ? 'red' : 'blue'), '">', i, '</span>'].join('');
}
for (var i = 1, l = 100; i <= l; i++) {
if (i % 10 === 0) {
out += getSpan(i) + '<br/>';
} else {
out += getSpan(i) + ' ';
if (i <= 10) out += ' ';
}
}
div.insertAdjacentHTML('beforeEnd', out);
DEMO
What about this:
JavaScript
for(var i=0;i<=9;i++)
{
for(var j=1;j<=10;j++)
{
document.write("<span style='color:" + (Math.floor(j/2)*2 === j ? "red" : "blue") + ";'>");
if(i*10+j<10)
document.write(" "+" ");
if((i*10+j)%7==0)
{
//document.write("<b>");
document.write(i*10+j+" ");
//document.write("</b>");
}
else
document.write(i*10+j+" ");
document.write("</span>");
if(j==10)
document.write("<br/>");
}
}
See this fiddle.
So here is an example of the function I need to replicate:
document.getElementById('img1').onmouseover = function() {
document.getElementById('img1').style.width = expandTo + '%';
expandCompensate(1);
}
document.getElementById('img1').onmouseout = function() {
expandReset();
}
The situation is that I have a for loop creating some div elements, and the number of them is dynamic. As of right now, I have it creating 4 div elements, so I created 4 iterations of the above functions for img1, img2, img3 and img4. But what I would like to do is to have the onmouseover and onmouseout functions created dynamically based on how many div elements I've decided to create (based on a variable).
Is there any way to do this? Here is all the code for context (it's not much), there are comments in the JS with explanations for everything. The part I'm trying to automate is at the bottom:
https://jsfiddle.net/4w0714su/3/
And here is the working example for context of what I'm trying to achieve:
http://www.ericsartor.ca/imgwide
FYI: The image is I picked were random, I just needed high res images. Just doing this for practice! Thanks to anyone that can help me figure this out!
I can't understand your code very well, but I'll answer particularly what you're asking.
You can achieve what you want by doing a loop:
for (var i = 0; i < 4; i++) {
document.getElementById('img' + i).onmouseover = function() {
this.style.width = expandTo + '%';
expandCompensate(Number(this.id.replace('img', '')));
};
document.getElementById('img' + i).onmouseout = function() {
expandReset();
}
}
Note: you can't use the i variable inside the event handlers' functions, because it will always be 4, since it will finish the loop, and will never be changed again.
Another way of doing that is by using an IIFE (Immediately-invoked function expression), e.g:
for (var i = 0; i < 4; i++) {
(function(n) {
document.getElementById('img' + n).onmouseover = function() {
this.style.width = expandTo + '%';
expandCompensate(n);
};
document.getElementById('img' + n).onmouseout = function() {
expandReset();
}
})(i);
}
Doing that, you're passing to a function the current i value, so in that scope, the value of n will be a different one for each execution, e.g 0, 1, 2 and 3.
An immediately-invoked function expression (or IIFE, pronounced
"iffy") is a JavaScript design pattern which produces a lexical scope
using JavaScript's function scoping.
This could be achieved by iterating all those DOM elements and binding events in a loop.
As we bind events in loop, and event callback is being executed later when loop would be iterated completely, we need to maintaing the value of current iteration using CLOSURE.
Try this snippet:
var pageHeight = document.getElementById('findBottom').getBoundingClientRect().bottom,
numOfPics = 4; //the number of div elements to create
//creates the div elements within a container div in the HTML document
for (var i = 1; i <= numOfPics; i++) {
document.getElementById('imgContain').innerHTML += '<div id="img' + i + '" class="imgPane"></div>';
}
//used to resize all divs if the window changes size
window.onresize = function() {
pageHeight = document.getElementById('findBottom').getBoundingClientRect().bottom;
for (var i = 1; i <= imgClasses.length; i++) {
document.getElementById('img' + i).style.height = pageHeight + 'px';
}
for (var i = 1; i <= imgClasses.length; i++) {
document.getElementById('img' + i).style.width = 100 / imgClasses.length + '%';
}
};
//sets the height of each div to be the mximum height of the page (without scrolling)
for (var i = 1; i <= numOfPics; i++) {
document.getElementById('img' + i).style.height = pageHeight + 'px';
}
//makes all the divs equal percentage widths
for (var i = 1; i <= numOfPics; i++) {
document.getElementById('img' + i).style.width = 100 / numOfPics + '%';
}
//the percentage of the page the hovered image will expand to
var expandTo = 40;
//function for when an image is hovered over
function expandCompensate(whichImg) {
for (var i = 1; i <= numOfPics; i++) {
if (i != whichImg)
document.getElementById('img' + i).style.width = (100 - expandTo) / (numOfPics - 1) + '%';
}
}
//function for when the hovered image is left to reset the widths
function expandReset() {
for (var i = 1; i <= numOfPics; i++) {
document.getElementById('img' + i).style.width = 100 / numOfPics + '%';
}
}
(function bindEvents() {
for (var i = 1; i <= numOfPics; i++) {
document.getElementById('img' + i).onmouseover = (function(i) {
return function() {
document.getElementById('img' + i).style.width = expandTo + '%';
expandCompensate(i);
}
})(i);
document.getElementById('img' + i).onmouseout = function() {
expandReset();
};
}
})();
body,
p,
div {
margin: 0;
padding: 0;
}
body {} #findBottom {
position: absolute;
bottom: 0;
}
.imgPane {
float: left;
background-position: center;
transition: width 0.25s;
}
#img1 {
background-image: url('http://www.ericsartor.ca/imgwide/img//1.jpg');
}
#img2 {
background-image: url('http://www.ericsartor.ca/imgwide/img//2.jpg');
}
#img3 {
background-image: url('http://www.ericsartor.ca/imgwide/img//3.jpg');
}
#img4 {
background-image: url('http://www.ericsartor.ca/imgwide/img//4.jpg');
}
<div id="imgContain"></div>
<!-- ABSOLUTE ELEMENTS -->
<div id="findBottom"></div>
<!-- ABSOLUTE ELEMENTS -->
I have four buttons which has click able property. Clicking on button will make a div slide down and clicking again on same div should close the div. I want to add a condition like, when I have a div open, the click property on rest of the three buttons should be disabled, what I did is
for (var i = 1; i <= 4; i++) {
$(".slide" + i).click(function () {
var openTab = $(this).attr('class');
openTab = openTab.replace('slide', '');
var facetGroup = $(this).attr("key");
if ($('#panel').is(':visible')) {
buttonCloser(openTab);
} else {
buttonOpener(openTab, facetGroup);
}
});
}
function buttonCloser(m) {
for (var j = 1; j <= 4; j++) {
if (j != m) {
//alert(j);
$(".slide" + j).bind("click");
} else {
$(".slide" + j).css({
"background-color": " #fff5c3",
"color": "#000000"
});
}
}
$("#panel").slideUp("slow");
}
function buttonOpener(m, n) {
for (j = 1; j <= 4; j++) {
if (j != m) {
$(".slide" + j).unbind("click");
} else {
$(".slide" + j).css({
"background-color": "#293345",
"color": "#fff5c3"
});
}
}
$("#panel").slideDown("slow");
refreshFacet(n);
}
The problem with this code is that the first time I open a div by clicking on slider, the other three click events are disabled, bt when I close that div, it will nt re-enable its click property. so it wont open anything..
Without seeing your actual mark-up, I'd suggest that you use simple jQuery, rather than mixing and matching between 'plain' JavaScript and jQuery:
var buttons = $('button[class^="slide"]'),
panel = $('#panel');
$(buttons).click(
function() {
var that = $(this);
if (panel.is(':visible')) {
if (that.hasClass('opener')) {
panel.slideToggle();
that.removeClass('opener');
}
else {
return false;
}
}
else {
panel.slideToggle();
that.addClass('opener');
}
});
JS Fiddle demo.
References:
addClass().
attribute begins with selector attribute^="value" selector.
hasClass().
removeClass().
slideToggle().