Combining getElementsByTagName and getElementsByClassName - javascript

I'm trying to combine getElementsByTagName and getElementsByClassName to narrow a search down and count the resulting nodes, but the second search always results in a length of 0, and I've no idea why.
<!DOCTYPE html>
<html>
<head></head>
<body>
<p>Stuff</p>
<p class="content">Stuff2</p>
<p>Stuff</p>
<p class="content">Stuff2</p>
<script type="text/javascript">
pElements = document.getElementsByTagName("p");
console.log(pElements);
for(i = 0; i < pElements.length; i++) {
console.log(pElements[i].getElementsByClassName("content").length);
}
//console.log(document.querySelectorAll('p.content').length);
</script>
</body>
</html>
I know I can use querySelectorAll for this like the line I have commented out, but I'd like to understand why the first solution isn't working, and what I can do to fix it.
Thanks!

The problem with the first example is that:
pElements[i].getElementsByClassName("content")
searches for children of the p element. But since it is actually on the same element, they are not found.
W3C reference:
"The getElementsByClassName() method returns a collection of an element's child elements with the specified class name"
EDIT: To find if a p element has the content class, instead of getElementsByClassName(), you could use
pElements[i].classList.contains("content")
which will return true if the element has the class. Reference
EDIT2: A more backwards-compatible way would be to get the className property, split it on spaces and iterate the array to see if the class is there.
var names = pElements[i].className.split(" ");
var found = false;
for(var i = 0; i < names.length; i++){
if(names[i] === "content"){
found = true;
break;
}
}
if(found){
//Your code here
}

You can NOT combine getElementsByTagName and getElementsByClassName. Because as mentioned by #juunas, pElements now consists of the result consisting of an array of all the <p> elements.
And when you apply the getElementsByClassName to this result-set, using pElements[i].getElementsByClassName("content"), it searches in the child elements of pElements.
Suggestive Result :
Use the getAttribute() function to check the class of each element in pElements, like,
pElements = document.getElementsByTagName("p");
console.log(pElements);
for(var i = 0, j = 0; i < pElements.length; i++) {
if (pElements[i].getAttribute("class") === "content") {
j++;
}
}
console.log("Length of the resulting nodes: ", j);

It's because your p-elements dont have any elements with the class "content". The p-elements itself have this class, so you cant find it and 0 is correct.
Change
<p class="content">Stuff2</p>
To
<p><span class="content">Stuff2</span></p>
And you will get 1 as the result.

Related

getElementsByClassName isn't returning all elements

I'm creating a button that I should highlight certain words within a specified class, but I am having issues with it returning all elements within the class. It will only work if I specify an index, so I'm assuming there may be something wrong with the existing "for loop". Any help is appreciated!
This will work, but only "highlights" the first element in the class, of course:
var bodyText = document.getElementsByClassName('test')[0].innerHTML;
for (var i = 0; i < searchArray.length; i++) {
bodyText = doHighlight(bodyText, searchArray[i], highlightStartTag,
highlightEndTag);}
document.getElementsByClassName('test')[0].innerHTML = bodyText;
return true;
This will not work at all:
var bodyText = document.getElementsByClassName('test').innerHTML;
for (var i = 0; i < searchArray.length; i++) {
bodyText = doHighlight(bodyText, searchArray[i], highlightStartTag,
highlightEndTag);}
document.getElementsByClassName('test').innerHTML = bodyText;
return true;
If you want to replace multiple words in multiple elements, you need two loops:
const testElements = document.getElementsByClassName('test');
for (const element of testElements) {
for (const search of searchArray) {
element.innerHTML = doHighlight(element.innerHTML, search, highlightStartTag, highlightEndTag);
}
}
As you can see getElementsByClassName is pluralized (Elements). Indeed a same class can be assigned to multiple HTML elements. You won't find any way to ommit the [0] and you shouldn't anyway as it might mean you're getting data from the wrong node. If you need data from a specific element that you can ensure is unique then you need to give it an id and use getElementById instead.
You cannot access innerHTML in something which returns an htmlcollection
document.getElementsByClassName('test').innerHTML
Because it's written in plain english: getElementsByClassName. plural.
"Elements".
with an "s" at the end...
meaning it's a (sort of) Array (an htmlcollection)

