Recursively remove all nested nodes in javascript - javascript

I've been trying to write something to remove all nodes within an element, including the nodes within those nodes. I feel like I've come pretty close with this
function clear(node) {
var l = node.childNodes.length;
for (var i = 0; i < l; i++) {
console.log(i, node.childNodes);
var child = node.childNodes[i];
if (!child.hasChildNodes()) {
var newchild = child.parentNode;
newchild.removeChild(child);
console.log("removed" + child);
clear(newchild);
} else {
clear(child);
}
}
}
And the markup looks something like this
<div>
<canvas></canvas>
<div id="diagbox">
<div id="diag_text">Here's some text</div>
</div>
<ul id="option_ul">
<li>Some text</li>
<li>Some text</li>
</ul>
</div>
And this is how far it gets before failing:
<div>
<div id="diagbox">
<div id="diag_text"></div>
</div>
<ul id="option_ul">
</ul>
</div>
It removes the text node in diag_text, but fails to remove the actual div. It also removes the text nodes in the li elements, and for some reason succeeds to remove the li elements as well, but stops at the ul. The canvas is also succesfully removed, which is odd because it's top level and I was assuming that's why the others weren't working. The whole thing stops because at some point a TypeError occurs, "child is undefined".
Not quite sure where I'm going wrong here
EDIT: Definitely should have specified: I'm wanting to go through and remove all of the nodes and not just the parents because the content of these elements are actually dynamically generated and need to be scrubbed for different use cases.

I think the recursive solution may be a lot simpler than you had in mind.
This function will delete each node only after all of its child nodes have been deleted, so you can scrub/free any resources you need to reclaim.
Working Live Example (open console):
var n = document.getElementById("parent");
function clearInner(node) {
while (node.hasChildNodes()) {
clear(node.firstChild);
}
}
function clear(node) {
while (node.hasChildNodes()) {
clear(node.firstChild);
}
node.parentNode.removeChild(node);
console.log(node, "cleared!");
}
clearInner(n);
<div id="parent">
<div></div>
<div></div>
<div>
<div></div>
<div></div>
<div>
<div></div>
<div></div>
</div>
</div>
<div></div>
<div></div>
</div>

Why not just
var n = document.getElementById("parent");
n.innerHTML="";

