How to get the DOM reference to an inserted document fragment - javascript

I am attempting to get the DOM reference of an inserted document fragment in vanilla Javascript. I'm currently using Node.appendChild() however the returned reference is the document fragment as outlined here: https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild#return_value
Is there any approach I could use to get the inserted DOM reference?
I did find the following Stack Overflow answer for a similar question but not related directly to document fragments, this solution uses either CSS animations or Mutation Observers but seems overkill for what I'm attempting. https://stackoverflow.com/a/38636244/13306195
const temp = document.createElement('template');
temp.innerHTML = '<span>Test</span>';
const neededReference = document.body.appendChild(temp.content);

The thing about document fragments is that you have to clone their content whenever appending them to the DOM in order to make them as DOM Nodes and not just the fragments.
const temp = document.createElement('template');
temp.innerHTML = '<span>Test</span>';
const neededReference =
document.body.appendChild(
temp.content.cloneNode(true).firstElementChild
);
console.log(neededReference); // should give the reference to span element

If the given child is a DocumentFragment, the entire contents of the DocumentFragment are moved into the child list of the specified parent node.
The return value of Node.appendChild() is the same thing with the document fragment which is empty.
I think you can put the children of document fragment into another container (e.g. an array). Those children are the real DOM refernces which are inserted.
const df = new DocumentFragment();
const t1 = document.createElement('template1');
t1.innerHTML = '<span>Test1</span>';
t1.id = "xxx"
const t2 = document.createElement('template2');
t2.innerHTML = '<span>Test2</span>';
df.appendChild(t1);
df.appendChild(t2);
document.body.appendChild(df);
window.alert(document.getElementById('xxx') === t1);

Related

How can I make javascript write out stuff into the HTML document multiple times

I am trying to write some stuff into the HTML document multiple times. It's the same lines of code I want to write out. Basically copy itself.
var a = "asd";
let added = document.createElement("div");
let addedP = document.createElement("p")
addedP.innerText = a;
added.append(addedP);
document.body.append(added);
document.body.append(added);
I tried to do this, it wrote out "asd" on my page once, but I wanted it to do it twice.
An element can't appear in more than one place at once.
If you append an element that is already part of the document then it will be moved.
If you want multiple elements then you need to create them with, for example, createElement or cloneNode.
How about a custom function that returns a unique element? This should help with adding them in since they should all be considered unique:
var a = "asd";
let id = 0;
const newDiv = () => {
const x = document.createElement("div");
x.id = (id++).toString(); // sets its id and adds 1 to id simultaneously
return x;
}
let addedP = document.createElement("p") // same thing can be done for a unique p as for the div
addedP.innerText = a;
let added = addedP
added.append(newDiv()) // Can still append! (since a new element is returned)
document.body.append(added);
document.body.append(newDiv());
duplicate elements using element.cloneNode()
Elaborating on an earlier answer, the created element can be cloned to make a copy using:
element.cloneNode(true)
See: https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode
The true parameter value tells the .cloneNode method to deep clone, meaning it should include the subtree (specifying descendent elements and text nodes).
The following snippet clones the div in the question with and without the deep clone parameter set. When the deep clone parameter is not set, the element is empty.
One caveat to be aware of is that if the original element has an id attribute set, it should be re-set to hold a different value for all subsequent clones to avoid having elements with duplicate ids in the page.
var a = "asd";
const added = document.createElement("div");
const addedP = document.createElement("p")
addedP.innerText = a;
added.append(addedP);
// make a copy of added, including subtree;
copyAdded = added.cloneNode(true)
// clone again without the parameter set true;
anotherCopy = added.cloneNode()
document.body.append(added);
document.body.append(copyAdded);
document.body.append(anotherCopy);
div {
min-height: 20px;
border: 1px solid black;
background: yellow;
margin: 5px;
}
The first div was created in javascript. The second and third <i>cloned</i> from the first. Note the last paragraph lacks content because the <i>deep clone</i> parameter of <b>element.cloneNode()</b> was not set. The second paragraph used <b>element.cloneNode(true)</b> to specify a deep clone where the element and its subtree is duplicated.

DOMParser parseFromString removing nodes only when iterating resulting body childNodes

