class VUtils { static makePublic() { if (VUtils.isUsed) { return; } this.initHandlers(); VUtils.isUsed = true; console.log("[VUtils] is now available in the Global Space! no VUtils. anymore needed"); } static initHandlers() { window.$ = this.$; window.$$ = this.$$; window.tryCatch = this.tryCatch; VUtils.nodePrototypes(); } static $(selector, from) { from = from || document; return from.querySelector(selector); } static $$(selector, from) { from = from || document; return from.querySelectorAll(selector); } static tryCatch(data, callback, error) { data = VUtils.wrap(data, []); error = error || console.error; callback = callback || console.log; try { callback(...data); } catch (e) { error(e); } } static forEach(items, cb, error) { for (let i = 0; i < items.length; i++) { VUtils.tryCatch([items[i], i], cb, error); } } static get(valueOne, value) { return this.wrap(valueOne, value); } static mergeKeys(root, target) { root = root || {}; let keys = Object.keys(root); for (let key of keys) { target[key] = root[key]; } return target; } static mergeOptions(target, root) { root = root || {}; let keys = Object.keys(root); for (let key of keys) { target[key] = VUtils.get(root[key], target[key]); } return target; } static wrap(valueOne, valueTwo) { let x = typeof valueTwo; if (!(valueOne instanceof Array) && valueTwo instanceof Array) { return [valueOne]; } if (x === 'string' && valueOne instanceof Array) { return valueOne.join("."); } return valueOne === undefined ? valueTwo : valueOne; } static nodePrototypes() { Node.prototype.find = function (selector) { return this.closest(selector); }; Node.prototype.createNew = function (tag, options) { let el = document.createElement(tag); el.classList.add(...VUtils.get(options.classes, [])); el.id = VUtils.get(options.id, ''); el.innerHTML = VUtils.get(options.content, ""); VUtils.mergeKeys(options.dataset, el.dataset); if (VUtils.get(options.append, true) === true) { this.appendChild(el); } return el; }; Node.prototype.addDelegatedEventListener = function (type, aim, callback, err) { if (!callback || !type || !aim) return; this.addMultiListener(type, (event) => { let target = event.target; if (event.detail instanceof HTMLElement) { target = event.detail; } if (target instanceof HTMLElement) { if (target.matches(aim)) { VUtils.tryCatch([event, target], callback, err); } else { const parent = target.find(aim); if (parent) { VUtils.tryCatch([event, parent], callback, err); } } } }); }; Node.prototype.addMultiListener = function (listener, cb, options = {}) { let splits = listener.split(" "); for (let split of splits) { this.addEventListener(split, cb, options); } }; } } VUtils.makePublic(); class VRipple { constructor(options = {}) { if (!VUtils.isUsed) { throw "VRipply is only with Public VUtils usable!" } let self = this; self.options = JSON.parse('{"classes":["btn-ripple__effect"],"target":"body","selector":".btn-ripple"}'); VUtils.mergeOptions(self.options, options); if (self.options.selector.indexOf("#") > -1) { throw "ID's are not allowed as selector!"; } this.instanceCheck(); this.ripples = []; requestAnimationFrame(this.initHandler.bind(this)); } instanceCheck() { let opts = this.options; const rawKey = [opts.target, opts.selector, opts.classes.join(".")].join(" "); VRipple.instances = VRipple.instances || {}; VRipple.instances[rawKey] = this; } initHandler() { let self = this; let selector = self.options.selector; let target = $(self.options.target); target.addDelegatedEventListener('mousedown touchstart', selector, (e, el) => { let pos = e.touches ? e.touches[0] : e; let parent = el.parentNode; let circle = el.createNew('span', self.options); let bounds = parent.getBoundingClientRect(); let x = pos.clientX - bounds.left; let y = pos.clientY - bounds.top; circle.style.top = y + 'px'; circle.style.left = x + 'px'; circle._mouseDown = true; circle._animationEnded = false; self.ripples.push(circle); }); document.body.addDelegatedEventListener('animationend', '.' + VUtils.get(self.options.classes, ''), self.rippleEnd.bind(self)) if (!document.body._vRippleInit) { document.body.addMultiListener('mouseup touchend mouseleave rippleClose', e => { let keys = Object.keys(VRipple.instances); for (let key of keys) { for (let ripple of VRipple.instances[key].ripples) { self.rippleEnd.bind(VRipple.instances[key])(e, ripple); } } }) document.body._vRippleInit = true; } } rippleEnd(ev, el) { const parent = el.parentNode; if (parent) { if (ev.type === 'animationend') { el._animationEnded = true; } else { el._mouseDown = false; } if (!el._mouseDown && el._animationEnded) { if (el.classList.contains('to-remove')) { el.parentNode.removeChild(el); this.ripples.splice(this.ripples.indexOf(el), 1) } else { el.classList.add('to-remove'); } } } } } const rippler = new VRipple(); (function () { window._openVSelect = null; requestAnimationFrame(e => { document.body.addEventListener('click', ev => { if (window._openVSelect && ev.target.closest('v-select') !== window._openVSelect) { window._openVSelect.toggle(false); } }) }) class VSelectElement extends HTMLElement { constructor() { super(); let self = this; self._in = this.attachInternals(); self._in.role = 'select'; self.setAttribute('tabindex', 0); self.update(); } static get formAssociated() { return true; } static get observedAttributes() { return ['required', 'validity']; } get required() { return this.hasAttribute('required'); } set required(flag) { this.toggleAttribute('required', Boolean(flag)); } get name() { return this.getAttribute('name'); } set name(val) { this.toggleAttribute('name', val); } get form() { return this._in.form; } get options() { return $$('v-options v-option', this); } get selected() { return $$('v-options v-option[selected]', this); } update() { let selected = [], lbl = $('v-label', this), fd = new FormData(); this.selected.forEach(e => { selected.push(e.innerText); fd.append(this.name, e.value); }) lbl.attributeChangedCallback('value', '', selected.join(", ")); if (this.required && selected.length === 0) { this._in.setValidity({customError: true}, "Option is needed"); } else { this._in.setValidity({}); } this._in.setFormValue(fd); } checkValidity() { return this._in.checkValidity(); } reportValidity() { return this._in.reportValidity(); } toggle(open) { if (window._openVSelect && open) { window._openVSelect.toggleSelect(false); } const options = $('v-options', this); if (!open || this.isOpen) { options.style.maxHeight = '0'; window._openVSelect = false; this.isOpen = false; this.update(); } else { options.focus(); let height = 0, children = options.children; for (let i = 0; i < children.length; i++) { height += children[i].offsetHeight; } options.style.maxHeight = height + 'px'; window._openVSelect = this; this.isOpen = true; } } } class VSelectOptionElement extends HTMLElement { constructor() { super(); this._in = this.attachInternals(); this._in.role = 'option'; this.addEventListener('click', e => { let parent = this.parentNode.parentNode, select = !this.selected; if (!parent.hasAttribute('multiple')) { parent.toggle(false); for (let item of parent.selected) { if (item !== this) { item.removeAttribute('selected'); } } } if (!this.disabled) { this.attributeChangedCallback('selected', false, select, true); this.parentNode.parentNode.update(); } }); } static get observedAttributes() { return ['selected', 'disabled', 'value']; } attributeChangedCallback(name, oldValue, newValue, force) { if (name === 'selected' && this.hasAttribute('disabled')) { this.removeAttribute(name); return; } if (name === 'disabled' && newValue === true && this.hasAttribute('selected')) { this.attributeChangedCallback('selected', false, false); } if (force) { if (newValue) { this.setAttribute(name, newValue); } else { this.removeAttribute(name); } } this[name] = newValue; } } class VLabel extends HTMLElement { constructor() { super(); this.empty = this.getAttribute('empty') || ""; this.innerHTML = this.getAttribute("value") || this.empty; this.addEventListener('click', this.openPopUp.bind(this)); } static get observedAttributes() { return ['empty', 'value']; } openPopUp() { this.parentNode.toggle(true); } attributeChangedCallback(name, oldValue, newValue) { if (name === 'value') { this.innerHTML = newValue || this.empty; } this[name] = newValue; } } customElements.define("v-label", VLabel); customElements.define("v-option", VSelectOptionElement); customElements.define("v-select", VSelectElement); })(); class FormHandler { constructor(selector, parent, cb, err) { this.cb = cb || console.log; this.err = err || console.err; $(parent).addDelegatedEventListener('submit', selector, this.handleEvent.bind(this)); } handleEvent(e, el) { e.preventDefault(); if (el.checkValidity()) { const url = el.action ?? ''; if (url === '') { console.error("No URL Found on Form", el); return; } fetch(el.action, { method: el.method.toUpperCase(), credentials: 'same-origin', body: new FormData(el), redirect: 'manual' }).then(res => { if (!res.ok) { throw new Error('Network response errored'); } return res.json() }).then(ev => this.cb(ev, el)).catch(ev => this.err(ev, el)); } else { VUtils.forEach($$('input', el), ele => { if (!ele.checkValidity()) { let parent = ele.parentNode; parent.classList.remove('valid'); parent.classList.add('invalid'); } }); } } } (function () { const body = $('body'); body.addDelegatedEventListener('change input', 'input', (e, el) => { let errorMessage = $('.error-message', el.find('form')); if (errorMessage) { errorMessage.classList.add('hide') } let parent = el.parentNode; if (el.value === "") { parent.classList.remove('focus') } else { parent.classList.add('focus') } if (el.checkValidity()) { parent.classList.add('valid'); parent.classList.remove('invalid'); } else { parent.classList.remove('valid'); parent.classList.add('invalid'); } }) if ($('#login')) { new FormHandler('form#login', 'body', () => { location.reload(); }, (e, el) => { $('.error-message', el).classList.remove('hide'); }) } })();