nextSibling instead of nextElementSibling? - javascript

This isn't exactly a problem in the code, but I cannot understand the reason behind my problem. First, please have a look at the code:
HTML:
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>Head First : Javascript</title>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="css.css">
<script type="text/javascript" src="cookie.js"></script>
</head>
<body>
<div id="row1">
<p>False</p>
<p>False</p>
<p>True</p>
<p>True</p>
<p>False</p>
</div>
<div id="row2">
<p>False</p>
<p>False</p>
<p>True</p>
<p>False</p>
<p>False</p>
</div>
<div id="row3">
<p>False</p>
<p>True</p>
<p>True</p>
<p>True</p>
<p>True</p>
</div>
<div id="row4">
<p>True</p>
<p>False</p>
<p>False</p>
<p>True</p>
<p>False</p>
</div>
<script type="text/javascript" src="javascript.js"></script>
</body>
</html>
CSS:
p{
display:inline-block;
width:50px;
margin:30px;
text-align:center;
}
JS:
function setElementColor(){
var numberRows = parseInt(window.prompt("Enter total number of rows that you are seeing on the screen."));
for(var i = 1; i <= numberRows; i++){
var firstElement = document.getElementById('row' + i).firstElementChild;
var counterElement;
//For now, total elements in 1 row = 5
for(var q = 1; q <= 5; q++){
if(q == 1){
firstElement.style.backgroundColor = "red";
counterElement = firstElement.nextSibling;
}
else{
counterElement.style.backgroundColor = "green";
}
counterElement = counterElement.nextElementSibling;
}
}
}
setElementColor();
Now, this might be a stupid question, but it will make sense (I believe). The code that you are seeing is correct, and works without a doubt. But, can you see that nextSibling property in the first if statement? That is what is confusing me.
Now, all paragraphs have spaces between each other, so this means that all browsers will see those spaces as textNode (except IE). Now, by the means of logic, there should have been a nextElementSibling property there, so that the code looks forward for the next Element, ignoring comments/textNodes, but I tried it, and that is not working.
Can someone give me a satisfying reasoning behind that?

In your first if, counterElement is set to the nextSibling, being the textnode
if
{
counterElement = firstElement.nextSibling; //now counterElement is the textnode
}
else
{
}
counterElement = counterElement.nextElementSibling; //no matter if the counterElement is a textnode or a <p> the next element in the sibling tree is a <p>
But regardless of q and the if..else branch taken, immediately after the if..else block, counterElement is set to the next Element sibling, so that regardless if the starting point is the textnode or the containing paragraph, the next element will be the sibling paragraph. The code would behave the same if you would set counterElement = firstElement in the q == 1 block
As an aside, to get rid of the fixed upperbound (columns) and loose the if..else at the same time, you could also use something like
for(var i = 1; i <= numberRows; i++){
var el = document.getElementById('row' + i).firstElementChild;
var q = 1;
while (el!=null){
el.style.backgroundColor = q++ == 1 ? "red" : "green";
el = el.nextElementSibling;
}
}

Related

document.getElementsByClassName(variables);

I'm trying to target an element through the use of two variables. I know how to do this for id's, but what I'm doing for classes doesn't seem to works. Does anyone have an idea what I'm doing wrong?
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script>
var colour = purple;
var number = 1;
function check() {
var x = document.getElementsByClassName(colour number);
x[0].innerHTML = "Hello World!";
}
</script>
</head>
<body>
<div>Username: </div>
<input id="test" type="text" onblur="check()">
<div class="1 purple">one</div>
<div class="2 red">two</div>
<div class="3 blue">three</div>
<div class="4 brown">four</div>
<div class="5 orange">five</div>
<div class="6 yellow">six</div>
<div class="7 white">seven</div>
</body>
</html>
Update
I've tried both options but neither seems to work. This is the updated script.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script>
function check() {
var colour = purple;
var number = one;
//var x = document.getElementsByClassName(colour + ' ' + number);
var x = document.querySelector('.' + colour + '.' + number);
x[0].innerHTML = "Hello World!";
}
</script>
</head>
<body>
<input id="test" type="text" onblur="check()">
<div class="one purple">one</div>
<div class="two red">two</div>
<div class="three blue">three</div>
<div class="four brown">four</div>
<div class="five orange">five</div>
<div class="six yellow">six</div>
<div class="seven white">seven</div>
</body>
</html>
Also note, this is only a test script. Once I get it working, I'll add a lot more divs, one for each color/number combination, so selecting on one class will not work.
The expression colour number is not a valid expression.
To specify the class names you need a string with space separated class names, for example "purple 1". Concatenate the strings with a space between them:
var x = document.getElementsByClassName(colour + ' ' + number);
However, as both the colors and numbers are unique, you only need to look for one of them:
var x = document.getElementsByClassName(colour);
or:
var x = document.getElementsByClassName(number);
Note: Class names that are only digits may be problematic in some situations. It's recommended that a class name doesn't start with a digit.
You should construct a string:
var colour = 'purple';
var number = 1;
var x = document.querySelector('.'+colour+'.'+number);

