How to traverse dom elements in raw JavaScript? - javascript

I do everything in jQuery but now am going back to learn JavaScript proper. So how could I do the equivalent of this in vanilla js:
$('ul li a', '#myDiv').hide();

var as = document.querySelectorAll("#myDiv ul li a"),
forEach = Array.prototype.forEach;
forEach.call(as, function(a) {
a.style.display = "none";
});
Live Example
.getElementById, .querySelectorAll, .forEach, .call
This works on all modern browsers and only breaks on legacy browsers (like IE8).
You don't want to do cross browser compliance by hand, you should use a DOM shim for that.
You can use the ES5-shim for .forEach support. and you can find a querySelectorAll polyfill here which uses Sizzle.
For detailed browser support see
can I use querySelectorAll
ES5 compatibility table
Don't listen to those guys complaining about browser compliance. Just slap on a whole bunch of polyfills using Modernizr and friends and then you can forget about IE!

Don't rely on querySelectorAll(). It doesn't work in IE <= 7 or FF 3. If you want to use Vanilla JS you need to learn how to write browser compatible code:
(function(){
var myDiv = document.getElementById('myDiv');
// Use querySelectorAll if it exists
// This includes all modern browsers
if(myDiv.querySelectorAll){
as = myDiv.querySelectorAll('ul li a');
for(var i = 0; i < as.length; i++)
as[i].style.display = 'none';
return;
}
// Otherwise do it the slower way in order to support older browsers
// uls contains a live list of ul elements
// that are within the element with id 'myDiv'
var uls = myDiv.getElementsByTagName('ul');
var lis = [];
for(var i = 0; i < uls.length; i++){
var l = uls[i].getElementsByTagName('li');
// l is a live list of lis
for(var j = 0; j < l.length; j++)
lis.push(l[j]);
}
// lis is an array of all lis which are within uls
//within the element with id 'myDiv'
var as = [];
for(var i = 0; i < lis.length; i++){
var a = lis[i].getElementsByTagName('a');
// a is a live list of anchors
for(var j = 0; j < a.length; j++)
a[j].style.display = 'none'; // Hide each one
}
})();
Here is a JSFiddle. Just so you're aware, getElementsByClassName() is another commonly used method of traversal which needs an alternative approach if it's not available (IE <= 8)

Since a UL element can only have LI as children, the fastest POJS method is to get all the ULs then all the As:
function doStuff() {
var uls = document.getElementsByTagName('ul');
var as;
for (var i=0, iLen=uls.length; i<iLen; i++) {
as = uls[i].geElementsByTagName('a');
for (var j=0, jLen=as.length; j<jLen; j++) {
as[j].style.display = 'none';
}
}
}
There doesn't seem any point to a querySelectorAll solution since the above is likely faster, not much more code and will work in every browser back to IE 4.

Good old document.getElementById or document.getElementsByTagName should get you started.

You can use querySelectorAll to find the elements, though keep in mind jQuery probably uses extra tricks for compatibility and so this may behave differently.
Then you could iterate over that list (code shown on the NodeList documentation page, linked from the above page) and set each element's element.style.display = "none".

Related

getElementsByClassName vs querySelectorAll

