javascript DOM out of sync? [duplicate] - javascript

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.

Related

Auto-Remove element after being created (Javascript/Jquery) [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I've been developing a few functions on JavaScript to display messages on the screen. The function I'm about is this:
showMsg(): Creates an element and appends it into another element.
What I want to do is, given that the function is activated via click, append a timer to this very element and remove it after 3 seconds.
I've tried many ways to accomplish it with setTimeouts and removing the elements within container with listeners like bind.('DOMSubtreeModified') on the container where showMsg() element is being appended to.
I need to give the created element a X timer and remove the element after that X timer ends. Like a sticky bomb, attached to the very element
Can you guys help me out?
Why do you need to have it set to the object in specific? If you want the item to get destroyed after a set time you can simply do this by having saving the object's reference to a variable then having it get destroyed with .remove() inside a setTimeout.
The instance of the setTimeout won't be destroyed so it will act just like having it connected to the object in most use cases.
For example: Here is what I have written that works for what I think you are going for here.
function spawnTemp() {
var deleteMe = $("<div style='height: 100px; width: 100px; background: lime;'>My Message</div>").appendTo("#eleContainer");
setTimeout(function() {
deleteMe.remove();
}, 1000);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<button id="spawnElement" onclick="spawnTemp();">Spawn Temporary</button>
<div id="eleContainer">
</div>
</body>
</html>
Note: in this code I use the appendTo() method to get the instance of the appended object, this allows me to then delete that instance through its reference I stored in the variable deleteMe.
If you were using append() then the stored variable would have been referencing the container that is the parent of the appended object.
If you absolutely need the element to have the timer attached to it, then you can use the more complex,
$("new html").appendTo('container').delay(2000).queue(function() { $(this).remove(); });
Though, it is hard to think of why you might use this over the alternative setTimeout().
Thank you guys, specially Liam for giving me the right approach I needed.
It finally worked out, but in a bit different way.
Calling the function showMsg and storing it into a variable wouldn't allow me to make the timer work, It will create the element and so but it won't remove it.
What I finally did was to create a variable and paste the "result" of the function showMsg(), and then the timer would work. It ended up like this:
THE ANSWER BY THE MOMENT
var elem = $("<div>You must be logged first my friend</div>").appendTo('.notification-bar');
setTimeout( function() {
$(elem).remove();
}, 3000);
And all works well by now.
THE PROBLEM I HAD, EXPLAINED
This is what I was supossed to do:
Functions I got:
createElement: Creates an element with the given attr, data and place
function createElement(element, attributes, position, text) {
var object = $(document.createElement(element)); // Element creation
$.each(attributes, function(key, value) { // Array of attributes
object.attr(key, value); // Attr: Value
});
$(object).appendTo(position); // Place
$(object).append(text); // Text
}
showMsg(): Uses createElement to create and place a div
function showMsg() {
createElement('div', null, '.notification-bar', "You must be logged first my friend");
}
onclick trigger: Called on body load. * This was supossed to work *
$('.reply-button').click(function(event) {
var elem = notifyUser('You must be logged first my friend');
setTimeout( function() {
$(elem).remove();
}, 3000);
});
If anyone has a clue of how to perform the previous problematic, please let me now
Thank you in advance ;)

JavaScript function fails to get reference to DOM element on second invocation [duplicate]

This question already has answers here:
Can a span be closed using <span />?
(10 answers)
Closed 4 years ago.
Below is a very simple function that will set the text content of a DOM element.
const display = (el, value) => {
el.textContent = value;
};
display(document.getElementById('element1'), 'One');
display(document.getElementById('element2'), 'Two');
<span id="element1" />
<span id="element2" />
Strangely enough, when invoked the second time, the function will error out with the familiar:
Uncaught TypeError: Cannot set property 'textContent' of null
At first I thought that the problem might be due to the fact that the second element might not have been loaded yet at the time of execution, but:
The script is inlined at the end of the body tag, so all DOM elements should have been loaded.
Invoking the function only on the second element correctly prints Two.
Replacing the calls to display() with console.log(document.getElementById('element1')) and console.log(document.getElementById('element2')) successfully prints out both elements.
Changing the order of the two calls will still only display One, but no error occurs.
So I'm stumped. The problem presents itself both in a stacksnippet and when loaded as a separate HTML page. It occurs in both current Firefox and Chrome.
Am I missing something very obvious here? Can anyone tell me what's going on?
const display = (el, value) => {
el.textContent = value;
};
display(document.getElementById('element1'), 'One');
display(document.getElementById('element2'), 'Two');
<span id="element1"></span>
<span id="element2"></span>
if you correct the span tags then it works correctly. so you close the span tag correctly.

Element custom properties not being kept after loop

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...

jQuery "add" Only Evaluated When "appendTo" Called

this has been driving me crazy since yesterday afternoon. I am trying to concatenate two bodies of selected HTML using jQuery's "add" method. I am obviously missing something fundamental. Here's some sample code that illustrated the problem:
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
</head>
<body>
<p id="para1">This is a test.</p>
<p id="para2">This is also a test.</p>
<script>
var para1 = $("#para1").clone();
var para2 = $("#para2").clone();
var para3 = para1.add(para2);
alert("Joined para: " + para3.html());
para3.appendTo('body');
</script>
</body>
</html>
I need to do some more manipulation to "para3" before the append, but the alert above displays only the contents of "para1." However, the "appendTo appends the correct, "added" content of para1 and para2 (which subsequently appears on the page).
Any ideas what's going on here?
As per the $.add,
Create a new jQuery object with elements added to the set of matched elements.
Thus, after the add, $para3 represents a jQuery result set of two elements ~> [$para1, $para2]. Then, per $.html,
Get the HTML contents of the first element in the set of matched elements or set the HTML contents of every matched element.
So the HTML content of the first item in the jQuery result ($para1) is returned and subsequent elements (including $para2) are ignored. This behavior is consistent across jQuery "value reading" functions.
Reading $.appendTo will explain how it works differently from $.html.
A simple map and array-concat can be used to get the HTML of "all items in the result set":
$.map($para3, function (e) { return $(e).html() }).join("")
Array.prototype.map.call($para3, function (e) { return $(e).html() }).join("")
Or in this case, just:
$para1.html() + $para2.html()
Another approach would be to get the inner HTML of a parent Element, after the children have been added.

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).

Categories

Resources