innerText is different in a cloned node - javascript

(NOTE: This question isn't the same as the similar one above, as it is about differences between attached and detached DOM trees.)
A simple bit of HTML containing a DIV with no whitespace between its elements:
<!DOCTYPE html>
<html>
<body>
<div><h1>The Title</h1><p>A paragraph.</p><p>A second paragraph.</p></div>
</body>
<script type="text/javascript">
const div = document.querySelector("div");
console.log(div.innerText);
const clone = div.cloneNode(true);
console.log(clone.innerText);
document.body.appendChild(clone);
console.log(clone.innerText);
</script>
</html>
I output innerText to the console three times.
The first time is that of the original DIV:
The Title
A paragraph.
A second paragraph.
The second is that of the cloned DIV, which I would expect to be the same, but is:
The TitleA paragraph.A second paragraph.​
The third is again that of the cloned DIV, but after it has been added to the document, now what I would expect it to be:
The Title
A paragraph.
A second paragraph.
Why is the spacing different when it is not part of the document?

This is a quirk of innerText for detached DOM nodes:
If the element itself is not being rendered (for example, is detached from the document or is hidden from view), the returned value is the same as the Node.textContent property.
This is because innerText takes CSS into account (in this case, the display: block properties of the tags to insert new lines (\n)).
From the algorithm for computing innerText, step 9 says:
If node's used value of 'display' is block-level or 'table-caption', then append 1 (a required line break count) at the beginning and end of items.
As you see, after you insert the cloned node into the DOM, then innerText returns what the original node did because it is able to compute those CSS properties:
const div = document.querySelector("div");
console.log("Original, innerText:", JSON.stringify(div.innerText));
console.log("Original, textContent:", JSON.stringify(div.textContent));
const clone = div.cloneNode(true);
console.log("Detached clone, innerText:", JSON.stringify(clone.innerText));
console.log("Detached clone, textContent:", JSON.stringify(clone.textContent));
document.body.appendChild(clone);
console.log("Attached clone, innerText:", JSON.stringify(clone.innerText));
console.log("Attached clone, textContent:", JSON.stringify(clone.textContent));
<div><h1>The Title</h1><p>A paragraph.</p><p>A second paragraph.</p></div>

Related

How to keep everything when inserting custom HTML as innerHTML

Suppose the following example:
let html = `<Parent customAttr={ value }>
<Child className="container" />
</Parent>`;
let div = document.createElement('div');
div.innerHTML = html;
// or
div.insertAdjacentHTML('beforeend', html);
console.log(div);
When inserting the html variable as innerHTML of the div, first letters of <Parent> and <Child> elements become lowercased, camelCase of the attributes are lost and { value } becomes wrapped into double quotes.
Is it possible and how to keep everything without these changes when inserting as innerHTML?
from mdn web docs :
What exactly happens when you set value of innerHTML? Doing so causes
the user agent to follow these steps:
The specified value is parsed as HTML or XML (based on the document
type), resulting in a DocumentFragment object representing the new set
of DOM nodes for the new elements. If the element whose contents are
being replaced is a element, then the element's
content attribute is replaced with the new DocumentFragment created in
step 1. For all other elements, the element's contents are replaced
with the nodes in the new DocumentFragment.
so you can't do what you want with innerHtml
You can simply add content to the element.
div.innerHTML += html

About querySelector() with multiple selectors

I had a situation in which I wanted to focus either an input tag, if it existed, or it's container if it didn't. So I thought of an intelligent way of doing it:
document.querySelector('.container input, .container').focus();
Funny, though, querySelector always returns the .container element.
I started to investigate and came out that, no matter the order in which the different selectors are put, querySelector always returns the same element.
For example:
var elem1 = document.querySelector('p, div, pre');
var elem2 = document.querySelector('pre, div, p');
elem1 === elem2; // true
elem1.tagName; // "P".
My question is: What are the "reasons" of this behavior and what "rules" (if any) make P elements have priority over DIV and PRE elements.
Note: In the situation mentioned above, I came out with a less-elegant but functional solution:
(document.querySelector('.container input') ||
document.querySelector('.container') ).focus();
document.querySelector returns only the first element matched, starting from the first element in the markup. As written on MDN:
Returns the first element within the document (using depth-first
pre-order traversal of the document's nodes|by first element in
document markup and iterating through sequential nodes by order of
amount of child nodes) that matches the specified group of selectors.
If you want all elements to match the query, use document.querySelectorAll (docs), i.e. document.querySelectorAll('pre, div, p'). This returns an array of the matched elements.
The official document says that,
Returns the first element within the document (using depth-first pre-order traversal of the document's nodes|by first element in document markup and iterating through sequential nodes by order of amount of child nodes) that matches the specified group of selectors.
So that means, in your first case .container is the parent element so that it would be matched first and returned. And in your second case, the paragraph should be the first element in the document while comparing with the other pre and div. So it was returned.
That's precisely the intended behavior of .querySelector() — it finds all the elements in the document that match your query, and then returns the first one.
That's not "the first one you listed", it's "the first one in the document".
This works, essentially, like a CSS selector. The selectors p, div, pre and pre, div, p are identical; they both match three different types of element. So the reason elem1.tagName == 'P' is simply that you have a <p> on the page before any <pre> or <div> tags.
You can try selecting all elements with document.querySelectorAll("p.a, p.b") as shown in the example below and using a loop to focus on all elements that are found.
<html>
<body>
<p class="a">element 1</p>
<p class="b">element 2</p>
<script>
var list=document.querySelectorAll("p.a, p.b");
for (let i = 0; i < list.length; i++) {
list[i].style.backgroundColor = "red";
}
</script>
</body>
</html>

What is the equivalent of jQuery's $(".cell:first") in D3?

I've tried
d3.select(".cell:first")
d3.selectAll(".cell").filter(":first")
d3.selectAll(".cell").select(":first")
but neither work
d3.select(".cell") already selects the first matched element:
Selects the first element that matches the specified selector string, returning a single-element selection. If no elements in the current document match the specified selector, returns the empty selection. If multiple elements match the selector, only the first matching element (in document traversal order) will be selected.
Source: https://github.com/mbostock/d3/wiki/Selections#wiki-d3_select
"How would I get the last item?"
D3 appears to return the results of d3.selectAll() in a collection, positioned in an array. For instance, requesting all paragraphs on the d3 homepage results in:
[ Array[32] ] // An array with a single array child. Child has 32 paragraphs.
So if we wanted to get the last paragraph from that collection, we could do the following:
var paragraphs = d3.selectAll("p");
var lastParag = paragraphs[0].pop();
Or more concisely:
var obj = d3.select( d3.selectAll("p")[0].pop() );
"What about :last-child?"
The :last-child selector isn't the same as getting the last element on a page. This selector will give you the elements that are the last child of their parent container. Consider the following markup:
<div id="foo">
<p>Hello</p>
<p>World</p>
<div>English</div>
</div>
<div id="bar">
<p>Oie</p>
<p>Mundo</p>
<div>Portuguese</div>
</div>
In this example, running d3.select("p:last-child") won't return any of your paragraphs. Even d3.selectAll("p:last-child") won't. Neither of those containers have a last child that is a paragraph (they are <div> elements: <div>English</div> and <div>Portuguese</div>).
If you want to get the first DOM element from the D3's selection, use .node() method:
var sel = d3.selectAll('p'); // all <P>, wrapped with D3.selection
var el = sel.node(); // the first <P> element

Moving the content of a DIV to another DIV with jQuery

http://www.frostjedi.com/terra/scripts/demo/jquery02.html
According to this link elements can be moved around by doing $('#container1').append($('#container2')). Unfortunately, it doesn't seem to be working for me. Any ideas?
See jsfiddle http://jsfiddle.net/Tu7Nc/1/
You must append not your div exactly, but your div's content(inner HTML) with Jquery's html() function.
HTML:
<div id="1">aaa</div>
<div id="2">bbb</div>​
Jquery:
$("#1").append($("#2").html());
Result:
aaabbb
bbb
It is best not to use html().
I ran into some issues due to html interpreting the contents as a string instead of a DOM node.
Use contents instead and your other scripts should not break due to broken references.
I needed to nest the contents of a DIV into a child of itself, here is how I did it.
example:
<div id="xy">
<span>contents</span>
</div>
<script>
contents = $("#xy").contents(); //reference the contents of id xy
$("#xy").append('<div class="test-class" />'); //create div with class test-class inside id xy
$("#xy").find(">.test-class").append(contents); //find direct child of xy with class test-class and move contents to new div
</script>
[EDIT]
The previous example works but here is a cleaner and more efficient example:
<script>
var content = $("#xy").contents(); //find element
var wrapper = $('<div style="border: 1px solid #000;"/>'); //create wrapper element
content.after(wrapper); //insert wrapper after found element
wrapper.append(content); //move element into wrapper
</script>
To move contents of a div (id=container2) to another div (id=container1) with jquery.
$('#container2').contents().appendTo('#container1');
You can also do:
var el1 = document.getElementById('container1');
var el2 = document.getElementById('container2');
if (el1 && el2) el1.appendChild(el2);
or as one statement, but not nearly as robust:
document.getElementById('container1').appendChild(document.getElementById('container2'));
Edit
On reflection (several years later…) it seems the intention is to move the content of one div to another. So the following does that in plain JS:
var el1 = document.getElementById('container1');
var el2 = document.getElementById('container2');
if (el1 && el2) {
while (el2.firstChild) el1.appendChild(el2.firstChild);
}
// Remove el2 if required
el2.parentNode.removeChild(el2);
This has the benefit of retaining any dynamically added listeners on descendants of el2 that solutions using innerHTML will strip away.
$('#container1').append($('#container2').html())
Well, this one could be an alternative if you want to Append:
document.getElementById("div2").innerHTML=document.getElementById("div2").innerHTML+document.getElementById("div1").innerHTML
if you wanted to rewrite contents:
document.getElementById("div2").innerHTML=document.getElementById("div1").innerHTML
I suggest a general approach with a function and jQuery:
function MoveContent(destId, srcId) {
$('#' + destId).append($('#' + srcId).contents().detach());
}
Content of a detached source node is appended to a destination node with call:
MoveContent('dest','src');
The first parameter is an id of a new parent node (destination), the second is an id of an old parent node (source).
Please see an example at: http://jsfiddle.net/dukrjzne/3/

Javascript - div content without innerHTML

I wanted to ask how to change div content, but not using innerhtml.
DEMO: http://jsfiddle.net/Cb6ME/
// get the div
var div = document.getElementById('foo');
// remove child nodes while at least one exists
while( div.childNodes[0] ) {
div.removeChild( div.childNodes[0] );
}
// create a new span element
var span = document.createElement( 'span' );
// give it some text content
span.appendChild( document.createTextNode("I'm new!!!") );
// append the span to the original div
div.appendChild( span );
You can use nodeValue to access the value of a node, however the value of a div. In your example you might have the following HTML...
<div id="myLovelyDiv">This is the text that you want to change</div>
and this script...
var myDiv = getElementById("myLovelyDiv");
myDiv.childNodes[0].nodeValue = "The text has been changed.";
but I fail to see why you wouldn't use
myDiv.innerHTML = "The text has been changed properly.";
A DIV element is a generic block level (by default) element in HTML used as a structural container to hold one or more block or inline elements.
Depending on what it is you want to change you can either select the sub-node in question directly, loop over the childNodes property to find the desired sub-node or completely rewrite the contents as html using innerHTML (which you stated you didn't want to do).
If you want to add content you can create a new element and use the appendChild(child) method of the DIV element to add to it's contents.
Is that what you were looking for?
I know I'm late but .textContent can be replaced for .innerHTML (if you only want to change the text and not code HTML).

Categories