How to remove shadow root from an HTML element decorated with Shadow DOM from a template?

I am studying import, templates, shadow DOM and custom elements in Chrome Canary (33.0.1712.3). In the grid layout, I have a specific content element (display area) that will display various web components or cloned lightweight DOM fragments imported from files.

However, I cannot redisplay the regular HTML DOM after adding the shadow DOM, because I don’t know how to remove the shadow root. Once created, the shadow root remains and interferes with the display of the regular DOM. (I reviewed various W3C specifications, such as an introduction to web components, shadow DOM, templates, Bidelman articles on HTML5 Rocks, etc.) I highlighted the problem in a simple example below:

Click "show a plain old div"; click "show shaded template"; click "show good old div". Check in devtools after every click. After the third press, there is no output under the buttons, but in devtools I see:

<div id="content"> #document-fragment <div id="plaindiv">Plain old div</div> </div> 

What do I need to add to removeShadow () to remove the shadow root and completely return the content element to its original state?

removing_shadows.html

 <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"/> <template id="shadowedTemplateComponent"> <style> div { background: lightgray; } #t { color: red; } </style> <div id="t">template</div> <script>console.log("Activated the shadowed template component.");</script> </template> <template id="plainDiv"> <div id="plaindiv">Plain old div</div> </template> </head> <body> <div> <input type="button" value="show plain old div" onclick="showPlainOldDiv()"/> <input type="button" value="show shadowed template" onclick="showShadowTemplate()"/> <div id="content"></div> </div> <script> function removeChildren(elt) { console.log('removing children: %s', elt); while (elt.firstChild) { elt.removeChild(elt.firstChild); } } function removeShadow(elt) { if (elt.shadowRoot) { console.log('removing shadow: %s', elt); removeChildren(elt.shadowRoot); // Leaves the shadow root property. // elt.shadowRoot = null; does not work // delete elt.shadowRoot; does not work // What goes here to delete the shadow root (#document-fragment in devtools)? } } function showPlainOldDiv() { console.log('adding a plain old div'); var host = document.querySelector('#content'); removeChildren(host); removeShadow(host); var template = document.querySelector('#plainDiv'); host.appendChild(template.content.cloneNode(true)); } function showShadowTemplate() { console.log('adding shadowed template component'); var host = document.querySelector('#content'); removeChildren(host); removeShadow(host); var template = document.querySelector('#shadowedTemplateComponent'); var root = host.shadowRoot || host.webkitCreateShadowRoot(); root.appendChild(template.content.cloneNode(true)); } </script> </body> </html> 
+9
source share
3 answers

You cannot remove the root shadow after adding it. However, you can replace it with a newer one.

As already mentioned, http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-301/ , the newest shadow root "wins" and becomes the rendering root.

You can replace the shadow root with a new shadow root that contains only the <content> pseudo-element to insert everything from the light DOM back into the shadow DOM. At this point, as far as I know, it will be functionally equivalent to no shadow DOM at all.

+1
source

The Shadow DOM specification has moved from v0 to v1.

One of the changes is that in v1 it is impossible to create a shadow root for yourself, and the host element can contain only one shadow root.

So, it seems that the answer to replacing the shadow root with a new empty shadow root is no longer valid.

+5
source

rmcclellan is correct that you cannot "remove" ShadowRoot v2. But you can fake it.

OuterHTML Partial Solution

 elementWithShadowDOMv2.outerHTML = elementWithShadowDOMv2.outerHTML; 

