InnerHTML creates new nodes (new references) each time - javascript

I'm creating a document object via DOMImplementation.createHTMLDocument() and i'm inserting some html with the innerHTML property.
after that i'm appending the nodes to the realdom like this:
function appenderController (nodes,target,uid){
for(let i = 0 ; i < nodes.children.length ; i++) {
if(nodes.children[i].children.length > 0){
if(nodes.nodeType === "9"){
appenderController(nodes.children[i],target);
continue;
}
let realnode = appender(target, nodes.children[i]);
appenderController(nodes.children[i],realnode);
}
if (uid && nodes.children[i].nodeName.toLowerCase() === uid) {
nodes.children[i].remove();
return;
}
appender(target, nodes.children[i]);
}
}
in some cases i'm adding more html to our document and because innerhtml creates the entire document again so i'm also adding special attribute to each node to keep track on which nodes i've already appended.
the problem arises when i'm calling appenderController recursively with nodes = fakedocument.body but inside another function i'm updating the innerhtml for our fakedocument and on the next iteration of appenderController it will continue iterating on the old fakedocument.body , how can i keep the reference to my fake document even if i use innerhtml and update the nodes again
EXAMPLE:
var html='
<html>
<head>
</head>
<body>
<div id="somediv1"></div>
</body>
</html>'
;
var dom = document.implementation.createHTMLDocument();
dom.documentElement.innerHTML = html;
var body = dom.body;
var html2='
<html>
<head>
</head>
<body>
<div id="somediv1"></div>
<div id="somediv2"></div>
</body>
</html>'
;
dom.documentElement.innerHTML = html2;
now if we look at "body" var we will not have "somediv2" , that's exactly what's happening in my function , in some point i'm updating the innerhtml of my fake document but the "appenderController" continues to iterate with the old nodes

Assign each element a unique ID. Then access elements by ID, both on the real DOM, and the temporary one.
Do not store references to elements, store their IDs instead.
—
You can also import nodes from one DOM into another instead of replacing the entire tree using innerHTML. That approach will not interfer with iterating parent elements.
https://developer.mozilla.org/en-US/docs/Web/API/Document/importNode

Related

JavaScript replaces nested HTML DOM elements and restores, Error

I am writing a webpage translation program.
I need to delete the DOM elements in the HTML that do not need to be translated,
and then restore the previously deleted DOM elements to the HTML after the translation of the remaining content is completed.
My own writing is:
Step 1: body_dom_del(), Replace the DOM element in HTML that does not need translation with a placeholder DOM element,
Step 2: body_dom_add(), After the translation is completed, replace the placeholder DOM element with the previously replaced DOM element.
Problems with my program:
Step 1: When two or more replaced DOM elements are nested, it will only be replaced with a placeholder DOM element,
Step 2: After the second replacement, it will fail because the number of DOM elements replaced for the first time is inconsistent with the number of occupied DOM elements.
How can I modify it?
Here is the code:
Example code execution steps and correct execution results:
Step 1: body_dom_del(), error: 3 DOM elements (img, pre, code) that are not translated are replaced by 2 placeholder DOM elements (code is nested within pre, so it is 2 DOM replaced by 1 placeholder DOM element )
Step 2: body_dom_add(), error, because the number of 3 deleted untranslated DOM elements and 2 placeholder DOM elements is inconsistent, the placeholder DOM element cannot be replaced back to the original untranslated DOM element
The correct program result should be:
After the two functions are executed, the HTML of the web page remains unchanged (the three replaced DOMs (pre, code, img) can still be replaced back in HTML)
test.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>error</title>
</head>
<body>
<img src="#">
<div>111111</div>
<!--the <pre> and <code> is nested,replace error-->
<pre>
this is pre
<div>this is in pre the div</div>
<code>this is code</code>
</pre>
<div>222222</div>
<script>
// Replace no translate DOM elements with placeholder DOM elements.
var body_dom_delete = body_dom_del();
// Other operations, omitted...
// restore no translate DOM elements into HTML,
// Error when special DOM element is nested
body_dom_add(body_dom_delete.body_dom, body_dom_delete.no_trans_dom)
function body_dom_del(){
let body_trans = document.body;
let no_trans_dom = body_trans.querySelectorAll("pre, code, img"); // no translate DOM element
for (let i=0; i<no_trans_dom.length; i++) {
let replace_no_trans_dom = document.createElement('span'); // create placeholder DOM elements
replace_no_trans_dom.setAttribute('class','placeholder');
let parent = no_trans_dom[i].parentNode;
parent.replaceChild(replace_no_trans_dom, no_trans_dom[i]); // replace
};
// return:
// body_dom: HTML DOM after replacing DOM elements,
// no_trans_dom: Array of no translate DOM elements to be replaced
return {'body_dom':body_trans, 'no_trans_dom':no_trans_dom};
}
function body_dom_add(body_trans, no_trans_dom){
let replace_no_trans_dom = body_trans.querySelectorAll(".placeholder");
// When no translate DOM elements are nested,
// the number of no translate elements and placeholder elements is inconsistent.
console.log(replace_no_trans_dom.length, no_trans_dom.length);
if(replace_no_trans_dom.length == no_trans_dom.length){
for (let i=0; i<replace_no_trans_dom.length; i++) {
let parent = replace_no_trans_dom[i].parentNode;
parent.replaceChild(no_trans_dom[i], replace_no_trans_dom[i]);
};
} else {
console.log('error! The number of special elements and placeholder elements is inconsistent.');
}
return body_trans;
}
</script>
</body>
</html>
yes, i got't it. thank you #Kaiido
Successful code:
test.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>error</title>
</head>
<body>
<img src="#">
<div>111111</div>
<!--the <pre> and <code> is nested,replace error-->
<pre>
this is pre
<div>this is in pre the div</div>
<code>this is code</code>
</pre>
<div>222222</div>
<script>
var body_dom_delete = body_dom_del();
body_dom_add(body_dom_delete.no_trans_dom, body_dom_delete.holder_array)
function body_dom_del(){
let body_trans = document.body;
let no_trans_dom = body_trans.querySelectorAll("pre, code, img"); // no translate DOM element
var holder_array = []
for (let i=0; i<no_trans_dom.length; i++) {
let replace_no_trans_dom = document.createElement('span'); // create placeholder DOM elements
replace_no_trans_dom.setAttribute('class','placeholder');
holder_array.push(replace_no_trans_dom)
// let parent = no_trans_dom[i].parentNode;
// parent.replaceChild(replace_no_trans_dom, no_trans_dom[i]); // replace
no_trans_dom[i].replaceWith(replace_no_trans_dom);
};
return {'body_dom':body_trans, 'no_trans_dom':no_trans_dom, 'holder_array':holder_array};
}
function body_dom_add(no_trans_dom, holder_array){
for (let i=0; i<no_trans_dom.length; i++) {
// let parent = replace_no_trans_dom[i].parentNode;
// parent.replaceChild(no_trans_dom[i], replace_no_trans_dom[i]);
holder_array[i].replaceWith(no_trans_dom[i]);
};
}
</script>
</body>
</html>

