I'm currently testing web components with Vue3 and wondering how this Shadow DOM really works. Some third party library is accessing elements with getElementById()
and throwing an error because the element is null
.
Apparently that's because there's no access from the web component to the actual DOM. So meaning the functions can't even find the HTML elements used in the components itself. Can anyone explain why that is exactly? And how would I access the elements then? Maybe with shadowRoot
?
Test.vue
:
<template>
<div id="test">Hello World!</div>
</template>
<script lang="js">
import {
ref,
onMounted
} from "vue";
export default {
setup(props) {
onMounted(() => {
// NULL
console.log(document.getElementById("test"));
});
}
}
</script>
main.js
:
import { defineCustomElement } from 'vue'
import Test from './Test.vue'
const ExampleElement = defineCustomElement(Test)
// register
window.customElements.define('test-component', ExampleElement)
Yes, shadowDOM is meant to encapsulate content.
If you do not want that behaviour, then do not use shadowDOM
But if you are using a tool, it might enforce shadowDOM on you,
In that case, ditch the tool and create a Component with Vanilla JavaScript, it ain't rocket science.
If you are learning Web Components it is best to learn the Technology first, and not a Tool... because a Fool with a Tool, is still a Fool.
If the Custom Element (with shadowDOM) exists in the DOM, and it is registered with mode:"open"
, you can query its contents with:
document.querySelector("test-component").shadowRoot.querySelector("#test")
If you want to find all Web Components in the page, you can do something like this:
// findElements takes a function definition, the output must be Truthy or Falsy
function findElements( accept = x => customElements.get(x.localName) || 0) {
function log() {
console.log(`%c findElements `, `background:purple;color:yellow`, ...arguments);
}
let node, elements = [], shadowRootCount = 0;
function diveNode( diveRoot ) {
// IE9 was last to implement the TreeWalker/NodeIterator API ... in 2011
let iterator = document.createNodeIterator(
diveRoot,
NodeFilter.SHOW_ELEMENT,
node => accept(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
);
while ( node = iterator.nextNode() ) {
if (node.shadowRoot) {
log(`dive into shadowRoot #${++shadowRootCount} at`, node.outerHTML);
[...node.shadowRoot.children].forEach( diveNode );
}
elements.push(node);
}
}
diveNode( document.body ); // initial dive location
log(elements.length, `elements found`,[elements]);
//return elements;
}
findElements((x) => true); // find all DOM elements
findElements(); // find all Custom Elements