Javascript add class if parent has class - javascript

I want to add a second class to a div if one of the parents has a certain class
This is what I did:
var h = document.querySelectorAll('h1,h2,h3,h4,p'); //GET ALL ELEMENTS
for (var i = 0; i < h.length; i++) { // LOOP THROUGH IT AND ADD A CLASS
if (h[i].classList.length === 0) {
h[i].classList.add("fontFit");
}
}
var image_large = document.getElementsByClassName('image_large');
var fontFit = document.getElementsByClassName('fontFit');
if ($(fontFit).parents().hasClass('image_large')){ //IF `fontFit` has a parent named `image_large`; execute the `for` loop.
for (var j = 0; j < image_large.length; j++) {
if (fontFit[j].classList.length === 1) {
fontFit[j].classList.add('addClass');
}
}
}
It doesn't throw me an error but I have no idea why it doesn't work.

Your code is suffering from a logic error. Your code seems to assume that by doing the if statement first that all elements in fontFit are going to have a parent with a class image_large. But that is not true, some might some might not, you would rather test inside the loop
for (var j = 0; j < fontFit.length; j++) {
//note uses Element#closest, a more recently added DOM method
//if no parent with that class is found null is returned
if( fontFit[j].closest('.image_large') ){
fontFit[j].classList.add('addClass');
}
}
Of course you could just as easily directly find all elements with class fontFit that are children of image_large by using the right selector and then just looping over that collection
var fontFit = document.querySelector('.image_large .fontFit');
fontFit.forEach(element=>element.classList.add('addClass'));
//Or in jQuery since you seem to have it
$('.image_large .fontFit').addClass('addClass');

Related

Why my code throw "Cannot read property 'parentNode' of undefined"?

So i want to delete SVGs of my iframe, this is my code :
var parent = document.querySelectorAll("#main");
var child = parent[0].childNodes;
var lengthOfNodes = child.length;
for (var j = 0; j < lengthOfNodes; j++) {
child[j].parentNode.removeChild(child[j]);
}
child is an array of my svg element.
It works, but sometimes this algo throw me "Cannot read property 'parentNode' of undefined" and i don't know why... I need to relaunch this algo to get it work.
This way it would be too easy to remove elements you are iterating through
and take advantage of refrences
var parent = document.querySelectorAll("#main");
var child = parent[0].childNodes;
child.forEach(c => c.remove());
or if you want to break from a loop somewhere in future then ForEach loop is not going to help then
var parent = document.querySelectorAll("#main");
var child = parent[0].childNodes;
for(var c of child){
//can get out of loop anytime
c.remove();
}
Every time you remove a child, there is one less elements in the children array, so a loop like for (let j=0; j<length; j++) will make j too big at some point.
Prefer a code structured like this:
// This code removes all <li> nodes
const parent = document.querySelector('ul');
// Converts parent.children to an array,
// then use forEach which automatically handles varying length
[...parent.children].forEach(child => parent.removeChild(child));
<ul>
<li>xxx</li>
<li>xxx</li>
<li>xxx</li>
<li>xxx</li>
</ul>
Note that in order to wipe out the content of a parent node you might prefer the more simpler parent.innerHTML = ''!
You are changing the length of child itself in every iteration, Try this:
for (var i=0, j = child.length; i<j; i++)
{
child[i].parentNode.removeChild(child[i]);
}
Sup, and what do you think about a simple while loop instead of for loop ? Like this :
var parent = document.querySelectorAll("#main");
var child = parent[0].childNodes;
var j = 0;
while (obj.length > 0) {
child[j].parentNode.removeChild(child[j]);
}
Much easier for delete all elements. But if we want to iterate on a livelist with multiple conditions, we can do a reverse loop :
for (var i = obj.length - 1; i >= 0; i--) {
parent.removeChild(child[i]);
}
If the parent is undefined, at the moment you call parent[0] the array is empty. Are the SVG's being dynamically loaded? If so you might want to wrap this in a callback.

Getting previous element in for loop

EDIT 2 (to make the problem more understandable)
The effect I am trying to achieve is the following: everytime an element enters the viewport an 'is-visible' class is added to it and the same 'is-visible' class is removed from the previous element.
Now I've managed to make it work but I run a for loop to remove all is-visible classes before adding the is-visible class to the element in viewport.
It works but in terms of performance I think it would be better to just remove the class from element[i -1]. And this were I can't get it working.
Here is a simplified fiddle were I try to make the element[i-1] solution work: https://jsfiddle.net/epigeyre/vm36fpuo/11/.
EDIT 1 (to answer some of the questions asked)
I have corrected an issue raised by #Catalin Iancu (thanks a lot for your precious help) by using a modulus operator ((i+len-1)%len).
ORIGINAL QUESTION (not really clear)
I am trying to get the previous element in a for loop (to change its class) with following code :
for (var i = 0; i < array.length; i++) {
if(array[i-1] && my other conditions) {
array[i-1].classList.remove('is-visible');
array[i].classList.add('is-visible');
}
}
But it's not removing the class for [i-1] element.
Here is a more complete piece of code of my module (this is running within a scroll eventlistener):
var services = document.getElementsByClassName('services'),
contRect = servicesContainer.getBoundingClientRect();
for (var i = 0; i < services.length; i++) {
var serviceRect = services[i].getBoundingClientRect();
if ( !services[i].classList.contains('active') && Math.round(serviceRect.left) < contRect.right && services[i-1]) {
services[i-1].classList.remove('is-visible');
services[i].classList.add('is-visible');
}
}
Thanks for your help!
Your if(array[i-1] && my other conditions) is always true, except for the very first case where array[-1] doesn't exist. Therefore, it will remove and then add the active class for each element, which will make it seem as only the first element's class has been removed.
What you need is a better if condition or a break statement, when the loop is not needed anymore
for (var i = 0; i < array.length; i++) {
if(array[i] && i != array.length - 1) {
array[i].classList.remove('active');
}
}
array[array.length - 1].classList.add('active');
The problem probably is that based on your code: services[i-1].classList.remove('active'); and services[i].classList.add('active'); the 'active' class you add in current iteration will be removed in next iteration!
So your code has logical errors, array index does not return all prev items!
What if you create a variable that contain the previous element?
var previous = array[0];
for (var i = 0; i < array.length; i++) {
if(previous && my other conditions) {
previous.classList.remove('active');
array[i].classList.add('active');
break;
}
previous = array[i];
}

