/** * _ ___ Another * / |/ (_)______ __ _____ * / / / __(_- { const form = dlg.querySelector('form') if (!form) return {} const fd = new FormData(form) const out = {} for (const [key, value] of fd.entries()) { if (Object.prototype.hasOwnProperty.call(out, key)) { out[key] = Array.isArray(out[key]) ? [...out[key], value] : [out[key], value] } else { out[key] = value } } return out } return new Promise(resolve => { const dlg = document.createElement('dialog') dlg.classList.add('bz-modal-dialog') dlg.innerHTML = `
${title}
${message}
` dlg.addEventListener('close', () => { const ok = dlg.returnValue.toLowerCase() === 'ok' if(ok) { resolve(getFields(dlg)) } else { resolve(ok) } dlg.remove() }) document.body.appendChild(dlg) dlg.showModal() }) } class Buildoz extends HTMLElement { // static is evaluated when the class is defined, therefore while buildoz.js is executing. // therefore document.currentScript refers to buildoz.js (but not later!!) static _buildozUrl = document.currentScript?.src ?? '' constructor(){ super() // always call super() first! this.attrs = {} // Usefull for relative dependencies, to keep lib fully portable this.buildozUrl = Buildoz._buildozUrl // was defined in the past } 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(['name']) } 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 if(name=='name') this.name = newValue } getBZAttribute(attrName){ // Little helper for defaults return(this.getAttribute(attrName) || this.defaultAttrs[attrName] ) } fireEvent(eventName, detail){ let myname = this.tagName.toLocaleLowerCase() myname = myname.substring(myname.indexOf('-')+1) const eventFullName = `bz:${myname}:${eventName}` this.dispatchEvent(new CustomEvent(eventFullName, { detail, bubbles: true, composed: true, })) } } class BZselect extends Buildoz { #value #fillFromMarkup = true constructor(){ super() this.value = null this.open = false this.value = null this.generalClickEvent = 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)) if(!this.optionscontainer) this.optionscontainer = document.createElement('div') this.optionscontainer.classList.add('options-container') this.append(this.optionscontainer) this.options = this.querySelectorAll('option') if(this.#fillFromMarkup){ //can only do it once and only if fillOptions was not already called !! for(const opt of this.options){ this.optionscontainer.append(opt) // Will move is to the right parent opt.addEventListener('click', this.onClick.bind(this)) if(opt.getAttribute('selected') !== null) this.onOption(opt.value, true) } this.#fillFromMarkup = false } } // 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') this.optionscontainer.classList.remove('open') } else { document.querySelectorAll('bz-select').forEach((sel) => { if((sel!==this) && sel.open) sel.toggle() }) opt.classList.add('open') this.optionscontainer.classList.add('open') } } this.open = !this.open } onClick(evt){ evt.stopPropagation() 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){ // Caution: you cannot count on connectedCallback to have run already, because one might fill before adding to the DOM const opt = document.createElement('option') opt.setAttribute('value', value) opt.innerHTML = markup opt.addEventListener('click',this.onClick.bind(this)) if(!this.optionscontainer) this.optionscontainer = document.createElement('div') this.optionscontainer.append(opt) this.options = this.querySelectorAll('option') this.#fillFromMarkup = false } fillOptions(opts, erase = true){ // Caution: you cannot count on connectedCallback to have run already, because one might fill before adding to the DOM 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) class BZslidePane extends Buildoz { constructor(){ super() this.open = false this.defaultAttrs = { side: 'bottom' } this.dragMove = this.dragMove.bind(this) this.dragEnd = this.dragEnd.bind(this) this.lastClientX = 0 this.lastClientY = 0 // Fill with innerHTML or other DOM manip should not allow coating to be removed this._observer = new MutationObserver(muts => { this.coat() }) } connectedCallback(){ super.connectedCallback() this.coat() this._observer.observe(this, { childList: true }) // Do this last } disconnectedCallback() { super.disconnectedCallback() this._observer.disconnect() } coat(){ if(this.handle && this.querySelector(this.dispatchEvent.handle)) return this._observer.disconnect() if(this.querySelector(this.dispatchEvent.handle)) this.querySelector(this.dispatchEvent.handle).remove() this.handle = document.createElement('div') this.handle.classList.add('handle') this.prepend(this.handle) this.handle.addEventListener('pointerdown', this.dragStart.bind(this)) this._observer.observe(this, { childList: true }) } dragStart(evt){ evt.target.setPointerCapture(evt.pointerId) this.dragStartX = evt.clientX this.dragStartY = evt.clientY this.lastClientX = evt.clientX this.lastClientY = evt.clientY this.handle.addEventListener('pointermove', this.dragMove) this.handle.addEventListener('pointerup', this.dragEnd) } dragMove(evt){ const box = this.getBoundingClientRect() const boundaryEl = this.offsetParent || this.parentElement const parentBox = boundaryEl.getBoundingClientRect() let width, height, min, max switch(this.getAttribute('side')){ case 'top': min = parseInt(this.getBZAttribute('minheight')) || 0 if(evt.clientY > (box.top + min)) height = (evt.clientY - box.top) else if(evt.clientY < this.lastClientY) height = min else if(evt.clientY > this.lastClientY) height = 0 else break max = parseInt(this.getBZAttribute('maxheight')) || Math.floor(parentBox.height/2) height = Math.min(height, parentBox.height, max) this.style.height = height+'px' break case 'bottom': min = parseInt(this.getBZAttribute('minheight')) || 0 if(evt.clientY < (box.bottom - min)) height = (box.bottom - evt.clientY) else if(evt.clientY > this.lastClientY) height = min else if(evt.clientY < this.lastClientY) height = 0 else break max = parseInt(this.getBZAttribute('maxheight')) || Math.floor(parentBox.height/2) height = Math.min(height, parentBox.height, max) this.style.height = height+'px' break case 'left': min = parseInt(this.getBZAttribute('minwidth')) || 0 if(evt.clientX < (box.left + min)) width = (evt.clientX - box.left) else if(evt.clientX > this.lastClientX) width = min else if(evt.clientX < this.lastClientX) width = 0 else break max = parseInt(this.getBZAttribute('maxwidth')) || Math.floor(parentBox.width/2) width = Math.min(width, parentBox.width, max) this.style.width = width+'px' break case 'right': min = parseInt(this.getBZAttribute('minwidth')) || 0 if(evt.clientX < (box.right - min)) width = (box.right - evt.clientX) else if(evt.clientX < this.lastClientX) width = min else if(evt.clientX > this.lastClientX) width = 0 else break max = parseInt(this.getBZAttribute('maxwidth')) || Math.floor(parentBox.width/2) width = Math.min(width, parentBox.width, max) this.style.width = width+'px' break } this.lastClientX = evt.clientX this.lastClientY = evt.clientY } dragEnd(evt){ evt.target.releasePointerCapture(evt.pointerId) this.handle.removeEventListener('pointermove', this.dragMove) this.handle.removeEventListener('pointerup', this.dragEnd) } } Buildoz.define('slidepane', BZslidePane)