Why my code throw "Cannot read property 'parentNode' of undefined"? - javascript

So i want to delete SVGs of my iframe, this is my code :
var parent = document.querySelectorAll("#main");
var child = parent[0].childNodes;
var lengthOfNodes = child.length;
for (var j = 0; j < lengthOfNodes; j++) {
child[j].parentNode.removeChild(child[j]);
}
child is an array of my svg element.
It works, but sometimes this algo throw me "Cannot read property 'parentNode' of undefined" and i don't know why... I need to relaunch this algo to get it work.

This way it would be too easy to remove elements you are iterating through
and take advantage of refrences
var parent = document.querySelectorAll("#main");
var child = parent[0].childNodes;
child.forEach(c => c.remove());
or if you want to break from a loop somewhere in future then ForEach loop is not going to help then
var parent = document.querySelectorAll("#main");
var child = parent[0].childNodes;
for(var c of child){
//can get out of loop anytime
c.remove();
}

Every time you remove a child, there is one less elements in the children array, so a loop like for (let j=0; j<length; j++) will make j too big at some point.
Prefer a code structured like this:
// This code removes all <li> nodes
const parent = document.querySelector('ul');
// Converts parent.children to an array,
// then use forEach which automatically handles varying length
[...parent.children].forEach(child => parent.removeChild(child));
<ul>
<li>xxx</li>
<li>xxx</li>
<li>xxx</li>
<li>xxx</li>
</ul>
Note that in order to wipe out the content of a parent node you might prefer the more simpler parent.innerHTML = ''!

You are changing the length of child itself in every iteration, Try this:
for (var i=0, j = child.length; i<j; i++)
{
child[i].parentNode.removeChild(child[i]);
}

Sup, and what do you think about a simple while loop instead of for loop ? Like this :
var parent = document.querySelectorAll("#main");
var child = parent[0].childNodes;
var j = 0;
while (obj.length > 0) {
child[j].parentNode.removeChild(child[j]);
}
Much easier for delete all elements. But if we want to iterate on a livelist with multiple conditions, we can do a reverse loop :
for (var i = obj.length - 1; i >= 0; i--) {
parent.removeChild(child[i]);
}

If the parent is undefined, at the moment you call parent[0] the array is empty. Are the SVG's being dynamically loaded? If so you might want to wrap this in a callback.

Related

scroll function inside a loop not working because of undefined property

I want to change the style of items while scrolling.
My code is working if I target the ID, but I have to target many items.
So I changed it for class name and add a "for" loop to get through every items.
It ended with the error "Cannot read property 'style' of undefined".
Can someone explain me where I am wrong ?
var gear = document.getElementsByClassName("rotate-block");
for (var i = 0; i < gear.length; i++) {
window.addEventListener("scroll", function() {
gear[i].style.transform = "rotate("+window.pageYOffset/2+"deg)";
});
};
Your code is using a closure-based access to i inside the scroll listeners.
Because you defined your index using var rather than let, all these closures reference the same i, which is evaluated when the listener is executed, not when it is defined.
After your last iteration of the for-loop, i is equal to gear.length, which means any of the listeners is trying to access gear[gear.length]. The highest index available on any array is length - 1 though.
To fix your issue, simply switch from
for (var i = 0; i < gear.length; i++)
to
for (let i = 0; i < gear.length; i++)
So this is the basis of the error you are describing...
...but
Why are you adding more than one scroll listener in the first place?
You probably instead want to iterate over gear inside the listener, at which point using var is perfectly fine since it's no longer accessed as a closure.
var gear = document.getElementsByClassName("rotate-block");
window.addEventListener("scroll", function() {
for (var i = 0; i < gear.length; i++) {
gear[i].style.transform = "rotate("+window.pageYOffset/2+"deg)";
}
});
For the future, I highly recommend to switch to using for...of to iterate over iterables:
window.addEventListener("scroll", function() {
for (const gear of document.getElementsByClassName("rotate-block")) {
gear.style.transform = "rotate("+window.pageYOffset/2+"deg)";
}
});

.each() add an attribute to a parent from jquery to js

