Strange behaviour whilst setting style.display in a simple function loop - javascript

Consider the following JavaScript:
function step(show)
{
for(var i = 1; i <= 5; i++)
{
document.getElementById('step' + show).style.display = show == i ? 'block' : 'none';
}
}
step(2);
In combination with this HTML:
<div id="step1">1</div>
<div id="step2">2</div>
<div id="step3">3</div>
<div id="step4">4</div>
<div id="step5">5</div>
I'd expect only #step2 to be shown, but instead I see the opposite result:
1
3
4
5
Here is a JSFiddle. What is causing this strange behaviour and how can I fix it?

I think you want:
document.getElementById('step' + i).style.display = show == i ? 'block' : 'none';
Notice the change here ------------------------^
DEMO: http://jsfiddle.net/5DNjc/2/
Without the change, you're always modifying the element with an id using the passed in parameter (static). So technically, you're always setting the display (of the target element) based on whether the last element passes the condition. The changing value is i.
To me, it makes it more readable if you separate out the logic, and might've helped you not encounter the problem in the first place :) Something like:
function step(show) {
for(var i = 1; i <= 5; i++) {
var curDiv = document.getElementById('step' + i);
var shouldBeShown = (i === show);
var newDisplay = shouldBeShown ? 'block' : 'none';
curDiv.style.display = newDisplay;
}
}
DEMO: http://jsfiddle.net/5DNjc/3/

document.getElementById('step' + i).style.display = show == i ? 'block' : 'none';
http://jsfiddle.net/3REbr/2/

Related

Javascript - add css style to element with class

I want to add only one css style in JS. I don't want to include jQuery for only one thing.
My code:
document.addEventListener('DOMContentLoaded', function() {
if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) {
var productAttr = document.getElementsByClassName('product-attributes');
productAttr.style.top = "-90px";
}
});
The error from console is:
TypeError: 'undefined' is not an object (evaluating 'productAttr.style.top = "-90px"')
If I want change other styles f.e. opacity or color, I get the same problem.
How can I fix this ?
Thanks in advance for help.
You need to loop through your results because getElementsByClassName() returns a collection of elements:
for(var i = 0; i < productAttr.length; i++)
{
productAttr[i].style.top = "-90px";
}
Maybe it's because you can not give negative values in CSS
top:"-90px" === bottom "90px"
Maybe now it would work
document.addEventListener('DOMContentLoaded', function() {
if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) {
var productAttr = document.getElementsByClassName('product-attributes');
productAttr.style.bottom = "90px";
}
});
It's preferred to have an id assigned to the targeted element then target using getElementById() as there cannot be elements with the same id, hence getElementByClassName() will return an array if there are multiple elements with the same className. If you need to target multiple elements, you should for-loop through them while applying the change to the nth item being looped:
for(x = 0; x < array.length; x++){
array[x].style.top = '-90px';
}
Gentle reminder: also remember to have position: relative|absolute|fixed to ensure 'top' attribute works
edited with thanks to Sebastien Daniel
When selecting a class you have to indicate what number is as an Tag, it is not like the id that there is only one.
It would be something like this code depending on which of the classes you want to apply it:
document.addEventListener('DOMContentLoaded', function() {
if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) {
var productAttr = document.getElementsByClassName('product-attributes');
productAttr[0].style.top = "-90px";
productAttr[1].style.top = "-90px";
productAttr[2].style.top = "-90px";
}
});

Changing background color of only one id instead of all