I am writing an electronjs app. I want to parse a string to DOM nodes and try to use DOMParser parseFromString for that. Here is the code:
let str = '<div id="customerList" style="display: none;"><ul></ul></div><script type="text/javascript" src="../js/customerList.js"></script>';
let nodes = new DOMParser().parseFromString(str, 'text/html').body.childNodes;
console.log(nodes);
This returns a NodeList with 2 elements, the expected div and scriptl, in it. If I add the following part in the code, the first element, the div, disappears from the NodeList:
let str = '<div id="customerList" style="display: none;"><ul></ul></div><script type="text/javascript" src="../js/customerList.js"></script>';
let nodes = new DOMParser().parseFromString(str, 'text/html').body.childNodes;
console.log(nodes);
for (let node of nodes) {
contentDiv.appendChild(node);
}
The for loop is after the console.log and somehow alters the behavior of the code before. I can't seem to figure out, why the code behaves like it does though...Since I want to provide information about the first element in an ipcRenderer call, this is actually quite frustrating at the moment. Why does the code behave like it does?
Node.appendChild() moves a node to the new destination. That's why it disappears from your node list.
You can clone the node to avoid that like so:
let str = '<div id="customerList" style="display: none;"><ul></ul></div><script type="text/javascript" src="../js/customerList.js"></script>';
let nodes = new DOMParser().parseFromString(str, 'text/html').body.childNodes;
console.log(nodes);
for (let node of nodes) {
contentDiv.appendChild(node.cloneNode());
}
This will append clones of all(!) nodes from the list and keep your nodes list as is.
Reference: https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild

Javascript: Add same <li> to mulitple <ul> [duplicate]

I just notice I couldn't do
var d1 = document.createElement('div');
var d2 = document.createElement('div');
var p = document.createElement('p');
d1.appendChild(p); // d1 has p now
d2.appendChild(p); // d2 has p now
// but where is p in d1 ?
Some would say it's logic, but well, when I first noticed that I thought how uncool it was.
Why isn't that possible ?
The DOM is a tree structure.
When you append an element, you change its parent.
A node, in the browser, is much more than just the text inside your P (that string could be shared, in fact). It also has a position, dimensions, a visibility, receives events that could have been fired in child elements, propagate events to its parent, and so on. Everything here depends on the position in the tree. Just like would many CSS selectors. It doesn't make a lot of sense to imagine it's the same element at two places, it's better to think about it as two nodes, with maybe some identical content.
If you want to have the same content at two places, you have to clone it.
jQuery's appendTo() method inserts "every element in the set of matched elements to the end of the target". Try this:
p.appendTo(".div-class1, .div-class2")
for AppendChild same element multiple times , we can use this way :
//main function
function appendChildMultiple(parent) {
//check function argument is an element
if (parent.nodeType !== undefined) {
const pTag = document.createElement("p");
pTag.innerText = "This is the appended element";
//finally append child to parent
parent.appendChild(pTag);
}
}
and :
// target the wrapper and create test elements
const wrapper = document.querySelector(".wrapper");
const d1 = document.createElement("div");
const d2 = document.createElement("div");
//append test elements to wrapper
wrapper.appendChild(d1);
wrapper.appendChild(d2);
//use appendChildMultiple function
appendChildMultiple(d1);
appendChildMultiple(d2);
//we appended "pTag" multiple times
if we use Functions , we can AppendChild same element multiple times whitout cloneNode
https://codepen.io/kiumad/pen/eYMNKYa

DOM - Replace node with an array of nodes (efficiently)

What would be an efficient way to replace a DOM node with an array of nodes
(which are a simple array of detached nodes and not HTMLCollection)
(please no jQuery answers)
Demo page
HTML
<body>
<header>
<div>foo</div>
<div>bar</div>
</header>
</body>
JS
// generate simple dummy array of detached DOM nodes
var link1 = document.createElement('a');
link1.innerHTML = 'xxx';
var link2 = document.createElement('a');
link2.innerHTML = 'yyy';
var nodesArr = [link1, link2];
// get the element to replace at some place in the DOM.
// in the case, the second <div> inside the <header> element
var nodeToReplace = document.querySelectorAll('header > div')[1];
// replace "nodeToReplace" with "nodesArr"
for(let node of nodesArr)
nodeToReplace.parentNode.insertBefore(node, nodeToReplace);
nodeToReplace.parentNode.removeChild(nodeToReplace);
You can use a DocumentFragment instead of the array:
var nodesFragment = document.createDocumentFragment();
nodesFragment.appendChild(link1);
nodesFragment.appendChild(link2);
nodeToReplace.replaceWith(nodesFragment); // experimental, no good browser support
nodeToReplace.parentNode.replaceChild(nodesFragment, nodeToReplace); // standard
However, just inserting multiple elements in a loop shouldn't be much different with regard to performance. Building a document fragment from an existing array might even be slower.
My initial solution was a straightforward iteration:
// iterate on the Array and insert each element before the one to be removed
for(let node of nodesArr)
nodeToReplace.parentNode.insertBefore(node, nodeToReplace);
// remove the chosen element
nodeToReplace.parentNode.removeChild(nodeToReplace);

How to get element in user-agent shadow root with JavaScript?

I need get elements from Shadow DOM and change it. How i can do it?
<div>
<input type="range" min="100 $" max="3000 $">
</div>
You cannot access a Shadow DOM created by the browser to display a control, that is called a #shadow-root (user-agent) in the Dev Tools. <input> is one example.
You can only access open custom Shadow DOM (the ones that you create yourself), with the { mode: 'open' } option.
element.attachShadow( { mode: 'open' } )
Update
It's true for most UX standard HTML elements: <input>, <video>, <textarea>, <select>, <audio>, etc.
3rd party edit 2022
The following might help to illustrate the question. Give there is only 1 <input type=range> in the html document this code shows if its children can be accessed.
// returns 1 as expected since only one input element is in the document
document.querySelectorAll("input").length;
// get a reference to <input type=range>
var rangeInput = document.querySelector("input");
// Is it a shadowRoot?
// if null then either
// - it is not a shadowRoot OR
// - its elements can not be accessed (mode == closed)
console.log(rangeInput.shadowRoot); // returns null
The code above shows that the internals of an <input type=range> can not be accessed.
To answer a generalized version of the OP's question:
Query shadow elements FROM ANYWHERE on the page?
It feels like the shadow root API is still lacking (or I just don't know it well enough). It seems to make querySelectorAll useless, in that querySelectorAll will not actually get all matching elements anymore, since it ignores all descendants in shadowRoots. Maybe there is an API that fixes that, but since I have not found any, I wrote my own:
This function recursively iterates all shadowRoots and gets you all matching elements on the page, not just those of a single shadowRoot.
/**
* Finds all elements in the entire page matching `selector`, even if they are in shadowRoots.
* Just like `querySelectorAll`, but automatically expand on all child `shadowRoot` elements.
* #see https://stackoverflow.com/a/71692555/2228771
*/
function querySelectorAllShadows(selector, el = document.body) {
// recurse on childShadows
const childShadows = Array.from(el.querySelectorAll('*')).
map(el => el.shadowRoot).filter(Boolean);
// console.log('[querySelectorAllShadows]', selector, el, `(${childShadows.length} shadowRoots)`);
const childResults = childShadows.map(child => querySelectorAllShadows(selector, child));
// fuse all results into singular, flat array
const result = Array.from(el.querySelectorAll(selector));
return result.concat(childResults).flat();
}
// examples:
querySelectorAllShadows('td'); // all `td`s in body
querySelectorAllShadows('.btn') // all `.btn`s in body
querySelectorAllShadows('a', document.querySelector('#right-nav')); // all `a`s in right menu
Here is an example:
var container = document.querySelector('#example');
//Create shadow root !
var root = container.createShadowRoot();
root.innerHTML = '<div>Root<div class="test">Element in shadow</div></div>';
//Access the element inside the shadow !
//"container.shadowRoot" represents the youngest shadow root that is hosted on the element !
console.log(container.shadowRoot.querySelector(".test").innerHTML);
Demo:
var container = document.querySelector('#example');
//Create shadow root !
var root = container.createShadowRoot();
root.innerHTML = '<div>Root<div class="test">Element in shadow</div></div>';
//Access the element inside the shadow !
console.log(container.shadowRoot.querySelector(".test").innerHTML);
<div id="example">Element</div>
I hope this will help you.

Categories