How to Access Shadow Dom Elements Through the Parent Document

Is it possible to access Shadow DOM elements through the parent document?

@int32_t is right in that Shadow DOM, by definition, is a way to fill a node with DOM that you want to hide from external sources (Encapsulation). The point is that you as the component author get to choose exactly which parts will be exposed to outside CSS or JavaScript and which will not.

Unfortunately, you cannot create a public JavaScript interface to your Shadow DOM without using another bleeding-edge spec called Custom Elements. If you choose to do that, it's as simple as adding custom public methods to your element's prototype. From these you can access the internals of your Shadow DOM (see the third example here).

You can, however, expose hooks for CSS to access the internals of your Shadow DOM without using Custom Elements. There are two ways to do that:

  1. Pseudo-elements
  2. CSS Variables

Pseudo-elements

Chrome and Firefox expose certain parts of their Shadow DOM to CSS through special pseudo-elements. Here's your example of the date input with the addition of a CSS rule that only applies to the numerical part of the date field through use of the Chrome-provided -webkit-datetime-edit pseudo-element.

Here's a partial list of the available WebKit pseudo-elements. You can also just enable the Show Shadow DOM option in DevTools and look for attributes named pseudo.

Component authors can also create their own pseudo-elements to expose parts of their Shadow DOM (see the 2nd example here).

CSS Variables

An even better way is to use CSS Variables, which you can enable with Enable experimental WebKit features in about:flags in Chrome. Then check out this fiddle which uses CSS Variables to communicate to the Shadow DOM what color it should use for its "theme."

Web Component: How to access Shadow DOM through script in parent tag?

Shadow root is accessible via #querySelector(sel).shadowRoot. The example below shows the difference between acessing native divs and those in ShadowDOM.

  // native divs
var divs = Array.prototype.slice.call(
document.getElementsByTagName('DIV')
);

divs.forEach(function(e) {
e.innerHTML = 'CHANGED';
});

// access shadowed divs
var shadowDivs = Array.prototype.slice.call(
document.querySelector('x-component').shadowRoot.children
); // or .querySelector('div')

shadowDivs.forEach(function(e) {
if(e.constructor === HTMLDivElement) { // divs only
e.innerHTML = 'CHANGED IN SHADOW';
}
});

Live preview: http://plnkr.co/edit/lNeTWF28jHP01ORCcAgU?p=preview

Access Element from within Shadow DOM

Looks like you have errors in both Element references and this Scope (in <script>)

const shadowRoot = this.attachShadow({mode: 'closed'}).appendChild(clone);

appendChild is your Nemesis here.

It returns the inserted element... not a shadow-root but a #document-fragment (cloned template)

fixed with:

 const shadowRoot = this.attachShadow({mode: 'closed'});
shadowRoot.appendChild(clone);

Then:

  • mode:closed will not assign this.shadowRoot ..

    which you can not re-use because it still is a read-only property

fixed with:

 this.Root = this.attachShadow({mode: 'closed'});
this.Root.appendChild(clone);

You can now do:

  connectedCallback() {
this.Root.querySelector('#outside').innerHTML = 'Changed';
}

I do not understand why you consider this not ideal
this.Root is accessible from all/any methods inside the Component

A good resource for all DOM methods is: https://javascript.info/modifying-document


<script> inside a <template> (this scope)

<template id='hello-world-template'>
<span id='inside'>Unchanged</span>
<script>
document.querySelector('#inside').innerHTML = 'Changed';
// Ideal, but does not work - no such element
</script>
</template>

You injected the template into a Component

document can not access elements inside any Component shadowDOM

doesn't matter if the shadowDOM is mode:closed or mode:open

The <script> scope will be window (since it wasn't assigned a scope)

(I do not think you can set the this scope for a SCRIPT)

To get the Component scope inside that <script> you have to be creative ..

and use your img onload= 'hack'

With the onload on the <style> element makes it a bit less of a hack (IMHO)

<template id='hello-world-template'>
<span id='inside'>Inside Unchanged,</span>
<script>
function templFunc() {
// this scope is the shadowRoot, not the component!
this.querySelector('#inside').innerHTML = 'Changed';
}
</script>
<style onload="templFunc.apply(this.getRootNode())">
#inside{
color:green;
}
</style>
</template>

one major issue: will only execute for the first used <hello-world> element!!

I didn't write this of the top of my head,

(Work in progess) JSFiddle playground (also showing referencing Component methods) at:

https://jsfiddle.net/CustomElementsExamples/zpamx728/

Update #1

Chromium (Edge/Chrome) and Opera are fine, FireFox v72.0.2 misbehaves:

  • the onload on a <STYLE> element will only fire for the first element
    I changed the JSFiddle to use your first hack using an <img>, and it fires for every element

  • templFunc() is loaded/scoped in shadowDOM, so is callable from component methods (see JSFiddle)

    but.. only in FireFox is NOT defined/available for the first element
    For now I consider this a FireFox bug... will investigate further... (to boldly go where...)

!!! Update #2 !!!

OOPS! Played some more with it.

Turns out all variables and functions from a cloned and imported SCRIPT

are hoisted to the global window scope

So above code works, but has a major side-effect...

That is also why FireFox complains about re- declaring let variables

how do I traverse elements within a shadow DOM

You could use the absolute path: use shadowRoot to get the Shadow DOM content.

document.querySelector( 'div#outer' ).shadowRoot.querySelector( 'div#inner' )

Or the relative path: use getRootNode() to get the Shadow DOM root

event.target.getRootNode().querySelector( 'div#inner' )

Example:

outer.attachShadow( { mode: 'open' } )    .innerHTML = `        <div id=inner></div>        <button>clicked</button>    `    outer.shadowRoot.querySelector( 'button' ).onclick = ev =>  ev.target.getRootNode().querySelector( 'div#inner' ).innerHTML = 'clicked'
<div id=outer></div>

Is it possible for a parent document to read the content of Shadow DOM elements?

What you want is to capture the composed/rendered dom tree. Here are a few examples of doing this that may be useful:

  • Polymer's inspector (type sinspect() in the console on a page using polymer) - code
  • Shadow DOM visualizer - code

The latter shows the <content>, which is probably not what you want. But you could easily modify it to only contain the rendered nodes.

Get html elements inside shadow root using javascript

You have to navigate to shadow-root first then you can get it:

const searchModule = document.querySelector('.search-module');
const searchModuleRoot = searchModule && searchModule.shadowRoot;

const title = searchModuleRoot.querySelector('.title');


Related Topics



Leave a reply



Submit