.each() add an attribute to a parent from jquery to js

I am a newbie in js and jquery and need help with rewriting my code from jquery to pure js.
I've got several parent divs. Each of them have a child div inside.
I want to add a class to both child and parent, but to parent as an attribute value in data-name.
Class names are stored in an array, in other words first parent and its child will get a array[0] class name, second parent and its child - array[1] class name, etc.
I use this jquery for this
$(".back").each(function(i) {
$(this).addClass(tile_array[i]);
$(this).parent().attr("data-name", tile_array[i]);
});
I tried to rewrite it in js like this:
var backs = document.querySelectorAll('back');
for (let i = 0; i < backs.length; i++) {
for (let j = 0; j < tile_array.length; j++) {
backs[i].classList.add(tile_array[j]);
backs[i].parentNode.setAttribute("data-name", tile_array[j]);
}
}
However, this does not work. How should I rewrite my code so that it works properly?
Thanks in advance!
try this : backs.length and tile_array.length are same .so no need ah inner loop
for (let i = 0; i < backs.length; i++) {
backs[i].classList.add(tile_array[i]);
backs[i].parentNode.setAttribute("data-name", tile_array[i]);
}
And add a class in querySelectorAll('.back')
You can skip the inner loop - you don't use it in your jQuery, why would you do that here?
Also, to set data attribute there is .dataset element property in Javascript. So your final code would be like:
var backs = document.querySelectorAll('back');
for (let i = 0; i < backs.length; i++) {
backs[i].classList.add(tile_array[i]);
backs[i].parentNode.dataset.name = tile_array[i]
}

custom querySelectorAll implemention

This was given to me as an interview question -- didn't get the job, but I still want to figure it out.
The objective is to write two querySelectorAll functions: one called qsa1 which works for selectors consisting of a single tag name (e.g. div or span) and another called qsa2 which accepts arbitrarily nested tag selectors (such as p span or ol li code).
I got the first one easily enough, but the second one is a bit trickier.
I suspect that, in order to handle a variable number of selectors, the proper solution might be recursive, but I figured I'd try to get something working that is iterative first. Here's what I've got so far:
qsa2 = function(node, selector) {
var selectors = selector.split(" ");
var matches;
var children;
var child;
var parents = node.getElementsByTagName(selectors[0]);
if (parents.length > 0) {
for (var i = 0; i < parents.length; i++) {
children = parents[i].getElementsByTagName(selectors[1]);
if (children.length > 0) {
for (var i = 0; i < parents.length; i++) {
child = children[i];
matches.push(child); // somehow store our result here
}
}
}
}
return matches;
}
The first problem with my code, aside from the fact that it doesn't work, is that it only handles two selectors (but it should be able to clear the first, second, and fourth cases).
The second problem is that I'm having trouble returning the correct result. I know that, just as in qsa1, I should be returning the same result as I'd get by calling the getElementsByTagName() function which "returns a live NodeList of elements with the given tag name". Creating an array and pushing or appending the Nodes to it isn't cutting it.
How do I compose the proper return result?
(For context, the full body of code can be found here)
Here's how I'd do it
function qsa2(selector) {
var next = document;
selector.split(/\s+/g).forEach(function(sel) {
var arr = [];
(Array.isArray(next) ? next : [next]).forEach(function(el) {
arr = arr.concat( [].slice.call(el.getElementsByTagName(sel) ));
});
next = arr;
});
return next;
}
Assume we always start with the document as context, then split the selector on spaces, like you're already doing, and iterate over the tagnames.
On each iteration, just overwrite the outer next variable, and run the loop again.
I've used an array and concat to store the results in the loop.
This is somewhat similar to the code in the question, but it should be noted that you never create an array, in fact the matches variable is undefined, and can't be pushed to.
You have syntax errors here:
if (parents.length > 0) {
for (var i = 0; i < parents.length; i++) {
children = parents[i].getElementsByTagName(selectors[1]);
if (children.length > 0) {
for (var i = 0; i < parents.length; i++) { // <-----------------------
Instead of going over the length of the children, you go over the length of the parent.
As well as the fact that you are reusing iteration variable names! This means the i that's mapped to the length of the parent is overwritten in the child loop!
On a side note, a for loop won't iterate over the elements if it's empty anyway, so your checks are redundant.
It should be the following:
for (var i = 0; i < parents.length; i++) {
children = parents[i].getElementsByTagName(selectors[1]);
for (var k = 0; k < children.length; i++) {
Instead of using an iterative solution, I would suggest using a recursive solution like the following:
var matches = [];
function recursivelySelectChildren(selectors, nodes){
if (selectors.length != 0){
for (var i = 0; i < nodes.length; i++){
recursivelySelectChildren(nodes[i].getElementsByTagName(selectors[0]), selectors.slice(1))
}
} else {
matches.push(nodes);
}
}
function qsa(selector, node){
node = node || document;
recursivelySelectChildren(selector.split(" "), [node]);
return matches;
}

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"));

Categories