class Buildoz extends HTMLElement { constructor(){ super() // always call super() first! this.attrs = {} } static get observedAttributes(){ //observable attributes triggering attributeChangedCallback // anything added here will be observed for all buildoz tags // in your child, add you local 'color' observable attr with : // return([...super.observedAttributes, 'color']) return([]) } static define(name, cls){ const tag = `bz-${name}` if(!customElements.get(tag)) { // no wild redefinition customElements.define(tag, cls) } return cls } connectedCallback(){ // added to the DOM this.classList.add('buildoz') } disconnectedCallback(){ // removed from the DOM } attributeChangedCallback(name, oldValue, newValue) { this.attrs[name] = newValue } } class BZselect extends Buildoz { constructor(){ super() this.value = null this.open = false this.defaultAttrs = { label: 'Select...', } } connectedCallback() { super.connectedCallback() this.button = document.createElement('button') this.button.textContent = this.getAttribute('label') || this.defaultAttrs.label this.prepend(this.button) this.button.addEventListener('click', this.toggle.bind(this)) this.options = this.querySelectorAll('option') for(const opt of this.options){ opt.addEventListener('click', this.onClick.bind(this)) if(opt.getAttribute('selected') !== null) this.onOption(opt.value, true) } } // static get observedAttributes(){ // Only if you want actions on attr change // return([...super.observedAttributes, 'myattr']) // } // // attributeChangedCallback(name, oldValue, newValue) { // super.attributeChangedCallback(name, oldValue, newValue) // } toggle(){ for(const opt of this.options){ if(this.open) opt.classList.remove('open') else opt.classList.add('open') } this.open = !this.open } onClick(evt){ const opt = evt.target.closest('option') if(opt && opt.value) this.onOption(opt.value) } onOption(value, silent=false){ this.value = value if(!silent) this.toggle() const opt = Array.from(this.options).find(opt => opt.value==value) if(value || (opt && opt.textContent)) this.button.textContent = opt.textContent else this.button.textContent = this.getAttribute('label') || this.defaultAttrs.label this.dispatchEvent(new Event('change', { bubbles: true, composed: false, cancelable: false })) } addOption(value, markup){ const opt = document.createElement('option') opt.setAttribute(value, value) opt.innerHTML = markup opt.addEventListener('click',this.onClick.bind(this)) this.append(opt) this.options = this.querySelectorAll('option') } fillOptions(opts, erase = true){ if(erase){ this.options.forEach(node => { node.remove() }) this.options = this.querySelectorAll('option') this.onOption('', true) // unselect last } for(const opt of opts) this.addOption(opt.value, opt.markup) } } Buildoz.define('select', BZselect)