Wrapping a reoccuring collection of elements in a div - javascript

This is kind of a two part question - I have an HTML page where I need to take reoccurring groups of elements such as:
<h2>1</h2>
<p>foo</p>
<p>bar</p>
<h2>2</h2>
<p>fizz</p>
and wrap them individually with divs on each heading:
<div>
<h2>1</h2>
<p>foo</p>
<p>bar</p>
</div>
<div>
<h2>2</h2>
<p>fizz</p>
</div>
I know about getting elements by tag name, but I can't work out how to take the group of elements and then replacing them.
Thank you for any help.

You can use Array.from(), .forEach() loop to iterate h2 elements collection, check if .nextElementSibling .tagName is "P" in while loop, if true, push elements to an array.
Use .forEach() on array of arrays of p elements, create <div> element, append h2 at index of array to div element, loop each element of inner array, call .appendChild() with p element as parameter.
var headers = document.querySelectorAll("h2");
var arr = [];
Array.from(headers).forEach(function(h2, index) {
arr[index] = [];
var curr = h2.nextElementSibling;
while (curr.tagName === "P") {
arr[index].push(curr);
var next = curr.nextElementSibling;
curr = next;
}
});
arr.forEach(function(html, index) {
var div = document.createElement("div");
document.body.appendChild(div);
div.appendChild(headers[index]);
html.forEach(function(el) {
div.appendChild(el)
})
});
div {
padding: 4px;
margin: 4px;
border: 2px solid green;
}
<h2>1</h2>
<p>foo</p>
<p>bar</p>
<h2>2</h2>
<p>fizz</p>

Related

How do I target all elements with the same class with getElementByClassName instead of just the first element with that class

I'm very new to JS, and really don't understand a lot of it. Trying to learn as I go.
I'm trying to add some new divs to buttons to style them to look like the rest of the buttons on my site as I cant edit the plugins HTML. I've managed to successfully do this for one button. But it won't work for the other buttons. I've tried to read into it and it looks like because I am using getElementsByClassName its only selecting the first button and not the others.
So I dont know if this is right or not and correct me if it ain't. but I think I need to set up a Node loop? so that getElementsByClassName doesn't just select the first node on the page. However I got no Idea how to set up a node loop and reading about it is just confusing me more.
Can someone help and possibly explain this to me so I can make sense of it for future reference.
Thanks
This is the code I currently have, I just don't know how to make it target all elements with that class rather than just the first element with that class.
var btnSwirls = document.createElement('div');
btnSwirls.id = 'dbtb-button-swirl-wrap';
btnSwirls.className = 'dbtb-button-swirl-wrap';
document.getElementsByClassName("dbtb-add-btn-assets")[0].appendChild(btnSwirls);
const btnSwirls = document.createElement('div');
btnSwirls.id = 'dbtb-button-swirl-wrap';
btnSwirls.className = 'dbtb-button-swirl-wrap';
document.getElementsByClassName("dbtb-add-btn-assets").forEach(element => {
element.appendChild(btnSwirls);
})
learn more about forEach(): https://www.youtube.com/watch?v=SXb5LN_opbA
learn more about arrow functions: https://www.youtube.com/watch?v=h33Srr5J9nY
learn more about var, let, and const: https://www.youtube.com/watch?v=9WIJQDvt4Us
First of all, you are handling an "array" of elements. For that, you'd need a loop to iterate over the array.
you shouldn't be using this
document.getElementsByClassName("dbtb-add-btn-assets")[0] <-- because this part [0] denotes that you are targeting the first element in the array, hence 0, since all arrays start with the index 0; i.e. [0, 1, 2, 3, ...] indices
so for iterating over an array you can either use a (for loop) or a (for of) loop
for loop:
let dbtb_add_btn_assets = document.getElementsByClassName("dbtb-add-btn-assets"); //you are assigning a variable to the array;
for(let i = 0; i < dbtb_add_btn_assets.length; i++) {
var btnSwirls = document.createElement('div');
btnSwirls.id = 'dbtb-button-swirl-wrap';
btnSwirls.className = 'dbtb-button-swirl-wrap'; //create btnswirls per iteration of the loop
dbtb_add_btn_assets[i].appendChild(btnSwirls);
}
the i is the current index of the loop, the i++ part of the for loop
will automatically add 1 to itself upon executing the statement inside
the for loop and ends when i is not less than the dbtb_add_btn_assets
length. Length meaning the number of elements inside the array.
for of:
let dbtb_add_btn_assets = document.querySelectorAll('.dbtb-add-btn-assets'); //personally I'd use querySelectorAll instead of getElementsByCLassName just add . for classes # for ids
for(let dbtb of dbtb_add_btn_assets) { //name whatever variable you want to use
var btnSwirls = document.createElement('div');
btnSwirls.id = 'dbtb-button-swirl-wrap';
btnSwirls.className = 'dbtb-button-swirl-wrap'; //create btnswirls per dbtb
dbtb.appendChild(btnSwirls);
}
the for of loop takes the contents from a specified array and put them into a temporary variable, successfully giving access to the individual content/object, and then automatically iterates over each one as you manipulate it however you like inside the loop.
You need to loop over all the elements. You only access the first of many elements using [0].
There are multiple ways to do this.
Here are two ways to do it using a sample application which just toggles a class (adds/ removes a class) every two seconds.
#1 - Use querySelectorAll() and forEach()
You can use querySelectorAll() to get a NodeList of HTML elements which match the given CSS selector .someClass.
Notice that the CSS selector requires a . before a class name.
window.addEventListener("DOMContentLoaded", e => {
const allElements = document.querySelectorAll(".someClass");
// add/ remove class every 2 seconds
setInterval(() => {
// loop over all elements and add/ remove a class
allElements.forEach(element => {
element.classList.toggle("anotherClass");
});
}, 2000)
})
.someClass {
padding: 20;
background-color: black;
color: white;
margin: 5px;
}
.anotherClass {
border: 2px solid red;
}
<div class="someClass">div 1</div>
<div class="someClass">div 2</div>
<div class="someClass">div 3</div>
<div class="someClass">div 4</div>
#2 - Use getElementsByClassName() and for .. of ... loop
Alternatively you can use getElementsByClassName() which returns a HTMLCollection. You can then use a for ... of ... loop to iterate over all the elements in the collection.
Notice that here for the getElementsByClassName() call we MUST NOT use a . before the class name.
window.addEventListener("DOMContentLoaded", e => {
const allElements = document.getElementsByClassName("someClass");
// loop
setInterval(() => {
// loop over all elements and add/ remove a class
for (const element of allElements) {
element.classList.toggle("anotherClass");
}
}, 2000)
})
.someClass {
padding: 20;
background-color: black;
color: white;
margin: 5px;
}
.anotherClass {
border: 2px solid red;
}
<div class="someClass">div 1</div>
<div class="someClass">div 2</div>
<div class="someClass">div 3</div>
<div class="someClass">div 4</div>
Please note: You should use DOMContentLoaded event so you wait till the HTML document is ready before you try to access the DOM.

