Changing classes with setInterval - javascript

I've made a 5x5 grid of tiles, and I'd like to create a tile that changes its class every 2 seconds.
Essentially, this tile would be turning, facing Up, Left, Down, Right -- in that particular order.
Right now, I'm putting all elements with a particular class into a nodeList/array.
Then, I iterate through each element, replacing the current color/class with the new one.
This kind of works, but appears to skip certain tiles, giving me wonky performance.
What am I doing wrong here?
function rotateTile(){
var tattleTowerUpArray = document.getElementsByClassName("tattleTowerUp");
var tattleTowerLeftArray = document.getElementsByClassName("tattleTowerLeft");
var tattleTowerDownArray = document.getElementsByClassName("tattleTowerDown");
var tattleTowerRightArray = document.getElementsByClassName("tattleTowerRight");
for(var i=0; i < tattleTowerUpArray.length; i++){
document.getElementById(tattleTowerUpArray.item(i).id).style.borderTopColor = "black";
document.getElementById(tattleTowerUpArray.item(i).id).style.borderLeftColor = "red";
document.getElementById(tattleTowerUpArray.item(i).id).classList.remove("tattleTowerUp");
document.getElementById(tattleTowerUpArray.item(i).id).classList.add("tattleTowerLeft");
}
for(var j=0; j < tattleTowerLeftArray.length; j++){
document.getElementById(tattleTowerLeftArray.item(j).id).style.borderLeftColor = "black";
document.getElementById(tattleTowerLeftArray.item(j).id).style.borderBottomColor = "red";
document.getElementById(tattleTowerLeftArray.item(j).id).classList.remove("tattleTowerLeft");
document.getElementById(tattleTowerLeftArray.item(j).id).classList.add("tattleTowerDown");
}
for(var k=0; k < tattleTowerDownArray.length; k++){
document.getElementById(tattleTowerDownArray.item(k).id).style.borderBottomColor = "black";
document.getElementById(tattleTowerDownArray.item(k).id).style.borderRightColor = "red";
document.getElementById(tattleTowerDownArray.item(k).id).classList.remove("tattleTowerDown");
document.getElementById(tattleTowerDownArray.item(k).id).classList.add("tattleTowerRight");
}
for(var l=0; l < tattleTowerRightArray.length; l++){
document.getElementById(tattleTowerRightArray.item(l).id).style.borderRightColor = "black";
document.getElementById(tattleTowerRightArray.item(l).id).style.borderTopColor = "red";
document.getElementById(tattleTowerRightArray.item(l).id).classList.remove("tattleTowerRight");
document.getElementById(tattleTowerRightArray.item(l).id).classList.add("tattleTowerUp");
}
}

Fixed your code and tried to comment it. You need to add class 'tattleTower' to EVERY tile element for this to work.
Just a couple of points:
1) getElementsByClassName returns a live NodeList. That means it looks for elements every time you access it(like when you access it's length property);
2) You can avoid using 4 loops and use just 1 loop with if statements, if you add a common class to ALL elements. Like in this example you could add a class 'tattleTower'. So each element would have 2 classes. eg: class='tattleTower tattleTowerLeft'.
3) I don't quite understand why you decided to change border styles with js. You could do it in CSS in those classes. You can explicitly define what properties you want transitions to work on if you're worried about it.
4) You don't need to use id to access a particular element within a loop. You can use el[i]. Eg. el[0] will give you the first element of the array.
5) Try to cache as much as possible by using variables if you perform expensive operations.
function rotateTile(){
// set up all variables at the top of the function
// use querySelectorAll for static NodeList, instead of live nodeList
var tattleTowerArray = document.querySelectorAll(".tattleTower"),
el, i, len, elClassList, elStyle;
// use one loop instead of four
// cache array's length for performance (to avoid asking for it on each iteration)
for (i = 0, len = tattleTowerArray.length; i < len; i++){
el = tattleTowerArray[i]; // cache element for performance
elClassList = el.classList; // cache element's classList
elStyle = el.style; // cache element's style object
// use 'if-else if' statements to check for class (you can change it to switch block, but i don't think it'd be best here)
if (elClassList.contains('tattleTowerUp')) {
elStyle.borderTopColor = "black";
elStyle.borderLeftColor = "red";
elClassList.remove("tattleTowerUp");
elClassList.add("tattleTowerLeft");
} else if (elClassList.contains('tattleTowerLeft')) {
elStyle.borderLeftColor = "black";
elStyle.borderBottomColor = "red";
elClassList.remove("tattleTowerLeft");
elClassList.add("tattleTowerDown");
} else if (elClassList.contains('tattleTowerDown')) {
elStyle.borderBottomColor = "black";
elStyle.borderRightColor = "red";
elClassList.remove("tattleTowerDown");
elClassList.add("tattleTowerRight");
} else if (elClassList.contains('tattleTowerRight')) {
elStyle.borderRightColor = "black";
elStyle.borderTopColor = "red";
elClassList.remove("tattleTowerRight");
elClassList.add("tattleTowerUp");
}
}
}