HOWEVER, there is a serious warning: although there are no visual changes, elementWithShadowDOMv2 still refers to the destroyed element with ShadowDOMv2, as if elementWithShadowDOMv2.parentNode.removeChild( elementWithShadowDOMv2 ) was called. It also removes event listeners in the element. Check out the demo below.

 var addShadowHere = document.getElementById("add-shadow-here"); addShadowHere.addEventListener("mouseenter", function() { addShadowHere.style.border = '2em solid blue'; }); addShadowHere.addEventListener("mouseleave", function() { addShadowHere.style.border = ''; }); var shadow = addShadowHere.attachShadow({mode:"open"}); var button = shadow.appendChild(document.createElement("button")); button.textContent = "Click Here to Destroy The ShadowDOMv2"; button.addEventListener("click", function() { addShadowHere.outerHTML = addShadowHere.outerHTML; update(); }); update(); function update() { // This just displays the current parent of the addShadowHere element document.getElementById("parent-value").value = "" + ( addShadowHere.parentNode && addShadowHere.parentNode.cloneNode(false).outerHTML ); } 
 <div id="add-shadow-here">Text Hidden By Shadow DOM</div> addShadowHere.parentNode => <input readonly="" id="parent-value" /> 

Notice how the blue frame stops working after removing the ShadowDOM. This is because event listeners are no longer registered in the new element: event listeners remain registered in the old element, which is now removed from the DOM.

Thus, you must update any links to the element and reconnect any event listeners. Here is an example of how you can get a link to a new item.

 function removeShadowWithCaveat(elementWithShadow) { if (!elementWithShadow.parentNode) return elementWithShadow.cloneNode(true); var parent = elementWithShadow.parentNode; var prior = elementWithShadow.previousSibling; elementWithShadow.outerHTML = elementWithShadow.outerHTML; return prior.nextSibling || parent.firstChild; } 

If you need access to elements that are naturally hidden by the existing shadow root and which will become available after removing the shadow root, then there is an alternative method that will save these nodes perfectly.

 function removeShadowWithCaveat(elementWithShadow) { if (!elementWithShadow.parentNode) return elementWithShadow.cloneNode(true); var ref = elementWithShadow.cloneNode(true); while (elementWithShadow.lastChild) ref.appendChild( elementWithShadow.lastChild ); elementWithShadow.parentNode.replaceChild(elementWithShadow, elementWithShadow); return ref; } 

Working solution

 var createShadowProp = ( "createShadowRoot" in Element.prototype ? "createShadowRoot" : "webkitCreateShadowRoot" ); function removeChildren(elt) { console.log('removing children: %s', elt); while (elt.firstChild) { elt.removeChild(elt.firstChild); } } function removeShadowWithCaveat(elementWithShadow) { if (!elementWithShadow.parentNode) return elementWithShadow.cloneNode(true); var ref = elementWithShadow.cloneNode(true); while (elementWithShadow.lastChild) ref.appendChild( elementWithShadow.lastChild ); elementWithShadow.parentNode.replaceChild(elementWithShadow, elementWithShadow); return ref; } function showPlainOldDiv() { console.log('adding a plain old div'); var host = document.querySelector('#content'); removeChildren(host); // Remove the shadow host = removeShadowWithCaveat(host); var template = document.querySelector('#plainDiv'); host.appendChild(template.content.cloneNode(true)); } function showShadowTemplate() { console.log('adding shadowed template component'); var host = document.querySelector('#content'); removeChildren(host); // Remove the shadow host = removeShadowWithCaveat(host); var template = document.querySelector('#shadowedTemplateComponent'); var root = host.shadowRoot || host[createShadowProp]({ "open": true }); root.appendChild(template.content.cloneNode(true)); } 
 <div> <input type="button" value="show plain old div" onclick="showPlainOldDiv()"/> <input type="button" value="show shadowed template" onclick="showShadowTemplate()"/> <div id="content"></div> </div> <template id="shadowedTemplateComponent" style="display:none"> <style> div { background: lightgray; } #t { color: red; } </style> <div id="t">template</div> <script>console.log("Activated the shadowed template component.");</script> </template> <template id="plainDiv" style="display:none"> <div id="plaindiv">Plain old div</div> </template> 

Also, pay attention to the misuse of vendor prefixes (a problem that too many developers face). You are right that at the time this question was asked, there was only a prefix version of createShadowRoot (which was webkitCreateShadowRoot ). However, you should ALWAYS check if a non-fixed version of createShadowRoot is available if browsers standardize the API in the future (which is the case now). It might be nice for your code to work today, but it's great that your code works after a few years.

0
source

Source: https://habr.com/ru/post/958449/


All Articles