Vanilla Javascript insertBefore() in for loop - javascript

I have the following html structure.
<div id="page1" class="page">
<div class="firstchild"></div>
<div class="secondchild"></div>
<div class="thirdchild"></div>
<div class="forthchild"></div>
</div>
<div id="page2" class="page">
<div class="firstchild"></div>
<div class="secondchild"></div>
<div class="thirdchild"></div>
<div class="forthchild"></div>
</div>
<div id="page3" class="page">
<div class="firstchild"></div>
<div class="secondchild"></div>
<div class="thirdchild"></div>
<div class="forthchild"></div>
</div>
And my javascript structure is.
var pageCLASS = frame.querySelectorAll(".page");
//var pageCLASS = frame.getElementsByClassName('page')[0];
var leng = pageCLASS.length;
for (var i = 0; i < leng; ++i) {
var pageID = frame.getElementById('page' + (i + 1));
var firstchild = frame.getElementsByClassName('firstchild')[0];
var secondchild = frame.getElementsByClassName('secondchild')[0];
var thirdchild = frame.getElementsByClassName('thirdchild')[0];
pageID.insertBefore(thirdchild, firstchild.nextSibling);
//pageCLASS.insertBefore(thirdchild, firstchild.nextSibling);
}
Now I have problems with the thirdchild being moved to below the firstchild and above the secondchild in all of page1, page2, and page3 together. The code above only moves it in page1, but for the other 2 which does nothing. The frame shown in the source is an iframe stored on the same domain with access to it's elements. Can I please get some advice on what I am doing wrong as I want to move all thirdchilds in each div to below the first child in each of their parent div?

The problem you are having is that you are constantly targeting the same elements with e.g.
var firstchild = frame.getElementsByClassName('firstchild')[0];
because this instruction always returns the first occurrence of such an element in the iframe and never the second or third.
In order to be sure that you are targeting the correct elements you can rewrite some of your code to only search for the elements that are contained within a certain parent and not inside the whole iframe.
You can then use something like this instead
var firstChild = pageID.querySelector('.firstchild');
which will only search for an element (the first occurrence) with class firstchild that is contained within some other element (in this case the element saved in pageID).
Check below (I exchanged the form for document so we can test here):
var pageCLASS = document.querySelectorAll(".page");
var leng = pageCLASS.length;
for (var i = 1; i <= leng; i++) {
var pageID = document.getElementById('page' + i);
var firstChild = pageID.querySelector('.firstchild');
var thirdChild = pageID.querySelector('.thirdchild');
firstChild.parentNode.insertBefore(thirdChild, firstChild.nextSibling);
}
.page {
border: 1px solid #09f;
}
<div id="page1" class="page">
<div class="firstchild">first child</div>
<div class="secondchild">second child</div>
<div class="thirdchild">third child</div>
<div class="forthchild">fourth child</div>
</div>
<div id="page2" class="page">
<div class="firstchild">first child</div>
<div class="secondchild">second child</div>
<div class="thirdchild">third child</div>
<div class="forthchild">fourth child</div>
</div>
<div id="page3" class="page">
<div class="firstchild">first child</div>
<div class="secondchild">second child</div>
<div class="thirdchild">third child</div>
<div class="forthchild">fourth child</div>
</div>

Related

Why Insert before first child not working

