JavaScript insertBefore not working properly - javascript

In an attempt to make my answer more flexible on this question:
function invertDivs(parentDiv) {
var first = document.getElementById(parentDiv).firstChild;
console.log(first);
var second = document.getElementById(parentDiv).lastChild;
console.log(second);
document.getElementById(parentDiv).insertBefore(second, first);
}
<div id="parent">
<div id="first">Div 1</div>
<div id="second">Div 2</div>
</div>
<button onclick="invertDivs('parent');">Invert Divs</button>
However, the divs are only inverted sometimes, not always.
An initial click on the button yields this on the console:
A second click:
After a bunch of clicks:
I'm confused as to what's wrong with the code. I select the first child of parent div, and do the same for the last child. Then I just insert the current second div before the first. That's the end of the function. They are also the direct children of the parent div, as required by the insertBefore function.

As mentioned in comments, firstChild and lastChild can return text nodes for the whitespace between elements. You can use firstElementChild and lastElementChild to ignore these.
function invertDivs(parentDiv) {
var first = document.getElementById(parentDiv).firstElementChild;
console.log(first);
var second = document.getElementById(parentDiv).lastElementChild;
console.log(second);
document.getElementById(parentDiv).insertBefore(second, first);
}
<div id="parent">
<div id="first">Div 1</div>
<div id="second">Div 2</div>
</div>
<button onclick="invertDivs('parent');">Invert Divs</button>
For some other workarounds, which you might need for older browsers, see element.firstChild is returning '<TextNode ...' instead of an Object in FF

