Element custom properties not being kept after loop - javascript

I have some more complex code with a strange behaviour that I've managed to reproduce here:
<!DOCTYPE html>
<html>
<body>
<div>things</div>
<div>stuff</div>
<div>other</div>
<div>misc</div>
<script>
var forEach = function (array, callback, scope) {
for (var i = 0; i < array.length; i++) {
callback.call(scope, array[i], i);
}
}
var d = document.querySelectorAll('div');
d[1].o = d[1].textContent;
forEach(d, function (el, i) {
d[1].innerHTML += '<p>div things</p> sdf d';
document.body.innerHTML += '<div>new div</div> fdsffsd fsdf';
alert(d[1].o);
});
</script>
</body>
</html>
I should get 4 alerts, each saying "stuff". And I do, until I do a hard refresh, and then a normal refresh. Then only the first alert says "stuff", and the others say "undefined". It appears the "o" property being added to div[1] is not being kept. It seems to be related to the innerHTML being added to the body in the loop. The innerHTML being added to the div doesn't seem problematic.
I cannot see what the problem is. Moreover, this only seems to happen in Chrome (v43) and not in Firefox.
Any ideas?

The reason this is happening when the body's innerHTML is updated is that the whole of the body's innerHTML needs to be reparsed. This means any custom properties attached to any elements are then lost, as these DOM elements are being recreated.
Thus one should probably not be using innerHTML with the += operator unless you're sure you know what you're doing.
Why it even worked sometimes is a mystery...

Related

Replacing a text inside of a div in GTM

Similar to this question I want to change some text inside a dynamic div. The explanation there didn't work for me so I started a new thread (rep too low to comment). What makes this div "dynamic" is a some script that calculate how much money you need to spend (based on added to cart items) to get free shipping. The statement I want to replace is always there. I guess you could call it erasing the part of the text. :)
My div:
<div class="free-shipping__content">Brakuje Ci 151,00 zł do darmowej dostawy (Paczkomaty InPost).</div>
My code in GTM (loaded on DOM ready):
<script type="text/javascript">
(function(){
var elements = document.querySelectorALL(".free_shipping__content");
for (i = 0; i < elements.length; ++i) {
var str = elements[i].innerHTML;
elements[i].innerHTML = str.replace(" (Paczkomaty InPost)", "");
}
})();
</script>
Thanks!
Image of surrounding divs
Good job on that closure.
document.querySelectorALL is not a function. You meant document.querySelectorAll.
Another problem is your css selector. You're using .free_shipping__content while the actual class name is different. Your selector should be .free-shipping__content
After these fixes, your code works. However, I already rewrote it to test properly, so I might as well offer a more readable and elegant solution:
document.querySelectorAll(".free-shipping__content").forEach(function (el) {
el.innerHTML = el.innerHTML.replace(" (Paczkomaty InPost)", "");
});

javascript DOM out of sync? [duplicate]

This question already has an answer here:
Javascript Console Log reporting object properties incorrectly
(1 answer)
Closed 1 year ago.
I'm playing with JavaScript and DOM for a (high school) class I'm teaching. Emphasizing that students use console.log for diagnostics. However, in using it I find the values retrieved from the DOM to be out of sync with the UI.
In the SSCE below, when the LI element is clicked, the first time it should log that the style is "color:red;", but it says style is "text-decoration:line-through;". I added the alert() to give time for anything async to finish but no joy.
If I remove the if( isOn ) clause, the attributes print out as expected, and after the first click attributes also print out as expected.
Guessing there's some synchronizing that needs to happen, just not sure what it may be ...
var theLi = document.getElementById("here");
theLi.addEventListener("click", crossOff);
theLi.setAttribute("style", "color:red;");
theLi.setAttribute("crossed", "off");
function crossOff(element) {
var isOn = this.getAttribute("crossed") == "on";
var attrs = this.attributes;
for (const attr of attrs) {
console.log(attr);
}
alert("huh??");
if (isOn) {
this.setAttribute("style", "text-decoration: none;");
this.setAttribute("crossed", "off");
} else {
this.setAttribute("style", "text-decoration: line-through;");
this.setAttribute("crossed", "on");
}
}
//Write function crossOff here.
<!DOCTYPE html>
<html>
<body>
<h3>To Do List:</h3>
<ul>
<li id=here>Eat</li>
</ul>
<script>
</script>
</body>
</html>
The problem would be that you are logging attribute nodes, which are still linked to the DOM. The console is known to render its output asynchronously, as well as to display DOM elements in a "user-friendly" representation - notice you can even edit them just like in the DOM inspector ("Elements" panel)!
This becomes more clear when you use console.dir instead, and explicitly expand the properties of the object:
In your particular case, don't log the attribute node but rather its contents, like name and value.