If the element has children insert a new div "i-new-element". parent2 and parent3 get a new div.
child3-parent3 has children but doesn't get a new giant. Why?
How can I make it possible for children who have children to get a new div?
it should look like:
<div id="child3-parent3">
<div id="i-new-element"></div>
<div id="child3"></div>
<div id="child4"></div>
</div>
var container = document.getElementById("container").querySelectorAll("#container > *");
container.forEach(function(div) {
{
if (div.hasChildNodes()) {
let parentElement = div;
let theFirstChild = parentElement.firstChild;
let newElement = document.createElement("div")
newElement.id = "i-new-eleemnt"
parentElement.insertBefore(newElement, theFirstChild)
}
}
});
<div id="container">
<div id="parent1"></div>
<div id="parent2">
<div id="child1"></div>
<div id="child2"></div>
<div id="child3-parent3">
<div id="child3"></div>
<div id="child4"></div>
</div>
</div>
<div id="parent3">
<div id="child5"></div>
<div id="child6"></div>
</div>
</div>
Using
var container = document.getElementById("container");
var elements = container.querySelectorAll(":scope *");
instead of
var container = document.getElementById("container").querySelectorAll("#container > *");
should fix your problem.
var container = document.getElementById("container");
var elements = container.querySelectorAll(":scope *");
elements.forEach(function(div){
{
if (div.hasChildNodes()) {
let parentElement = div;
let theFirstChild = parentElement.firstChild;
let newElement = document.createElement("div")
newElement.id = "i-new-eleemnt"
parentElement.insertBefore(newElement, theFirstChild)
}
}
});
<div id="container">
<div id="parent1"></div>
<div id="parent2">
<div id="child1"></div>
<div id="child2"></div>
<div id="child3-parent3">
<div id="child3"></div>
<div id="child4"></div>
</div>
</div>
<div id="parent3">
<div id="child5"></div>
<div id="child6"></div>
</div>
</div>
Edit: Further information about the :scope CSS pseudo-class can be found here and here.
I believe your problem is the following, even it is not described well in your sample HTML:
<div id="parent1"></div> per instance is actually not an empty node but contains some text i.e. <div id="parent1">some text here</div>.
You might believe or not, but node.hasChildNodes() will count any text as a child node (text node, nodeType = 3), so it will always return true is any text is present.
To avoid that, you can filter the text nodes first or just use this workaround:
Replace this line:
if (div.hasChildNodes()) {
with that line:
if (div.children.length) {
children property is not counting text nodes.
That's all you have to do, I believe.
var container = document.querySelectorAll("#container > *");
container.forEach(function(div) {
{
if (div.children.length) {
let parentElement = div;
let theFirstChild = parentElement.children[0];
let newElement = document.createElement("div")
newElement.id = "i-new-eleemnt";
newElement.innerHTML = 'i-new-eleemnt'
parentElement.insertBefore(newElement, theFirstChild);
}
}
});
<div id="container">
<div id="parent1">parent1</div>
<div id="parent2">parent2
<div id="child1">child1</div>
<div id="child2">child2</div>
<div id="child3-parent3">child3-parent3
<div id="child3">child3</div>
<div id="child4">child4</div>
</div>
</div>
<div id="parent3">parent3
<div id="child5">child5</div>
<div id="child6">child6</div>
</div>
</div>

How to retrieve the div first child element sibling node using querySelector?

I have the DOM structure like below
<div class="table_body">
<div class="table_row">
<div class="table_cell">first</div>
<div class="table_cell">chocolate products</div><!-- want to access this div content -->
</div>
<div class="table_row">
<div class="table_cell">third</div>
<div class="table_cell">fourth</div>
</div>
</div>
From the above HTML I want to access the div content of second div with classname table_cell inside first table_row div.
So basically I want to retrieve the content of div with classname table_cell with content chocolate products.
I have tried to do it like below
const element = document.querySelector('.rdt_TableBody');
const element1 = element.querySelectorAll('.rdt_TableRow')[0]
const element2 = element1.querySelectorAll('.rdt_TableCell')[0].innerHTML;
When I log element2 value it gives some strange output and not the text "chocolate products"
Could someone help me how to fix this. Thanks.
You can use:
the :nth-of-type pseudo-selector
combined with the immediate-child selector (>)
Example:
const selectedDiv = document.querySelector('.table_body > div:nth-of-type(1) > div:nth-of-type(2)');
Working Example:
const selectedDiv = document.querySelector('.table_body > div:nth-of-type(1) > div:nth-of-type(2)');
selectedDiv.style.color = 'white';
selectedDiv.style.backgroundColor = 'red';
<div class="table_body">
<div class="table_row">
<div class="table_cell">first</div>
<div class="table_cell">chocolate products</div> //want to access this div content
</div>
<div class="table_row">
<div class="table_cell">third</div>
<div class="table_cell">fourth</div>
</div>
</div>
In your code
element1.querySelectorAll('.table_cell')[0], this is targeting the first element i.e., <div class="table_cell">first</div>. That's the reason why you are not getting the expected output.
I have made it to element1.querySelectorAll('.table_cell')[1], so that it'll target <div class="table_cell">chocolate products</div>.
const element = document.querySelector('.table_body');
const element1 = element.querySelectorAll('.table_row')[0]
const element2 = element1.querySelectorAll('.table_cell')[1].innerHTML;
console.log(element2);
<div class="table_body">
<div class="table_row">
<div class="table_cell">first</div>
<div class="table_cell">chocolate products</div>
</div>
<div class="table_row">
<div class="table_cell">third</div>
<div class="table_cell">fourth</div>
</div>
</div>
Since the element that you want to target is the last div with having class table_cell, you can use :last-of-type on table_cell class using document.querySelector. But otherwise you can also use :nth-of-type if there are more than 2 elements and you want to target any element in between first and last.
Below is the example using :last-of-type.
const elem = document.querySelector(".table_row > .table_cell:last-of-type");
console.log(elem?.innerHTML);
<div class="table_body">
<div class="table_row">
<div class="table_cell">first</div>
<div class="table_cell">chocolate products</div> //want to access this div content
</div>
<div class="table_row">
<div class="table_cell">third</div>
<div class="table_cell">fourth</div>
</div>
</div>
For more info you can refer :nth-of-type, :last-of-type and child combinator(>).

Wrapping elements while looping through HTMLCollection causes problem

I want to wrap each item of the container in a div. When I loop through HTMLCollection, some elements are accessed multiple times while others are left out
HTML
<div class="container">
<div class="item_1"></div>
<div class="item_2"></div>
<div class="item_3"></div>
<div class="item_4"></div>
<div class="item_5"></div>
<div class="item_6"></div>
<div class="item_7"></div>
<div class="item_8"></div>
<div class="item_9"></div>
</div>
JS
const container = document.querySelector('.container');
const items = container.children;
for(let i = 0; i < items.length; i++) {
const wrapper = document.createElement('div');
wrapper.classList.add('wrapper');
wrapper.appendChild(items[i]);
container.appendChild(wrapper);
}
Looping directly through HTMLCollection gives this bizarre result
<div class="container">
<div class="item_2"></div>
<div class="item_4"></div>
<div class="item_6"></div>
<div class="item_8"></div>
<div class="wrapper">
<div class="item_1"></div>
</div>
<div class="wrapper">
<div class="item_5"></div>
</div>
<div class="wrapper">
<div class="item_9"></div>
</div>
<div class="wrapper">
<div class="wrapper">
<div class="item_7"></div>
</div>
</div>
<div class="wrapper">
<div class="wrapper">
<div class="wrapper">
<div class="wrapper">
<div class="item_3"></div>
</div>
</div>
</div>
</div>
</div>
problem gets solved when I convert HTMLCollection to an Array
const items = Array.from(container.children);
I can't understand what causes such behavior
You were iterating the container.children list which you were also changing during the iterations. This messed up the iteration. You can solve this, as you mentioned yourself, by converting the container.children to an array because then you are not iterating over the live container.children list but over an array copy of that. This copy is still referring to the correct child elements so they are moved correctly with the appendChild() function.
As an alternative you can use the querySelecterAll() to retrieve all the elements you want to wrap.
const container = document.querySelector('.container');
const items = container.querySelectorAll('.container > *');
for(let i = 0; i < items.length; i++) {
const wrapper = document.createElement('div');
wrapper.classList.add('wrapper');
wrapper.appendChild(items[i]);
container.appendChild(wrapper);
}
.wrapper {
background-color: red;
}
<div class="container">
<div class="item_1">1</div>
<div class="item_2">2</div>
<div class="item_3">3</div>
<div class="item_4">4</div>
<div class="item_5">5</div>
<div class="item_6">6</div>
<div class="item_7">7</div>
<div class="item_8">8</div>
<div class="item_9">9</div>
</div>

How to find a parent with a known class in plain JavaScript

Please look at this html code first:
<div class="parent">
<div class="child">
</div>
<div class="child">
<!-- Some code here -->
</div>
</div>
<div class="parent>
<!-- some code here -->
</div>
Suppose, I'm in "child" class, & I need to get the closest parent class of a "child". Remember, there are more than one "parent" class, I just need the closest "parent" only using plain JavaScript, without using JQuery.
How to do that?
You can try using closest()
document.querySelectorAll('.child').forEach(function(child){
console.log(child.closest('.parent'));
});
<div class="parent">
<div class="child">
</div>
<div class="child">
<!-- Some code here -->
</div>
</div>
<div class="parent">
<!-- some code here -->
</div>
You can create a helper function and resuse it.Hope this is what you are looking for -
let getParentClass = node => {
return node.parentNode.classList.value;
}
let child = document.querySelectorAll('.child')[0]; // find the node
console.log(getParentClass(child));
<div class="parent">
<div class="child">
</div>
<div class="child">
<!-- Some code here -->
</div>
</div>
<div class="parent">
<!-- some code here -->
</div>
You can use parentNode to get the closest parent. Or you can use closest from vanilla js.
const child = document.querySelector('.child');
const closestParent = child.parentNode;
const child = document.querySelector('.child');
const closestParent = child.parentNode;
console.log(closestParent);
// Or you can use closest
const closest = child.closest('.parent');
console.log(closest);
<div class="parent">
<div class="child">
</div>
<div class="child">
<!-- Some code here -->
</div>
</div>
<!DOCTYPE html>
<div class="parent">
<div class="child">
</div>
<div class="child">
<!-- Some code here -->
</div>
</div>
<div class="parent">
<!-- some code here -->
</div>
<script>
const child = document.getElementsByClassName('child')[0];
console.log(child)
const parent = child.closest('.parent');
console.log(parent)
</script>
Use this.
var parent = document.querySelectorAll(".child");
for(var p = 0; p < parent.length; p++) {
parent[p].parentElement.classList.add("newClass");
//parent[p].//do something
}
or directly quote the parent
var parent = document.querySelectorAll(".child");
for(var p = 0; p < parent.length; p++) {
parent[p].closest(".parent").classList.add("newClass");
//parent[p].closest(".parent").//do something
}

Wrapping an anchor tag around dynamically generated divs

I have a div that generates multiple elements inside it:
<div class="lists">
<?php for($i=0;$i<6;$i++) { ?>
<div class="list history[[$i]]" id="history[[$i]]">
<div class="info">
<div class="picture monophoto">
<div class="text">BO</div>
<div class="img" style="background-image: url();"></div>
</div>
<div class="right">
<div class="lineone">John Smith</div>
<div class="linetwo">Daily Essentials</div>
</div>
</div>
<div class="boxes">
<div class="left">
<div class="box box1"></div>
</div>
<div class="right">
<div class="box box2"></div>
<div class="box box3"></div>
</div>
</div>
<a class="cbutton whiteonblack">VIEW LIST<!--SEE <span class="owner">JOHN'S</span>--></a>
</div>
<?php } ?>
</div>
I am trying to wrap the following div with an anchor tag so it links:
<div class="boxes"> </div>
Using jQuery I am trying to wrap this using jQuery that is part of a loop:
for(var i = 0; i < listLength; i++){
for(var y = 0; y < result.history[i].length; y++){
var history = document.getElementById('history' + i);
history.querySelector('.boxes').wrap('');
}
}
This is not resulting in an anchor tag showing up at all on the DOM. What am I doing wrong and how do I fix it?
Edit: I clarified which div
Edit 2: To clarify, each of the links are actually going to be dynamically generated. I am just using google.com as an example. So effecting all of a specific class wont work.
querySelector returns a NodeList object, wrap() is a jquery function, they won't work together, try this :
for(var i = 0; i < listLength; i++){
for(var y = 0; y < result.history[i].length; y++){
$('#history' + i).find('.boxes').first().wrap('');
}
}
You can do this in a single line by selecting the .list .boxes elements:
$('.lists .boxes').wrap('')
Example fiddle
Note that this will only work if you are using HTML5, otherwise it would be invalid to have a block level element (a div) inside an inline element (the a).

Categories

Resources