Firstly you don't need to remove the child nodes. Removing the parent will do the trick. So, something of the form:
while (node.firstChild) {
node.removeChild(node.firstChild)
}
will do the trick.
As to your specific question: childNodes is dynamic. As your remove elements it changes, so indexing into it from a static length will not work. You will note that in my example above I only ever look at firstChild, since after each removeChild it will point to a new element.
So to fix your code either use something dynamic like this or process the array in reverse:
for (var i = l - 1; i >= 0; i--) {...
This will work because you are chopping nodes from the end and the previous children remain in place.

Related

How do I loop through HTML DOM nodes?

I want to loop through a nested HTML DOM node, as shown below:
<div id="main">
<div class="nested-div-one">
<div class="nested-div-two">
<div class="nested-div-three">
</div>
</div>
</div>
<div class="nested-div-one">
<div class="nested-div-two">
<div class="nested-div-three">
</div>
</div>
</div>
</div>
How would I do this using Javascript to loop through every single one of the dividers?
I am guessing OP was not specific for DIV elements, here's a more dynamic approach:
So first you wanna get the first container, in your case it's:
var mainEl = document.getElementById('main');
Once you have that, each DOM element has a .children property with all child nodes. Since DOM is a tree object, you can also add a flag to achieve recursive behavior.
function visitChildren(el, visitor, recursive) {
for(var i = 0; i < el.children.length; i++) {
visitor(children[i]);
if(recursive)
visitChildren(children[i], visitor, recursive);
}
}
And now, let's say you want to change all div backgrounds to red:
visitChildren(mainEl, function(el) { el.style.background = 'red' });
You can use vanilla javascript for this
document.querySelectorAll('div').forEach(el => {
// el = div element
console.log(el);
});

CSS - Parent child selector not working

I have the following code:
.recipe
.ingredients
= f.simple_fields_for :ingredients do |ingredient|
= render 'ingredient_fields', f: ingredient
.row#links
.col-xs-12
= link_to_add_association "", f, :ingredients
%hr
I need to select the ingredients div using jquery in the format of $("#links")["closest"](".recipe > .ingredients") but this doesn't select anything.
It's frustrating though as $("#links")["closest"](".recipe > .row") will return the correct div.
Fiddle of what works and what I want: https://jsfiddle.net/yL6dr4s1/
According to jQuery documentation, closest method tries to find element matching the selector by testing the element itself and
traversing up through DOM.
It does not go through siblings of the element.
Based on your requirements, it seems like you want to traverse the tree for getting match in siblings. jQuery has siblings method to do that. So one solution would be to use siblings method like:
$("#links")["siblings"](".recipe > .ingredients")
Another soultion would be to get closest parent and then use children as answered by #mhodges
As for the query $("#links")["closest"](".recipe > .row"):
It works fine because closest method finds the match in the element itself.
Here is the example to showcase that:
$(document).ready(function() {
// Match found because it is parent
console.log($("#links")["closest"](".wrapper").length);
// No match found because element is sibling
console.log($("#links")["closest"](".row1").length);
// No match found because element is sibling
console.log($("#links")["closest"](".row3").length);
// Match found because it is element itself
console.log($("#links")["closest"](".row2").length);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div class="row1">
<span>Content1</span>
</div>
<div class="row2" id="links">
<span>Content2</span>
</div>
<div class="row3">
<span>Content3</span>
</div>
</div>
I am not sure of your requirements on using the exact selector/syntax you provided, but this selector works exactly how you want it to.
$(this).closest(".recipe").children(".ingredients").append('<br/><input type="text" value="Flour">');
Edit
This is the closest I could get:
$(this)["closest"](".recipe").children(".ingredients").append('<br/><input type="text" value="Flour">');
I don't think you can use the selectors in the way you propose.
As far as the DOM is concerned (and jQuery), the element defined by ingredient and the element defined by row are not related. You have to traverse up to the parent element, then back down to get to the child.
Here is a fiddle that hopefully demonstrates the issue.
If you can change it so that ingredient and row are both within the same parent div, you might have more luck with your test selector syntax.
When jQuery gets to buggy, doesn't have a certain option or just becomes to messy to use for a certain operation, it is good we also have access to good old plain javascript.
document.querySelector('#addToIngredients').addEventListener('click' , function(e) {
var recipe = getClosest(e.target,'recipe');
if (recipe) {
var ingred = recipe.querySelector('.ingredients');
ingred.innerHTML += '<br/><input type="text" value="Flour">';
}
});
function getClosest(elem,cls) {
var el = elem.parentNode;
while (el){
if (el.className.indexOf(cls) > -1) {
return el;
}
el = el.parentNode;
}
return false;
}
<div class="recipe">
<div class="ingredients">
<input type="text" value="Eggs"><br/>
<input type="text" value="Flour">
</div>
<div class="row">
<div class="col-xs-12">
Add to .ingredients
</div>
</div>
<hr/>
</div>
Of course they can be combined
$(function() {
$("#addToIngredients").on('click', function(e) {
var recipe = getClosest(e.target,'recipe');
if (recipe) {
var ingred = recipe.querySelector('.ingredients');
ingred.innerHTML += '<br/><input type="text" value="Flour">';
}
});
})

How can I count how many divs whose ids begin with a certain text that are inside a div using JQuery?

Here's the html:
<div class="col-sm-12" id="ProdutosPedido">
<div class="row">
<div class="col-sm-12 formProdutoAdd" id="produto_1">
...
</div>
</div>
</div>
As things happen within the page, divs are appended inside #ProdutosPedido, and #produto_1 increments to #produto_2 and so on.
This is not working for me:
console.log($("#ProdutosPedido > [id^=produto_]").length);
I need to iterate over these "produto_" and use the 'i' to refer to the current div, but I don't know how to do it. My example logs 0, and that should not be the case, since it starts with 1.
Since your produto divs are not direct children of ProdutosPedido, but its descendants, you need to use the following selector:
$("#ProdutosPedido [id^=produto_]")
Here is the working JSFiddle demo.
Pure Javascript Solution
function CountDiv() {
var nodes = document.getElementById('ProdutosPedido').getElementsByTagName('*');
var Count = 0;
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].id.substring(0, 8) == 'produto_')
Count++;
}
alert(Count);
}

