-
Notifications
You must be signed in to change notification settings - Fork 376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HTML Modules: JavaScript Scoping of ShadowDom Children #959
Comments
Correct me if I am wrong - The funny part to me is that this issue made me aware that this is actually not possible. As a regular developer, I expected the below code to work. <!DOCTYPE html>
<html lang="en">
<head>
<title>Hello World!</title>
<script src="/script.js" type="module"></script>
</head>
<body>
<template id="buttonTemplate">
<button onclick="this.clickHandler()">My button</button>
</template>
<my-button>Button</my-button>
</body>
</html> customElements.define('my-button',
class extends HTMLElement {
constructor() {
super();
let template = document.getElementById('buttonTemplate');
let templateContent = template.content;
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(templateContent.cloneNode(true));
}
clickHandler() {
console.log(true)
}
}
); I think this is a great idea. That should allow to write less code in the component's class (making them look cleaner) and result in expected (?) behavior. EDIT: okay I can see in the title now that it explicitly intends to refer to the HTML Modules. I still think that the code above should work. I think in this example |
From @Jamesernator in #645:
@Jamesernator made a good point about private members. However, I still feel that this may still be viable for public members. |
From @clshortfuse in #645:
Quoting this in here for posterity's sake. |
@clshortfuse The only problem with this is that it still scopes the event to the element/node, and not the class itself. At that point, any reference to I also wanted to address one point that someone else brought up in a different conversation. The concern was that you would lose access to the The general problem right now is that there is no intuitive way to reference the class associated with the custom element inside the DOM of the custom element. The class can reference the DOM, but the DOM cannot reference the class instance. This makes libraries like React, Vue.js, and others far more attractive to developers, which becomes problematic when trying to make the argument for leveraging agnostic web components. In my personal opinion, for whatever its worth, this is probably one of the single biggest pieces that could be introduced to the web component standards to bolster the adoption of pure web components: Intuitive access to the class instance via |
@Swivelgames in your case you can just do My use case was With events Given any ShadowDOM element, you can get the ShadowRoot of the Web Component in question with If you want to access the Web Component's static fields, you access Still, I don't recommend cloning templates like that. I moved to setting up a list of events to be bound on |
@clshortfuse You're absolutely correct. Oops. Thanks for calling me out on that haha. So, really, to boil down the request, it would be to change the context ( |
This would actually simplify your previous example down to: <template id="myCustomElementTemplate">
<button onclick="this.handleClick.call(event.currentTarget, event)">Click me!</button>
</template> But yeah, I would agree that just using it as a prototype method would be more intuitive, like <template id="myCustomElementTemplate">
<button onclick="this.handleClick(event)">Click me!</button>
</template> |
I don't think any proposal here should be part of HTML modules, which don't really have a lot to do with custom elements other than being a container for templates and possibly declarative custom element declarations. Binding expressions in templates to a JS objects should be done at the template instantiation and declarative custom elements layers. |
Direct import of HTML templates as import styles from './Card.css' assert { type: 'css' };
+import template from './Card.html' assert { type: 'html' };
import Container from './Container.js';
export default Container
.extend()
.css(styles)
- .html`
- <slot id=primary-action name=primary-action></slot>
- <div id=outline></div>
- `
+ .html(template)
.autoRegister('mdw-card'); Perhaps extending <template id="myCustomElementTemplate">
- <button id=button><slot></slot></button> // Manually bind in constructor
- <button on-click="{handleClick}"><slot></slot></button> // Custom attribute requiring interpolation
- <button onclick="this.getRootNode().host.handleClick()"><slot></slot></button> // Unintuitive way to auto-bind
+ <button onclick="host.handleClick()"><slot></slot></button> // Clear
</template>
<script type="module">
/* ... */
class myCustomElement extends HTMLElement {
static {
customElements.define('x-button', this);
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "open" });
const template = document.getElementById("myCustomElementTemplate");
shadowRoot.appendChild(template.content.cloneNode(true));
- // Manual bind
- shadowRoot.getElementById('button').addEventListener('click', this.handleClick);
- // Interpolate
- for (const el of shadowRoot.querySelectorAll('[on-click]')) {
- el.addEventListener('click', this[
- /^{(.+)}$/.exec(el.getAttribute('on-click'))[1]
- ]);
- }
}
handleClick() {
window.alert('You clicked me!');
}
}
/* ... */
</script>
<x-button>Click me</x-button> This way the browser doesn't really have to parse anything or walk the tree. It just uses the standard Edit: Removing the event listener is simply done by removing the attribute, or setting |
+ <template id="hostScopeTemplate">
+ <div>
+ <script>var host = null</script>
+ <button hidden onclick="host = this.getRootNode().host" />
+ </div>
+ </template>
<template id="myCustomElementTemplate">
<button onclick="host.handleClick()"><slot></slot></button>
</template>
<script type="module">
+ let scopeTemplate;
+ function setHostInScope(shadowRoot) {
+ scopeTemplate = scopeTemplate ?? document.getElementById('hostScopeTemplate');
+ shadowRoot.appendChild(scopeTemplate.content.cloneNode(true));
+ shadowRoot.querySelector('button').click()
+ shadowRoot.querySelector('div').remove();
+ }
/* ... */
class myCustomElement extends HTMLElement {
static {
customElements.define('x-button', this);
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "open" });
+ setHostInScope(shadowRoot);
const template = document.getElementById("myCustomElementTemplate");
shadowRoot.appendChild(template.content.cloneNode(true));
}
handleClick() {
window.alert('You clicked me!');
}
}
/* ... */
</script>
<x-button>Click me</x-button> Hacky, but it works. It's probably cleaner to have The click workaround is because scripts can't know it's element/context. If it wasn't for that, we could eliminate the hidden click and it would be one liner that doesn't need to be removed. In practice, I can strip out of a bunch of JS code related to interpolation (no passive though). Edit: Turns out scripts aren't sandboxed in shadow DOM. Related: https://discourse.wicg.io/t/script-tags-scoped-to-shadow-root-script-scoped-src/4726/12 |
This may require its own champion and proposal/explainer, but I feel like it falls well enough in line that it might be worth mentioning in relation to HTTP Modules.
The idea is, essentially, a mechanism to allow children of a ShadowRoot to access the instance of the element intuitively. This may include waiting to evaluate JS references in attributes for children of a
template
tag until the ShadowDom is registered. Even if all we do is set the context (this
) to the currently relevant instance, or even provide agetter
on all children for retrieving the instance of the element. (i.e.,this.shadowRoot
,this.root
,this.context
,this.parent
, or something similar).This would alleviate what is, in my opinion, under-utilization of inline JavaScript as it is right now. Being able to use
on*=
attributes, and others, while referencing the included class would be incredibly useful.Again, this may very well need to be in its own proposal.
This could potentially pave the way for other mechanisms, including (one which is certainly out of the scope of this proposal): binding attribute values to instance properties, and so on.
Originally posted by @Swivelgames in #645 (comment)
The text was updated successfully, but these errors were encountered: