From d08bd478fee60e683d7c48af5eebea4890c78b4b Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Wed, 1 Jul 2026 14:42:40 -0400 Subject: [PATCH] - Added tabs and upload component --- asset/webcomponent/app.js | 15 ++-- asset/webcomponent/module/ui-popup.js | 115 ++++++++++++++++--------- asset/webcomponent/module/ui-tabs.js | 6 +- asset/webcomponent/module/ui-upload.js | 67 ++++++++++++++ view/webcomponent/ui-popup.phtml | 8 +- view/webcomponent/ui-textarea.phtml | 1 - view/webcomponent/ui-upload.phtml | 30 +++++++ 7 files changed, 188 insertions(+), 54 deletions(-) create mode 100644 asset/webcomponent/module/ui-upload.js create mode 100644 view/webcomponent/ui-upload.phtml diff --git a/asset/webcomponent/app.js b/asset/webcomponent/app.js index 3515fc5..ac99c47 100644 --- a/asset/webcomponent/app.js +++ b/asset/webcomponent/app.js @@ -1,17 +1,20 @@ - import { UiTabs } from "./module/ui-tabs.js"; -window.customElements.define("ui-tabs", UiTabs); +window.customElements.get("ui-tabs") || window.customElements.define("ui-tabs", UiTabs); import { UiPopup } from "./module/ui-popup.js"; -window.customElements.define("ui-popup", UiPopup); +window.customElements.get("ui-popup") || window.customElements.define("ui-popup", UiPopup); import { UiTextarea } from "./module/ui-textarea.js"; -window.customElements.define("ui-textarea", UiTextarea); +window.customElements.get("ui-textarea") || window.customElements.define("ui-textarea", UiTextarea); import { UiSelect } from "./module/ui-select.js"; -window.customElements.define("ui-select", UiSelect); +window.customElements.get("ui-select") || window.customElements.define("ui-select", UiSelect); + +import { UiUpload, UiUploadMedia } from "./module/ui-upload.js"; +window.customElements.get("ui-upload") || window.customElements.define("ui-upload", UiUpload); +window.customElements.get("ui-upload-media") || window.customElements.define("ui-upload-media", UiUploadMedia); import { UiNotification } from "./module/ui-notification.js"; -window.customElements.define("ui-notification", UiNotification); +window.customElements.get("ui-notification") || window.customElements.define("ui-notification", UiNotification); import './module/input.js'; \ No newline at end of file diff --git a/asset/webcomponent/module/ui-popup.js b/asset/webcomponent/module/ui-popup.js index 36d8206..b2acf9b 100644 --- a/asset/webcomponent/module/ui-popup.js +++ b/asset/webcomponent/module/ui-popup.js @@ -6,6 +6,9 @@ const config = { }; class UiPopup extends Webcomponent { + #scrollPosition; + #options = {}; + constructor() { super(); @@ -25,36 +28,34 @@ class UiPopup extends Webcomponent { } handleOptions() { - if ( this.options ) { - if ( ! this.options['skip-keys'] ) { - document.addEventListener('keyup', function(e) { - if ( this.visible() ) { + if ( this.options['skip-keys'] === undefined ) { + document.addEventListener('keyup', function(e) { + if ( this.visible() ) { - let trigger, ev; + let trigger, ev; - e.preventDefault(); - console.log(this.visible()); + e.preventDefault(); - switch ( e.keyCode ) { - case 13: - trigger = this.querySelector('[action="confirm"]'); - break; + switch ( e.keyCode ) { + case 13: + trigger = this.querySelector('[action="confirm"]'); + break; - case 27: - trigger = this.querySelector('[action="close"]'); - break; - } - - if (trigger) { - ev = new CustomEvent('click', {trigger: trigger}); - trigger.dispatchEvent(ev); - } + case 27: + trigger = this.querySelector('[action="close"]'); + break; } - }.bind(this)); - } + + if (trigger) { + ev = new CustomEvent('click', {trigger: trigger}); + trigger.dispatchEvent(ev); + } + } + }.bind(this)); } } + attach() { let triggers = document.querySelectorAll('[ui-popup]'); @@ -78,10 +79,19 @@ class UiPopup extends Webcomponent { element.addEventListener("click", this.action.bind(this, element), false); }.bind(this)); - this.hide(); + if ( this.classList.contains("show") ) { + this.classList.remove("show"); + + setTimeout(() => this.show(), 100); + } + else { + this.hide(); + } } action(element, e) { + let retval; + if ( ! element.attributes.action ) { throw "A button was clicked onto which no action was bound."; } @@ -90,7 +100,7 @@ class UiPopup extends Webcomponent { case "confirm": case "yes": case "ok": - let retval = this.actionFollowLink(element) || + retval = this.actionFollowLink(element) || this.actionClickButton(element); if (! retval) { @@ -114,8 +124,13 @@ class UiPopup extends Webcomponent { break; } + // Disable multiple clicks + if (retval !== undefined) { + element.setAttribute('disabled', 'disabled'); + } + this.dispatchEvent(new CustomEvent('action:' + element.attributes.action.value)); - this.trigger.dispatchEvent(new CustomEvent('action:' + element.attributes.action.value)); + this.trigger && this.trigger.dispatchEvent(new CustomEvent('action:' + element.attributes.action.value)); if ( element.getAttribute("action") ) { e.preventDefault(); @@ -126,10 +141,15 @@ class UiPopup extends Webcomponent { hide() { this.classList.remove('visible'); + + window.scrollTo(window.scrollX, this.scrollPosition); } show() { + this.scrollPosition = window.scrollY; this.classList.add('visible'); + + window.scrollTo(window.scrollX, 0); } visible() { @@ -140,8 +160,8 @@ class UiPopup extends Webcomponent { //if ( element.getAttribute('follow-link') !== null ) { // Automatically redirect to given location if it exists. - if ( this.trigger.tagName === "A" ) { - let href = this.trigger.getAttribute('href'); + if ( this.trigger && this.trigger.tagName === "A" ) { + const href = this.trigger.getAttribute('href'); if ( href ) { window.location = href; @@ -155,19 +175,19 @@ class UiPopup extends Webcomponent { } actionClickButton(element) { + if (this.trigger) { + const tag = this.trigger.tagName.toLowerCase(); - //if ( element.getAttribute('button-click') !== null ) { - let tag = this.trigger.tagName.toLowerCase(); + // Automatically redirect to given location if it exists. + if ((tag === "button" || tag === "input") && this.trigger.getAttribute("type").toLowerCase() === "submit") { + this.pause = true; - // Automatically redirect to given location if it exists. - if ( ( tag === "button" || tag === "input" ) && this.trigger.getAttribute("type").toLowerCase() === "submit") { - this.pause = true; + this.trigger.click(); - this.trigger.click(); + this.pause = false; - this.pause = false; - - return true; + return true; + } } //} @@ -179,8 +199,6 @@ class UiPopup extends Webcomponent { return false; } - //if ( element.getAttribute('send-form') !== null ) { - //let form = this.trigger.tagName === "FORM" ? this.trigger : this.trigger.closest('FORM'); let form = element.tagName === "FORM" ? element : element.closest('FORM'); if ( form ) { @@ -192,7 +210,7 @@ class UiPopup extends Webcomponent { submitBtn.click(); } else { - form.submit(); + form.requestSubmit(); } return true; @@ -234,7 +252,6 @@ class UiPopup extends Webcomponent { args.options['input'] && Object.keys(args.options['input']).forEach( key => this.querySelectorAll(`[name="${key}"]`).forEach(function(f) { - console.log(f); f.value = args.options['input'][key]; f.dispatchEvent(new Event("change")); }) @@ -244,6 +261,8 @@ class UiPopup extends Webcomponent { key => { let form = this.querySelector(`form${key}`); + form = form ? form : this.closest(`form${key}`); + if (form) { form.setAttribute('action', args.options['form_url'][key]); } @@ -298,6 +317,22 @@ class UiPopup extends Webcomponent { return this._message = value; } + get options() { + return this.#options; + } + + set options(value) { + return this.#options = value; + } + + get scrollPosition() { + return this.#scrollPosition; + } + + set scrollPosition(value) { + return this.#scrollPosition = value; + } + get title() { return this._title; } diff --git a/asset/webcomponent/module/ui-tabs.js b/asset/webcomponent/module/ui-tabs.js index b7e643d..35949da 100644 --- a/asset/webcomponent/module/ui-tabs.js +++ b/asset/webcomponent/module/ui-tabs.js @@ -13,11 +13,9 @@ const config = { class UiTabs extends Webcomponent { constructor() { super(config); - - this.init(); } - init() { + connectedCallback() { window.addEventListener("hashchange", this.hashChange.bind(this), false); //this.querySelectorAll(this.config.item).forEach(ele => ele.addEventListener("click", this.clickTab.bind(this))); @@ -32,7 +30,7 @@ class UiTabs extends Webcomponent { hashChange(e) { let anchor = e.newURL.split('#')[1]; - document.querySelectorAll("#" + anchor + this.config.content).forEach(function(content) { + document.querySelectorAll(`[id='${anchor}']${this.config.content}`).forEach(function(content) { content.parentNode.querySelectorAll(this.config.content + "." + this.config.activeClass).forEach(ele => ele.classList.remove(this.config.activeClass)); content.classList.add(this.config.activeClass); }.bind(this)); diff --git a/asset/webcomponent/module/ui-upload.js b/asset/webcomponent/module/ui-upload.js new file mode 100644 index 0000000..ce40ff2 --- /dev/null +++ b/asset/webcomponent/module/ui-upload.js @@ -0,0 +1,67 @@ + +import { Webcomponent } from './webcomponent.js'; + +const config = { + content: { + wrapper: ".wrapper", + list: ".item-list" + } +}; + +class UiUpload extends Webcomponent { + #input; + #uploadmedia; + + constructor() { + super(); + } + + connectedCallback() { + this.attachInput(); + + this.#uploadmedia = document.querySelector(`ui-upload-media[data-name="${this.name}"]`) + } + + attachInput() { + this.#input = this.querySelector('input:not([type="hidden"])'); + this.addEventListener('click', () => this.#input.click()); + this.#input.addEventListener('change', this.changeMedia.bind(this)); + } + + changeMedia(e) { + let inputHtml = this.#input.outerHTML; + + Array.from(this.#input.files).forEach(file => { + const base64Res = new FileReader(); + this.#uploadmedia.addThumbnail(base64Res, this.#input); + base64Res.readAsDataURL(file) + }); + + this.getSlot("input").insertAdjacentHTML('beforeend', inputHtml); + this.attachInput(); + } + + get name() { + return this.dataset['name']; + } +} + +class UiUploadMedia extends Webcomponent { + addThumbnail(media, input) { + const wrapper = this.shadowRoot.querySelector(".new-media-wrapper"), + mediaItem = this.shadowRoot.querySelector(".new-media-item[hidden]").cloneNode(true); + + mediaItem.removeAttribute('hidden'); + wrapper.append(mediaItem); + this.getSlot("input").append(input); + + media.onload = f => { + mediaItem.querySelector('img').setAttribute('src', media.result); +// mediaItem.querySelector('.name-input').value = file.name; + } + + } + +} + +export { UiUpload, UiUploadMedia } diff --git a/view/webcomponent/ui-popup.phtml b/view/webcomponent/ui-popup.phtml index 990b0dc..fc9921e 100644 --- a/view/webcomponent/ui-popup.phtml +++ b/view/webcomponent/ui-popup.phtml @@ -1,14 +1,16 @@