我正在尝试使用灯光元素设计web组件,我需要一个关于事件的帮助。正如我们从附加的代码片段中看到的,我们可以在html模板中使用@change="${this.handleEvent}"
,并在lit Element组件中处理函数handleEvent(e){}
。通过这种方式,事件被限制和控制仅在lit web组件中。
然而,当其他人使用我们的web组件时,他们无法控制或访问定义组件中这些事件的值。例如,如果我们有一个index.html文件,我使用它像<my-element onchange="handleEvent(e)"></my-element>
,我应该有访问onchange事件和调用索引文件内的函数。
那么,有没有一种方法来实现这一类似的行为常规html事件,而不是写事件,这是有限的在lit元素web组件
<script src="https://unpkg.com/@webcomponents/webcomponentsjs@latest/webcomponents-loader.js"></script>
<script type="module">
import { LitElement, html, css } from 'https://unpkg.com/lit-element/lit-element.js?module';
class MyElement extends LitElement {
static get properties() {
return {
checked: { type: Boolean, attribute: true }
};
}
static get styles() {
return [
css`
div {
padding: 10px;
width: 90px;
border: 2px solid orange;
}
`
];
}
render() {
return html`
<div>
<input
@change="${this.handleEvent}"
?checked="${this.checked}"
type="checkbox" /> Checkbox
</div>
`;
}
handleEvent(e) {
console.log(`Checkbox marked as: ${e.target.checked}`);
}
}
customElements.define('my-element', MyElement);
</script>
// Index.html
<my-element></my-element>
// I am expecting to pass an event and handle it in the importing
component.
// Something like: **<my-element onchange="handleEvent(e)"}></my-
element>**
如果您想要监听元素外部的事件,您应该像这样分派一个事件:
const event = new Event('my-event', {bubbles: true, composed: true});
myElement.dispatchEvent(event);
关于事件的Lit文档很好地概述了如何以及何时分派事件https://lit.dev/docs/components/events/#dispatching-events
通常对于您自己的自定义元素,您可能还需要定义您自己的API来提供给组件的消费者。这通常还需要定义你自己的组件发出的事件。
请看这个简单的(没有文字,但你明白了)例子:
customElements.define('foo-bar', class extends HTMLElement {
input = document.createElement('input');
constructor() {
super();
this.input.type = 'checkbox';
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(this.input);
this.input.addEventListener('change', this.handleChange.bind(this));
}
// replaces the internal change event with an event you publish to the outside world
// this event is part of your defined API.
handleChange(e) {
e.stopPropagation();
const stateChange = new CustomEvent('state-change', {
bubbles: true,
composed: true,
detail: { checked: this.input.checked }
});
this.dispatchEvent(stateChange);
}
});
document.addEventListener('state-change', (e) => { console.log(e.detail); })
<foo-bar></foo-bar>
如果您还希望像标准HTML元素那样支持声明性事件绑定(并且使用它被广泛认为是不好的做法),您可以使用观察到的属性来实现:
customElements.define('foo-bar', class extends HTMLElement {
input = document.createElement('input');
constructor() {
super();
this.input.type = 'checkbox';
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(this.input);
this.input.addEventListener('change', this.handleChange.bind(this));
this.declarativeValueChangeListener = this.declarativeValueChangeListener.bind(this);
}
// replaces the internal change event with an event you publish to the outside world
// this event is part of your defined API.
handleChange(e) {
e.stopPropagation();
const stateChange = new CustomEvent('state-change', {
bubbles: true,
composed: true,
detail: { value: this.input.value }
});
this.dispatchEvent(stateChange);
}
static get observedAttributes() {
return [ 'onstatechange' ];
}
attributeChangedCallback(attr, oldVal, newVal) {
if (oldVal === newVal) return; // nothing changed, nothing to do here
if (newVal === null) { // attribute was removed
this.removeEventListener('state-change', this.declarativeValueChangeListener)
} else { // attribute was added
this.addEventListener('state-change', this.declarativeValueChangeListener)
}
}
declarativeValueChangeListener() {
const functionStr = this.getAttribute(this.constructor.observedAttributes[0]);
eval(functionStr);
}
});
function baz() { console.log('baz executed through declarative binding of an outside handler!'); }
<foo-bar onstatechange="baz()"></foo-bar>
我不明白为什么你需要事件,
当单击在input
上可以执行全局函数或局部方法。
不需要老旧的bind
繁文缛节,不需要observedAttributes
可以使用默认的onchange
,因为所有事件都存在于HTMLElement
上
只有输入比如textarea
实际上fireChange Event
在任何其他HTMLElement上使用都不会发生任何事情,您必须自己调用this.onchange()
或document.querySelector("foo-bar").onchange()
。
可以不使用您自己的属性名,如值将始终是一个字符串,而不会被浏览器引擎解析为JS代码。
你需要eval(code)
来执行组件作用域中的代码,并使onchange="this.baz()"
工作。
customElements.define('foo-bar', class extends HTMLElement {
constructor() {
let input = document.createElement("input");
input.type = "checkbox";
super()
.attachShadow({mode: 'open'})
.append(input,"click me to execute ", this.getAttribute("onchange"));
this.onclick = (evt) => { // maybe you only want input.onclick
//evt.stopPropagation();
let code = this.getAttribute("onchange");
try {
eval(code); // this.onchange() will only execute global functions
} catch (e) {
console.error(e,code);
}
}
}
baz() {
console.log("Executed baz Method");
}
});
function baz() {
console.log("Executed baz Function");
}
<foo-bar onchange="baz()"></foo-bar>
<foo-bar onchange="this.baz()"></foo-bar>
<style>
foo-bar { display:block; zoom:2 }
</style>
重要提示
shadowDOM是你的救命稻草。
onchange
事件frominput
不转义shadowDOM(像
composed:true
的自定义事件)
如果没有shadowDOM,父元素上的所有onchange
声明都将触发,因为Event会显示:
<div onchange="console.log(666)">
<input onchange="console.log(this)" type="checkbox">
</div>
<style>
input { zoom:3 }
</style>
这是一个很好的例子,一个shadowRoot在一个常规的HTMLElement上可以有value,而不需要声明自定义元素