Related

Using array to store elements doesn't let you change the properties

const divArr = [];
for (let i = 0; i < 5; i++) {
document.getElementById("h").innerHTML += `<div id=${i}>hello</div>`;
divArr.push(document.getElementById(`${i}`));
}
console.log(divArr);
let divArrIndex = 0;
setInterval(() => {
document.getElementById(`${divArrIndex}`).style.backgroundColor = 'green';
if (divArrIndex > 4) {
divArrIndex = 0;
}
divArrIndex += 1;
}, 1000 / 1);
<div id="h">alsdjf;lkajsdf</div>
The code above successfully turns all the divs green.
But, when I use
divArr[divArrIndex].style.backgroundColor = "green"
instead of
document.getElementById(`${divArrIndex}`).style.backgroundColor='green';
I only get the last div green.
Why?
codepen: https://codepen.io/sai-nallani/pen/KKopOXZ?editors=1111
By reassignment to innerHTML, you are destroying and recreating the contents of #h in each iteration of the loop. You create #0, then discard it and create #0 and #1, then discard those and create #0, #1, #2... So the elements you push into the array don't exist any more in your document (though references to them in divArr still keep the garbage collector from destroying them outright).
When you change the colour of divArr[0], you are changing the colour of an element that only exists in memory, but is not in DOM any more. However, #4 is still the original #4, it has not been discarded, since you have performed no further assignments to innerHTML.
One solution is to gather all the divs after you have constructed them all. You can use another loop, but the easiest way would be:
const divArr = Array.from(document.querySelectorAll('#h > div'));
(Depending on what you are doing with it next, you may not need Array.from since NodeList should suffice for many purposes.)
Another solution is to construct elements in their own right, not by changing the parent's innerHTML:
const hEl = document.querySelector('#h');
for (let i = 0; i < 5; i++) {
const divEl = document.createElement('div');
divEl.textContent = 'Hello';
divEl.id = i;
hEl.appendChild(divEl);
divArr.push(divEl);
}
This way, every element is created and added to #h without affecting any other elements already there.

Using querySelectorAll to get ALL elements with that class name, not only the first

