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:
- Pseudo-elements
- 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 assignthis.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 idealthis.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 Componentdocument
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 elementtemplFunc()
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
Html5 Drag and Drop Anywhere on the Screen
Enter Key Press Behaves Like a Tab in JavaScript
Formdata.Append("Key", "Value") Is Not Working
How to Use JSON File in HTML Code
HTML Anchor Link - Href and Onclick Both
Access Event to Call Preventdefault from Custom Function Originating from Onclick Attribute of Tag
Reactjs: "Uncaught Syntaxerror: Unexpected Token <"
How to Determine Which HTML Page Element Has Focus
How to Draw a Line Between Two Divs
How to Create Checkbox Inside Dropdown
JavaScript Localstorage Object Broken in Ie11 on Windows 7
How to Change a <Select> Value from JavaScript
Html5 Audio Tag on Safari Has a Delay
Document.Getelementsbyclassname().Innerhtml Always Returns "Undefined"