var change = function() {
var elem = document.getElementsByTagName("body");
var count = 0;
count++;
var color = "";
var colors = ["#ff6051", "#ff9f51", "#ffdf51", "#b6ff51", "#51adff", "#3e65c1", "#6414ef"];
for (var i = 0; i < colors.length; i++) {
if (count == i + 1) {
color = colors[i];
}
}
elem[0].style.backgroundColor = color;
}
<button onclick="change()">Click me</button>
I want the background color of the body to change when I click the button.
But the number of variable "count" doesn't seem to increase. What should I do to make the number increase?
Declare the variabe count outside the function so that it gets the global scope whenever you update.
DEMO
var count = 0;
var change = function() {
var elem = document.getElementsByTagName("body");
count++;
console.log('##count',count);
var color = "";
var colors = ["#ff6051", "#ff9f51", "#ffdf51", "#b6ff51", "#51adff", "#3e65c1", "#6414ef"];
for (var i = 0; i < colors.length; i++) {
if (count == i + 1) {
color = colors[i];
}
}
elem[0].style.backgroundColor = color;
}
<button onclick="change()">Click me</button>
Without changing your code too much, you can avoid the loop and iterate over the array by comparing the current color. This also avoids holding a count iterator.
Note:
Some browsers return backgroundColor as an rgb value (e.g., rgb( ###, ###, ###)), which is why rgb2hex is used to convert the it to the hex value like that stored in the colors array.
var change = function() {
var el = document.querySelector("body");
var colors = ["#ff6051", "#ff9f51", "#ffdf51", "#b6ff51", "#51adff", "#3e65c1", "#6414ef"];
var currentColor = rgb2hex( el.style.backgroundColor );
var colorIndex = colors.indexOf( currentColor );
// If at last color, cycle back to front
if (colorIndex == colors.length-1)
colorIndex = -1;
el.style.backgroundColor = colors[colorIndex + 1];
}
/** Converts decimal to hex **/
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
/** Converts rgb string to hex string **/
function rgb2hex(rgb) {
if (rgb.search("rgb") == -1)
return rgb;
else {
rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))?\)$/);
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
}
<button onclick="change()">Click me</button>
Each call to your function is resetting count to 0 because you are setting it to zero on the second line of the function.
If you set it to 0 outside the function once, this will solve the count problem.
However, there is an additional problem (that you didn't mention): after counting to 7, you run out of colours in your array because count exceeds the bounds of the array. I would lose the for loop since it is unnecessary (use count to index into the array instead) and just reset count when it reaches the size of the array.
var count = 0;
var colors = ["#ff6051", "#ff9f51", "#ffdf51", "#b6ff51", "#51adff", "#3e65c1", "#6414ef"];
var change = function() {
var elem = document.getElementsByTagName("body");
if (count == colors.length) {
count = 0;
}
elem[0].style.backgroundColor = colors[count];
count++;
}
<button onclick="change()">Click me</button>
The count variable has local scope, so it will not exist after the anonymous function expression referred by change variable finishes execution. For it to sustain its life time across repeated function calls on button click action it should be declared in global scope outside the anonymous function expression:
var count = 0; //now count has global scope.
var change = function() {
var elem = document.getElementsByTagName("body");
count++;
var color = "";
var colors = ["#ff6051", "#ff9f51", "#ffdf51", "#b6ff51", "#51adff", "#3e65c1", "#6414ef"];
for (var i = 0; i < colors.length; i++) {
if (count == i + 1) {
color = colors[i];
}
}
elem[0].style.backgroundColor = color;
}
<button onclick="change()">Click me</button>
Note: You should also consider giving some default color inside the for loop as after 7 clicks it will be setting the backgroundColor to empty string.
var color = "#000000"; //default black color may be
To achieve expected result, use below option
No need of for loop
After 7 clicks , loop runs again
One issue with your code is, first background color will always be skipped due i+1 and after 7 clicks , it comes back to white background
var count = 0;
var colors = ["#ff6051", "#ff9f51", "#ffdf51", "#b6ff51", "#51adff", "#3e65c1", "#6414ef"];
var change = function() {
if(count == colors.length + 1){
count =0;
}
++count;
var elem = document.getElementsByTagName("body");
elem[0].style.backgroundColor = colors[count];
}
https://codepen.io/divyar34/pen/EbZxPm
You can generically count the invocations of any function by "lifting" the function (wrapping it) with a function that just does that.
This leaves you with a simple function that doesn't need to know if it is being counted, which you can wrap with a counter only when you need it.
function countingWrapper(f,reportf)
{
var counter = 0;
return function()
{
reportf( ++counter)
return f.apply(this, arguments);
}
}
function doSomething() { console.log('boo!'); }
function reportSomething(n) { console.log('did something '+ n + ' times.')}
var newDoSomething = countingWrapper(doSomething, reportSomething);
newDoSomething();
newDoSomething();
newSoSomething();
Related
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 have a string of random 1's and 0's displayed via jQuery. I would now like to select a random number and change it's color. Is it better to work with an array, or a $(div).text() string? I can grab a number from either, but how do I insert it back into the div?
var numbArray = [];
for(i=0; i<10; i++)
{
var randomNumbers = Math.round(Math.random());
$('#numbs').prepend(randomNumbers);
numbArray[i] = randomNumbers;
}
<div id="numbs">0000110111 </div>
The div above is the result of the code, but how do I select a random item, change its color, and display in the original output?
Thanks,
You can locate the number at a certain index, wrap it with the desired color and rebuild the string and set it back to the div using html() and use Math.floor(Math.random() * 10) to generate the random number from zero to the length of the characters you have.
var index = 3;
var originalElementValue;
function colorStringValue(strIndex)
{
strIndex = parseInt(strIndex);
var character = originalElementValue.charAt(strIndex);
$("#numbs").html(originalElementValue.substr(0, strIndex) + "<span style='color:red'>" + character + "</span>" + originalElementValue.substr(strIndex+1));
}
$(document).ready(function(){
originalElementValue = $("#numbs").text();
colorStringValue(index);
$("#strIndex").click(function(){
var rand = Math.floor(Math.random() * 10) + 0 ;
$("#rand").html(rand);
colorStringValue(rand);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="strIndex" > Generate Random Number </button>
<br />
Random Number : <span id="rand"></span>
<br />
<div id="numbs">0000110111</div>
You need to pick a random index from the number string and append some element around that particular number to give it some style.
var number = '0000110111';
var index = Math.floor(Math.random() * number.length);
for(var i = 0; i < number.length; i++) {
var n = number.charAt(i);
if(i == index) {
$('#numbs').append($('<span/>').css('color', 'red').text(n));
} else {
$('#numbs').append(n);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="numbs"></div>
var numbArray = [];
for(i = 0; i< 10; i++) {
var randomNumbers = Math.round(Math.random());
numbArray[i] = randomNumbers;
$('#numbs').prepend(number);
}
var randomNumberSelection = numbArray[Math.floor((Math.random() * (numbArray.length-1)) + 1)];
$('#numbs').html("");
var number;
for(number in numbArray) {
if(number == randomNumberSelection) {
var colorColorCodedNumber = ""+number;
colorColorCodedNumber = colorColorCodedNumber.fontcolor("blue");//blue!
$('#numbs').prepend(colorColorCodedNumber);
} else {
$('#numbs').prepend(number);
}
}
I believe you're looking for something along the lines of this, or at least this is what I took from what you were asking.
In this example be aware you'll see we clear the element then simply reiterate over the array you stored earlier. That is how you 'update' it.
If I understand the question right you want to set a color for a specific position in the div. This means you have to create a span (or another html-element) inside the div at at a random position with a specific color. I havent tested this code below but I guess you could something like this: (in this example red color for the random item)
var randomIndex= Math.floor(Math.random() * 9); //Random number between 0 and 9.
var currentContent = $("#numbs").html();
var randomItem= currentContent.charAt(randomIndex);
newContent = '';
for(i=0; i<10; i++) {
if (i == randomIndex) {
newContent = newContent +
'<span style="background:red;">' + randomItem + '</span>';
}
else {
newContent = newContent + currentContent.charAt(i);
}
}
$("#numbs").html( newContent );
Is this what you are looking for? I just gave it a try. :)
var numbArray = [];
var sample = "<span style='color:#%COLOR%'>%NUM%</span>";
for(i=0; i<10; i++)
{
var randomNumbers = Math.round(Math.random());
var html = sample.replace('%NUM%', randomNumbers);
var randomColor = Math.round((Math.random()* 100000000 )%16777215).toString(16);
html = html.replace('%COLOR%', randomColor);
$('#numbs').prepend(html );
numbArray[i] = randomNumbers;
}
I assumed that you want random colors too.
Good answers by all; thanks! I didn't think of appending the DOM and redisplaying. I went with assigning each number an id and then using css without appending. I was looking for numbers that would turn a color and when all the numbers were that color the script would stop. I don't know which method would perform the best but this way is okay for my limited numbers.
var whiteNumbs =
[0,1,1,0,1,1,0,1,0,1,1,0,0,0,0,1,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0]
for(var i=0; i<whiteNumbs.length; i++)
{
$("#numbs").append('<span class="white" id="num_' + i + '">' +
whiteNumbs[i] + '</span>');
}
function MakeRed()
{
var randomNumber = Math.floor(Math.random() * whiteNumbs.length-1);
var changeCSS = "#num_" + randomNumber;
$(changeCSS).removeClass('white');
$(changeCSS).addClass("red");
if ($("#numbs span").hasClass("white") )
{
setTimeout(MakeRed,1000);
}
else
{
return false;
}
};
MakeRed();
Please scroll down to bold text if you want to go straight to the question
I have made a page that consists of a grid of 9 tiles (divs).
Between 1-9 of those tiles could potentially have a slider inside it.
The sliders are all setup via a jQuery each function e.g
_gridSlider.each(function(){
// count slides, setup slider etc
}); // end slider each function
Everything works fine except the sliders all change at the same time and so I want to add some diversity into the start times.
Right now I create a random ID between 1 and X (X being the number of sliders) inside of the each function for each slider like so
_gridSlider.each(function(){
var _sliderID = Math.floor((Math.random() * _numSliders) + 1);
}); // end slider each function
I then start the sliders at a different time based around this ID like so
var _sliderStart = _sliderID + '000';
setTimeout(function() {
startTimer();
}, _sliderStart);
This works fine the only problem is that it is possible for 2 or more sliders to have the same ID, what I need is to assign each slider an ID between 1 and X but make sure that each slider has a different ID.
The end result would be have 1 timer starting at 1 second, another at 2 seconds, another at 3 seconds etc
You can use this function:
function generateId(numSliders) {
var store = generateId._store;
if (!store) {
generateId._store = {};
}
do {
var id = Math.floor(Math.random()*numSliders*1000+1);
} while (store[id])
store[id] = true;
return id;
}
Then you can use generated id as a start time:
var sliderId = generateId(slidersNumber);
var sliderStart = sliderId; // without + '000'
UPD generateId._store keeps used IDs inside itself. In that function store is used as a property of its function, to not add redundant variable to the namespace. You can put it outside of the generateId function. For example:
var store = {};
function generateId(numSliders) {
do {
var id = Math.floor(Math.random()*numSliders*1000+1);
} while (store[id])
return id;
}
But in that case you're polluting the namespace with redundant variable.
store inside of the function is used just to shorten the code a little. If you want you can write:
function generateId(numSliders) {
if (!generateId._store) {
generateId._store = {};
}
do {
var id = Math.floor(Math.random()*numSliders*1000+1);
} while (generateId._store[id])
generateId._store[id] = true;
return id;
}
An example using shuffle.
function createNumberSeries(howMany) {
var result = [];
for (var count = 1; count <= howMany; count += 1) {
result.push(count);
}
return result;
}
function shuffle(obj) {
var i = obj.length;
var rnd, tmp;
while (i) {
rnd = Math.floor(Math.random() * i);
i -= 1;
tmp = obj[i];
obj[i] = obj[rnd];
obj[rnd] = tmp;
}
return obj;
}
function createDivs(ids) {
var length = ids.length;
for (var index = 0; index < length; index += 1) {
var div = document.createElement('div');
div.id = ids[index];
div.className = 'initial';
document.body.appendChild(div);
}
}
function colourDivs(howMany) {
for (var index = 1; index <= howMany; index += 1) {
setTimeout((function(id) {
return function() {
document.getElementById(id).className += ' colorMe';
};
}(index)), index * 1000);
}
}
var numSliders = 10;
var sliderIds = shuffle(createNumberSeries(numSliders));
createDivs(sliderIds);
colourDivs(numSliders);
.initial {
height: 10px;
width: 10px;
border-style: solid;
border-width: 1px;
}
.colorMe {
background-color: blue
}
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);
}
}
I'm currently working on a little jquery project. I want to build Conway's game of life with javascript/jquery/html. But I can't figure out, how to detect if a cell has alive neighbours. But I know I have to make use of Arrays.
Here what I came up with so far:
$(document).ready(function () {
var $create_grid = $('#create_grid');
var $run = $('#run');
var $reset = $('#reset');
var $random = $('#random');
var $cells = {};
var $active_cells = {};
$create_grid.click(function () {
var width = $("[name='width']").val();
var height = $("[name='height']").val();
var cellsize = $("[name='cellsize']").val();
var $table = $('#game');
if (width.length != 0 && height.length != 0 && cellsize.length != 0) {
for (i = 1; i <= height; i++) {
$('table').append('<tr id="' + i + '"></tr>');
}
for (i = 1; i <= width; i++) {
$('table tr').append('<td class="test" id="' + i + '"></td>');
}
$cells = $('table#game td');
$cells.css('width', cellsize);
$cells.css('height', cellsize);
} else { alert("Please fill out all the fields!"); }
$create_grid.hide('fast');
$('ul.parameters').hide('fast');
$random.css('display', 'block');
$reset.css('display', 'block');
//RESET CELLS
$reset.click(function () {
$cells.removeClass('alive');
});
//DRAW CELLS
var isDown = false;
$cells.mousedown(function () {
isDown = true;
})
.mouseup(function () {
isDown = false;
});
$cells.mouseover(function () {
if (isDown) {
$(this).toggleClass('alive');
}
});
$cells.click(function () {
$(this).toggleClass('alive');
});
});
//RANDOM PATTERN
function shuffle(array) {
var m = array.length, t, i;
// While there remain elements to shuffle…
while (m) {
// Pick a remaining element…
i = Math.floor(Math.random() * m--);
// And swap it with the current element.
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
}
$random.click(function () {
$(shuffle($cells).slice(0, 30)).addClass("alive");
});
//RUN SIMULATION
$run.click(function simulate() {
//GET NEIGHBOUR CELLS
$cells_alive = $('#game td.alive').length;
for (var c = 1; c <= $cells_alive; c++) {
alert(c);
};
});
});
Your assigned ids are not unique. All rows and all columns have ids from 1..n respectively 1..m. So every number from 1..min(n,m) is used twice. You should change this.
You might also want to assign some classes or data-attributes or just anything which makes it possible to actually select any of the html elements you create.
This for example sets some data-attributes to all tr and td tags.
for (i = 1; i <= height; i++) {
var elem = $('<tr></tr>');
elem.data('column', i);
$('table').append(elem);
}
for (i = 1; i <= width; i++) {
var elem = $('<td></td>');
elem.data('row', i);
$('table tr').append();
}
$cells = $('table#game td');
$cells.css('width', cellsize);
$cells.css('height', cellsize);
If you have coordinates (x, y) you may select all neighbors like
$('tr[data-column='+(x-1)+'] td[data-row='+y+'],
tr[data-column='+(x+1)+'] td[data-row='+y+'],
tr[data-column='+x+'] td[data-row='+(y-1)+'],
tr[data-column='+x+'] td[data-row='+(y+1)+']');
(You might want to consider class instead for efficiency reasons. Although I do not know if this makes a notable difference.)
EDIT:
Here's a question about the performance of data- vs class selectors: Are data attribute css selectors faster than class selectors?