if I use
var temp = document.querySelectorAll(".class");
for (var i=0, max=temp.length; i<max; i++) {
temp[i].className = "new_class";
}
everything works fine. All nodes change their classes.
But, with gEBCN:
var temp = document.getElementsByClassName("class");
for (var i=0, max=temp.length; i<max; i++) {
temp[i].className = "new_class";
}
I get error. Code jumps out of the loop at some point, not finishing the job with msg "can't set className of null".
I understand that this is static vs live nodelist problem (I think), but since gEBCN is much faster and I need to traverse through huge list of nodes (tree), I would really like to use getElementsByClassName.
Is there anything I can do to stick with gEBCN and not being forced to use querySelectorAll?
That's because HTMLCollection returned by getElementsByClassName is live.
That means that if you add "class" to some element's classList, it will magically appear in temp.
The oposite is also true: if you remove the "class" class of an element inside temp, it will no longer be there.
Therefore, changing the classes reindexes the collection and changes its length. So the problem is that you iterate it catching its length beforehand, and without taking into account the changes of the indices.
To avoid this problem, you can:
Use a non live collection. For example,
var temp = document.querySelectorAll(".class");
Convert the live HTMLCollection to an array. For example, with one of these
temp = [].slice.call(temp);
temp = Array.from(temp); // EcmaScript 6
Iterate backwards. For example, see #Quentin's answer.
Take into account the changes of the indices. For example,
for (var i=0; i<temp.length; ++i) {
temp[i].className = "new_class";
--i; // Subtract 1 each time you remove an element from the collection
}
while(temp.length) {
temp[0].className = "new_class";
}
Loop over the list backwards, then elements will vanish from the end (where you aren't looking any more).
for (var i = temp.length - 1; i >= 0; i--) {
temp[i].className = "new_class";
}
Note, however, that IE 8 supports querySelectorAll but not getElementsByClassName, so you might want to prefer querySelectorAll for better browser support.
Alternatively, don't remove the existing class:
for (var i=0, max=temp.length; i<max; i++) {
temp[i].className += " new_class";
}

Select and add class in javascript

Cross Platform if possible, how can I select classes in Javascript (but not Jquery please -MooTools is fine though-) on code that I can't add an ID?
Specifically, I want to add the class "cf" on any li below:
HTML
<div class="itemRelated">
<ul>
<li class="even">
<li class="odd">
<li class="even">
I tried to fiddle it but something is missing:
Javascript
var list, i;
list = document.getElementsByClassName("even, odd");
for (i = 0; i < list.length; ++i) {
list[index].setAttribute('class', 'cf');
}
JSFiddle
ps. This question phenomenally has possible duplicates, (another one) but none of the answers makes it clear.
Using plain javascript:
var list;
list = document.querySelectorAll("li.even, li.odd");
for (var i = 0; i < list.length; ++i) {
list[i].classList.add('cf');
}
Demo
For older browsers you could use this:
var list = [];
var elements = document.getElementsByTagName('li');
for (var i = 0; i < elements.length; ++i) {
if (elements[i].className == "even" || elements[i].className == "odd") {
list.push(elements[i]);
};
}
for (var i = 0; i < list.length; ++i) {
if (list[i].className.split(' ').indexOf('cf') < 0) {
list[i].className = list[i].className + ' cf';
}
}
Demo
Using Mootools:
$$('.itemRelated li').addClass('cf');
Demo
or if you want to target specific by Class:
$$('li.even, li.odd').addClass('cf');
Demo
I know this is old, but is there any reason not to simply do this (besides potential browser support issues)?
document.querySelectorAll("li.even, li.odd").forEach((el) => {
el.classList.add('cf');
});
Support: https://caniuse.com/#feat=es5
Using some newer browser objects and methods.
Pure JS:
Details: old fashioned way, declaring stuff at the beginging than iterating in one big loop over elements with index 'i', no big science here. One thing is using classList object which is a smart way to add/remove/check classes inside arrays.
var elements = document.querySelectorAll('.even','.odd'),
i, length;
for(i = 0, length = elements.length; i < length; i++) {
elements[i].classList.add('cf');
}
Pure JS - 2:
Details: document.querySelectorAll returns an array-like object which can be accessed via indexes but has no Array methods. Calling slice from Array.prototype returns an array of fetched elements instantly (probably the fastest NodeList -> Array conversion). Than you can use a .forEach method on newly created array object.
Array.prototype.slice.call(document.querySelectorAll('.even','.odd'))
.forEach(function(element) {
element.classList.add('cf');
});
Pure JS - 3:
Details: this is quite similar to v2, [].map is roughly that same as Array.prototype.map except here you declare an empty array to call the map method. It's shorter but more (ok little more) memory consuming. .map method runs a function on every element from the array and returns a new array (adding return in inside function would cause filling the returned values, here it's unused).
[].map.call(document.querySelectorAll('.even','.odd'), function(element) {
element.classList.add('cf');
});
Pick one and use ;)
querySelectorAll is supported in IE8, getElementsByClassName is not, which does not get two classes at the same time either. None of them work in iE7, but who cares.
Then it's just a matter of iterating and adding to the className property.
var list = document.querySelectorAll(".even, .odd");
for (var i = list.length; i--;) {
list[i].className = list[i].className + ' cf';
}
FIDDLE
As others mention for selecting the elements you should use .querySelectorAll() method. DOM also provides classList API which supports adding, removing and toggling classes:
var list, i;
list = document.querySelectorAll('.even, .foo');
for (i = 0; i < list.length; i++) {
list[i].classList.add('cf');
}
As always IE9 and bellow don't support the API, if you want to support those browsers you can use a shim, MDN has one.
If you want to select elements with different classes all together then the best choice is querySelectorAll.
querySelectorAll uses CSS selectors to select elements. As we add the same CSS properties to different elements by separating them by a comma in the same way we can select those elements using this.
.even, .odd {
font-weight: bold;
}
Both elements with class 'even' and 'odd' get bold.
let list = document.querySelectorAll('.even, .odd');
Now, both the elements are selected.
+Point: you should use classList.add() method to add class.
Here is the complete code for you.
let list = document.querySelectorAll('.even, .odd');
for (i = 0; i < list.length; ++i) {
list.classList.add('cf');
}

