I have created a web ponent, and would like to access the elements from within the ponent.
Am using .attachMode({mode:'closed'}), so the parent has no access.
<template id='hello-world-template'>
<span id='inside'>Unchanged,</span> <span id='outside'>Unchanged</span>
<script>
document.querySelector('#inside').innerHTML = 'Changed';
// Ideal, but does not work - no such element
</script>
</template>
<hello-world></hello-world>
<script>
customElements.define('hello-world',
class extends HTMLElement {
constructor() {
super();
var template = document.getElementById('hello-world-template')
var clone = template.content.cloneNode(true)
const shadowRoot = this.attachShadow({mode: 'closed'}).appendChild(clone);
}
connectedCallback() {
this.shadowRoot.querySelector('#outside').innerHTML = 'Changed';
// Not ideal, and also does not work - this.shadowRoot has no querySelector
}
});
</script>
Some attempts:
- Within the document fragment - this, self, window, and document all refer to the parent window. And none can access the shadow root.
- Tried to store the shadowroot in a global variable and access it from inside the fragment or connectedCallback.
Even if that worked, it would defeat the point of using {mode:'closed'}, but anyways it did not work.
I have a hack that works, but cannot imagine that I have to use it.
The whole point of encapsulation is that things can be self contained, but what good does that do us if the JS cannot act on the other items in its container?
If this is the solution, would love a tip to explain the logic of the way ponents where implemented.
Here's the hack, though: include an image that runs the JS onload.
<template id='hello-world-template'>
<span id='inside'>Unchanged,</span> <span id='outside'>Unchanged</span>
<script>
function runner(img){
let doc = img.parentNode;
doc.querySelector('#inside').innerHTML = 'Changed';
}
</script>
<img src='data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' onload="runner(this)">
</template>
<hello-world></hello-world>
Note regarding similar questions (25048359, 16633057, 55101967, etc.) - those answers will not work when mode is closed, which I need.
I have created a web ponent, and would like to access the elements from within the ponent.
Am using .attachMode({mode:'closed'}), so the parent has no access.
<template id='hello-world-template'>
<span id='inside'>Unchanged,</span> <span id='outside'>Unchanged</span>
<script>
document.querySelector('#inside').innerHTML = 'Changed';
// Ideal, but does not work - no such element
</script>
</template>
<hello-world></hello-world>
<script>
customElements.define('hello-world',
class extends HTMLElement {
constructor() {
super();
var template = document.getElementById('hello-world-template')
var clone = template.content.cloneNode(true)
const shadowRoot = this.attachShadow({mode: 'closed'}).appendChild(clone);
}
connectedCallback() {
this.shadowRoot.querySelector('#outside').innerHTML = 'Changed';
// Not ideal, and also does not work - this.shadowRoot has no querySelector
}
});
</script>
Some attempts:
- Within the document fragment - this, self, window, and document all refer to the parent window. And none can access the shadow root.
- Tried to store the shadowroot in a global variable and access it from inside the fragment or connectedCallback.
Even if that worked, it would defeat the point of using {mode:'closed'}, but anyways it did not work.
I have a hack that works, but cannot imagine that I have to use it.
The whole point of encapsulation is that things can be self contained, but what good does that do us if the JS cannot act on the other items in its container?
If this is the solution, would love a tip to explain the logic of the way ponents where implemented.
Here's the hack, though: include an image that runs the JS onload.
<template id='hello-world-template'>
<span id='inside'>Unchanged,</span> <span id='outside'>Unchanged</span>
<script>
function runner(img){
let doc = img.parentNode;
doc.querySelector('#inside').innerHTML = 'Changed';
}
</script>
<img src='data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' onload="runner(this)">
</template>
<hello-world></hello-world>
Note regarding similar questions (25048359, 16633057, 55101967, etc.) - those answers will not work when mode is closed, which I need.
Share Improve this question asked Feb 6, 2020 at 14:53 SamGoodySamGoody 14.5k10 gold badges84 silver badges93 bronze badges 2-
1
you can shorten your hack with:
<img src='' onerror="runner(this)"/>
– Danny '365CSI' Engelman Commented Feb 6, 2020 at 16:02 -
1
Cute! Or
<img src='//:0' onload='runner(this)'>
. – SamGoody Commented Feb 6, 2020 at 22:33
1 Answer
Reset to default 4Looks 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 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 ponent!
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/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 ponent 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 plains about re- declaring let
variables