Changing HTML in JavaScript without innerHTML

Let's say my code was something pretty simple like this:
let content = "";
for(let i=0; i<array.length; i++){
content+='<h1>array[i]</h1>';
}
document.getElementById('some_id').innerHTML = content;
I don't like the idea of putting HTML in my JavaScript code, but I don't know any other way of inserting elements into the DOM without using innerHTML, JQuery's html() method, or simply creating new DOM elements programmatically.
In the industry or for best practices, what's the best way to insert HTML elements from JavaScript?
Thanks in advance!
You can use a DOMParser and ES6 string literals:
const template = text => (
`
<div class="myClass">
<h1>${text}</h1>
</div>
`);
You can create a in memory Fragment:
const fragment = document.createDocumentFragment();
const parser = new DOMParser();
const newNode = parser.parseFromString(template('Hello'), 'text/html');
const els = newNode.documentElement.querySelectorAll('div');
for (let index = 0; index < els.length; index++) {
fragment.appendChild(els[index]);
}
parent.appendChild(fragment);
Since the document fragment is in memory and not part of the main DOM tree, appending children to it does not cause page reflow (computation of element's position and geometry). Historically, using document fragments could result in better performance.
Source: https://developer.mozilla.org/en-US/docs/Web/API/Document/createDocumentFragment
Basically you can use whatever template you want because it's just a function that return a string that you can feed into the parser.
Hope it helps
You can use the createElement() method
In an HTML document, the document.createElement() method creates the HTML element specified by tagName, or an HTMLUnknownElement if tagName isn't recognized.
Here is an example,
document.body.onload = addElement;
function addElement () {
// create a new div element
var newDiv = document.createElement("div");
// and give it some content
var newContent = document.createTextNode("Hi there and greetings!");
// add the text node to the newly created div
newDiv.appendChild(newContent);
// add the newly created element and its content into the DOM
var currentDiv = document.getElementById("div1");
document.body.insertBefore(newDiv, currentDiv);
}
<!DOCTYPE html>
<html>
<head>
<title>||Working with elements||</title>
</head>
<body>
<div id="div1">The text above has been created dynamically.</div>
</body>
</html>
A flexible and more faster (efficient) way to insert HTML elements using JavaScript's insertAdjacentHTML method. It allows you to specify exactly where to place the element. Possible position values are:
'beforebegin'
'afterbegin'
'beforeend'
'afterend'
Like this:
document.getElementById("some_id").insertAdjacentElement("afterbegin", content);
Here's a Fiddle example
Creating the element programmatically instead of via HTML should have the desired effect.
const parent = document.getElementById('some_id');
// clear the parent (borrowed from https://stackoverflow.com/questions/3955229/remove-all-child-elements-of-a-dom-node-in-javascript)
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
// loop through array and create new elements programmatically
for(let i=0; i<array.length; i++){
const newElem = document.createElement('h1');
newElem.innerText = array[i];
parentElement.appendChild(newElem);
}

Append method not appending to all elements

I have created my own JS library. In that i am trying to define append method like this:
append: function (els) {
var elChild = document.createElement('span');
elChild.innerHTML = els;
for(i = 0; i < this.length; i++){
this[i].appendChild(elChild);
}
}
Now i am calling this append method in my script tag of HTML page like this:
<body>
<h1 class="first_heading">hello</h1>
<h1 class="second_heading">hi</h1>
<button>Test Me</button>
</body>
<script>
dome.get('h1').append('<p>some text</p>');
</script>
But the problem is all h1 tags not appending the paragraph text. Only last h1 is appending paragraph text. Any solution?
https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild:
The Node.appendChild() method adds a node to the end of the list of children of a specified parent node. If the given child is a reference to an existing node in the document, appendChild() moves it from its current position to the new position
In other words, the same node can't appear in multiple places in a document. You have to call document.createElement('span') separately for each child you want to create.
As mentioned this this corresponds to the object in scope. In this scenario, it is the window object. Here is a simple way to append the string to all headers
function append(tagname, text) {
// get all tag names
all_t = document.getElementsByTagName(tagname);
// loop through and append the string to the inner html of each tag
for (var x = 0; x < all_t.length; ++x) {
all_t[x].innerHTML += text
}
}
append('h1', '<p>some text</p>')
<h1 class="first_heading">hello</h1>
<h1 class="second_heading">hi</h1>
<button id="b">Test Me</button>

How to delete specified children?

<body>
<p>dfg</p>
<h1>yoyo</h1>
<h1>yoyo2</h1>
<ul>
<li>somo</li>
</ul>
</body>
For example I want to delete only h1 from body. The other children should stay
You can use a CSS selector.
You can do it using jQuery or VanillaJS. For instance, here is my code for VanillaJS.
var headers = document.querySelectorAll('h1');
headers.forEach(function(h) { h.remove(); });
This will effectively remove the headers from the DOM.
We can create our own fn to remove node by tag for usability. please review this one:
function rem(tag) {
var h = document.querySelectorAll(tag); //return NodeList not array
[].forEach.call(h,function(elm) {
elm.parentNode.removeChild(elm);
});
}
//passing tag to remove
rem('p');
<body>
<p>dfg</p>
<h1>yoyo</h1>
<h1>yoyo2</h1>
<ul>
<li>somo</li>
</ul>
</body>
You can use getElementsByTagName to get an HTMLCollection (not an array) of the h1 tags, which is live.
When an element is removed, the elements update their indexes accordingly which means that you have to remove each element from the last position to the first.
Javascript solution:
var h1Elems = document.getElementsByTagName('h1');
for(var i = h1Elems.length - 1; i >= 0; i--){
h1Elems[i].parentElement.removeChild(h1Elems[i]);
}
See this code working in this jsfiddle

jQuery .text('') on multiple nested elements

I wanted to remove all text from html and print only tags. I Ended up writing this:
var html = $('html');
var elements = html.find('*');
elements.text('');
alert(html.html());
It only out prints <head></head><body></body>. Was not that suppose to print all tags. I've nearly 2000 tags in the html.
var elements = html.find('*');
elements.text('');
That says "find all elements below html, then empty them". That includes body and head. When they are emptied, there are no other elements on the page, so they are the only ones that appear in html's content.
If you really wnat to remove all text from the page and leave the elements, you'll have to do it with DOM methods:
html.find('*').each(function() { // loop over all elements
$(this).contents().each(function() { // loop through each element's child nodes
if (this.nodeType === 3) { // if the node is a text node
this.parentNode.removeChild(this); // remove it from the document
}
});
})
You just deleted everything from your dom:
$('html').find('*').text('');
This will set the text of all nodes inside the <html> to the empty string, deleting descendant elements - the only two nodes that are left are the two children of the root node, <head></head> and <body></body> with their empty text node children - exactly the result you got.
If you want to remove all text nodes, you should use this:
var html = document.documentElement;
(function recurse(el) {
for (var i=0; i<el.childNodes.length; i++) {
var child = el.childNodes[i];
if (child.nodeType == 3)
el.removeChild(child);
else
recurse(child);
}
})(html);
alert(html.outerHTML);
Try this instead
$(function(){
var elements = $(document).find("*");
elements.each(function(index, data){
console.log(data);
});
});
This will return all the html elements of page.
lonesomeday seems to have the right path, but you could also do some string rebuilding like this:
var htmlString=$('html').html();
var emptyHtmlString="";
var isTag=false;
for (i=0;i<htmlString.length;i++)
{
if(htmlString[i]=='<')
isTag=true;
if(isTag)
{
emptyHtmlString+=htmlString[i];
}
if(htmlString[i]=='>')
isTag=false;
}
alert(emptyHtmlString);

Categories

Resources