Storing for loop result to push into HTML

EDIT: Solved. After the helpful people here helped me solve this I realised this issue was to do with my getElementsByClassName selector. Sorry if the title is misleading. The original question is below.
I am getting the expected results from this function's for loop, but I can't get them to print to the HTML.
Could anybody help point out what I'm missing? A point in the right direction would do, I can do some legwork myself.
Any advice is much appreciated.
HTML:
<input type="text" name="searchString" class="searchString">
<span class="longestWordInput"></span>
<span class="longestWordCountInput"></span>
<button class="generate">Generate</button>
JavaScript:
function longestWordFunc() {
var stringSplit = document.querySelector(".searchString").value.split(" ");
let longestWordCount = 0;
let longestWord = "";
for(var i = 0; i < stringSplit.length; i++) {
if(stringSplit[i].length > longestWordCount) {
longestWordCount = stringSplit[i].length;
longestWord = stringSplit[i]
}
}
//Logging expected result
console.log(longestWordCount)
console.log(longestWord)
//Print to HTML not working
document.getElementsByClassName("longestWordInput").innerHTML = longestWord;
document.getElementsByClassName("longestWordCountInput").innerHTML = longestWordCount;
};
document.querySelector(".generate").addEventListener("click", longestWordFunc);
The problem is that getElementsByClassName() returns a NodeList (an array-like structure containing all elements with the specified class name) instead of a single element.
You can access your single span element like this
document.getElementsByClassName("longestWordInput")[0].innerHTML = longestWord;
or you could use querySelector() instead
document.querySelector(".longestWordInput").innerHTML = longestWord;
Hi I believe you need to use getElementsByClassName like the below
document.getElementsByClassName("longestWordInput")[0].innerHTML = longestWord;
document.getElementsByClassName("longestWordCountInput")[0].innerHTML = longestWordCount;

For loop only iterates once when trying to remove classes from elements

In Javascript I have a function that should find the elements on the page that have the "connected" class, and when a button is clicked the classes for these elements are cleared. I have written this code:
var prev_connected = document.getElementsByClassName("connected");
if (prev_connected.length > 0) {
for (var j = 0; j < prev_connected.length; j++) {
prev_connected[j].removeAttribute("class");
}
}
However, it only ever deletes the class attribute of the first "connected" element on the page. When I have two "connected" elements, I have confirmed that the "prev_connected" array does hold 2 values, but for some reason the for loop never reaches the 2nd one. Is there something I'm doing wrong? Thanks.
The result of getElementsByClassName is live, meaning that as you remove the class attribute it will also remove that element from the result. Using querySelectorAll is more widely supported and returns a static NodeList.
Also, you can more easily iterate the list using a for...in loop.
I would not recommend making an extra copy of the live list just to make it static, you should use a method that returns a static NodeList instead.
var prev_connected = document.querySelectorAll(".connected");
document.getElementById('demo').onclick = function() {
for(var i in Object.keys(prev_connected)) {
prev_connected[i].removeAttribute("class");
}
}
.connected {
background: rgb(150,200,250);
}
<div class="connected">Hello</div>
<div class="connected">Hello</div>
<div class="connected">Hello</div>
<div class="connected">Hello</div>
<div class="connected">Hello</div>
<button id="demo">Remove the classes!</button>
This is due to prev_connected being a live nodelist. When you update the element with that class it removes it from the nodelist which means the length of the nodelist reduces by one which means j is trying to find element 2 in an nodelist of length 1 which is why it doesn't work after the first iteration.
You can see this happening in the console in this demo.
One way you can fix this is by converting the nodelist to an array:
var prev_connected = [].slice.call(document.getElementsByClassName("connected"));
You should iterate in the opposite direction and use elem[i].classList.remove('name') for removing class name from each element Demo
document.getElementById("button").onclick = function () {
var prev_connected = document.getElementsByClassName("connected");
console.log(prev_connected);
for (var i = prev_connected.length - 1; i >= 0; i--) {
prev_connected[i].classList.remove("connected");
console.log(i, prev_connected[i - 1]);
}
}
There are another answers: https://stackoverflow.com/a/14409442/4365315