ParentNode and insertBefore

I created a prompt box with a question. After answering, you receive the answer in a div created with JavaScript called id2. Now I am trying to place my id2 in front of id1 which is the parentNode. So it will show the answer above the first div id1. Can someone explain to me why it's not working?
<!DOCTYPE html>
<html>
<head>
<title>lab7</title>
<meta charset="utf-8" lang="en" name="my page" />
<style>
.class1 {
width: 100%;
height: 60px;
background-color: #BCC6CC;
}
</style>
<script type="text/javascript">
function loadQ() {
var firstdiv = document.createElement("div");
firstdiv.setAttribute("class", "class1");
firstdiv.setAttribute("id", "id1");
var jw = prompt("Which movie is number 1 Box Office 2015?","Jurassic World");
document.getElementById("id2").innerHTML ="" + jw + " Made $652,198,011 Total Gross Sales";
document.getElementById("id2").style.backgroundColor = "#786D5F";
var id1 = document.getElementByTagName("div")[0];
var parent1 = id1.parentNode();
var beforeME = document.getElementByTagName("id2");
parent1.insertBefore(id1, beforeME);
};
</script>
</head>
<body onload="loadQ()">
<div id="id1" class="class1">
<br>
<b>Top 2 Box Office Movie for 2015</b>
</div>
<div class="class2" id="id2">
</div>
</body>
</html>
There are at least two issues in your code:
document.getElementsByTagName needs an s (it returns multiple elements).
To get an object by id, you need to use document.getElementById.
I rewrote my code and made it more clear. So, each div id and class are define.
<script type="text/javascript">
function loadQ() {
var firstdiv = document.createElement("div");
firstdiv.setAttribute("class", "class1");
firstdiv.setAttribute("id", "id1");
firstdiv.style.backgroundColor = "#BCC6CC;";
document.getElementById('id1').innerHTML ="<h2 style='padding:20px;'>Top Box Office Movie 2015</h2>","<br><br>";
var seconddiv = document.createElement("div");
seconddiv.setAttribute("class", "class2");
seconddiv.setAttribute("id", "id2");
seconddiv.style.backgroundColor = "#786D5F";
var reper = document.getElementById('id1');
var parinte = reper.parentNode;
parinte.insertBefore(seconddiv, reper);
</script>

Easiest way to display childNodes NodeList? (Beginner)

I'm attempting to navigate the DOM tree and retrieve html comments and display them in alert box. This is as far as I can get, my alert box keeps returning empty. How do I properly display a nodeList array? I've searched for hours and cant seem to find any info that makes sense.
<!DOCTYPE html>
<html>
<head>
<title>Hidden Comments</title>
<h1 style="text-align:center">Hidden Comments</h1>
<script>
function concatComs(){
var c=document.getElementById('body');
var array=[];
for(var i=0;c.childNodes.length<i;i++){
if(c.childNodes[i].nodeType==8) array[i]=c[i];
}
alert(array.toString());
}
</script>
</head>
<body id="body" style="text-align: center">
<!--you-->
<h2>Find the hidden comments!</h2>
<p>Look closely and you'll find them!</p><!--found-->
<input type="button" value="Go!" onClick="concatComs()"/>
<!--them-->
</body>
</html>
Your for loop should start like:
for(var i=0; i < c.childNodes.length; i++){
Additionally, you probably want to add c.childNodes[i] to your array.
function concatComs(){
var c = document.getElementById('body');
var array=[];
for(var i=0; i < c.childNodes.length; i++){
if(c.childNodes[i].nodeType==8) {
array.push(c.childNodes[i]);
}
}
var result = "";
for(i in array) {
result += array[i].textContent + " ";
}
document.write(result);
}
<div id="body" style="text-align: center">
<!--you-->
<h2>Find the hidden comments!</h2>
<p>Look closely and you'll find them!</p><!--found-->
<input type="button" value="Go!" onClick="concatComs()"/>
<!--them-->
</div>
You can select comment strings with a regex expression like this
match(/<!--.*?-->/g)
then you can trim the first 4 and last 3 letter for each string
substr(4,comments[i].length-7)
Final result is like this
<!DOCTYPE html>
<html>
<head>
<title>Hidden Comments</title>
<script>
function concatComs(){
var comments = document.body.innerHTML.match(/<!--.*?-->/g);
for (var i = 0; i < comments.length; i++)
comments[i] = comments[i].substr(4,comments[i].length-7);
alert(comments);
}
</script>
</head>
<body id="body" style="text-align: center">
<h1 style="text-align:center">Hidden Comments</h1>
<!--you-->
<h2>Find the hidden comments!</h2>
<p>Look closely and you'll find them!</p><!--found-->
<input type="button" value="Go!" onClick="concatComs()"/>
<!--them-->
</body>
</html>
btw you should place your h1 tag inside the body not head.

Updating innerHTML in javascript but erasing past innerHTML

Using Javascript, I want to toggle on and off between 2 images.
So far I am using a counter and then changing my innerHTML based off if the counter is even or odd for when the image is clicked
It works the first time i click the image by replacing the image already there, but after the first time it keeps on adding images instead.
How can i make it change the image after the second time rather than add images when I click?
<!DOCTYPE html>
<html>
<head>
<script>
var x = 0;
function myFunction() {
x++;
var div1 = document.getElementById('div1');
if (x % 2 != 0) {
var y = document.write('<img src="http://www.computric.com/wp-content/uploads/2011/09/119709197585381818TzeenieWheenie_Power_On_Off_Switch_red_2.svg_.med_.ng" alt="sometext" onclick=myFunction()>');
div1.appendChild.innerHtml = y;
} else if (x % 2 == 0) {
var y = document.write('<img src="http://0.tqn.com/d/pcsupport/1/0/Y/A/-/-/on.jpg" alt="some_text" onclick=myFunction()>')
div1.appendChild.innerHTML = y;
}
}
</script>
</head>
<body>
<div id="div1" style="font-size:12px" onclick=myFunction()> <span onclick=myFunction()>
<img src="http://0.tqn.com/d/pcsupport/1/0/Y/A/-/-/on.jpg" alt="some_text">
</span>
</div>
</body>
</html>
If you are only trying to achieve the toggle effect something like this should work:
<!DOCTYPE html>
<html>
<head>
<script>
var x=0;
function myFunction(){
var div1=document.getElementById('div1');
if(x==0){
x=1;
document.getElementById("myimgid").src = "http://www.computric.com/wp-content/uploads/2011/09/119709197585381818TzeenieWheenie_Power_On_Off_Switch_red_2.svg_.med_.ng";
}
else{
x=0;
document.getElementById("myimgid").src = "http://0.tqn.com/d/pcsupport/1/0/Y/A/-/-/on.jpg";
}
}
</script>
</head>
<body>
<div id="div1" style="font-size:12px" onclick=myFunction()>
<span>
<img id="myimgid" src="http://0.tqn.com/d/pcsupport/1/0/Y/A/-/-/on.jpg" alt="some_text">
</span>
</div>
</body>
</html>

Absolute positioning breaks in IE8

I am working on developing an asp.net control that I need to be able to drop into other applications. The control is basically a custom dropdown in which a div gets displayed or hidden when another element is clicked.
The problem I am having is in trying to get the dynamic div to align below the element that gets clicked. I wrote a javascript function which should, in theory, allow me to specify two elements and the desired alignment and then move the second element to the correct position in relation to the first element.
I have three test cases which relate to places where I currently expect this control will be used, my current markup and javascript work in all three cases for IE7 but fails for one of the cases in FF3.5 and IE8-standards mode. I have been playing with this for a while and have yet to come up with an answer that fixes the problem case without breaking one of the others. (Note that 90+% of my users are on IE7 with a slow migration towards IE8)
I am looking for any suggestions other than adding a compatibility mode directive to the page, that does fix things in IE8 but I would prefer an alternative if one is possible since I may not always have control over where this is used. Here is an HTML doc which illustrates the relevant markup and javascript along with the test cases. Case three is the one which has problems, instead of aligning neatly under the input element the div is overlapping vertically and offset to the right by a distance equivalent to the width of the select element.
(Note that the real pages utilize a reset style sheet adapted from the one published by Eric Meyer, including/omitting this style sheet has no relevant effect on these test cases.)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title></title>
</head>
<body>
<script type="text/javascript">
var VAlign = { "top": -1, "center": 0, "bottom": 1 };
var HAlign = { "left": -1, "center": 0, "right": 1 };
function AlignElements(element1, vAlign1, hAlign1, element2, vAlign2, hAlign2) {
var List1 = BuildOffsetList(element1);
var List2 = BuildOffsetList(element2);
var Index1 = List1.length - 1;
var Index2 = List2.length - 1;
while (Index1 >= 0 && Index2 >= 0 && List1[Index1] == List2[Index2]) {
Index1--;
Index2--;
}
element2.style.top = "";
element2.style.left = "";
var OT1 = 0;
var OL1 = 0;
var OT2 = 0;
var OL2 = 0;
while (Index1 >= 0) {
OT1 += List1[Index1].offsetTop;
OL1 += List1[Index1].offsetLeft;
Index1--;
}
while (Index2 >= 0) {
OT2 += List2[Index2].offsetTop;
OL2 += List2[Index2].offsetLeft;
Index2--;
}
var top = (OT1 - OT2);
if (vAlign1 == VAlign.bottom) {
top += element1.offsetHeight;
} else if (vAlign1 == VAlign.center) {
top += (element1.offsetHeight / 2);
}
if (vAlign2 == VAlign.bottom) {
top -= element2.offsetHeight;
} else if (vAlign2 == VAlign.center) {
top -= (element2.offsetHeight / 2);
}
var left = (OL1 - OL2);
if (hAlign1 == HAlign.right) {
left += element1.offsetWidth;
} else if (hAlign1 == HAlign.center) {
left += (element1.offsetWidth / 2);
}
if (hAlign2 == HAlign.right) {
left -= element2.offsetWidth;
} else if (hAlign2 == HAlign.center) {
left -= (element2.offsetWidth / 2);
}
element2.style.top = top + "px";
element2.style.left = left + "px";
}
function BuildOffsetList(elelment) {
var I = 0;
var List = new Array();
var Element = elelment;
while (Element) {
List[I] = Element;
Element = Element.offsetParent;
I++;
}
return List;
}
</script>
Case 1
<div>
<div id="control1" style=" display:inline; position:relative;">
<div id="control1_div1" style="background-color:Blue; height:75px; width:150px; position:absolute;"></div>
<input id="control1_txt1" type="text" style="width:150px;" />
<script type="text/javascript">
AlignElements(document.getElementById("control1_txt1"), VAlign.bottom, HAlign.left, document.getElementById("control1_div1"), VAlign.top, HAlign.left);
</script>
</div>
</div>
<div style="height:100px;"></div>
Case 2
<div>
<div id="Nav" style="float:left; width:200px; height:150px; background-color:Aqua;"></div>
<div id="Content" style="margin-left:200px; height:150px; background-color:#ddd;">
<div style="margin-left:100px;">
<h5 style="float:left; margin-left:-100px; width:90px; margin-right:10px; text-align:right; font-weight:.9em;">Label</h5>
<div id="control2" style=" display:inline; position:relative;">
<div id="control2_div1" style="background-color:Blue; height:75px; width:150px; position:absolute;"></div>
<input id="control2_txt1" type="text" style="width:150px;" />
<script type="text/javascript">
AlignElements(document.getElementById("control2_txt1"), VAlign.bottom, HAlign.left, document.getElementById("control2_div1"), VAlign.top, HAlign.left);
</script>
</div>
</div>
</div>
</div>
<div style="height:100px;"></div>
Case 3
<div>
<select><option>something</option></select>
<br />
<select><option>something else</option></select>
<div id="control3" style=" display:inline; position:relative;">
<div id="control3_div1" style="background-color:Blue; height:75px; width:150px; position:absolute;"></div>
<input id="control3_txt1" type="text" style="width:150px;" />
<script type="text/javascript">
AlignElements(document.getElementById("control3_txt1"), VAlign.bottom, HAlign.left, document.getElementById("control3_div1"), VAlign.top, HAlign.left);
</script>
</div>
</div>
</body>
</html>
The third case is breaking apart because of the inline display of the parent div - it cause the relative position to have no effect as far as I know.
To test such case use float instead, here is working example:
http://jsfiddle.net/yahavbr/estYF/1/
Thanks to Shadow Wizard for getting me thinking in the right direction. Turns out that the issue is that my absolutely positioned elements do not move to their 0,0 point when I clear the top and left properties. If I change the code to explicitly put them at 0,0 before calculating the offset difference then everything works beautifully.
element2.style.top = "";
element2.style.left = "";
becomes
element2.style.top = "0px";
element2.style.left = "0px";

Categories