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

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]
}

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.

Javascript add class if parent has class

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

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];
}

Delete object by attribute in javascript

I have a few different tables on the same page but unfortunately they were not assigned any unique id's. I want to remove a table using a JS command, but since id cannot be used, is it possible to delete a table based on a certain attribute it has? For example, is there a command to delete all tables on the page that have the attribute: width="25%" ?
You can use querySelectorAll to do that.
var x = document.querySelectorAll("table[width='25%']");
for (var i=0; i<x.length; i++) { //returns array of elements that match the attribute selector
x[i].remove(); //call prototype method defined below
}
Removing is tricky, I found this code that makes a nice remove method
Element.prototype.remove = function() {
this.parentElement.removeChild(this);
}
NodeList.prototype.remove = HTMLCollection.prototype.remove = function() {
for(var i = 0, len = this.length; i < len; i++) {
if(this[i] && this[i].parentElement) {
this[i].parentElement.removeChild(this[i]);
}
}
}
This creates a prototype remove() function that iterates the node and deletes the children.
Please note that querySelectorAll will not work in IE8 or below, but the poster of the prototype method said that it should work in IE8 but not 7.
I know this already has some solutions, but I'll offer up one more alternative.
var tables = document.getElementsByTagName('table');
for(var i = 0; i < tables.length; i++){
if(tables[i].getAttribute('width') == "25%"){
tables[i].parentNode.removeChild(tables[i]);
}
}
Demo at http://codepen.io/michaelehead/pen/HfdKx.
Yes you can. The easiest way is to use JQuery.
In your javascript code you would just write:
$("[attribute=value]").remove()
So in your case it could be something like $("table[width='25%']").remove()

For cycle or for each with js objects

I got a problem to add a onclick event to object who can be many times in same page
I am trying to
var i;
for (i = 1; i<=10; i++) {
var tmpObj='lov_DgId_D_'+i;
var tmpObj2=tmpObj.getElementsByTagName('a')[0];
if (tmpObj2 != null) {
tmpObj2.onclick= DgIdOnClick;
}
}
But got a error TypeError:
Object lov_DgId_D_1 has no method 'getElementsByTagName' , but this is working
lov_DgId_D_2.getElementsByTagName('a')[0].onclick= DgIdOnClick;
This ibject lov_DgId_D_ can be from 1 like lov_DgId_D_1 or lov_DgId_D_99 u.t.c
What wil be the best solution to add onclick to all lov_DgId_D_* objects ?
As you use jquery, the simplest is
for (i = 1; i<=10; i++) {
$('#lov_DgId_D_'+ i+ ' a').click(DgIdOnClick);
}
If you want to bind your event handler to all a elements inside elements whose id starts with lov_DgId_D_, then it's as simple as
$('[id^="lov_DgId_D_"] a').click(DgIdOnClick);
The problem you have is a confusion between the id of an element and the actual element. Some code that should work for you is this one:
var i;
for (i = 1; i<=10; i++) {
var tmpObj=document.getElementById('lov_DgId_D_'+i); // <-- here
var tmpObj2=tmpObj.getElementsByTagName('a')[0];
if (tmpObj2 != null) {
tmpObj2.onclick= DgIdOnClick;
}
}
Slightly easier to read code:
for (var i=0; i<=10; i++) {
var anchor = document.querySelector('#lov_DgId_D_'+i + ' a');
if (anchor) anchor.onclick = DgIdOnClick;
}
A note: this code attaches a click event to the first anchor (a element) inside each element with the id lov_DgId_D_n, with n being 1->10. Your original code seems to want to do the same thing.
Another note: usually when you iterate over elements using their id's to identify them, you are better suited to add a class to those elements instead. It provides for more maintaintable code and probably easier to understand as well.

Categories