Get an element's nth-child number in pure JavaScript

I am making a function for my site where I set a data attribute which contains the nth-child number of that element.
My HTML markup:
<html>
<body>
<section class="hardware">some text, nth-child is one</section>
<section class="hardware">some text, nth-child is two</section>
<section class="hardware">some text, nth-child is three</section>
<section class="hardware">some text, nth-child is four</section>
<section class="hardware">some text, nth-child is five</section>
</body>
</html>
My JavaScript so far:
var selector = document.getElementsByClassName('hardware');
for(var i = 0; i <= selector.length; i++) {
var index = selector[i] //get the nth-child number here
selector[i].dataset.number = index;
}
How can I get the nth-child number of an element with pure JavaScript (not jQuery), is this even possible in JavaScript?
Check out this previous answer HERE.
It uses
var i = 0;
while( (child = child.previousSibling) != null )
i++;
//at the end i will contain the index.
When you say "number", do you mean 1, 2, etc or "one", "two", etc?
If 1, 2, etc, then the number is simply i+1...
If "one", "two", etc, then you need to get the text inside the element, then probably use a Regexp to parse it and get the value you want.
Simply incrementing the index linearly will only work if all the elements matching that class name are the only element children of the same parent, with no other elements that could interfere with :nth-child(), as shown exactly in the given markup. See this answer for an explanation on how other elements might interfere. Also review the Selectors spec on :nth-child().
One way to achieve this that is more foolproof is to loop through the child nodes of each element's parent node, incrementing a counter for each child node that is an element node (since :nth-child() only counts element nodes):
var selector = document.getElementsByClassName('hardware');
for (var i = 0; i < selector.length; i++) {
var element = selector[i];
var child = element.parentNode.firstChild;
var index = 0;
while (true) {
if (child.nodeType === Node.ELEMENT_NODE) {
index++;
}
if (child === element || !child.nextSibling) {
break;
}
child = child.nextSibling;
}
element.dataset.number = index;
}
JSFiddle demo
Note that this will apply the correct index regardless of where the given element is in the DOM:
If a particular section.hardware element is the first and only child of a different section, it will be assigned the correct index of 1.
If a .hardware element is the second child of its parent, even if it is the only one with that class (i.e. it follows some other element without the class), it will be assigned the correct index of 2.
I'm going to answer the questions with the following assumptions:
Your hardware classed elements are all siblings
You are interested in nth child not nth child + 1
They can be mixed with other elements:
<body>
<section class="hardware">some text, nth-child is zero</section>
<section class="software"></section>
<section class="hardware">some text, nth-child is two</section>
</body>
(I'm making these assumptions, because this is the problem I'm facing, thought it could be useful)
So the main difference is that instead of querying the elements that belong to a given class, I'm going to get the (direct) children of the body, and filter them.
Array.from(document.body.children)
.map((element, index) => ({element, index}))
.filter(({element}) => element.classList.contains('hardware'))
The resulting array will look like this:
[
{element: section.hardware, index: 0}
{element: section.hardware, index: 2}
]
You can split the text at the spaces at get the last word from each split-array:
var hards = document.getElementsByClassName('hardware');
for (var i=0; i < hards.length; i++) {
var hardText = hards[i].innerText || hard[i].textContent;
var hardList = hardText.split(' ');
var hardLast = hardList[hardList.length - 1];
alert(hardLast);
}
I am using || here because Firefox does not support innerText, while IE does not support textContent.
If the elements only contain text then innerHTML can be used instead of innerText/textContent.
[].slice.call(elem.parentElement.childNodes).indexOf(elem)