I am a newbie in js and jquery and need help with rewriting my code from jquery to pure js.
I've got several parent divs. Each of them have a child div inside.
I want to add a class to both child and parent, but to parent as an attribute value in data-name.
Class names are stored in an array, in other words first parent and its child will get a array[0] class name, second parent and its child - array[1] class name, etc.
I use this jquery for this
$(".back").each(function(i) {
$(this).addClass(tile_array[i]);
$(this).parent().attr("data-name", tile_array[i]);
});
I tried to rewrite it in js like this:
var backs = document.querySelectorAll('back');
for (let i = 0; i < backs.length; i++) {
for (let j = 0; j < tile_array.length; j++) {
backs[i].classList.add(tile_array[j]);
backs[i].parentNode.setAttribute("data-name", tile_array[j]);
}
}
However, this does not work. How should I rewrite my code so that it works properly?
Thanks in advance!
try this : backs.length and tile_array.length are same .so no need ah inner loop
for (let i = 0; i < backs.length; i++) {
backs[i].classList.add(tile_array[i]);
backs[i].parentNode.setAttribute("data-name", tile_array[i]);
}
And add a class in querySelectorAll('.back')
You can skip the inner loop - you don't use it in your jQuery, why would you do that here?
Also, to set data attribute there is .dataset element property in Javascript. So your final code would be like:
var backs = document.querySelectorAll('back');
for (let i = 0; i < backs.length; i++) {
backs[i].classList.add(tile_array[i]);
backs[i].parentNode.dataset.name = tile_array[i]
}

custom querySelectorAll implemention