swap 2 div's index using pure javascript

I have a parent div and it has 9 same div's am trying to swap two div's index. Following is my code:
HTML:
<div id="cont" class="container">
<div class="no">1</div>
<div class="no">2</div>
<div class="no">3</div>
<div class="blank"></div>
<div class="no">4</div>
<div class="no">5</div>
<div class="no">6</div>
<div class="no">7</div>
<div class="no">8</div>
</div>
now I want to swap say 5th and 6th indexed elements. I have no clue how to do that in JavaScript. I know there is function called .index() but how to do that in pure JS.
Here's one implementation: http://jsfiddle.net/x8hWj/2/
function swap(idx1, idx2) {
var container = document.getElementById('cont');
// ditch text nodes and the like
var children = Array.prototype.filter.call(
container.childNodes,
function(node) {
return node.nodeType === 1;
}
);
// get references to the relevant children
var el1 = children[idx1];
var el2 = children[idx2];
var el2next = children[idx2 + 1];
// put the second element before the first
container.insertBefore(el2, el1);
// now put the first element where the second used to be
if (el2next) container.insertBefore(el1, el2next);
else container.appendChild(el1);
}
This starts by getting a list of all element child nodes, then uses insertBefore to rearrange them.

Using JQuery to find the widths of all children under the body tag down to a specific generation

I'm trying to find the widths of set of HTML elements on a web page.
Basically, I want to get the widths of:
The html element
The body element
All elements directly under the body element [ancestor]
All children directly under 'ancestor' down to the fourth generation (if at all it goes that far down)
So looking at the HTML code below:
<html>
<head>
<title></title>
</head>
<body>
<div>
<div>
<div>
<div>
<div>Test 1</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div>Test 2
<div>Test 2 - Child</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
I can get the widths of html, body, and the 'ancestors' [(1), (2), and (3)] pretty easily with:
console.log($('html').outerWidth());
console.log($('body').outerWidth());
$('body').children().each(function(){
console.log(this.outerWidth());
});
However, I'm not sure how to get the descendants of the 'ancestor' down to the 4th generation. So 4th generation would be where you have Test 1 and Test 2, Test 2 - Child would be fifth generation.
FYI I'm doing this on pages that I cannot control so identifiers like id and class will not be available for me to target. Also the descendants may vary tag-wise, they might not always be divs.
You would have to traverse the tree 3 times down. it's easy with recursion:
EDIT: fiddle contributed by asker: http://jsfiddle.net/WqupP/2/
function traverseDownBy(generations, start){
var children = [],
generation_num_start = generations+2,
generation_num,
counter = 0
;
function traverseDown(node){
counter++;
if(counter <= 1)
{
generation_num = generation_num_start;
}
else
{
generation_num--;
}
if(generation_num == 0){
generation_num = generation_num_start;
return;
}
children.push(node);
node.children().each( function () { traverseDown($(this)); } );
}
traverseDown(start);
return children;
}
var children = traverseDownBy(4, $('body'));
$(children).each( function (){
$('#result').append('<p>'+$(this).attr('id')+': '+$(this).width()+'</p>');
});
or even
(function(){ //private. member protection.
var generations = 4;
function traverseDown(node){
generations--;
if(generations == 0) return;
console.log(node.outerWidth());
node.children().each( function () { traverseDown($(this)); } );
}
traverseDown($('body'));
})()
I hope I got the problem...
Well, a bad solutions is to combine multiple instances of .children() function. Example:
var childrens = $('body').children().add(
$('body').children().children().add(
$('body').children().children().children().add(
$('body').children().children().children().children()
)
)
childrens.each(blablah);
Source: JQuery's "find()" with depth limit, I can't use .children()
How to combine two jQuery results
Simply:
$('body').find('*').each(function(k,v) {
console.log($(this).width());
});

Categories