Remove and Set an Element's Text - javascript

Secrets of the JavaScript Ninja shows how to remove and set an element's text:
HTML
<div id="test">Hey!
<div id="child">delete me</div>
</div>
.
Javascript
var b = document.getElementById("test");
console.log("b before:", b);
while(b.firstChild) {
console.log("removing child:",b.firstChild);
b.removeChild(b.firstChild);
}
console.log("b's value post remove:", b);
b.appendChild(document.createTextNode("Some new text."));
var text = b.textContent || b.innerText;
console.log("text:", text);
Here's the console output:
b before: <div id=​"test">​Some new text.​</div>​
removing child: "Hey!"
removing child: <div id=​"child">​delete me​</div>​
removing child: " "
b's value post remove: <div id=​"test">​Some new text.​</div>​
text: Some new text.
How could b equal Some new text.​ when the HTML is clearly set to Hey!?
Also, why would the b's value post remove: output show up as Some new text even though it hasn't been set yet?
http://jsfiddle.net/X6fYM/

If you're wondering why the console shows you something from later when you logged it before, it's because the console is (partially) a live display of what's in the DOM, not a bunch of strings written out at a moment in time. More about that in this question and its answers.
Basically, if you log an object, the console may treat that as a living display and update it if you change the object. If you log a string, the console will correctly display that as an unchanging thing. So:
var div = document.createElement("div");
console.log("div", div);
div.appendChild(document.createTextNode("foo"));
...can (subject to various conditions) show that the div contains foo, because the console updated the display when we changed it. (Live Example, open the console to see)
If you single-step through the code in a debugger, you can watch the console show one thing, then change it as we change its contents.

Perhaps a little more explanation is in order.
Remember that each item in markup has some sort of element-context. Even text is a Node.
So, there are three Nodes inside of #test at the beginning of your script:
Hey!
<div id="child">delete me</div>
[a blank text node]
This is why you see the output as the while statement works its way through #test's child nodes, removing each one in turn.
Having run your jsFiddle in Firefox, I'm not sure why you're seeing the output you posted:
b's value post remove: <div id=​"test">​Some new text.​</div>​
as it does not display that for me. It only displays that there is a div with the ID of test.
Also, in the beginning, you're creating a pointer to the test node, which is why, when you perform this call:
b.appendChild(document.createTextNode("Some new text."));
b suddenly has content... it never went away, you didn't dispose of the pointer to that object, and so when you test its contents, it displays the new text node that you just added to it.

Related

how does JavaScript outerHTML function behave?