GetElementByID - Multiple IDs

doStuff(document.getElementById("myCircle1" "myCircle2" "myCircle3" "myCircle4"));
This doesn't work, so do I need a comma or semi-colon to make this work?
document.getElementById() only supports one name at a time and only returns a single node not an array of nodes. You have several different options:
You could implement your own function that takes multiple ids and returns multiple elements.
You could use document.querySelectorAll() that allows you to specify multiple ids in a CSS selector string .
You could put a common class names on all those nodes and use document.getElementsByClassName() with a single class name.
Examples of each option:
doStuff(document.querySelectorAll("#myCircle1, #myCircle2, #myCircle3, #myCircle4"));
or:
// put a common class on each object
doStuff(document.getElementsByClassName("circles"));
or:
function getElementsById(ids) {
var idList = ids.split(" ");
var results = [], item;
for (var i = 0; i < idList.length; i++) {
item = document.getElementById(idList[i]);
if (item) {
results.push(item);
}
}
return(results);
}
doStuff(getElementsById("myCircle1 myCircle2 myCircle3 myCircle4"));
This will not work, getElementById will query only one element by time.
You can use document.querySelectorAll("#myCircle1, #myCircle2") for querying more then one element.
ES6 or newer
With the new version of the JavaScript, you can also convert the results into an array to easily transverse it.
Example:
const elementsList = document.querySelectorAll("#myCircle1, #myCircle2");
const elementsArray = [...elementsList];
// Now you can use cool array prototypes
elementsArray.forEach(element => {
console.log(element);
});
How to query a list of IDs in ES6
Another easy way if you have an array of IDs is to use the language to build your query, example:
const ids = ['myCircle1', 'myCircle2', 'myCircle3'];
const elements = document.querySelectorAll(ids.map(id => `#${id}`).join(', '));
No, it won't work.
document.getElementById() method accepts only one argument.
However, you may always set classes to the elements and use getElementsByClassName() instead. Another option for modern browsers is to use querySelectorAll() method:
document.querySelectorAll("#myCircle1, #myCircle2, #myCircle3, #myCircle4");
I suggest using ES5 array methods:
["myCircle1","myCircle2","myCircle3","myCircle4"] // Array of IDs
.map(document.getElementById, document) // Array of elements
.forEach(doStuff);
Then doStuff will be called once for each element, and will receive 3 arguments: the element, the index of the element inside the array of elements, and the array of elements.
getElementByID is exactly that - get an element by id.
Maybe you want to give those elements a circle class and getElementsByClassName
document.getElementById() only takes one argument. You can give them a class name and use getElementsByClassName() .
Dunno if something like this works in js, in PHP and Python which i use quite often it is possible.
Maybe just use for loop like:
function doStuff(){
for(i=1; i<=4; i++){
var i = document.getElementById("myCiricle"+i);
}
}
Vulgo has the right idea on this thread. I believe his solution is the easiest of the bunch, although his answer could have been a little more in-depth. Here is something that worked for me. I have provided an example.
<h1 id="hello1">Hello World</h1>
<h2 id="hello2">Random</h2>
<button id="click">Click To Hide</button>
<script>
document.getElementById('click').addEventListener('click', function(){
doStuff();
});
function doStuff() {
for(var i=1; i<=2; i++){
var el = document.getElementById("hello" + i);
el.style.display = 'none';
}
}
</script>
Obviously just change the integers in the for loop to account for however many elements you are targeting, which in this example was 2.
The best way to do it, is to define a function, and pass it a parameter of the ID's name that you want to grab from the DOM, then every time you want to grab an ID and store it inside an array, then you can call the function
<p id="testing">Demo test!</p>
function grabbingId(element){
var storeId = document.getElementById(element);
return storeId;
}
grabbingId("testing").syle.color = "red";
You can use something like this whit array and for loop.
<p id='fisrt'>??????</p>
<p id='second'>??????</p>
<p id='third'>??????</p>
<p id='forth'>??????</p>
<p id='fifth'>??????</p>
<button id="change" onclick="changeColor()">color red</button>
<script>
var ids = ['fisrt','second','third','forth','fifth'];
function changeColor() {
for (var i = 0; i < ids.length; i++) {
document.getElementById(ids[i]).style.color='red';
}
}
</script>
For me worked flawles something like this
doStuff(
document.getElementById("myCircle1") ,
document.getElementById("myCircle2") ,
document.getElementById("myCircle3") ,
document.getElementById("myCircle4")
);
Use jQuery or similar to get access to the collection of elements in only one sentence. Of course, you need to put something like this in your html's "head" section:
<script type='text/javascript' src='url/to/my/jquery.1.xx.yy.js' ...>
So here is the magic:
.- First of all let's supose that you have some divs with IDs as you wrote, i.e.,
...some html...
<div id='MyCircle1'>some_inner_html_tags</div>
...more html...
<div id='MyCircle2'>more_html_tags_here</div>
...blabla...
<div id='MyCircleN'>more_and_more_tags_again</div>
...zzz...
.- With this 'spell' jQuery will return a collection of objects representing all div elements with IDs containing the entire string "myCircle" anywhere:
$("div[id*='myCircle']")
This is all! Note that you get rid of details like the numeric suffix, that you can manipulate all the divs in a single sentence, animate them... Voilá!
$("div[id*='myCircle']").addClass("myCircleDivClass").hide().fadeIn(1000);
Prove this in your browser's script console (press F12) right now!
As stated by jfriend00,
document.getElementById() only supports one name at a time and only returns a single node not an array of nodes.
However, here's some example code I created which you can give one or a comma separated list of id's. It will give you one or many elements in an array. If there are any errors, it will return an array with an Error as the only entry.
function safelyGetElementsByIds(ids){
if(typeof ids !== 'string') return new Error('ids must be a comma seperated string of ids or a single id string');
ids = ids.split(",");
let elements = [];
for(let i=0, len = ids.length; i<len; i++){
const currId = ids[i];
const currElement = (document.getElementById(currId) || new Error(currId + ' is not an HTML Element'));
if(currElement instanceof Error) return [currElement];
elements.push(currElement);
};
return elements;
}
safelyGetElementsByIds('realId1'); //returns [<HTML Element>]
safelyGetElementsByIds('fakeId1'); //returns [Error : fakeId1 is not an HTML Element]
safelyGetElementsByIds('realId1', 'realId2', 'realId3'); //returns [<HTML Element>,<HTML Element>,<HTML Element>]
safelyGetElementsByIds('realId1', 'realId2', 'fakeId3'); //returns [Error : fakeId3 is not an HTML Element]
If, like me, you want to create an or-like construction, where either of the elements is available on the page, you could use querySelector. querySelector tries locating the first id in the list, and if it can't be found continues to the next until it finds an element.
The difference with querySelectorAll is that it only finds a single element, so looping is not necessary.
document.querySelector('#myCircle1, #myCircle2, #myCircle3, #myCircle4');
here is the solution
if (
document.getElementById('73536573').value != '' &&
document.getElementById('1081743273').value != '' &&
document.getElementById('357118391').value != '' &&
document.getElementById('1238321094').value != '' &&
document.getElementById('1118122010').value != ''
) {
code
}
You can do it with document.getElementByID Here is how.
function dostuff (var here) {
if(add statment here) {
document.getElementById('First ID'));
document.getElementById('Second ID'));
}
}
There you go! xD

Categories

Resources