I'm need to count specific divs and after each X divs insert another div with a paragraph in it. All in pure JavaScript.
I am done with counting the specific divs but I don't know how to handle the loop and insert the container and the paragraph.
<div class="main">
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
</div>
<script>
count = document.getElementsByClassName("special-div").length; // Output: X
</script>
A basic for statement would be suitable for this problem.
Use querySelectorAll instead of getElementsByClassName as it's easier to manage - QSA returns an array-like static node list whereas gEBCN returns a live HTML collection which may trip you up when you start inserting new elements during its iteration.
To find out where you should add the new div element use the remainder operator (also called "modulo"). As you iterate over the node list check to see if dividing the current index + 1 (arrays are zero-based) by the specified number results in zero. If it does, add in the new div element after the current one.
Note: I've separated out the code to create a new div element into a function to keep things simple.
const divs = document.querySelectorAll('.special-div');
// Loop over the array-like structure that
// qsa returns. Find the remainder of dividing
// the index by the supplied `n` argument. If it's
// zero create a new div element and add it `after`
// the current div
function insertAfter(n, text) {
for (let i = 0; i < divs.length; i++) {
if ((i + 1) % n === 0) {
const div = createDiv(text);
divs[i].after(div);
}
}
}
// For convenience a function that creates
// a new paragragh element wrapped in a div.
// It uses the text passed in as an argument
function createDiv(text) {
const div = document.createElement('div');
div.className = 'special-div';
const para = document.createElement('p');
para.textContent = 'new!';
div.appendChild(para);
return div;
}
// Call the main function. The arguments are
// the count where you add a new div, and the
// text you want to appear in the paragraph
insertAfter(2, 'new!');
div.special-div p { color: red; }
<div class="main">
<div class="special-div">one</div>
<div class="special-div">two</div>
<div class="special-div">three</div>
<div class="special-div">four</div>
<div class="special-div">five</div>
<div class="special-div">six</div>
</div>
This iterates the nodes with the special-div class, keeping track of the index. For non-zero indexes divisible by interval it creates and inserts a new div.
const interval = 3;
const parent = document.getElementById("parent");
const elements = [...document.getElementsByClassName("special-div")];
const divWithText = text => {
const newNode = document.createElement("div");
newNode.appendChild(document.createTextNode(text));
return newNode
};
elements.forEach((node,i) => {
if (i && i % interval === 0) {
parent.insertBefore(divWithText("new div"), node);
}
});
<div id="parent" class="main">
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
</div>
Sorry, I'm not quite sure what output you are expecting is like.
How about using .insertAdjacentHTML(<position>, <text>)? It inserts HTML code to a specified element. You don't need to count divs :)
element.insertAdjacentHTML()
You can choose a target element by changing element like main in my code.
position: 'beforeend'
This means "Just inside the element, after its last child."
In the case of Example 1, right before </div> of .main
position: 'afterend'
This means "After the element. Only valid if the element is in the DOM tree and has a parent element."
In the case of Example 2, right after </div> of .special-div
Element.insertAdjacentHTML() #MDN
Example 1
const main = document.querySelector('.main');
let html = `
<div>
<p>new</p>
</div>
`
// Insert html at the end of all children of .main
main.insertAdjacentHTML('beforeend', html);
<div class="main">
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
<div class="special-div">div 1</div>
</div>
Example 2
const targetDivs = document.querySelectorAll('.special-div');
let html = `
<div>
<p>new</p>
</div>
`
// Insert html after every specific div
targetDivs.forEach(div => div.insertAdjacentHTML('afterend', html));
// you can't do this: targetDivs.insertAdjacent()
// Because targetDivs is a NodeList which is like an array [].
// You have to use .insertAdjacent() on every div one by one,
// not on an array(NodeList)
<div class="main">
<div class="special-div">div 1</div>
<hr>
<div>Ignore this div</div>
<hr>
<div class="special-div">div 1</div>
<hr>
<div class="special-div">div 1</div>
</div>
You can try it like this:
var divs = document.getElementsByClassName('special-divs');
var afterHowMany= 5;
for( var i = 0; i < divs.length; i+=afterHowMany )
divs.slice(i,i+afterHowMany).wrapAll('<div class="inserted-div"></div>');
This recursively adds a new div after every 5 elements. You can take any other number apart from 5 as well.
Related
I am trying to detect if the selected element is the first child with the given class with jQuery. In the example, you can see that it is the first child with class .item but the selector :first-of-type doesn't work for that because it is not the first div.
How can this be solved?
var selectedItem = $('.list').find('.item.selected');
var isFirst = selectedItem.is(':first-of-type');
console.log('Is item first? ' + isFirst);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="list">
<div class="search"></div>
<div class="item selected">Item 1</div>
<div class="item">Item 2</div>
<div class="item">Item 3</div>
</div>
we can use is method to check if two elements are the same or not.
so, a solution would have two variables, one is the currently selected element and one is the first element.
let selectedItem = $('.list').find('.item.selected');
let firstElement = $('.list').find('.item:first');
console.log($(selectedItem).is(firstElement)); // this returns true, false
jsfiddle
Using jquery's .index() overload you can apply it to a predefined collection (jquery object):
var isFirst = $(".list .item").index($('.list .item.selected')) == 0;
Updated snippet:
var items = $(".list .item");
var selectedItem = $('.list .item.selected');
var isFirst = items.index(selectedItem) == 0;
console.log(isFirst)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="list">
<div class="search"></div>
<div class="item selected">Item 1</div>
<div class="item">Item 2</div>
<div class="item">Item 3</div>
</div>
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>
I have the following Html:
<div class="box-row">
<div class="box c2-3">
<div class="box-overlay"></div>
<div class="box-letter"></div>
</div>
<div class="box c2-4">
<div class="box-overlay"></div>
<div class="box-letter"></div>
</div>
<div class="box c2-5">
<div class="box-overlay"></div>
<div class="box-letter"></div>
</div>
<div class="box c2-6 trr">
<div class="box-overlay trr"></div>
<div class="box-letter"></div>
</div>
</div>
I want to randomly select one of the elements with class: c2-3, c2-4, c2-5, c2-6 and trigger a click.
This is the code I have thus far:
var map = [
'c2-3', 'c2-4', 'c2-5', 'c2-6',
];
var x = Math.floor((Math.random() * 4));
var element = document.getElementsByClassName(map[x]);
At this point I want to trigger the click and am unsure how to do it:
element.trigger('click'); ??
Use element.click(); instead of element.trigger('click'); but also, you need to either get only a single element, or loop over the returned HTMLCollection from .getElementsByClassName().
For example, to loop:
var elements = document.getElementsByClassName(map[x])
elements.forEach(element => element.click())
...Or, to get a single element (still using getElementsByClassName):
var element = document.getElementsByClassName(map[x])[0]
element.click()
Alternatively, you can use querySelector:
var element = document.querySelector(`.${map[x]}`)
element.click()
I'm currently struggling on a very simple javascript task, but I'm new to it, so its confusing me a lot.
e.g. html
<div class="item">
<div class="title">Item 1 Title</div>
<div class="description-1">lorum</div>
<div class="description-2">ipsum</div>
<div class="description-combined"></div>
</div>
So I need to combine paragraphs 1 & 2, and replace the empty info in paragraph 3. I don't use jQuery yet, so my research has caused struggle because of this.... i currently have:
var p1 = getElementsByClassName ('description-1').innerHTML;
var p2 = getElementsByClassName ('description-2').innerHTML;
var p3 = p1 + P2
document.getElementsByClassName ('description-combined').innerHTML = p3
I did have p3 to have p1.concat(p2) but that didn't work. I'm using it as an external file, so i may be missing out on putting something in my HTML file too.
The edit changes the question.
What I'd probably do is loop through the .item elements, combining the descriptions within.
document.getElementsByClassName is a property of document, not a freestanding function, and it returns a list of matching elements. It's also not as widely supported as document.querySelector and document.querySelectorAll, so I'd probably use those; for what we're talking about, we'll also want Element#querySelector.
// Get a list of the items and loop through it
Array.prototype.forEach.call(document.querySelectorAll(".item"), function(item) {
// Get the two description divs, and the combined, that
// are *within* this item
var d1 = item.querySelector(".description-1");
var d2 = item.querySelector(".description-2");
var c = item.querySelector(".description-combined");
// Set the combined text (this assumes we have them all)
c.innerHTML = d1.innerHTML + d2.innerHTML;
});
.description-combined {
color: green;
}
<div class="item">
<div class="title">Item 1 Title</div>
<div class="description-1">One description 1</div>
<div class="description-2">One description 2</div>
<div class="description-combined"></div>
</div>
<div class="item">
<div class="title">Item 2 Title</div>
<div class="description-1">2 description 1</div>
<div class="description-2">2 description 2</div>
<div class="description-combined"></div>
</div>
<div class="item">
<div class="title">Item 3 Title</div>
<div class="description-1">3 description 1</div>
<div class="description-2">3 description 2</div>
<div class="description-combined"></div>
</div>
The Array.prototype.forEach.call(list, function() { ... }); thing is a way to loop through anything that's like an array, but isn't an array. It's explained more in this other answer, which also has several alternatives.
I'm trying to wrap multiple same class divs into a div and to skip divs not with the same class. .wrap doesn't combine them, and .wrapAll throws the non-classed divs underneath. I've been tinkering around with attempts to create an alternate solution but with no avail.
Original:
<div class="entry">Content</div>
<div class="entry">Content</div>
<div class="entry">Content</div>
<div>Skip in wrap</div>
<div class="entry">Content</div>
<div class="entry">Content</div>
<div class="entry">Content</div>
continued...
Wanted Result:
<div>
<div class="entry">Content</div>
<div class="entry">Content</div>
<div class="entry">Content</div>
</div>
<div>Skip in wrap</div>
<div>
<div class="entry">Content</div>
<div class="entry">Content</div>
<div class="entry">Content</div>
</div>
You can loop pretty quickly through your <div> elements using a for loop. In the below code, just change the initial selector here to grab all those siblings divs, e.g. #content > div.entry or wherever they are:
var divs = $("div.entry");
for(var i=0; i<divs.length;) {
i += divs.eq(i).nextUntil(':not(.entry)').andSelf().wrapAll('<div />').length;
}
You can give it a try here. We're just looping through, the .entry <div> elements using .nextUntil() to get all the .entry elements until there is a non-.entry one using the :not() selector. Then we're taking those next elements, plus the one we started with (.andSelf()) and doing a .wrapAll() on that group. After they're wrapped, we're skipping ahead either that many elements in the loop.
I just whipped up a simple custom solution.
var i, wrap, wrap_number = 0;
$('div').each(function(){ //group entries into blocks "entry_wrap_#"
var div = $(this);
if (div.is('.entry')) {
wrap = 'entry_wrap_' + wrap_number;
div.addClass(wrap);
} else {
wrap_number++;
}
});
for (i = 0; i <= wrap_number; i++) { //wrap all blocks and remove class
wrap = 'entry_wrap_' + i;
$('.' + wrap).wrapAll('<div class="wrap"/>').removeClass(wrap);
}
You could alternatively append new divs to your markup, and then append the content you want wrapped into those.
If your markup is this:
<div class="wrap">
<div class="col-1"></div>
<div class="col-1"></div>
<div class="col-1"></div>
<div class="col-1"></div>
<div class="col-1"></div>
<div class="col-2"></div>
<div class="col-2"></div>
<div class="col-2"></div>
<div class="col-2"></div>
<div class="col-2"></div>
</div>
Use the following to append two new divs (column-one and column-two) and then append the appropriate content into those divs:
// Set vars for column content
var colOne = $('.col-1').nextUntil('.col-2').addBack();
var colTwo = $('.col-2').nextAll().addBack();
// Append new divs that will take the column content
$('.wrap').append('<div class="column-first group" /><div class="column-second ground" />');
// Append column content to new divs
$(colOne).appendTo('.column-first');
$(colTwo).appendTo('.column-second');
Demo here: http://codepen.io/zgreen/pen/FKvLH