How can I get a div and all its children with their respective computedStyles?

I would like to grab the HTML element and all it's computedStyles.
I managed to make it work when the HTML element has no children, but what if the HTML element has many children, how can I grab all the computedStyles of each child
here's what I have so far:
The user clicks on an html element, I grab the HTML element using event.target.cloneNode(true)
I then set an Id to the cloned element
I grab the computedStyle
Then I add the style to my cloned element
private onClickHtmlCssGrabber = (event) => {
// clone the html element
let clickedHtmlElement = event.target.cloneNode(true)
// set an id to the cloned element
clickedHtmlElement.setAttribute('id', 'clonedElement')
// get the computed style of the element
let clikedHtmlElementStyle = getComputedStyle(event.target).cssText
// set the style on my newly cloned element
clickedHtmlElement.style.cssText = clikedHtmlElementStyle
}
So basically at this point I end up with something like:
<div id="clonedElement" style="... all the coputedStyle">Example text</div>
But my problem occurs when the HTML element that I want to extract has many children:
<div class="style1">
<div class="style-child-1">
<div class="style-child-2">
example text child 2
<div>
<div class="style-child-3">
example text child 3
</div>
<div>
</div>
So how can I extract the whole HTML element with its children and their styles ?
So I was curious about this myself and created a quick example to walk through an element. Should also work without any children in it, because the loop will simply not run.
// container where we want to put the clone
const targetContainer = document.querySelector('.target');
// your element that you want to clone
const root = document.querySelector('.style1');
const rootWalker = document.createTreeWalker(
root,
NodeFilter.SHOW_ELEMENT,
{ acceptNode: function(node) { return NodeFilter.FILTER_ACCEPT; } }
);
// now clone the element and also creat a walker for it
const rootClone = root.cloneNode(true);
const cloneWalker = document.createTreeWalker(
rootClone,
NodeFilter.SHOW_ELEMENT,
{ acceptNode: function(node) { return NodeFilter.FILTER_ACCEPT; }}
);
// clone styles for the root element itself
rootClone.style.cssText = getComputedStyle(root).cssText
rootClone.className = '' // simply clearing it to see if styles are copied
// get the first child node, if there is no child, .nextNode() will return null
let nextRootNode = rootWalker.nextNode();
let nextCloneNode = cloneWalker.nextNode();
// now if we have a child node, the loop is running
while (nextRootNode !== null) {
// get styles from original to clone
nextCloneNode.style.cssText = getComputedStyle(nextRootNode).cssText;
nextCloneNode.className = '' // again removing classes for demonstration
// get next node, again if there are no more, null will be returned and loop will stop
nextRootNode = rootWalker.nextNode();
nextCloneNode = cloneWalker.nextNode();
}
// simply append the cloned node to your desired target
targetContainer.append(rootClone);
.style1 {
background: yellow;
padding: 10px;
margin: 5px 0;
}
.style-child-1 {
background: green;
padding: 10px;
}
.style-child-2 {
color: blue;
}
.style-child-3 {
color: orange;
text-transform: uppercase;
}
<div class="style1">
<div class="style-child-1">
<div class="style-child-2">
example text child 2
</div>
<div class="style-child-3">
example text child 3
</div>
</div>
</div>
<div class="target">
</div>
If you want you can grab this and fit it to your needs. Eg. creating a function and run it on click.