select all elements of class and iterate over each even object (nth child (even) replacement)

I'm displaying the alphabet with each letter having the same background (width 31px ). I need half of the letters to have a width of 30px. This is handled by:
div.alpha:nth-child(even) {width: 30px;}
However, this does not work in ie because of the pseudo compatibility issues.
I'm looking for a pure javascript alternative that works with ie IN quirks mode.
I tried:
function letterwidth () {
var a = document.querySelectorAll ('div.alphabet');
for (var i = 0, b = a.length; i < b; i++){
if (i % 2 == 0) {
a[i].style.width = '30px';
}
}
}
but I get an error:
SCRIPT438: Object doesn't support property or method 'querySelectorAll'
If querySelector isn't supported by the browser, you'll need to use regular DOM traversal methods :
var el = [],
div = document.getElementsByTagName('div');
for (var i=0; i<div.length; i++) {
if (div[i].className.indexOf('alphabet') != -1)
el.push(div[i]);
}
for (var i=0; i<el.length; i++){
if (i % 2 == 0)
el[i].style.width = '30px';
}
FIDDLE
I am assuming you are using an old version of Internet Explorer that does not support document.querySelectorAll
Browsers where you can use querySelectorAll.
Object doesn't support property or method 'querySelectorAll'
Obsolete browser.
jQuery is pure JavaScript. So unless you want to rewrite the selector parser yourself to finally get something similar to the one from jQuery, i suggest you directly use it.

JavaScript loop through all elements with tagname, alert

var all = document.getElementsByTagName("a");
for (var i=0, max=all.length; i < max; i++) {
alert(x.innerHTML);
}
The purpose of this script is obvious: it tries to loop through all the elements with the tag name a, and alert the contents of each one.
It doesn't run right.
It works fine, with one element, it alerts it's contents, but when there are more then one, it starts echoing undefined for each.
You haven't provided a definition for x. Try this:
var all = document.getElementsByTagName("a");
for(var i = 0, max = all.length; i < max; i++)
{
alert(all[i].innerHTML);
}
you should use alert(all[i].innerHTML). x is undefined
x is obviously undefined. You need to have something like:
var all = document.getElementsByTagName("a");
for (var i = 0, x; x = all[i++];)
alert(x.innerHTML);
However alert in a loop is really annoying, I would suggest to use console.log instead.
Tips: in browsers that supports already for…of, such Firefox, you can simply have:
var all = document.getElementsByTagName("a");
for (var x of all)
console.log(x.innerHTML);
Of course you can't use that on webside across browsers, it's just something good to know in the upcoming ES6 – or if you're going to write a Firefox's extension for instance.