I've been trying to figure out a way to create a tic tac toe game for school and I'm stuck. I'm trying to change the background color of the div I click on using an if else if. It sort of works but instead of changing the BG color of each individual square I clicked, all 9 squares change colors. If I change the "boxes[i]" to "this", I get an "undefined" and the code only puts an x or an o.
var turns = 'X';
var xColor = "#1772e8";
var oColor = "#e15258";
function changeColor() {
var boxes = document.querySelectorAll(".box");
for (var i = 0; i < boxes.length; i += 1) {
if( turns === 'X' ) {
boxes[i].style.backgroundColor = oColor;
} else {
boxes[i].style.backgroundColor = xColor;
}
}
}
function click() {
if ( this.id === "box1" ) {
if (document.getElementById("box1").innerHTML === ""){
document.getElementById("box1").innerHTML = turns;
changeTurn();
changeColor();
}
}
Any help would be appreciated.
You are selecting every single .box element on the page, which is why they are all changing.
I would try something like this (assuming that you are calling changeColor() from an onClick):
HTML:
<div class="box" onclick="changeColor(this)"> </div>
JS:
function changeColor(clickedBox) {
if( turns === 'X' ) {
clickedBox.style.backgroundColor = oColor;
} else {
clickedBox.style.backgroundColor = xColor;
}
}
By passing the clicked box to the function you know the exact box you have to deal to (note that we pass "this" in the onClick definition, which is why your earlier attempts at using "this" came out as undefined).
EDIT:
Here is an example on Codepen
If jQuery is ok, do this
$(document).ready(function(){
$(".box").click(function(){
//If else statement here
$(this).css("background-color","[color of your choice]");
}
});

tags underneath headings disappear when clicked again w/ javascript no idea why

I have pasted the javascript below but also a link to my codepen so you can see exactly what I am talking about.
I would like the heading to be clicked and expose the text below. On another click I would like for the text to go back to hidden. Multiple headings can be opened at the same time. What is happening with my current setup is you can click once to show, click again to hide and then when you click again to show nothing shows, if you keep clicking the text and headings below are eaten/dissapear. I would prefer to do this without jquery. thanks for any help.
http://codepen.io/jrutishauser/pen/YPrrNa
var clickToShow = function () {
if (this.nextElementSibling.className === 'open'){
this.nextElementSibling.remove('open');
} else if (this.nextElementSibling.className != 'open') {
this.nextElementSibling.className = 'open';
}
};
var articleHeadings = document.getElementsByTagName('h3');
for (var index = 0; index < articleHeadings.length; index++){
articleHeadings[index].onclick = clickToShow;
}
var subArticleHeadings = document.getElementsByTagName('h4');
for (var index2 = 0; index2 < subArticleHeadings.length; index2++){
subArticleHeadings[index2].onclick = clickToShow;
}
Change this.nextElementSibling.remove('open') to this.nextElementSibling.className = ''. I believe remove() method removes the element, not the class.
You can do it like this also. This is the correct way of doing it.
var clickToShow = function () {
element=this.nextElementSibling;
if (element.className === 'open'){
element.className=element.className.replace('open','');
} else if (element.className != 'open') {
element.className = 'open';
}
};

How to reduce 180 lines of code down to 20 in Javascript?

I have a lot of click handler functions which are almost (textually and functionally) identical. I've got a menu with maybe 10 items in it; when I click on an item, the click handler simply makes one div visible, and the other 9 div's hidden. Maintaining this is difficult, and I just know there's got to be a smart and/or incomprehensible way to reduce code bloat here. Any ideas how? jQuery is Ok. The code at the moment is:
// repeat this function 10 times, once for each menu item
$(function() {
$('#menuItem0').click(function(e) {
// set 9 divs hidden, 1 visble
setItem1DivVisible(false);
// ...repeat for 2 through 9, and then
setItem0DivVisible(true);
});
});
// repeat this function 10 times, once for each div
function setItem0DivVisible(on) {
var ele = document.getElementById("Item0Div");
ele.style.display = on? "block" : "none";
}
Create 10 div with a class for marking
<div id="id1" class="Testing">....</div>
<div id="id2" class="Testing">....</div>
<div id="id3" class="Testing">....</div>
and apply the code
$('.Testing').each(function() {
$(this).click(function() {
$('.Testing').css('display', 'none');
$(this).css('display', 'block');
}
}
$(document).ready(function (){
$("div").click(function(){
// I am using background-color here, because if I use display:none; I won't
// be able to show the effect; they will all disappear
$(this).css("background-color","red");
$(this).siblings().css("background-color", "none");
});
});
Use .siblings() and it makes everything easy. Use it for your menu items with appropriate IDs. This works without any for loops or extra classes/markup in your code. And will work even if you add more divs.
Demo
Fiddle - http://jsfiddle.net/9XSJW/1/
It's hard to know without an example of the html. Assuming that there is no way to traverse from the menuItem to ItemDiv - you could use .index and .eq to match up the elements based on the order they match with the selector.
var $menuItems = $("#menuItem0, #menuItem1, #menuItem2, ...");
var $divs = $("#Item0Div, #Item1Div, #Item2Div, ...");
$menuItems.click(function(){
var idx = $(this).index();
// hide all the divs
$divs.hide()
// show the one matching the index
.eq(idx).show();
})
Try
function addClick(i) {
$('#menuItem'+i).click(function(e) {
// set nine divs hidden, 1 visble
for( var j = 0; j < 10; ++j ) {
var ele = document.getElementById("Item"+j+"Div");
ele.style.display = (i == j ? "block" : "none");
}
});
}
// One click function for all menuItem/n/ elements
$('[id^="menuItem"]').on('click', function() {
var id = this.id; // Get the ID of the clicked element
$('[id^="Item"][id$="Div"]').hide(); // Hide all Item/n/Div elements
$('#Item' + id + 'Div').show(); // Show Item/n/Div related to clicked element
});
Obviously this would be much more logical if you were using classes instead:
<elem class="menuItem" data-rel="ItemDiv-1">...</elem>
...
<elem class="ItemDiv" id="ItemDiv-1">...</elem>
$('.menuItem').on('click', function() {
var rel = $(this).data('rel'); // Get related ItemDiv ID
$('.ItemDiv').hide(); // Hide all ItemDiv elements
$('#' + rel).show(); // Show ItemDiv related to clicked element
});
Save the relevant Id's in an array - ["Item0Div", "Item1Div", ...]
Create a generic setItemDivVisible method:
function setItemDivVisible(visible, id) {
var ele = document.getElementById(id);
ele.style.display = visible ? "block" : "none";
}
And set your click handler method to be:
function(e) {
var arrayLength = myStringArray.length;
for (var i = 0; i < idsArray.length; i++) {
setItemDivVisible(idsArray[i] === this.id, idsArray[i]);
}
}
I think this will do the trick

$(d).attr("id") is undefined javascript

I have a variable d that I use like this:
$(function() {
for(i = 1; i <= 31; i++) {
var d = '#days' + i;
if ($(d).attr("id").substr(4,2) == 11) {
$(d).addClass("date_has_event");
//console.log("diez");
} else {
console.log("otro");
}
}
}
However I get the following error in firebug:
$(d).attr("id") is undefined
index.html (L23) (?)()()
jquery.min.js (L27) onreadystatechange()()
jquery.min.js (L27) onreadystatechange()()
jquery.min.js (L21) nodeName()([function(), function()], function(), undefined)
onreadystatechange()()
I really don't understand why. Does anyone know?
Edit
I'm sorry for the poor explanation I had to run, here's what's going on a little bit more detailed. I am generating a calendar using javascript. each td has a different id (hence the #days + i) and I am running it from 1 to 31 so I can cover the longer months. However I am getting the error I mentioned above. I am also using the jQuery library to enable me to select more easily (i.e. instead of getElementById just #days)
Why not just check if i == 11, then do your processing on it? It would still only fire on $('#days11'). Edit: If you need to make sure the element exists as well, just slap that into the conditional.
$(function(){
for(i = 1; i <= 31; i++){
var d = '#days' + i;
// if($(d) && i == 11){
if(i == 11){
$(d).addClass("date_has_event");
//console.log("diez");
}else{
console.log("otro");
}
}
}
Ok, new answer. the way you are doing this is not very "jqueryish". lets step back a bit. from what I can tell you have an html structure something like:
<div id="days1"></div>
<div id="days2"></div>
...
You are then running this against every item with a days(num) id? A better solution is this, if you want to add a class to every element with a date in it, first apply a class:
<div class="days"></div>
<div class="days"></div>
Your code can then be
$(function(){
$(".days").each(function(i){
if($(this).substr(4,2) == 11){
$(this).addClass("date_has_event");
}
});
});
Since you are selecting by id, this is redundant:
if ($(d).attr("id").substr(4,2) == 11)
because the ID attribute of d is d.
Is most simple to do:
if (i == 11)
Missing the closing bracket?
$(function() {
for(i = 1; i <= 31; i++) {
var d = '#days' + i;
if (i == 11) {
$(d).addClass("date_has_event");
//console.log("diez");
} else {
console.log("otro");
}
}
// shouldn't the next line be });
}

Categories

Resources