我想建立一个HTML自定义元素,它的行为应该和本地的元素几乎一样。<select>
元素,但除此之外,每次属性或子节点发生变化时,它还必须调用一定的更新函数。(后面的故事。这是在使用 靴带选择 元素,在elm框架内。另见 最后一个问题.)
使用LitElement框架,我能够建立一个有效的自定义元素(称为 <lit-select>
)类似于上面的描述。但遗憾的是,我没能让它接受 html <option>
或 <optgroup>
元素作为子元素。取而代之的是,用户必须将选项列表作为json编码的字符串传递给某个属性。
也就是说,不需要调用
<lit-select>
<option>foo</option>
<option>bar</option>
</lit-select>
用户必须调用
<lit-select items='["foo", "bar"]'></lit-select>
这样一来,我就得改变我的定义了。<lit-select>
以使第一次通话成为可能?我是知道的。<slot>
元素,但不幸的是,这个元素不允许在 <select>
所以浏览器就直接删除了。
先谢谢你
更新1
其实有一些限制使得这个问题比我一开始想象的更有挑战性。
slot
元素,因为这些都是影子DOM特定的。附录
我的定义是 <lit-select>
:
import { LitElement, html, customElement, property } from 'lit-element';
import * as $ from 'jquery';
import 'bootstrap';
import 'bootstrap-select';
@customElement('lit-select')
export class LitSelect extends LitElement {
@property({ type : Array }) items = []
updated() {
$(this).find(".selectpicker").selectpicker('refresh');
}
createRenderRoot() {
return this;
}
private renderItem(item: string) {
return html`
<option>
${item}
</option>
`;
}
render() {
return html`
<select class="selectpicker" data-live-search = "true">
${this.items.map(item => this.renderItem(item))}
</select>
`;
}
}
为什么不从一个 原生的W3C标准自定义元素
你要做的就是 移动 一些 <option>
元素。
<template id=MY-SELECT>
<h3>My Never Todo List</h3>
<select multiple></select>
</template>
<my-select name="NTD">
<option>Grow up</option>
<option>Learn React</option>
<option>Use Lit</option>
<option>Forget W3C Custom Elements API</option>
</my-select>
<script>
customElements.define("my-select", class extends HTMLElement {
static get observedAttributes() {
return ["name"]; //use one to trigger attributeChangedCallback
}
connectedCallback() {
console.log('connected');
//clone template
this.append(document.getElementById(this.nodeName).content.cloneNode(true));
//MOVE options inside SELECT:
this.querySelector('select').append(...this.querySelectorAll('option'));
}
attributeChangedCallback() {
setTimeout(() => this.updated(...arguments))// execute after Event Loop is done
}
updated(name,oldValue,newValue){
console.log('updated',name,oldValue,newValue);
}
})
</script>
这个解决方案如何?
import {LitElement, html } from 'lit-element';
export class GraniteSelect extends LitElement {
static get properties() {
return {
options: {
type: Object,
}
}
}
constructor() {
super();
this.options = [];
}
connectedCallback() {
super.connectedCallback();
this.observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.removedNodes.forEach((node) => {
if (node.nodeName === 'OPTION' ) {
this.options = [ ...this.options, node ];
console.log(`options - ${this.options}`);
console.dir(this.options);
}
});
});
});
this.observer.observe(this, {
childList: true,
});
}
firstUpdate() {
this.observer.disconnect();
}
createRenderRoot() {
/**
* Render template without shadow DOM. Note that shadow DOM features like
* encapsulated CSS and slots are unavailable.
*/
return this;
}
render() {
console.log('Rendering', this.options);
return html`
<select class="selectpicker" data-live-search = "true">
${this.options}
</select>
`;
}
}
window.customElements.define('granite-select', GraniteSelect);
它使用了light-dom(即 createShadowRoot
部分),并且它将你的元素的所有选项子代放在 select
. 非选项的子代会被忽略,但你可以对它们做任何事情。
你可以在 https:/stackblitz.comeditjs-14dcae。
你对这种做法怎么看?