JavaScript - select the last item from the 'for' loop

Let's say I have a simple HTML markup with three elements and a javascript loop looking through all of them. What I need to do is to select the last one of those items.
This pile of code will run a loop, select all elements with some_div class, and paste some text inside them...
What if I only wanted the last item on the list to be selected and changed?
Is there a way for me to only select the last item from the loop and then do some operations like adding a specific class to this exact element?
var elements = document.getElementsByClassName('some_div');
for (var i=0; i<elements.length; i++) {
elements[i].innerHTML = 'hello';
}
.some_div {
height: 30px;
width: 100px;
border-bottom: solid 2px black;
}
<body>
<div class="some_div"></div>
<div class="some_div"></div>
<div class="some_div"></div>
</body>
Selecting the last match for a class (e.g., with a CSS selector) is awkward¹, but you can easily access the last match:
var elements = document.getElementsByClassName('some_div');
var last = elements[elements.length - 1];
if (last) {
last.innerHTML = 'hello';
}
Live Example:
var elements = document.getElementsByClassName('some_div');
var last = elements[elements.length - 1];
if (last) {
last.innerHTML = 'hello';
}
.some_div {
height: 30px;
width: 100px;
border-bottom: solid 2px black;
}
<body>
<div class="some_div"></div>
<div class="some_div"></div>
<div class="some_div"></div>
</body>
¹ (or impossible? :nth-last-of-type applies to element type, not class...)
since elements is an array-like object, you can easily index the last item
const elements = document.getElementsByClassName('some_div');
if(elements.length > 0){
const lastElement = elements[elements.length-1]
}

jQuery how to removeClass of only certain elements

