From 6bb9d16f0688492073ff33e08b59f1ed4e0a3840 Mon Sep 17 00:00:00 2001 From: Karel-Jan Van Haute Date: Thu, 18 Apr 2024 10:10:41 +0200 Subject: [PATCH 1/2] Optimize autocomplete component Fixes #305 --- .../js/components/autocomplete.component.ts | 85 ++++++++++++++----- tailoff/js/utils/domHelper.ts | 69 ++++++++++++--- 2 files changed, 124 insertions(+), 30 deletions(-) diff --git a/tailoff/js/components/autocomplete.component.ts b/tailoff/js/components/autocomplete.component.ts index 613c22ca..37236754 100644 --- a/tailoff/js/components/autocomplete.component.ts +++ b/tailoff/js/components/autocomplete.component.ts @@ -12,9 +12,9 @@ interface AutocompleteOption { export class AutocompleteComponent { constructor() { Array.from(document.querySelectorAll("[data-s-autocomplete]")).forEach( - (autocomplete, index) => { + (autocomplete) => { if (autocomplete.tagName === "SELECT") { - new Autocomplete(autocomplete as HTMLSelectElement, index); + new Autocomplete(autocomplete as HTMLSelectElement); } } ); @@ -23,10 +23,28 @@ export class AutocompleteComponent { document.documentElement, "select[data-s-autocomplete]", (autocompletes) => { - Array.from(autocompletes).forEach((ac: HTMLSelectElement, index) => { - new Autocomplete(ac, index); + Array.from(autocompletes).forEach((ac: HTMLSelectElement) => { + if (!ac.hasAttribute("data-s-autocomplete")) return; + new Autocomplete(ac); }); }, + "data-s-autocomplete" + ); + + DOMHelper.onDynamicContent( + document.documentElement, + "select[data-s-autocomplete-init]", + (autocompletes) => { + Array.from(autocompletes).forEach((ac: HTMLSelectElement) => { + const oldList = document.getElementById( + `autocompleteList${ac.getAttribute("data-s-autocomplete-init")}` + ); + if (oldList) { + oldList.remove(); + } + }); + }, + false, true ); } @@ -36,7 +54,7 @@ class Autocomplete { private siteLang = SiteLang.getLang(); private lang; - private autocompleteListIndex: number = 0; + private autocompleteListIndex: string = ""; private selectElement: HTMLSelectElement; private autocompleteElement: HTMLDivElement; @@ -45,6 +63,7 @@ class Autocomplete { private autocompleteInputWrapper: HTMLDivElement; private autocompletePlaceholderElement: HTMLDivElement; private autocompleteListElement: HTMLUListElement; + private autocompleteListReference: HTMLElement; private statusElement: HTMLDivElement; private freeTypeOption: HTMLOptionElement; @@ -82,16 +101,21 @@ class Autocomplete { backspace: 8, }; - constructor(autocomplete: HTMLSelectElement, index) { + constructor(autocomplete: HTMLSelectElement) { + autocomplete.removeAttribute("data-s-autocomplete"); + autocomplete.setAttribute("data-s-autocomplete-init", ""); this.getLang().then(() => { - this.init(autocomplete, index); + this.init(autocomplete); }); } - private init(autocomplete: HTMLSelectElement, index) { - this.autocompleteListIndex = index; + private init(autocomplete: HTMLSelectElement) { + this.autocompleteListIndex = DOMHelper.getPathTo(autocomplete); + autocomplete.setAttribute( + "data-s-autocomplete-init", + this.autocompleteListIndex + ); this.selectElement = autocomplete; - autocomplete.removeAttribute("data-s-autocomplete"); this.selectMutationObserver = new MutationObserver( this.selectMutation.bind(this) @@ -149,6 +173,14 @@ class Autocomplete { this.autocompleteSelectElement.classList.add(c); }); + this.autocompleteListReference = autocomplete.hasAttribute( + "data-s-autocomplete-reference" + ) + ? document.querySelector( + autocomplete.getAttribute("data-s-autocomplete-reference") + ) + : this.autocompleteElement; + this.autocompleteSelectElement.addEventListener("click", () => { if (!this.isDisabled) { this.hidePlaceholder(); @@ -176,7 +208,10 @@ class Autocomplete { ); this.inputElement = document.createElement("input"); - this.inputElement.setAttribute("aria-controls", `autocompleteList${index}`); + this.inputElement.setAttribute( + "aria-controls", + `autocompleteList${this.autocompleteListIndex}` + ); this.inputElement.setAttribute("autocapitalize", "none"); this.inputElement.setAttribute("type", "text"); this.inputElement.setAttribute("autocomplete", "off"); @@ -228,9 +263,19 @@ class Autocomplete { this.autocompleteSelectElement.insertAdjacentElement("beforeend", icon); + // const previousList = document.getElementById( + // `autocompleteList${this.autocompleteListIndex}` + // ); + // if (previousList) { + // previousList.remove(); + // } this.autocompleteListElement = document.createElement("ul"); - this.autocompleteListElement.setAttribute("id", `autocompleteList${index}`); + this.autocompleteListElement.setAttribute( + "id", + `autocompleteList${this.autocompleteListIndex}` + ); this.autocompleteListElement.setAttribute("role", "listbox"); + this.autocompleteListElement.classList.add("autocomplete-list"); this.autocompleteListElement.classList.add("hidden"); if (this.isMultiple) { this.autocompleteListElement.setAttribute("aria-multiselectable", "true"); @@ -242,7 +287,7 @@ class Autocomplete { this.menuClickListener ); - this.autocompleteElement.insertAdjacentElement( + this.autocompleteListReference.insertAdjacentElement( "beforeend", this.autocompleteListElement ); @@ -476,12 +521,12 @@ class Autocomplete { private onKeyDown(e) { switch (e.keyCode) { - // case this.keys.enter: - // e.preventDefault(); - // // if (this.isFreeType) { - // // this.hideMenu(); - // // } - // break; + case this.keys.enter: + e.preventDefault(); + if (this.isFreeType) { + this.hideMenu(); + } + break; case this.keys.backspace: if ( this.inputElement.value == "" && @@ -758,6 +803,8 @@ class Autocomplete { }); this.hideMenu(); this.hidePlaceholder(); + + this.hoverOption = null; this.inputElement.focus(); this.inputElement.size = Math.max(this.inputElement.value.length + 1, 1); } diff --git a/tailoff/js/utils/domHelper.ts b/tailoff/js/utils/domHelper.ts index fc39ad1e..ea98cfb1 100644 --- a/tailoff/js/utils/domHelper.ts +++ b/tailoff/js/utils/domHelper.ts @@ -15,28 +15,51 @@ export class DOMHelper { document.body.appendChild(script); } + public static loadScriptContent(content) { + var script = document.createElement("script"); + script.type = "text/javascript"; + script.innerHTML = content; + document.body.appendChild(script); + } + public static onDynamicContent( parent: Element, selector: string, callback: Function, - includeAttributes: boolean | string = false + includeAttributes: boolean | string = false, + checkRemoved: boolean = false ) { const mutationObserver: MutationObserver = new MutationObserver( (mutationsList) => { for (let mutation of mutationsList) { if (mutation.type === "childList") { - Array.from(mutation.addedNodes).forEach((node: HTMLElement) => { - if (node.nodeType == 1) { - const results = node.querySelectorAll(selector); - if (results.length > 0) { - callback(results); - } else { - if (node.matches(selector)) { - callback([node]); + if (checkRemoved) { + Array.from(mutation.removedNodes).forEach((node: HTMLElement) => { + if (node.nodeType == 1) { + const results = node.querySelectorAll(selector); + if (results.length > 0) { + callback(results); + } else { + if (node.matches(selector)) { + callback([node]); + } } } - } - }); + }); + } else { + Array.from(mutation.addedNodes).forEach((node: HTMLElement) => { + if (node.nodeType == 1) { + const results = node.querySelectorAll(selector); + if (results.length > 0) { + callback(results); + } else { + if (node.matches(selector)) { + callback([node]); + } + } + } + }); + } } if (mutation.type === "attributes" && includeAttributes) { if (typeof includeAttributes == "string") { @@ -69,4 +92,28 @@ export class DOMHelper { subtree: true, }); } + + public static getPathTo(element) { + if (element.id !== "") return "#" + element.id; + + if (element === document.body) return element.tagName.toLowerCase(); + + var ix = 0; + var siblings = element.parentNode.childNodes; + for (var i = 0; i < siblings.length; i++) { + var sibling = siblings[i]; + + if (sibling === element) + return ( + this.getPathTo(element.parentNode) + + "-" + + element.tagName.toLowerCase() + + (ix + 1) + ); + + if (sibling.nodeType === 1 && sibling.tagName === element.tagName) { + ix++; + } + } + } } From c0d250057849c1045f941118cada5909f40c886f Mon Sep 17 00:00:00 2001 From: Karel-Jan Van Haute Date: Tue, 17 Sep 2024 16:14:50 +0200 Subject: [PATCH 2/2] Add pagination back and forward fix to the filter component. Fixes #374 --- tailoff/js/components/filter.component.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tailoff/js/components/filter.component.ts b/tailoff/js/components/filter.component.ts index 6f4042e6..f64d9fcd 100644 --- a/tailoff/js/components/filter.component.ts +++ b/tailoff/js/components/filter.component.ts @@ -257,9 +257,14 @@ export class FilterComponent { }, false ); + + window.addEventListener('popstate', (event) => { + this.showLoading(); + this.getFilterData(window.location.href, false, false); + }); } - private getFilterData(url, clearPage = false) { + private getFilterData(url, clearPage = false, pushState = true) { if (this.getFilterTimeout) { clearTimeout(this.getFilterTimeout); } @@ -295,7 +300,9 @@ export class FilterComponent { _self.ariaLiveElement.innerHTML = responseElement.querySelector('.js-filter-aria-live').innerHTML; - history.pushState('', 'New URL: ' + url, url); + if (pushState) { + history.pushState('', 'New URL: ' + url, url); + } _self.scrollToStart();