I've ditched jquery about 9(ish) months ago and needed a selector engine (without all the hassle and don't mind ie<7 support) so i made a simplified version of document.querySelectorAll by creating this function:
// "qsa" stands for: "querySelectorAll"
window.qsa = function (el) {
var result = document.querySelectorAll(el)[0];
return result;
};
This works perfectly fine for 95% of the time but I've had this problem for a while now and i have researched mdn, w3c, SO and not to forget Google :) but have not yet found the answer as to why I only get the first element with the requested class.
And I know that only the first element being returned is caused by the "[0]" at the end, but the function won't work if I remove it so I've tried to make a for loop with an index variable that increases in value depending on the length of elements with that class like this:
window.qsa = function (el) {
var result, el = document.querySelectorAll(el);
for(var i = 0; i < el.length; ++i) {
result = el[i];
}
return result;
};
Again that did not work so I tried a while loop like this:
window.qsa = function (el) {
var result, i = 0, el = document.querySelectorAll(el);
while(i < el.length) {
i++;
}
result = el[i];
return result;
};
By now I'm starting to wonder if anything works? and I'm getting very frustrated with document.querySelectorAll...
But my stubborn inner-self keeps going and I keep on failing (tiering cycle) so I know that now is REALLY the time to ask these questions :
Why is it only returning the first element with that class and not all of them?
Why does my for loop fail?
Why does my while loop fail?
And thank you because any / all help is much appreciated.
Why is it only returning the first element with that class and not all of them?
Because you explicitly get the first element off the results and return that.
Why does my for loop fail?
Because you overwrite result with a new value each time you go around the end of loop. Then you return the last thing you get.
Why does my while loop fail?
The same reason.
If you want all the elements, then you just get the result of running the function:
return document.querySelectorAll(el)
That will give you a NodeList object containing all the elements.
Now that does what you say you want, I'm going to speculate about what your real problem is (i.e. why you think it doesn't work).
You haven't shown us what you do with the result of running that function, but my guess is that you are trying to treat it like an element.
It isn't an element. It is a NodeList, which is like an array.
If you wanted to, for instance, change the background colour of an element you could do this:
element.style.backgroundColor = "red";
If you want to change the background colour of every element in a NodeList, then you have to change the background colour of each one in turn: with a loop.
for (var i = 0; i < node_list.length; i++) {
var element = node_list[i];
element.style.backgroundColor = "red";
}
You are returning a single element. You can return the array. If you want to be able to act on all elements at once, jQuery style, you can pass a callback into your function;
window.qsa = function(query, callback) {
var els = document.querySelectorAll(query);
if (typeof callback == 'function') {
for (var i = 0; i < els.length; ++i) {
callback.call(els[i], els[i], i);
}
}
return els;
};
qsa('button.change-all', function(btn) {
// You can reference the element using the first parameter
btn.addEventListener('click', function(){
qsa('p', function(p, index){
// Or you can reference the element using `this`
this.innerHTML = 'Changed ' + index;
});
});
});
qsa('button.change-second', function(btn) {
btn.addEventListener('click', function(){
var second = qsa('p')[1];
second.innerHTML = 'Changed just the second one';
});
});
<p>One</p>
<p>Two</p>
<p>Three</p>
<button class='change-all'>Change Paragraphs</button>
<button class='change-second'>Change Second Paragraph</button>
Then you can call either use the callback
qsa('P', function(){
this.innerHTML = 'test';
});
Or you can use the array that is returned
var pList = qsa('p');
var p1 = pList[0];
This loop
for(var i = 0; i < el.length; ++i) {
result = el[i];
}
overwrites your result variable every time. That's why you always get only one element.
You can use the result outside though, and iterate through it. Kinda like
var result = window.qsa(el)
for(var i = 0; i < result.length; ++i) {
var workOn = result[i];
// Do something with workOn
}

An error occur when I use appendChild ("<br>") in a genuine exist reference

This is a beginner's question...
I want to write some code to initialize a bunch of seats picture in my web page,
create and set attribute to them,
and make them change line every 9 seats (4 rows, and for every row it has 9 seats), here is the code
function initSeats() {
var seatsDiv = document.getElementById("seats");
//Initialize the appearence of all seats
for (var i = 0; i < seats.length; i++) {
for (var j = 0; j < seats[i].length; j++) {
var currentSeatIndex = i * seats[i].length + j;
if (seats[i][j]) {
//if current seat is available(true), create a new IMG element and set some attributes;
} else {
//if current seat is unavailable(true), create a new IMG element and set some attributes;
}
}
seatsDiv.appendChild("<br>");//here is the problem
}
}
what I want to do is when one of the outer loop finished, add a at the end,
But then I got a "NotFoundError" in Chrome that looks like the node seatsDiv doesn't exist
so after all only one row of seats were successfully initialized.
Is appendChild supposed to append anything at the position? Or should I use some other method?
appendChild expects an HTMLElement rather than a string, try switching to:
seatsDiv.appendChild(document.createElement("br"));

