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 } getBZAttribute(attrName){ // Little helper for defaults return(this.getAttribute(attrName) || this.defaultAttrs[attrName] ) } } class BZselect extends Buildoz { #value constructor(){ super() this.value = null this.open = false this.value = null this.defaultAttrs = { label: 'Select...', } } connectedCallback() { super.connectedCallback() this.button = document.createElement('button') this.button.textContent = this.getBZAttribute('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, 'disabled']) // } // attributeChangedCallback(name, oldValue, newValue) { // super.attributeChangedCallback(name, oldValue, newValue) // } get value(){ return(this.#value) } set value(v) { this.#value = v if(this.options && this.options.length>0) { const opt = Array.from(this.options).find(opt => opt.value==v) if(v || (opt && opt.textContent)) this.button.textContent = opt.textContent else this.button.textContent = this.getBZAttribute('label') this.dispatchEvent(new Event('change', { bubbles: true, composed: false, cancelable: false })) } } 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){ if(this.getAttribute('disabled') !== null) return this.value = value if(!silent) this.toggle() } 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 = this.querySelectorAll('option') 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) class BZtoggler extends Buildoz { #value constructor(){ super() this.open = false this.defaultAttrs = { labelLeft:'', labelRight:'', trueValue: 'yes', falseValue: 'no', classOn:'', classOff:'', tabindex:0, disabled:false } } connectedCallback(){ super.connectedCallback() this.labelRight = document.createElement('div') this.labelRight.classList.add('toggle-label-right') this.labelRight.innerHTML = this.getBZAttribute('labelRight') this.labelLeft = document.createElement('div') this.labelLeft.classList.add('toggle-label-left') this.labelLeft.innerHTML = this.getBZAttribute('labelLeft') this.switch = document.createElement('div') this.switch.classList.add('toggle-switch') this.toggleBar = document.createElement('span') this.toggleBar.classList.add('toggle-bar') this.thumb = document.createElement('span') this.thumb.classList.add('toggle-thumb') this.toggleBar.append(this.thumb) this.switch.append(this.toggleBar) this.appendChild(this.labelLeft) this.appendChild(this.switch) this.appendChild(this.labelRight) this.setAttribute('tabindex', this.getBZAttribute('tabindex')) this.switch.addEventListener('click', this.toggle.bind(this)) } turnOn() { if(this.getBZAttribute('classOff')) this.switch.classList.remove(this.getBZAttribute('classOff')) if(this.getBZAttribute('classOn')) this.switch.classList.add(this.getBZAttribute('classOn')) this.thumb.classList.add('turned-on') this.#value = this.getBZAttribute('trueValue') this.focus() } turnOff() { if(this.getBZAttribute('classOn')) this.switch.classList.remove(this.getBZAttribute('classOn')) if(this.getBZAttribute('classOff'))this.switch.classList.add(this.getBZAttribute('classOff')) this.thumb.classList.remove('turned-on') this.#value = this.getBZAttribute('falseValue') this.focus() } toggle(event) { if(event) { event.preventDefault(); event.stopPropagation(); } if(this.getBZAttribute('disabled')) return if(this.#value == this.getBZAttribute('trueValue')) this.turnOff() else this.turnOn() this.dispatchEvent(new Event('change', { bubbles: true, composed: false, cancelable: false })) } get value(){ return(this.#value) } set value(v) { this.#value = v if(this.defaultAttrs){ if(this.#value == this.getBZAttribute('trueValue')) this.turnOn() else this.turnOff() this.dispatchEvent(new Event('change', { bubbles: true, composed: false, cancelable: false })) } } } Buildoz.define('toggler', BZtoggler)