Difference between a selected node and its imported node - javascript

In the below code, I commented A and B at each lines.
What is the different between A and B? And which is recommended?
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<template id="templatetest">
<div>test</div>
</template>
<body></body>
<script type="text/javascript">
var t = document.querySelector('#templatetest');
var n = document.importNode(t.content, true);
document.body.appendChild(n); //A
document.body.appendChild(t.content); //B
</script>
</html>
And one more question..
The above code renders two "test"s. But when I switch positions of A and B like the below code, only one "test" is rendered somehow. I would like to know why this happens.
<script type="text/javascript">
var t = document.querySelector('#templatetest');
document.body.appendChild(t.content); //B
var n = document.importNode(t.content, true);
document.body.appendChild(n); //A
</script>

The points is importNode creates a copy of the node and returns it, then you can add it in a new parent using appendChild. It's intended to be used when you're getting nodes from anoother documents and inserting it in your doc (for example, when you're moving nodes between different XML files), this function will fix things like the ownerDocument property and other internal things I believe.
The appendChild just to append nodes to other nodes, if the node you is appending is already a child of any other node, first it will remove it from there and move it to its new parent.
In your first example:
A - you create a copy of t.content (n - importNode) and append it to the body;
B - you just append t.content to body, thus removing it from it's original parent
Your second example
B - You moves the content of #templatetest to the body
A - #templatetest is now empty, it has no more content, so there is nothing to you copy and append in your body anymore

Related

Losing a node reference? (javascript)

So I have this JS code :
var pElt = document.createElement("p");
var aElt = document.createElement("a");
aElt.textContent = "Text";
aElt.href = "#";
pElt.appendChild(aElt);
aElt.style.color = "red";
pElt.innerHTML += "<span> and more text</span>";
//aElt.style.color = "red";
document.getElementById("content").appendChild(pElt);
console.log(aElt); // always show the red attribute
There's probably some answer around here, but I cannot even describe the problem ; so I went with "losing node reference", even though it's not what happens here. (edit: in fact, that's what happens here, silly :))
So... Please try the code as it is. It works, the link is red, everyone is happy. Now comment the "aElt.style.color = "red";" line, then uncomment the other one, two lines below.
...
It does not work, the link still appear in black. What I thought is that the pointer linked to my node was either not valid anymore or the aElt was moved to a different memory address. But when I type "console.log(aElt)", it outputs the node correctly (well... I think it does), so I don't get why I can't access it after the .innerHTML change.
What interests me is what happens under the hood :)
Thanks!
index.html :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Question!</title>
</head>
<body>
<div id="content"></div>
<script src="script.js"></script>
</body>
</html>
When you overwrite the content of the <p> element by setting it's innerHTML, you're effectively turning the <a> back into HTML text, appending the <span> (as text), and then recreating new DOM nodes in the <p>. Your old reference still refers to the original <a> you created.
You could instead create that <span> the same way you created the <a>, and append that node to the <p> instead of overwriting .innerHTML.

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.

After cloning an element find the original element in the document

I clone my mainSection like this (I have to clone it because, there are new elements added to #main over AJAX, and I don't want to search through them):
$mainSection = $('#main').clone(true);
then i search through the cloned main section for an element:
var searchTermHtml = 'test';
$foundElement = $mainSection.filter(":contains('"+searchTermHtml+"')");
When I find the string 'test' in the #mainSection I want to get the original element from it in the $mainSection so I can scroll to it via:
var stop = $foundElementOriginal.offset().top;
window.scrollTo(0, stop);
The question is: how do I get the $foundElementOriginal?
Since you're changing the content of #main after cloning it, using structural things (where child elements are within their parents and such) won't be reliable.
You'll need to put markers of some kind on the elements in #main before cloning it, so you can use those markers later to relate the cloned elements you've found back to the original elements in #main. You could mark all elements by adding a data-* attribute to them, but with greater knowledge of the actual problem domain, I expect you can avoid being quite that profligate.
Here's a complete example: Live Copy
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<meta charset="utf-8">
<title>Example</title>
</head>
<body>
<div id="main">
<p>This is the main section</p>
<p>It has three paragraphs in it</p>
<p>We'll find the one with the number word in the previous paragraph after cloning and highlight that paragraph.</p>
</div>
<script>
(function() {
"use strict";
// Mark all elements within `#main` -- again, this may be
// overkill, better knowledge of the problem domain should
// let you narrow this down
$("#main *").each(function(index) {
this.setAttribute("data-original-position", String(index));
});
// Clone it -- be sure not to append this to the DOM
// anywhere, since it still has the `id` on it (and
// `id` values have to be unique within the DOM)
var $mainSection = $("#main").clone(true);
// Now add something to the real main
$("#main").prepend("<p>I'm a new first paragraph, I also have the word 'three' but I won't be found</p>");
// Find the paragraph with "three" in it, get its original
// position
var originalPos = $mainSection.find("*:contains(three)").attr("data-original-position");
// Now highlight it in the real #main
$("#main *[data-original-position=" + originalPos + "]").css("background-color", "yellow");
})();
</script>
</body>
</html>

Add innerHTML before an element in JavaScript? [duplicate]

This question already has answers here:
How can I implement prepend and append with regular JavaScript?
(12 answers)
Closed 8 years ago.
Now I am not talking about creating children or a child node, I literally mean HTML. This is what I want to do:
I have a basic node, for example:
<div id="foo"></div>
And I have a string of HTML, that I want to append before that element (kinda like what innerHTML does, with the difference, that I am obviously putting it before, not inside), e.g.:
"<span>hello, world</span><div></div>Foo<img src="logo.png" />Bar"
Now I want to insert that HTML before the div, so my outcome would be:
<span>hello, world</span><div></div>Foo<img src="logo.png" />Bar<div id="foo"></div>
Is there any way I can do this in JavaScript (without any library)? Thanks!
The most elegant and shortest solution is to call insertAdjacentHTML on foo with "beforeBegin" and html string as arguments. Pure JS.
document.getElementById("foo").insertAdjacentHTML("beforeBegin",
"<div><h1>I</h1><h2>was</h2><h3>inserted</h3></div>");
DEMO
The easiest way is to use a helper element for the parsing, then grab its contents. Like this:
var foo = document.getElementById("foo");
var parent = foo.parentNode;
var helper = document.createElement('div');
helper.innerHTML = yourHTMLString;
while (helper.firstChild) {
parent.insertBefore(helper.firstChild, foo);
}
There, we create a temporary helper, then assign the string to it to generate the contents, then move those child nodes (which are a mix of elements and text nodes) into foo's parent node, just in front of foo.
Note that depending on the HTML content, you may need a different helper element. For instance, if the string defined table rows or cells.
Complete example - live copy
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Inserting Before</title>
</head>
<body>
<div>Before foo</div>
<div id="foo"></div>
<div>After foo</div>
<script>
setTimeout(function() {
var yourHTMLString = "<span>hello, world</span><div></div>Foo<img src=\"logo.png\" />Bar";
var foo = document.getElementById("foo");
var parent = foo.parentNode;
var helper = document.createElement('div');
helper.innerHTML = yourHTMLString;
while (helper.firstChild) {
parent.insertBefore(helper.firstChild, foo);
}
}, 500);
</script>
</body>
</html>

why is my d3 data outputting after body?

This is a simple page that demonstrates some basic functionality of d3. I made a dataset var dataset = [3,1,4,1,5]; and would like to output it as well as some paragraphs. The data is appearing, but after the body! Strange ...
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title> demo project</title>
<script type="text/javascript" src="d3/d3.v2.js"></script>
</head>
<body>
<script type="text/javascript">
d3.select("body").append("p").text("hello!");
d3.select("p").append("text").text(" hello!!");
d3.select("body").append("p").text("hello2!");
d3.select("p:nth-child(3)").append("text").text(" hello2!!");
var dataset = [3,1,4,1,5];
d3.select("p:nth-child(3n+1)")
.data(dataset)
.enter()
.append("p")
.text(function(d) { return d; });
d3.select("p:nth-child(7n + 1)").append("text").text("hello againss?");
</script>
</body>
</html>
the page looks like this:
and the DOM looks like this (note the data shows up after the body close tag):
Also note that the line d3.select("p:nth-child(7n + 1)").append("text").text("hello againss?"); was intended to be printed after all my data, but it does not show up.
The short answer is that in your particular case the enter() selection's parentNode is the document (and not the body).
Let's take a simple example to see what an enter() selection looks like. Assuming we have a document with a body without any p elements.
var ps = d3.select("body").selectAll("p")
.data([0, 1, 2]);
Since no p elements existed yet, the enter() selection will have three elements. Let's inspect the enter selection:
You see that the inner array has a property named parentNode. When you add new elements using selection.append() or selection.insert() the new elements will be created as children of that parentNode.
So, inspecting ps.enter()[0].parentNode will reveal the body element. It now becomes clear that, in a data join, the selection before the selectAll specifies the parentNode; in the above case that was d3.select("body").
What if we had omitted the select("body") part in the data join?
// example of bad data join
var ps2 = d3.selectAll("p")
.data([0, 1, 2]);
It turns out that in this case ps2.enter()[0].parentNode is the #document! That means that if you add elements using this enter() selection, they will become the document's direct children. The append method will add them to the end of the document; i.e. after the body.
The last case is basically what you've encountered. Your data join and enter expression is not correct; it should follow this pattern:
d3.select(parent).selectAll(element)
.data(data)
.enter().append(element);
BTW, there is no HTML text element. So, append("text") doesn't seem meaningful.

Categories

Resources