Javascript get more than one id at a time

I'm just wondering if its possible in javascript to get more than one id at a time, without the use of JQuery. I'm checking the background color of each cell in a dynamically created table. For instance, I have this code:
var black = "rgb(0, 0, 0)";
if(document.getElementById("cell1").style.backgroundColor == black &&
document.getElementById("cell2").style.backgroundColor == black)
{
alert("Two cells are black!");
}
Would it be possible to do something like this:
var black = "rgb(0, 0, 0)";
if(document.getElementById("cell1","cell2").style.backgroundColor == black)
{
alert("Two cells are black!");
}
I'm trying not to use JQuery at all as I'm not too familiar with it.
With modern browsers you can do something similar using querySelectorAll (compatibility matrix), but you'd still have to loop over the resulting NodeList:
var nodes = document.querySelectorAll("#cell1, #cell2");
var count = 0;
for (var index = 0; index < nodes.length; ++index) {
if (nodes[index].style.backgroundColor == black) {
++count;
}
}
if (nodes.length === count) {
alert("Both are black");
}
Doesn't really buy you anything over, say:
var cells = ["cell1", "cell2"];
var count = 0;
for (var index = 0; index < cells.length; ++index) {
if (document.getElementById(cells[index]).style.backgroundColor == black) {
++count;
}
}
if (cells.length === count) {
alert("All cells are black");
}
So in short: No, there isn't really anything more useful you can do.
Not natively. You could write your own fairly easily, though:
function getElementsById(elements)
{
var to_return = [ ];
for(var i = 0; i < elements.length; i++)
{
to_return.push(document.getElementById(elements[i]));
}
return to_return;
}
This will accept an array of IDs as the parameter, and return the elements in an array. You might also want to look into the document.querySelector method.
No,
without using jQuery or other javascript helper libraries.
querySelector is not supported by IE7 and below which still represents a fairly large proportion of the traffice http://caniuse.com/#feat=queryselector
I have upvoted #BenM's answer, but I'd like to suggest another option.
Your issue:
I'm checking the background color of each cell
In this case, it would make more sense to attach the id to the table itself. Your selector becomes (assuming no nested table):
var cells = document.getElementById("myTable").getElementsByTagName("td");
or for recent browsers:
var cells = querySelectorAll("#myTable>tbody>tr>td");
For the record, here is another way if all your cells have a similar id "cellxxx":
var cells = querySelectorAll("td[id^='cell']");

how to access element whose id is variable

I need to access elements in html file using javascript, their names are like arr_1, arr_2, arr_3, I wish to use a loop to dynamically create the id then to access them like below:
for(var i=0; i< 10; i++) {
var id = "arr_" + i;
$document.getElementById('id')....
}
But it doesn't work. I remember there is an function to allow me do that, anyone know what that is?
You don't need the dollar sign preceding document, and you should pass your id variable to the getElementById function, not a string containing 'id':
for(var i=0; i< 10; i++) {
var id = "arr_" + i;
var element = document.getElementById(id);
// work with element
}
You might also want to check if getElementById actually found your element before manipulating it, to avoid run-time errors:
if (element) {
element.style.color = '#ff0000';
}
for (var i = 0; i < 10; i++) {
var obj = document.getElementById("arr_" + i);
obj.style.border = "1px solid red";
}
change
$document.getElementById('id')
to
$document.getElementById(id)
Since this question was published and answered quite correctly several times by using document.getElementById(id), another contender has entered the fray, querySelector, which takes any valid CSS selector and returns the first matched element.
Note that because querySelector takes a CSS selector, selecting an ID requires prefixing the ID with #.
for(var i=0; i< 10; i++) {
// Added the # here in the id variable.
var id = "#arr_" + i;
// assuming $document is a reference to window.document in the browser...
var element = $document.querySelector(id);
if (element) {
// do something with element
}
}
getElementById is typically faster than querySelector (in some browsers, twice as fast), which makes sense, since it doesn't have to invoke the CSS subsystem to find the element.
However, the option exists, and Stack Overflow is nothing if not thorough when answering questions.

Categories