How to run through each checked checkbox with a classname using JavaScript without jQuery

Is there an immediate equivalent in javascript for the below jquery code?
$('.checkbox').each(function() {
if ($(this).is(':checked')) {
//logic here
}
});
I'm trying to run through all the checkboxes on a page with class = 'checkbox' - the client doesn't want to use jQuery, so I need an alternative for the above.
I'm hoping I can avoid writing a long function from scratch to do this and simply use something built-in to JavaScript, but it's looking like it's not possible.
Many older browsers don't support querySelectorAll or getElementsByClassName, so you'd have to loop over all <input> elements in those browsers. It's always best to check for those functions first, though.
Secondly, you should never use $(this).is(":checked") — not even in jQuery — it's a very slow path to take when looking for this.checked.
This should get you going:
var base = document,
inps, tmp, i = 0, reg = /\bcheckbox\b/;
// getElementsByClassName is the fastest method
if (base.getElementsByClassName)
inps = base.getElementsByClassName("checkbox");
// Followed by querySelectorAll
else if (base.querySelectorAll)
inps = base.querySelectorAll(".checkbox");
// But if neither exist, loop through all the elements and check the class
else {
inps = [];
var tmp = base.getElementsByTagName("input");
i = tmp.length;
while (i--) {
if (reg.test(tmp[i].className)
inps.push(tmp[i]);
}
}
// Finally, loop through the matched elements and apply your logic
i = inps.length;
while (i--) {
var current = inps[i];
if (current.checked) {
// logic here
}
}
In the example above, you can change the value of base to any element. This means that, if all these elements have a common parent or ancestor node, you can set that element as the base and it should run faster, e.g:
var base = document.getElementById("myForm");
var checkboxes = document.getElementsByClassName('checkbox');
for(var i = 0; i < checkboxes.length; i++){
if(checkboxes[i].checked){}
else {}
}
See comments below. you can use getElementsByTagName for older versions of IE and other older browsers.
Try the following
var all = document.getElementsByClassName('checkbox');
for (var i = 0; i < all.length; i++) {
var current = all[i];
if (current.checked) {
// Logic here
}
}
JavaScript has built-in methods for getting DOM elements by ID, or by tag name, but selecting by class isn't supported in older versions of IE. However, it would be fairly fast to obtain all inputs and test them for the checkbox type:
var x=document.getElementsByTagName("input");
for (var i=0; i<x.length; i++) {
if (x[i].type === "checkbox" && x[i].checked) {
// do something
}
}
You can also test if their class is "checkbox", but this gets complicated if they have more than one class. If they don't:
var x=document.getElementsByTagName("input");
for (var i=0; i<x.length; i++) {
if (x[i].className === "checkbox" && x[i].checked) {
// do something
}
}
It's kind of browser dependent then. But with a modern browser you'd use document.getElementsByClassName('checkbox') to get an array that you'd iterate through, then is(':checked') becomes the more common if(array[i].checked){}.
Feel free to read about the compatible browsers. You'll find that it doesn't work in Internet Explorer 5.5, 6, and 7.
I think jQuery works around this in sizzle about here.
So it might look like:
var allCheckbox;
if (document.getElementsByClassName) {
allCheckbox = document.getElementsByClassName('checkbox');
} else {
allCheckbox = new Array();
var i = 0;
var all = document.getElementsByTagName('*') {
for (var j=0; j < all.length; j++) {
//Comparison cribbed from sizzle
if ((" " + (all[j].className || all[j].getAttribute("class")) + " ")
.indexOf('checkbox') > -1
) {
allCheckbox[i++] = all[j];
}
}
}
for (var i = 0; i < allChecked.length; i++) {
if (allChecked[i].checked) {
// Logic here
}
}
Final note: getElementsByTagName('*') doesn't work with Internet Explorer 5.5.

Categories

Resources