Trying to change an element's DOM's properties, but changes ignored after I use document.body.innerHTML +=. If order switched, things work fine

I had a hard time phrasing that question's title, but could not find anything more concise.
I want to attach some event oninput to an element (here an input field). But for some reason it didn't work. I narrowed the issue to the schematic MWE (complete MWE at the end).
addEvent();
document.body.innerHTML += "a";
addEvent() was simply a function which changed the oninput property of an input field. My issue was that addEvent() was ignored.
To make sure addEvent(); ran normally, I also modified the value of the input field, and its backgroundColor in the body of the function. Yet, when I ran the code, the oninput and value modifications were nowhere to be found, but the backgroundColor had been modified as per the function.
More mystifying to me, if I write
document.body.innerHTML += "a";
addEvent();
Things work as expected.
My question is in two parts:
how to I fix the code of addEvent(), so that no matter if I write document.body.innerHTML += "a" before or after, the result would be the same?
why does the backgroundColor run fine, while the rest seems to be ignored?
Here is my complete MWE below:
function addEvent() {
var fieldScore = document.getElementById("foo");
fieldScore.style.backgroundColor = "rgb(0,255,0)";
fieldScore.value = "a";
fieldScore.oninput = function () {
console.log("bar");
}
}
// document.body.innerHTML = buildForm();
addEvent();
document.body.innerHTML += "a";
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<form><input type="text" value="" name="foo" id="foo"></form>
<script type="text/javascript" src="mwe.js"></script>
</body>
</html>
Expected: same, but with a in the input field as well.
innerHTML += is almost always an anti-pattern. It makes the browser do this:
Loop through all the children and the children's children, etc., of the element on which you do it and build an HTML string.
Append text to that string.
Destroy all of those child elements (and their children, etc.), parse the HTML string, and replace them with new elements created as a result of parsing the string.
In that process, event handlers and any other non-HTML information on the elements that are destroyed is lost.
If you need to append to an element, use appendChild or insertAdjacentHTML. For instance:
document.body.appendChild(document.createTextNode("a"));
or
document.body.insertAdjacentHTML("beforeend", "a");

check if an input with specific type is selected

