From 8258ca94763f3037e11600a4f4bfa8762af702a5 Mon Sep 17 00:00:00 2001 From: Karel-Jan Van Haute Date: Thu, 18 Apr 2024 09:56:48 +0200 Subject: [PATCH] Provide DataLayer hooks for components with ajax callbacks Fixes #310 --- tailoff/js/components/filter.component.ts | 335 +++++++++++++--------- 1 file changed, 207 insertions(+), 128 deletions(-) diff --git a/tailoff/js/components/filter.component.ts b/tailoff/js/components/filter.component.ts index 4001d1bd..7b10ee32 100644 --- a/tailoff/js/components/filter.component.ts +++ b/tailoff/js/components/filter.component.ts @@ -1,6 +1,6 @@ -import { FormPrototypes } from '../utils/prototypes/form.prototypes'; -import { ScrollHelper } from '../utils/scroll'; -import { DOMHelper } from '../utils/domHelper'; +import { FormPrototypes } from "../utils/prototypes/form.prototypes"; +import { ScrollHelper } from "../utils/scroll"; +import { DOMHelper } from "../utils/domHelper"; FormPrototypes.activateSerialize(); @@ -35,19 +35,19 @@ export class FilterComponent { constructor(options: Object = {}) { this.options = { ...this.options, ...options }; - this.jsChange = document.createEvent('HTMLEvents'); - this.jsChange.initEvent('jschange', false, true); + this.jsChange = document.createEvent("HTMLEvents"); + this.jsChange.initEvent("jschange", false, true); - this.formElement = document.querySelector('.js-filter-form'); + this.formElement = document.querySelector(".js-filter-form"); if (this.formElement) { - this.formElement.addEventListener('submit', (e) => { + this.formElement.addEventListener("submit", (e) => { e.preventDefault(); this.getFormAction(); }); - this.resultsElement = document.querySelector('.js-filter-results'); - this.extraInfoElement = document.querySelector('.js-filter-extra-info'); + this.resultsElement = document.querySelector(".js-filter-results"); + this.extraInfoElement = document.querySelector(".js-filter-extra-info"); if (!this.resultsElement) { console.log( @@ -56,7 +56,7 @@ export class FilterComponent { return; } - this.ariaLiveElement = document.querySelector('.js-filter-aria-live'); + this.ariaLiveElement = document.querySelector(".js-filter-aria-live"); if (!this.ariaLiveElement) { console.log( "You must have an element with class 'js-filter-aria-live' defined in order for the filter plugin to work." @@ -64,63 +64,77 @@ export class FilterComponent { return; } - this.submitButtonElement = this.formElement.querySelector('button[type=submit]'); + this.submitButtonElement = this.formElement.querySelector( + "button[type=submit]" + ); if (this.submitButtonElement) { this.initSubmitButton(); } this.filterChangeElements = Array.from( - this.formElement.querySelectorAll('input:not(.no-hook), select:not(.no-hook)') + this.formElement.querySelectorAll( + "input:not(.no-hook), select:not(.no-hook)" + ) ); this.initFilterChangeElements(this.filterChangeElements); - this.showMoreOptionElements = Array.from(this.formElement.querySelectorAll('.js-filter-show-more')); + this.showMoreOptionElements = Array.from( + this.formElement.querySelectorAll(".js-filter-show-more") + ); if (this.showMoreOptionElements.length > 0) { this.initShowMore(); } DOMHelper.onDynamicContent( document.documentElement, - '.js-filter-form input:not(.no-hook), .js-filter-form select:not(.no-hook)', + ".js-filter-form input:not(.no-hook), .js-filter-form select:not(.no-hook)", (inputs) => { this.initFilterChangeElements(Array.from(inputs)); } ); - DOMHelper.onDynamicContent(document.documentElement, '.js-filter-clear', (inputs) => { - this.clearFilterButtonElement = inputs[0]; - this.initClearFilter(); - }); + DOMHelper.onDynamicContent( + document.documentElement, + ".js-filter-clear", + (inputs) => { + this.clearFilterButtonElement = inputs[0]; + this.initClearFilter(); + } + ); } else { return; } - this.loaderAnimationElement = document.querySelector('.js-filter-loader'); + this.loaderAnimationElement = document.querySelector(".js-filter-loader"); if (this.loaderAnimationElement) { this.initLoader(); } - this.filterMobileToggleButtonElement = document.querySelector('.js-filter-mobile-toggle'); + this.filterMobileToggleButtonElement = document.querySelector( + ".js-filter-mobile-toggle" + ); if (this.filterMobileToggleButtonElement) { - this.filterMobileCollapseElement = document.querySelector('.js-filter-mobile-collapse'); + this.filterMobileCollapseElement = document.querySelector( + ".js-filter-mobile-collapse" + ); if (this.filterMobileCollapseElement) { this.initFilterToggle(); } } - this.clearFilterButtonElement = document.querySelector('.js-filter-clear'); + this.clearFilterButtonElement = document.querySelector(".js-filter-clear"); if (this.clearFilterButtonElement) { this.initClearFilter(); } this.initReloadedClicks(); - this.scrollToElement = document.querySelector('.js-filter-scroll-position'); + this.scrollToElement = document.querySelector(".js-filter-scroll-position"); } private initSubmitButton() { - this.submitButtonElement.addEventListener('click', (event) => { + this.submitButtonElement.addEventListener("click", (event) => { event.preventDefault(); this.getFormAction(); }); @@ -128,35 +142,43 @@ export class FilterComponent { private initFilterChangeElements(elements) { elements.forEach((el) => { - el.addEventListener('change', () => { + el.addEventListener("change", () => { this.getFormAction(); }); - el.addEventListener('jschange', (e) => { + el.addEventListener("jschange", (e) => { this.getFormAction(); }); }); } private initLoader() { - if (!this.loaderAnimationElement.classList.contains('hidden')) { - this.loaderAnimationElement.classList.add('hidden'); + if (!this.loaderAnimationElement.classList.contains("hidden")) { + this.loaderAnimationElement.classList.add("hidden"); } } private initFilterToggle() { - this.filterMobileToggleButtonElement.setAttribute('role', 'button'); - this.filterMobileToggleButtonElement.classList.add('open'); - this.filterMobileToggleButtonElement.setAttribute('aria-expanded', 'true'); - this.filterMobileToggleButtonElement.setAttribute('aria-controls', 'filterMobileCollapseArea'); + this.filterMobileToggleButtonElement.setAttribute("role", "button"); + this.filterMobileToggleButtonElement.classList.add("open"); + this.filterMobileToggleButtonElement.setAttribute("aria-expanded", "true"); + this.filterMobileToggleButtonElement.setAttribute( + "aria-controls", + "filterMobileCollapseArea" + ); - this.filterMobileCollapseElement.setAttribute('id', 'filterMobileCollapseArea'); - this.filterMobileCollapseElement.setAttribute('role', 'region'); + this.filterMobileCollapseElement.setAttribute( + "id", + "filterMobileCollapseArea" + ); + this.filterMobileCollapseElement.setAttribute("role", "region"); - window.addEventListener('resize', this.checkMobileCollapse.bind(this)); + window.addEventListener("resize", this.checkMobileCollapse.bind(this)); this.checkMobileCollapse(); - this.filterMobileToggleButtonElement.addEventListener('click', (e) => { + this.filterMobileToggleButtonElement.addEventListener("click", (e) => { e.preventDefault(); - this.openFilterMobileToggle(this.filterMobileCollapseElement.classList.contains('hidden')); + this.openFilterMobileToggle( + this.filterMobileCollapseElement.classList.contains("hidden") + ); }); } @@ -169,28 +191,37 @@ export class FilterComponent { private openFilterMobileToggle(open: boolean) { if (open) { - this.filterMobileCollapseElement.classList.remove('hidden'); - this.filterMobileToggleButtonElement.classList.add('open'); - this.filterMobileToggleButtonElement.setAttribute('aria-expanded', 'true'); + this.filterMobileCollapseElement.classList.remove("hidden"); + this.filterMobileToggleButtonElement.classList.add("open"); + this.filterMobileToggleButtonElement.setAttribute( + "aria-expanded", + "true" + ); - const resizeEvent = document.createEvent('HTMLEvents'); - resizeEvent.initEvent('resize', false, true); + const resizeEvent = document.createEvent("HTMLEvents"); + resizeEvent.initEvent("resize", false, true); window.dispatchEvent(resizeEvent); } else { - this.filterMobileCollapseElement.classList.add('hidden'); - this.filterMobileToggleButtonElement.classList.remove('open'); - this.filterMobileToggleButtonElement.setAttribute('aria-expanded', 'false'); + this.filterMobileCollapseElement.classList.add("hidden"); + this.filterMobileToggleButtonElement.classList.remove("open"); + this.filterMobileToggleButtonElement.setAttribute( + "aria-expanded", + "false" + ); } } private initClearFilter() { - this.clearFilterButtonElement.setAttribute('role', 'button'); - this.clearFilterButtonElement.addEventListener('click', (e) => { + this.clearFilterButtonElement.setAttribute("role", "button"); + this.clearFilterButtonElement.addEventListener("click", (e) => { e.preventDefault(); this.clearForm(); this.showLoading(); - this.getFilterData(window.location.origin + window.location.pathname, true); + this.getFilterData( + window.location.origin + window.location.pathname, + true + ); }); this.checkClearButtonStatus(); } @@ -199,30 +230,32 @@ export class FilterComponent { const formData = new FormData(this.formElement); let isEmpty = true; formData.forEach((value, key) => { - if (value !== '' && value !== undefined) { + if (value !== "" && value !== undefined) { isEmpty = false; } }); if (this.clearFilterButtonElement) { if (isEmpty) { - if (!this.clearFilterButtonElement.hasAttribute('data-s-always-show')) { - this.clearFilterButtonElement.classList.add('hidden'); + if (!this.clearFilterButtonElement.hasAttribute("data-s-always-show")) { + this.clearFilterButtonElement.classList.add("hidden"); } } else { - this.clearFilterButtonElement.classList.remove('hidden'); + this.clearFilterButtonElement.classList.remove("hidden"); } } } private initShowMore() { this.showMoreOptionElements.forEach((el) => { - el.querySelector('button').addEventListener('click', (e) => { + el.querySelector("button").addEventListener("click", (e) => { e.preventDefault(); - Array.from(el.parentElement.querySelectorAll('.js-filter-extra-content')).forEach((extra, index) => { - extra.classList.remove('hidden'); + Array.from( + el.parentElement.querySelectorAll(".js-filter-extra-content") + ).forEach((extra, index) => { + extra.classList.remove("hidden"); if (index == 0) { - extra.querySelector('input').focus(); + extra.querySelector("input").focus(); } }); el.parentNode.removeChild(el); @@ -232,22 +265,28 @@ export class FilterComponent { private initReloadedClicks() { document.addEventListener( - 'click', + "click", (e) => { // loop parent nodes from the target to the delegation node - for (let target = e.target; target && !target.isSameNode(document); target = target.parentElement) { - if (target.matches('.js-filter-pagination a')) { + for ( + let target = e.target; + target && !target.isSameNode(document); + target = target.parentElement + ) { + if (target.matches(".js-filter-pagination a")) { e.preventDefault(); const href = (target as HTMLAnchorElement).href; - if (href != 'javascript:void(0);') { + if (href != "javascript:void(0);") { this.showLoading(); this.getFilterData((target as HTMLAnchorElement).href); } break; } - if (target.matches('.js-clear-filter-element')) { + if (target.matches(".js-clear-filter-element")) { e.preventDefault(); - const data = JSON.parse(target.getAttribute('data-filter-elements')); + const data = JSON.parse( + target.getAttribute("data-filter-elements") + ); this.clearElements(data); break; } @@ -273,48 +312,63 @@ export class FilterComponent { // Go back to page 1 when set changes if (clearPage) { - const regexResult = window.location.pathname.match(/([^\?\s]+\/)([p][0-9]{1,3}.?)(.*)/); + const regexResult = window.location.pathname.match( + /([^\?\s]+\/)([p][0-9]{1,3}.?)(.*)/ + ); if (regexResult && regexResult[1]) { - url = regexResult[1] + '?' + this.formElement.serialize(); + url = regexResult[1] + "?" + this.formElement.serialize(); } } this.xhr = new XMLHttpRequest(); - this.xhr.open('GET', url, true); + this.xhr.open("GET", url, true); this.xhr.onload = function () { if (this.status >= 200 && this.status < 400) { - const responseElement = document.implementation.createHTMLDocument(''); + const responseElement = + document.implementation.createHTMLDocument(""); responseElement.body.innerHTML = this.response; - const resultsBlock = responseElement.querySelector('.js-filter-results'); + const resultsBlock = + responseElement.querySelector(".js-filter-results"); if (resultsBlock) { _self.resultsElement.innerHTML = resultsBlock.innerHTML; - _self.ariaLiveElement.innerHTML = responseElement.querySelector('.js-filter-aria-live').innerHTML; + const scripts = _self.resultsElement.querySelectorAll("script"); + if (scripts.length > 0) { + Array.from(scripts).forEach((script) => { + eval(script.innerHTML); + }); + } + + _self.ariaLiveElement.innerHTML = responseElement.querySelector( + ".js-filter-aria-live" + ).innerHTML; - history.pushState('', 'New URL: ' + url, url); + history.pushState("", "New URL: " + url, url); _self.scrollToStart(); _self.hideLoading(); _self.styleClear(); } else { - console.error('Could not find data on returned page.'); + console.error("Could not find data on returned page."); } - const extraInfoBlock = responseElement.querySelector('.js-filter-extra-info'); + const extraInfoBlock = responseElement.querySelector( + ".js-filter-extra-info" + ); if (extraInfoBlock) { _self.extraInfoElement.innerHTML = extraInfoBlock.innerHTML; } _self.checkClearButtonStatus(); } else { - console.error('Something went wrong when fetching data.'); + console.error("Something went wrong when fetching data."); } }; this.xhr.onerror = function () { - console.error('There was a connection error.'); + console.error("There was a connection error."); }; this.xhr.send(); @@ -335,10 +389,16 @@ export class FilterComponent { if (this.loaderAnimationElement) { if (window.innerWidth < this.mobileBreakpoint) { if (!this.options.disableScrollOnMobile) { - ScrollHelper.scrollToY(this.loaderAnimationElement, this.scrollSpeed); + ScrollHelper.scrollToY( + this.loaderAnimationElement, + this.scrollSpeed + ); } } else { - ScrollHelper.scrollToY(this.loaderAnimationElement, this.scrollSpeed); + ScrollHelper.scrollToY( + this.loaderAnimationElement, + this.scrollSpeed + ); } } } @@ -347,25 +407,32 @@ export class FilterComponent { private getFormAction() { this.showLoading(); - let url = this.formElement.getAttribute('action') + '?' + this.formElement.serialize(); - if (this.formElement.getAttribute('action') === '') { - url = window.location.origin + window.location.pathname + '?' + this.formElement.serialize(); + let url = + this.formElement.getAttribute("action") + + "?" + + this.formElement.serialize(); + if (this.formElement.getAttribute("action") === "") { + url = + window.location.origin + + window.location.pathname + + "?" + + this.formElement.serialize(); } this.getFilterData(url, true); } private showLoading() { if (this.loaderAnimationElement) { - this.loaderAnimationElement.classList.remove('hidden'); + this.loaderAnimationElement.classList.remove("hidden"); // this.loaderAnimationElement.focus(); //This fucks up the scroll to for some reason. - this.resultsElement.classList.add('hidden'); + this.resultsElement.classList.add("hidden"); } } private hideLoading() { if (this.loaderAnimationElement) { - this.loaderAnimationElement.classList.add('hidden'); - this.resultsElement.classList.remove('hidden'); + this.loaderAnimationElement.classList.add("hidden"); + this.resultsElement.classList.remove("hidden"); this.ariaLiveElement.focus(); } } @@ -380,39 +447,45 @@ export class FilterComponent { el.checked = false; this.clearElement(el); } else { - const el = this.formElement.querySelector(`select[name='${element.name}']`) as HTMLSelectElement; + const el = this.formElement.querySelector( + `select[name='${element.name}']` + ) as HTMLSelectElement; if (el) { - if (el.type == 'select-multiple') { + if (el.type == "select-multiple") { Array.from(el.selectedOptions).forEach((option) => { if (option.value == element.value) { option.selected = false; } }); } else { - el.value = ''; + el.value = ""; } this.clearElement(el); } } } else { const el = - (this.formElement.querySelector(`input[name='${element.name}']`) as HTMLInputElement) ?? - (this.formElement.querySelector(`select[name='${element.name}']`) as HTMLSelectElement); - el.value = ''; + (this.formElement.querySelector( + `input[name='${element.name}']` + ) as HTMLInputElement) ?? + (this.formElement.querySelector( + `select[name='${element.name}']` + ) as HTMLSelectElement); + el.value = ""; this.clearElement(el); } }); this.getFormAction(); - const filterElementsCleared = document.createEvent('HTMLEvents'); - filterElementsCleared.initEvent('filterElementsCleared', false, true); + const filterElementsCleared = document.createEvent("HTMLEvents"); + filterElementsCleared.initEvent("filterElementsCleared", false, true); document.dispatchEvent(filterElementsCleared); this.checkClearButtonStatus(); } private clearElement(el: HTMLElement) { el.dispatchEvent(this.jsChange); - const chipButton = el.nearest('button[aria-haspopup]'); + const chipButton = el.nearest("button[aria-haspopup]"); if (chipButton) { chipButton.dispatchEvent(this.jsChange); } @@ -421,35 +494,35 @@ export class FilterComponent { private styleClear() { if ( this.clearFilterButtonElement && - this.clearFilterButtonElement.getAttribute('data-active-class') && - this.clearFilterButtonElement.getAttribute('data-inactive-class') + this.clearFilterButtonElement.getAttribute("data-active-class") && + this.clearFilterButtonElement.getAttribute("data-inactive-class") ) { let active = true; const elements = Array.from(this.formElement.elements); elements.forEach((el) => { - if (el.tagName === 'INPUT' || el.tagName === 'SELECT') { - const type = el.getAttribute('type').toLowerCase(); + if (el.tagName === "INPUT" || el.tagName === "SELECT") { + const type = el.getAttribute("type").toLowerCase(); switch (type) { - case 'text': - case 'password': - case 'textarea': - case 'hidden': - if (el.getAttribute('value') !== '') { + case "text": + case "password": + case "textarea": + case "hidden": + if (el.getAttribute("value") !== "") { active = false; } break; - case 'radio': - case 'checkbox': + case "radio": + case "checkbox": if ((el as HTMLInputElement).checked) { active = false; } break; - case 'select-one': - case 'select-multi': - if (el.getAttribute('selectedIndex') !== '-1') { + case "select-one": + case "select-multi": + if (el.getAttribute("selectedIndex") !== "-1") { active = false; } break; @@ -459,12 +532,18 @@ export class FilterComponent { if (active) { this.clearFilterButtonElement.classList.remove( - this.clearFilterButtonElement.getAttribute('data-inactive-class') + this.clearFilterButtonElement.getAttribute("data-inactive-class") + ); + this.clearFilterButtonElement.classList.add( + this.clearFilterButtonElement.getAttribute("data-active-class") ); - this.clearFilterButtonElement.classList.add(this.clearFilterButtonElement.getAttribute('data-active-class')); } else { - this.clearFilterButtonElement.classList.remove(this.clearFilterButtonElement.getAttribute('data-active-class')); - this.clearFilterButtonElement.classList.add(this.clearFilterButtonElement.getAttribute('data-inactive-class')); + this.clearFilterButtonElement.classList.remove( + this.clearFilterButtonElement.getAttribute("data-active-class") + ); + this.clearFilterButtonElement.classList.add( + this.clearFilterButtonElement.getAttribute("data-inactive-class") + ); } } } @@ -474,28 +553,28 @@ export class FilterComponent { const elements = Array.from(this.formElement.elements); elements.forEach((el) => { - if (el.tagName === 'INPUT') { - const type = el.getAttribute('type').toLowerCase(); + if (el.tagName === "INPUT") { + const type = el.getAttribute("type").toLowerCase(); switch (type) { - case 'text': - case 'password': - case 'textarea': - case 'hidden': - el.setAttribute('value', ''); + case "text": + case "password": + case "textarea": + case "hidden": + el.setAttribute("value", ""); break; - case 'radio': - case 'checkbox': + case "radio": + case "checkbox": if ((el as HTMLInputElement).checked) { (el as HTMLInputElement).checked = false; } break; - case 'select-one': - case 'select-multi': - el.setAttribute('selectedIndex', '-1'); - el.setAttribute('value', ''); + case "select-one": + case "select-multi": + el.setAttribute("selectedIndex", "-1"); + el.setAttribute("value", ""); el.dispatchEvent(this.jsChange); break; @@ -503,20 +582,20 @@ export class FilterComponent { break; } } - if (el.tagName === 'SELECT') { - el.setAttribute('selectedIndex', '-1'); - (el as HTMLSelectElement).value = ''; + if (el.tagName === "SELECT") { + el.setAttribute("selectedIndex", "-1"); + (el as HTMLSelectElement).value = ""; el.dispatchEvent(this.jsChange); } - if (el.tagName === 'BUTTON' && el.hasAttribute('aria-haspopup')) { + if (el.tagName === "BUTTON" && el.hasAttribute("aria-haspopup")) { el.dispatchEvent(this.jsChange); } this.clearElement(el as HTMLElement); console.log(el.tagName); }); - const filterFormCleared = document.createEvent('HTMLEvents'); - filterFormCleared.initEvent('filterFormCleared', false, true); + const filterFormCleared = document.createEvent("HTMLEvents"); + filterFormCleared.initEvent("filterFormCleared", false, true); document.dispatchEvent(filterFormCleared); this.styleClear();