I have a jQuery project where I'm looping through a list of elements that all have the same class. What I need is for the 1st two elements class to be removed. Therefore I'm using the .removeClass() method. However, I don't know how to use that method and only remove the 1st two. It's moving all of them. Can someone help me without changing the direction of my code.
function putSiblingsInTableForEachH2() {
// line above sets function that will ultimately store siblings of each h2.toggler into a HTML table
var togglerHeaders = $("h2.toggler");
// line above sets variable togglerHeaders to all h2 elements with a class of ".toggler"
for (i = 0; i < togglerHeaders.length; i++) {
// line above: for loop that loops through array "togglerHeaders"
var currentH2Element = togglerHeaders[i];
// line above sets variable currentH2Element to togglerHeaders at position i
if (currentH2Element == togglerHeaders[0] || togglerHeaders[1]) {
$("h2").removeClass("toggler");
}
var siblingsofH2 = $(currentH2Element).nextUntil("h2.toggler");
// line above access siblings that are in h2.toggler element
// line says: set variable "siblingsofH2" to the current h2.toggler element you're on actual sibling elements but only siblings that are in between current element and next element that is "h2.toggler"
$(siblingsofH2).wrapAll("<table></table>");
// line above says: after correct sibling elements are stored to variable siblingsofH2 wrap elements in HTML table
} // line ends for loop
} // line ends function
putSiblingsInTableForEachH2();
// line above actually runs function
Of course $("h2").removeClass("toggler"); will remove class from all, because it is referring to all.
I slightly edited your lines:
if (i < 2) {
$(currentH2Element).removeClass("toggler");
}
Plain JS .querySelectorAll with :nth-child formula
var firstTwo = document.querySelectorAll(".my-class:nth-child(-n+2)");
This is what I came up with.
var putThingsInTable = query => {
var matches = document.querySelectorAll(query);
if(!matches) return;
var table = document.createElement('table')
table.appendChild(document.createElement('tr'))
var i = 0;
var toAdd = [];
matches.forEach(match => {
if(i > 1){
toAdd.push(match);
}
i++
});
toAdd.forEach(element => {
var td = document.createElement('td');
td.appendChild(element);
table.children[0].appendChild(td)
});
return table;
}
document.body.appendChild(putThingsInTable('h2.toggler'))
table, th, td {
border: 1px solid black;
}
<h2 class="toggler">a</h2>
<h2 class="toggler">a</h2>
<h2 class="toggler">a</h2>
<h2 class="toggler">c</h2>
<h2 class="toggler">c</h2>
<h2 class="toggler">w</h2>
<h2 class="toggler">w</h2>
<h2 class="toggler">w</h2>
<h2 class="toggler">w</h2>
Answer
This is the line of jQuery you are looking for ...
$("h2.toggler:gt(1)")
Explanation
:gt(1) Will select all elements at an index greater than 1 within the matched set
See ... https://api.jquery.com/gt-selector/
Example
For full example see ...
https://codepen.io/stephanieschellin/pen/MPdWVw
or ...
$('h2.toggler:gt(1)').each(function(e) {
$(this).nextUntil('h2').wrapAll('<table></table>')
})
table {
color: blue;
border: solid 3px black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h2 class="toggler">one</h2>
<span>one</span>
<span>one</span>
<h2 class="toggler">two</h2>
<span>two</span>
<span>two</span>
<h2 class="toggler">three</h2>
<span>three</span>
<span>three</span>
<span>three</span>
<h2 class="toggler">four</h2><span>four</span>
<span>four</span>
<span>four</span>
<h2 class="toggler">five</h2>
<span>five</span>
<span>five</span>
<span>five</span>
<span>five</span>
<h2 class="toggler">six</h2>
<span>six</span>
<span>six</span>

Get children with tagname as <div> only in javascript

I have a HTML as:
<div id="xyz">
<svg>......</svg>
<img>....</img>
<div id = "a"> hello </div>
<div id = "b"> hello
<div id="b1">I m a grand child</div>
</div>
<div id = "c"> hello </div>
</div>
I want to get all the children with tags as "div" of the parent element with id = xyz in a javascript variable.
Such that my output should be:
"<div id = "a"> hello </div>
<div id = "b"> hello
<div id="b1">I m a grand child</div>
</div>
<div id = "c"> hello </div>"
You can simply get the #xyz div first, then find all div children:
var childDivs = document.getElementById('xyz').getElementsByTagName('div')
// ^ Get #xyz element; ^ find it's `div` children.
The advantage of this method over Document.querySelectorAll is that these selectors work in pretty much every browser, as opposed to IE 8/9+ for the queryselector.
You can use querySelectorAll:
var childDivs = document.querySelectorAll('#xyz div')
A method to transform the divs to a string (to store or to alert) could be:
var divsHtml = function () {
var divhtml = [],
i = -1,
divs = document.querySelectorAll('#xyz div');
while (i++ < divs.length) {
divs[i] && divhtml.push(divs[i].outerHTML);
}
return divhtml.join('');
}();
If you need compatibility for older browsers (i.c. IE<8) use #Cerbrus' method to retrieve the divs, or use a shim.
To avoid double listing of (nested) divs, you may want to use
var divsHtml = function () {
var divhtml = [],
i = -1,
divs = document.querySelector('#xyz').childNodes;
while (i++ < divs.length) {
divs[i] &&
/div/i.test(divs[i].tagName) &&
divhtml.push(divs[i].outerHTML);
/* ^ this can also be written as:
if(divs[i] && /div/i.test(divs[i].tagName) {
divhtml.push(divs[i].outerHTML)
}
*/
}
return divhtml.join('');
}();
[edit 2021] Seven years old, this answer. See this snippet for another approach.
If you want only the immediate children of xyz, you can call
var childrendivs = document.querySelectorAll('#xyz > div');
or calculate them yourself, if you use an older browser without document.querySelectorAll-Support
var childrendivs = [],
children = document.getElementById('xyz').children;
for(var i = 0; i < children.length; i++){
if (children[i].tagName == "DIV") {
childrendivs.push(children[i]);
}
}
Unless I misunderstood, this is exactly what getElementsByTagName does.
To get only the direct children of a specific element tag:
// All `div` children of document (body) (including nested)
document.querySelectorAll('div')
.forEach(elm => elm.classList.add('querySelectorAll'))
// only direct children of document (body) which matches a `div` selector
const directDivs = [...document.body.children]
.filter(elm => elm.matches('div'))
// style only the `div`
.forEach(elm => elm.classList.add('direct-div-children'))
*:not(body):not(html) {
margin: 5px;
padding: 5px;
border: 3px solid black;
}
.querySelectorAll {
background: lightyellow;
}
.direct-div-children {
border: 3px solid red;
}
<div>
A
<div>A1</div>
<div>A2</div>
</div>
<p>not a div</p>
<div>B</div>
<div>C</div>

Categories