You're not taking into account text nodes. In your HTML example above, there are 5 nodes.
[0] => TextNode
[1] => #first
[2] => TextNode
[3] => #second
[4] => TextNode
It seems pretty evident that you don't care about the text nodes here. You have quite a few options.
One option would be to filter out all the text nodes. (Can't use Array.prototype.filter method because childNodes is not an array, but a NodeList)
This will give you an array of only DOM elements.
function invertDivs(parentNodeId) {
var childElements = [],
parentNode = document.getElementById(parentNodeId);
//Filter out the child nodes that aren't elements.
//parentNode.childNodes is a NodeList, and not an array (even though it looks like one)
for (var i = 0; i < parentNode.childNodes.length; ++i) {
if (parentNode.childNodes[i].nodeType === 1)
childElements.push(parentNode.childNodes[i]);
}
parentNode.insertBefore(childElements[childElements.length - 1], childElements[0]);
}
<div id="parent">
<div id="first">Div 1</div>
<div id="second">Div 2</div>
</div>
<button onclick="invertDivs('parent');">Invert Divs</button>
Another option would be to use the more modern DOM API properties: See Barmar or GolezTrol's answers. They would be much more performant if you audience has support for IE9+ browsers.

It's not random. If I click 2 times, to add Div 2 to the end of the list, then click 3 times to get Div 1 at the end of the list. This pattern repeats.
The reason is because there are also next nodes inbetween. This is the whitespace inbetween the elements.
To work around this, use the children attribute. This selects the child elements (instead of nodes).
function invertDivs(parentDiv) {
var parent = document.getElementById(parentDiv);
var first = parent.children[0];
console.log(first);
var second = parent.children[parent.children.length-1];
console.log(second);
document.getElementById(parentDiv).insertBefore(second, first);
}
<div id="parent">
<div id="first">Div 1</div>
<div id="second">Div 2</div>
</div>
<button onclick="invertDivs('parent');">Invert Divs</button>

The answer to your question is given in the MDN docs(https://developer.mozilla.org/en-US/docs/Web/API/Node/firstChild) for Node.firstChild. If you refer to docs you will understand why you are getting the #text as the first Node.

Related

How to remove one css class from div container with multiple elements and classes?

I want to get all elements which belong to two specific different classes and remove and add these classes seperately from these elements. I tried:
.concealed {
display: none;
}
.slide {
background: #333;
color: #fff;
}
// <div class="slide concealed">myText</div><br>
<div class="slide"><div class="concealed">myText</div></div><br>
<div class="slide"><div class="concealed">myText_2</div></div><br>
<div class="slide"><div class="concealed">myText_3</div></div><br>
// var slides = document.getElementsByClassName('slide');
// var slides = document.querySelectorAll('.slide, .concealed');
var slides = document.getElementsByClassName('slide concealed');
slides[0].classList.remove("concealed");
As you can see I tried several ways to achieve this but I can only remove and add "concealed" when I do var slides = document.getElementsByClassName('concealed'); . When doing getElementsByClassName with multiple classnames it seems to miss out concealed and only get slide. E.g. doing slides[0].classList.remove("concealed"); after document.getElementsByClassName('slide concealed'); has no effect.
I am sure I am missing something, this can't that hard to implement. Any ideas? Thanks.
The issue is that getElementsByClassName is a live HTMLCollection. When an element no longer matches (because you've removed the class), it's removed from the collection:
const list = document.getElementsByClassName("example");
console.log(list.length); // 2
console.log(list[0].innerHTML); // "A"
list[0].classList.remove("example");
console.log(list.length); // 1
console.log(list[0].innerHTML); // "B"
<div class="example">A</div>
<div class="example">B</div>
This means that if you're doing a loop and operate on the first entry, then increment your index, you'll miss what used to be the second entry because it's the first entry now.
A couple of ways to fix that:
Loop backward, or
Convert the HTMLCollection into an array before starting, or
Use querySelectorAll to get a snapshot NodeList instead, so that the lists contents don't change while you're updating.
Here's an example removing the classes red and bold from a series of elements using querySelectorAll so the NodeList is static:
setTimeout(() => {
const list = document.querySelectorAll(".red, .bold");
for (let n = 0; n < list.length; ++n) {
list[n].classList.remove("red");
list[n].classList.remove("bold");
}
}, 800);
.red {
color: red;
}
.bold {
font-weight: bold;
}
<div class="red">red 1</div>
<div class="bold">bold 1</div>
<div class="red bold">red 2 and bold 2</div>
<div class="bold">bold 3</div>
<div class="bold">bold 4</div>
See also my answer here: NodeList is now officially iterable, meaning you should be able to use a for-of loop on it. That answer shows how to polyfill that on slightly-older environments that haven't implemented it yet. It also shows how to add iterability to HTMLCollection (but note that HTMLCollection is not specified to be iterable).

JavaScript possible to select all elements with classname that starts with (...)? [duplicate]

I'm trying to only show certain divs. The way I have decided to do this is to first hide all elements that start with "page" and then only show the correct divs. Here's my (simplified) code:
<form>
<input type="text" onfocus="showfields(1);">
<input type="text" onfocus="showfields(2);">
</form>
<div class="page1 row">Some content</div>
<div class="page1 row">Some content</div>
<div class="page2 row">Some content</div>
<div class="page2 row">Some content</div>
<script>
function showfields(page){
//hide all items that have a class starting with page*
var patt1 = /^page/;
var items = document.getElementsByClassName(patt1);
console.log(items);
for(var i = 0; i < items.length; i++){
items[i].style.display = "none";
}
//now show all items that have class 'page'+page
var item = document.getElementsByClassName('page' + page);
item.style.display = '';
}
</script>
When I console.log(items); I get a blank array. I'm pretty sure the regexp is right (get all items starting with 'page').
The code I'm using is old school JS, but I'm not adverse to using jQuery. Also if there is a solution that doesn't use regexp, that's fine too as I'm new to using regexp's.
getElementsByClassName only matches on classes, not bits of classes. You can't pass a regular expression to it (well, you can, but it will be type converted to a string, which is unhelpful).
The best approach is to use multiple classes…
<div class="page page1">
i.e. This div is a page, it is also a page1.
Then you can simply document.getElementsByClassName('page').
Failing that, you can look to querySelector and a substring matching attribute selector:
document.querySelectorAll("[class^=page]")
… but that will only work if pageSomething is the first listed class name in the class attribute.
document.querySelectorAll("[class*=page]")
… but that will match class attributes which mention "page" and not just those with classes which start with "page" (i.e. it will match class="not-page".
That said, you could use the last approach and then loop over .classList to confirm if the element should match.
var potentials = document.querySelectorAll("[class*=page]");
console.log(potentials.length);
elementLoop:
for (var i = 0; i < potentials.length; i++) {
var potential = potentials[i];
console.log(potential);
classLoop:
for (var j = 0; j < potential.classList.length; j++) {
if (potential.classList[j].match(/^page/)) {
console.log("yes");
potential.style.background = "green";
continue elementLoop;
}
}
console.log("no");
potential.style.background = "red";
}
<div class="page">Yes</div>
<div class="notpage">No</div>
<div class="some page">Yes</div>
<div class="pageXXX">Yes</div>
<div class="page1">Yes</div>
<div class="some">Unmatched entirely</div>
Previous answers contain parts of the correct one, but none really gives it.
To do this, you need to combine two selectors in a single query, using the comma , separator.
The first part would be [class^="page"], which will find all the elements whose class attribute begins with page, this selector is thus not viable for elements with multiple classes, but this can be fixed by [class*=" page"] which will find all the elements whose class attribute have somewhere the string " page" (note the space at the beginning).
By combining both selectors, we have our classStartsWith selector:
document.querySelectorAll('[class^="page"],[class*=" page"]')
.forEach(el => el.style.backgroundColor = "green");
<div class="page">Yes</div>
<div class="notpage">No</div>
<div class="some page">Yes</div>
<div class="pageXXX">Yes</div>
<div class="page1">Yes</div>
<div class="some">Unmatched entirely</div>
You can use jQuery solution..
var $divs = $('div[class^="page"]');
This will get all the divs which start with classname page
$(document).ready(function () {
$("[class^=page]").show();
$("[class^=page]").hide();
});
Use this to show hide div's with specific css class it will show/hide all div's with css class mention.

Asynchronously remove children of a DOM element?

I have a div#parent element which has a few thousand sibling children div#1, div#2, div#3 like so:
<div id="parent">
<div id="1"> </div>
<div id="2"> </div>
<div id="3"> </div>
…
…
…
<div id="N"> </div>
</div>
What is the best way to remove all children of the parent node -- preferably asynchronously or through a separate process?
Here are a few standard answers from the wild that deal with slow and fast, but not async/e̶f̶f̶i̶c̶i̶e̶n̶t̶l̶y̶ or non-blocking:
Option 1:
var myNode = document.getElementById("foo");
myNode.innerHTML = '';
Option 2:
let parentNode = document.getElementById("parent");
while (parentNode.firstChild) {
parentNode.removeChild(parentNode.firstChild);
}
Since a while-loop gives a "busy state" in the dom's single threaded environment I wonder if there is a possibility to remove the nodes from the dom asynchronously/e̶̶̶f̶̶̶f̶̶̶i̶̶̶c̶̶̶i̶̶̶e̶̶̶n̶̶̶t̶̶̶l̶̶̶y̶ or in a non-blocking fashion?
Obviously innerHTML = '' will remove all items simultaneously. If this is too slow and you want to remove the elements little by little, then you need something similar to your second approach.
let parentNode = document.getElementById("parent");
(function remove() {
// Remove elements in groups of 100
for(let i=0; i<100 && parentNode.firstChild; ++i) {
parentNode.removeChild(parentNode.firstChild);
}
// Continue asynchronously after 50ms
setTimeout(remove, 50);
})();
Adjust the group size and the time delays as needed.

Count number of first layer/set of children in cloned element

I am trying to count the number of it's first layer of children not including it's child of child of child.. This is my codes
HTML
<div class="container">
<div class="row">
<div class="btn-container col-md-12">
Hello
</div>
</div>
</div>
JS
$('a').click(function(){
var c = $(this).closest('.row').clone();
$(this).after( c );
var count = $(this).closest('.row').find('.btn-container').length;
alert(count);
});
The alert should always return 1 since the cloned element is appended inside of it.
jsFiddle
I tried ..
.find() but also as I expected, it will count all inside of it.. Child of child of child and so on...
var parent = $(this).closest('.row');
var count = $('.btn-container', parent).length;
But still can not get what I want.
I already think of adding a name/class specifying to them. Like btn-container first-children ..
But I am wondering if there is a jQuery trick that will make it simplier.
find('.btn-container') will select all descendants .btn-container. You should use direct child selector > like following.
$('a').click(function () {
var c = $(this).closest('.row').clone();
$(this).after(c);
var count = $(this).closest('.row').find('>.btn-container').length;
//---------------------------------------^^--------------------
alert(count);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="row">
<div class="btn-container col-md-12">
Hello
</div>
</div>
</div>
Rather than find() you could use children(). From jQuery .children() documentation:
The .children() method differs from .find() in that .children() only
travels a single level down the DOM tree while .find() can traverse
down multiple levels to select descendant elements (grandchildren,
etc.) as well
var count = $(this).closest('.row').children('optional-selector').length;

Select children where not within container jquery

This would seem like an easy problem but I am having some trouble figuring this one out. The example given is a SSCCE and I have a larger problem that this attempts to solve. In order for this to work the query must NOT contain any immediate child selectors (>) due to the dom tree being a bit more complex than this example.
My goal here is to select all children whom aren't underneath a parent who contains a class. In this example I am trying to select the 2 div containers hello and world but not foo and bar.
Here is a plunker that has the code in it for your convience. http://plnkr.co/edit/4zsKAFts5X7X2kLADj2V?p=preview
Using this HTML:
<div id="root" class="parent">
<div>
<div class="child">hello</div>
<div class="child">world</div>
</div>
<div class="parent">
<div>
<div class="child">foo</div>
<div class="child">bar</div>
</div>
</div>
</div>
And this Javascript:
var root = $('#root');
$(':not(.parent) .child', root).css('font-weight', 'bold');
I am seeing this result:
hello
world
foo
bar
But what I would like to get is
hello
world
foo
bar
To reiterate I want to get all elements with class child who dont have a parent with class parent starting from a given node (in this example #root).
var root = $('#root');
$('.child', root).not($("#root .parent .child")).css('font-weight', 'bold');
jsFiddle example
It might not be pretty but here you go:
$('#root').find('.child').filter(function(){
if($(this).parents('.parent').first().attr('id') === 'root'){
return 1;
}
else{
return 0;
}
}).css('font-weight', 'bold');
http://jsfiddle.net/PDZL8/
JSFiddle: http://jsfiddle.net/TrueBlueAussie/78G6N/3/
var root = $('#root');
$('#root').find('.child').filter(function(){
return $(this).closest('.parent').is(root);
}).css('font-weight', 'bold');
I also improved j08691's solution so that it uses the root node supplied, rather than duplicating the selector (which is not portable):
http://jsfiddle.net/TrueBlueAussie/78G6N/4/
var root = $('#root');
$('.child', root).not(root.find(".parent .child")).css('font-weight', 'bold');
You can use this :
$('#root').find('.child').filter(function(){
return $(this).parents('.parent').length <= 1;
}).css('font-weight', 'bold');
It check the number of parent the div has and if it is lower or equal than 1, it return the child.
Fiddle : http://jsfiddle.net/akwPb/
$("#root .child:not(#root .parent * .child)")
.css("font-weight", "bold")
jsfiddle http://jsfiddle.net/guest271314/UJJXU/

Categories

Resources