I just started learning about DOM Web API and the behavior of outerHTML function seems a bit odd for me.
This is my code:
const heading = document.getElementById('heading');
heading.innerHTML = 'This is a <span>Heading</span>';
console.log(heading.innerHTML);
heading.outerHTML = '<h2>Hello World!</h2>';
console.log(heading.outerHTML);
Output:
This is a <span>Heading</span>
<h1 id="heading">This is a <span>Heading</span></h1>
For what I know DOM changes happen synchronously and therefore I expect the result for the second log to be <h2>Hello World!</h2> but the output is quite confusing.
Ok lets try to give an answer to that step by step.
First, you get the elemnet 'heading' ID and assign it to the heading variable.
Sets the innerHTML of the heading element ('This is a Head...)
Log innerHTML of the heading element.
Set outherHTML of heading element (Hello World!.. which replaces the heading element with th enew element in the DOM
Log otherHTML of the heading element. BUT , heading element has been replaced in the DOM. OutherHTML property refers to the serialized HTML of the element as it was before, and that is why u see the original tag h1 in the output.
to get what you want, you could try to define a new variable using DOM:
const heading2 = document.getElementById('heading');
console.log(heading2.outerHTML);
this will give you the output ure looking for.

React "Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node" with Chrome Extension

I've seen similar questions to this one, but none solve my problem.
I have a chrome extension where I highlight some content on the page by creating a fragment and replacing characters with spans, like this:
for (const node of textChildNodes(parentNode)) {
const fragment = document.createDocumentFragment();
const matches = node.textContent.match(regex);
const split = node.textContent.split(regex);
split.forEach((content, i) => {
fragment.appendChild(document.createTextNode(content));
if (i !== split.length-1) {
const clone = elem.cloneNode(true);
clone.appendChild(document.createTextNode(matches[i]));
fragment.appendChild(clone);
}
});
node.parentElement.replaceChild(fragment, node);
With "textChildNodes()" being a function that returns only the nodes that are text nodes, like this:
const textChildNodes = obj => Array.from(obj.childNodes)
.filter(node => node.nodeName === "#text");
It works pretty well, as shown in the picture below:
The problem is, in cases like this (as in a Facebook post with a "See more" button), the nodes that were highlighted have the same parent as the node with "See more", as shown in the picture below:
When I click on "See more", the Facebook post disappears and the error below shows up on the console:
This doesn't happen if the button "See more" doesn't have a "sibling" node highlighted.
I have tried changing the fragment with a div and put all text nodes inside spans, but it still happens.
Maybe the parent node, when having it's children changed, loses its "identity", and then when React goes to delete something from it, it crashes? How can I fix it?
For anyone that comes here with a similar issue, I ended up not being able to solve it, so I just abandoned the "replaceChild()" way of doing it.
#wOxxOm left a possible solution in the comment section of this question, though:
You can override Node.prototype.removeChild in page context to and simply use child.remove() inside.
Instead, I emptied the data of the node that was going to be replaced, making it "invisible" (this nodes are all text nodes, so I couldn't assign a style to it, like "display: none").
Then, I added the fragment (the highlighted node) after that first node.
This way, all nodes are preserved, and it won't throw any error. Clearly, React was trying to remove a child node that wasn't there anymore (because it had been replaced by me with "replaceChild()").
So, in the end, I went from:
node.parentElement.replaceChild(fragment, node);
to:
node.data = '';
node.after(fragment);
Put text in tag (ex: span/div).
From this:
<span class="highlighted">Foo</spa>
Bar
To this:
<span class="highlighted">Foo</spa>
<span>Bar</span>
Hope this help!

How can I manipulate the child node of this element?

My Chrome Extension needs to change the size of the child node to the following.
Notice how the 'width' is set for the parent DIV...my Chrome Extension did that.
I can show the following element in the console by:
var divContainer2 = $(divContainer)[0];
console.log('Now divContainer is: ', divContainer2);
The element shown in the console by the above (just the start of it, but showing the parent/child relationship):
<div class="v7wOcf ZGnOx" style="margin-top: 80px; width: 1207px;">
<div jscontroller="Zptowf" jsaction="R6jwd:hKrBwc;c96EGd:v8OFqc;QmtCl:.CLIENT;qVp5ue:.CLIENT;AE9bOd:.CLIENT;MmB7ud:.CLIENT;zkkUY:.CLIENT;lyIVcf:.CLIENT;wuANJc:.CLIENT;voP7ud:.CLIENT" jsmodel="PuTOgd IaLzN tZ2gdc dSSknb PTCFbe ephE9e lkzLle OqPTdc nQnzVc VeaFK uArcre" data-without-stream-item-materials="" class="DReKqd" data-submission-id="1" data-view-id="46"><div jscontroller="cs6ocd" jsaction="rcuQ6b:npT2md;KDsQaf:Qp7hp;qFgNIJf-Wvd9Cc Yiql6e iTy5c editable" tabindex="0" role="textbox" aria-required="true" aria-multiline="true"
I want to apply similar styling to the child (the DIV on the second line...with the 'jscontroller').
To make things very explicit I've tried:
var divContainer3 = divContainer2[0];
var divContainer3 = divContainer2.children[0];
And others...
What am I missing? Why can't I grab the childnode of the object I can so clearly push to console and manipulate?
Thank you for any help/direction/guidance...
The issue wasn't syntax...it was dynamic content loading...and so when I was grabbing the childNodes (or trying to) the element didn't exist.
What continues to be confusing for me is that the CONSOLE will show an element called out in console.log even though it didn't exist at the time the particular line of code ran.
So I could SEE the element, and it's nodes/length, in the console but the very next line of code that simply logged the length would come up zero. Because when the length code ran the element didn't really exist.
I added a little 'wait' routine to make sure the thing is there before I try to manipulate it.

How do the document object model values filled by last changed values?

while studying the DOM , i wrote the below script :
console.log(document);//how this will generate the last update id value
var x = document.getElementById("old").getAttribute("id");
var y = document.getElementById("old").setAttribute("id","IDChanged");
console.log(document);
<div id="old">first</div>
both of results are :
<div id="IDChanged"><div>
after running this snippet, i found that both of the results are generating the html document with the same id which is the IDCHANGED , and what I expect is that the first console.log will generate a document with div , its id is old and the second console.log will generate the document with the div id is IDChanged.
SO, HOW to do this work?
You are logging the document object (for some reason that makes no sense), when you are really more interested in the div element. I don't know why you say that you see IDChanged logged twice when neither of your console.log() statements would produce that at all, they would both log the document object, not the div.
If you get rid of your first console.log() and change the last one to:
console.log(x,y);
you will see the results you wanted., but really, forget about document and focus on the div. I think this is what you are looking for to see the id before and after the changes.
// Get a refernece to the div
var x = document.querySelector("div");
// Report the contents of the document before doing anything:
console.log(x);
// Change the ID of the element
x.setAttribute("id","IDChanged");
// Report the contents of the document after DOM manipulation:
console.log(x);
<div id="old">first</div>

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.

Categories

Resources