I'm using the folowing code to check if an input field is selected but I don't no where the problems is, because it is not working!
<html>
<head>
<title>Test </title>
<head>
<body>
<form >
<input type="text" id="select" value="select"/>
</form>
<script type="text/javascript" >
var d, len,i, j, el;
d=document.forms;
len=d.length;
for(i=0; i<len; i++){
el=d[i].elements;
for(j=0;j<el.length; j++)
if(el[j].type == "text" && el[j].focus())
{
alert("you selected an input field with type text");
}
}
</script>
</body>
</html>
Any help would be much appreciated! Thanks!
This script is run as soon as it is loaded you need to use an event handler.
window.onload=function(){
var els=document.getElementsByTagName('input');
//Gets all the input elements
for(var i=0;i<els.length;i++){
//Attach an event handler that gets called when the element has focus
els[i].onfocus=checkElem;
}
//for your example you could do this all in one line since you only have one element.
//document.getElementById('select').onfocus=checkElem;
}
var checkElem(){
//Check to see if the input element that registered the event is text or not
if(this.type=='text'){
alert('You have selected a text input');
}
else{
alert('You have selected a non-text input');
}
}
This is a naive implementation. I'm not sure what you are trying to accomplish. Usually it is better to implement some sort of event delegation on the form element. Search for event delegation and you will find a number of resources.
I take the following portion of your code :
df=document.forms;
len=d.length;
for(i=0; i<len; i++)
els=d[i].elements;
A few notes about those four lines :
First line : df now contains the list of forms, ok
Second line : but where does this d come from ?
It looks like it's not initialized, so doesn't contain much...
... so I don't think you'll be able to get its length
Fourth line : same thing
If d is not initialized, it doesn't point to anything, and doesn't contain anything
Are you sure you must use d and df ? And not just only df everywhere ?
Same thing a bit later, in your second for() loop : you are using a variable called el, but where do you initialize that variable ?
What I see is this :
els=d[i].elements;
for(j=0;j<el.length; j++)
So, you put the elements in a variable called els ; and, then, you try to read from a variable called el...
Basically : before reading from a variable, you should make sure it contains something ;-)
And, in order to help you, you should install a debugging extension such as Firebug for Firefox (or use the console provided by Chrome) : it often gives interesting error messages, like this :
(source: pascal-martin.fr)
edit after the OP has edited the question
Next step, I would say, is to add {} where those are needed.
If you do not put {} after a for(), only one instruction will be done in the loop.
In short, if you have this :
for (i=0 ; i<10 ; i++)
console.log('hello');
console.log('world');
Then hello will be displayed 10 times ; but world will only be displayed one.
If you want the two console.log calls to be in the for loop, you must use brackets :
for (i=0 ; i<10 ; i++) {
console.log('hello');
console.log('world');
}
So, even if you don't have syntax errors, now, your code is probably not looping like you think it is.
Finally, if you want to find out which element has the focus, you should take a look at the document.activeElement property (quoting) :
Returns the currently focused element, that is, the element that will
get keystroke events if the user types any.
It seems it's not quite standard yet, as it's part of the HTML-5 spec, but it seems to be supported by several browsers :
Originally introduced as a proprietary DOM extension in Internet
Explorer 4, this property also is supported in Opera and Safari (as of
version 4).

Cyclic adding/removing of DOM nodes causes memory leaks in JavaScript?

I'm trying to display dynamically changeable data manipulating with DOM elements (adding/removing them). I found out a very strange behavior of almost all browsers: after I removed a DOM element and then add a new one the browser is not freeing the memory taken by the removed DOM item. See the code below to understand what I mean. After we run this page it'll eat step-by-step up to 150 MB of memory. Can anyone explain me this strange behavior? Or maybe I'm doing something wrong?
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<script type="text/javascript">
function redrawThings() {
// Removing all the children from the container
var cont = document.getElementById("container");
while ( cont.childNodes.length >= 1 ) {
cont.removeChild(cont.firstChild);
}
// adding 1000 new children to the container
for (var i = 0; i < 1000; i++) {
var newDiv = document.createElement('div');
newDiv.innerHTML = "Preved medved " + i;
cont.appendChild(newDiv);
}
}
</script>
<style type="text/css">
#container {
border: 1px solid blue;
}
</style>
</head>
<body onload='setInterval("redrawThings()", 200);'>
<div id="container"> </div>
</body>
</html>
I can't reproduce this on FF 3.6.8/Linux, but 200 ms for a timer is rather small with that much of DOM re-rendering. What I notice on my machine is that when doing JavaScript-intensive things besides running this script, like typing in this answer box, memory usage increases, but is released again when I stop typing (in my case, to something around 16% of memory usage).
I guess that in your case the browser's garbage collector just doesn't have enough ‘free time’ to actually remove those nodes from memory.
Not sure if it'll affect timing, and it's probably really bad practice, but instead of looping through the child nodes, could you not just set the innerHTML of the div to "" ??
It's because removing a Node from the DOM Tree doesn't delete it from memory, it's still accessible, so the following code will work:
var removed = element.removeChild(element.firstChild);
document.body.appendChild(removed);
That code will remove the first child from element, and then after it has been removed, append it to the end of the document.
There really is nothing you can do except make your code more efficient with less removals.
For more info, check out the Node.removeChild page at the Mozilla Developer Center.

Categories

Resources