This was given to me as an interview question -- didn't get the job, but I still want to figure it out.
The objective is to write two querySelectorAll functions: one called qsa1 which works for selectors consisting of a single tag name (e.g. div or span) and another called qsa2 which accepts arbitrarily nested tag selectors (such as p span or ol li code).
I got the first one easily enough, but the second one is a bit trickier.
I suspect that, in order to handle a variable number of selectors, the proper solution might be recursive, but I figured I'd try to get something working that is iterative first. Here's what I've got so far:
qsa2 = function(node, selector) {
var selectors = selector.split(" ");
var matches;
var children;
var child;
var parents = node.getElementsByTagName(selectors[0]);
if (parents.length > 0) {
for (var i = 0; i < parents.length; i++) {
children = parents[i].getElementsByTagName(selectors[1]);
if (children.length > 0) {
for (var i = 0; i < parents.length; i++) {
child = children[i];
matches.push(child); // somehow store our result here
}
}
}
}
return matches;
}
The first problem with my code, aside from the fact that it doesn't work, is that it only handles two selectors (but it should be able to clear the first, second, and fourth cases).
The second problem is that I'm having trouble returning the correct result. I know that, just as in qsa1, I should be returning the same result as I'd get by calling the getElementsByTagName() function which "returns a live NodeList of elements with the given tag name". Creating an array and pushing or appending the Nodes to it isn't cutting it.
How do I compose the proper return result?
(For context, the full body of code can be found here)
Here's how I'd do it
function qsa2(selector) {
var next = document;
selector.split(/\s+/g).forEach(function(sel) {
var arr = [];
(Array.isArray(next) ? next : [next]).forEach(function(el) {
arr = arr.concat( [].slice.call(el.getElementsByTagName(sel) ));
});
next = arr;
});
return next;
}
Assume we always start with the document as context, then split the selector on spaces, like you're already doing, and iterate over the tagnames.
On each iteration, just overwrite the outer next variable, and run the loop again.
I've used an array and concat to store the results in the loop.
This is somewhat similar to the code in the question, but it should be noted that you never create an array, in fact the matches variable is undefined, and can't be pushed to.
You have syntax errors here:
if (parents.length > 0) {
for (var i = 0; i < parents.length; i++) {
children = parents[i].getElementsByTagName(selectors[1]);
if (children.length > 0) {
for (var i = 0; i < parents.length; i++) { // <-----------------------
Instead of going over the length of the children, you go over the length of the parent.
As well as the fact that you are reusing iteration variable names! This means the i that's mapped to the length of the parent is overwritten in the child loop!
On a side note, a for loop won't iterate over the elements if it's empty anyway, so your checks are redundant.
It should be the following:
for (var i = 0; i < parents.length; i++) {
children = parents[i].getElementsByTagName(selectors[1]);
for (var k = 0; k < children.length; i++) {
Instead of using an iterative solution, I would suggest using a recursive solution like the following:
var matches = [];
function recursivelySelectChildren(selectors, nodes){
if (selectors.length != 0){
for (var i = 0; i < nodes.length; i++){
recursivelySelectChildren(nodes[i].getElementsByTagName(selectors[0]), selectors.slice(1))
}
} else {
matches.push(nodes);
}
}
function qsa(selector, node){
node = node || document;
recursivelySelectChildren(selector.split(" "), [node]);
return matches;
}

Javascript getElemtsByClass select [all]

I want to select all elements with the css class
.arrow-down
Sorry but i simply dont find the correct answer, for my problem!
I have an javascript code:
document.getElementsByClassName("arrow-down")[0].style.borderTopColor=""+blu+"";
so how do i select not the first but [all] or is there a way to [1;2;3;]??
getElementsByClassName("arrow-down")[all]
getElementsByClassName("arrow-down")[1;2...]
I tried many things but simply dont get it!
Greetings from germany!
You need to iterate over the list of returned results.
var elements = document.getElementsByClassName("arrow-down");
for (var i = 0, len = elements.length; i < len; i++){
elements[i].style.borderTopColor = blu;
}
If you want to only do a specific subset based on the index, then you can add a condition that checks the value of i. I'm also assuming that blu here is a variable you have defined somewhere?
for (var i = 0, len = elements.length; i < len; i++){
if (i === 1 || i === 2 || i === 3){
elements[i].style.borderTopColor = blu;
}
}
Unfortunately, JavaScript does not have a shorthand for accessing a specific subset of array values, or for applying changes to multiple elements at once. That is something that jQuery does automatically for you. For instance, with jQuery you could write this as:
$('.arrow-down').css('borderTopColor', blu);
document.getElementsByClassName("arrow-down") does select all of such elements.
These are returned in a node list (which can be treated as an array), which is why using [0] on that returns the first element.
Loop over the different elements that the expression returns and act on them:
var elements = document.getElementsByClassName("arrow-down");
var elementsNum = elements.length)
for(var i = 0; i < elementsNum; i++)
{
var anElement = elements[i];
// do something with anElement
}

How can I loop through ALL DOM elements on a page?

I'm trying to loop over ALL elements on a page, so I want to check every element that exists on this page for a special class.
So, how do I say that I want to check EVERY element?
You can pass a * to getElementsByTagName() so that it will return all elements in a page:
var all = document.getElementsByTagName("*");
for (var i=0, max=all.length; i < max; i++) {
// Do something with the element here
}
Note that you could use querySelectorAll(), if it's available (IE9+, CSS in IE8), to just find elements with a particular class.
if (document.querySelectorAll)
var clsElements = document.querySelectorAll(".mySpeshalClass");
else
// loop through all elements instead
This would certainly speed up matters for modern browsers.
Browsers now support foreach on NodeList. This means you can directly loop the elements instead of writing your own for loop.
document.querySelectorAll('*').forEach(function(node) {
// Do whatever you want with the node object.
});
Performance note - Do your best to scope what you're looking for by using a specific selector. A universal selector can return lots of nodes depending on the complexity of the page. Also, consider using document.body.querySelectorAll instead of document.querySelectorAll when you don’t care about <head> children.
Was looking for same. Well, not exactly. I only wanted to list all DOM Nodes.
var currentNode,
ni = document.createNodeIterator(document.documentElement, NodeFilter.SHOW_ELEMENT);
while(currentNode = ni.nextNode()) {
console.log(currentNode.nodeName);
}
To get elements with a specific class, we can use filter function.
var currentNode,
ni = document.createNodeIterator(
document.documentElement,
NodeFilter.SHOW_ELEMENT,
function(node){
return node.classList.contains('toggleable') ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
}
);
while(currentNode = ni.nextNode()) {
console.log(currentNode.nodeName);
}
Found solution on
MDN
As always the best solution is to use recursion:
loop(document);
function loop(node){
// do some thing with the node here
var nodes = node.childNodes;
for (var i = 0; i <nodes.length; i++){
if(!nodes[i]){
continue;
}
if(nodes[i].childNodes.length > 0){
loop(nodes[i]);
}
}
}
Unlike other suggestions, this solution does not require you to create an array for all the nodes, so its more light on the memory. More importantly, it finds more results. I am not sure what those results are, but when testing on chrome it finds about 50% more nodes compared to document.getElementsByTagName("*");
Here is another example on how you can loop through a document or an element:
function getNodeList(elem){
var l=new Array(elem),c=1,ret=new Array();
//This first loop will loop until the count var is stable//
for(var r=0;r<c;r++){
//This loop will loop thru the child element list//
for(var z=0;z<l[r].childNodes.length;z++){
//Push the element to the return array.
ret.push(l[r].childNodes[z]);
if(l[r].childNodes[z].childNodes[0]){
l.push(l[r].childNodes[z]);c++;
}//IF
}//FOR
}//FOR
return ret;
}
For those who are using Jquery
$("*").each(function(i,e){console.log(i+' '+e)});
Andy E. gave a good answer.
I would add, if you feel to select all the childs in some special selector (this need happened to me recently), you can apply the method "getElementsByTagName()" on any DOM object you want.
For an example, I needed to just parse "visual" part of the web page, so I just made this
var visualDomElts = document.body.getElementsByTagName('*');
This will never take in consideration the head part.
from this link
javascript reference
<html>
<head>
<title>A Simple Page</title>
<script language="JavaScript">
<!--
function findhead1()
{
var tag, tags;
// or you can use var allElem=document.all; and loop on it
tags = "The tags in the page are:"
for(i = 0; i < document.all.length; i++)
{
tag = document.all(i).tagName;
tags = tags + "\r" + tag;
}
document.write(tags);
}
// -->
</script>
</head>
<body onload="findhead1()">
<h1>Heading One</h1>
</body>
</html>
UPDATE:EDIT
since my last answer i found better simpler solution
function search(tableEvent)
{
clearResults()
document.getElementById('loading').style.display = 'block';
var params = 'formAction=SearchStocks';
var elemArray = document.mainForm.elements;
for (var i = 0; i < elemArray.length;i++)
{
var element = elemArray[i];
var elementName= element.name;
if(elementName=='formAction')
continue;
params += '&' + elementName+'='+ encodeURIComponent(element.value);
}
params += '&tableEvent=' + tableEvent;
createXmlHttpObject();
sendRequestPost(http_request,'Controller',false,params);
prepareUpdateTableContents();//function js to handle the response out of scope for this question
}
Getting all elements using var all = document.getElementsByTagName("*"); for (var i=0, max=all.length; i < max; i++); is ok if you need to check every element but will result in checking or looping repeating elements or text.
Below is a recursion implementation that checks or loop each element of all DOM elements only once and append:
(Credits to #George Reith for his recursion answer here: Map HTML to JSON)
function mapDOMCheck(html_string, json) {
treeObject = {}
dom = new jsdom.JSDOM(html_string) // use jsdom because DOMParser does not provide client-side Window for element access
document = dom.window.document
element = document.querySelector('html')
// Recurse and loop through DOM elements only once
function treeHTML(element, object) {
var nodeList = element.childNodes;
if (nodeList != null) {
if (nodeList.length) {
object[element.nodeName] = []; // IMPT: empty [] array for parent node to push non-text recursivable elements (see below)
for (var i = 0; i < nodeList.length; i++) {
console.log("nodeName", nodeList[i].nodeName);
if (nodeList[i].nodeType == 3) { // if child node is **final base-case** text node
console.log("nodeValue", nodeList[i].nodeValue);
} else { // else
object[element.nodeName].push({}); // push {} into empty [] array where {} for recursivable elements
treeHTML(nodeList[i], object[element.nodeName][object[element.nodeName].length - 1]);
}
}
}
}
}
treeHTML(element, treeObject);
}
Use *
var allElem = document.getElementsByTagName("*");
for (var i = 0; i < allElem.length; i++) {
// Do something with all element here
}
i think this is really quick
document.querySelectorAll('body,body *').forEach(function(e) {
You can try with
document.getElementsByClassName('special_class');

Categories