From 1b19844cd907fa0ebb47ee003cd0d161ed14ee2b Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Sun, 12 Jan 2025 22:45:01 +0100 Subject: [PATCH 1/9] fix(core dom): Do not break querySelectorAllAndMe, if passed element is not a real element but something like a text node. --- src/core/dom.js | 2 +- src/core/dom.test.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/core/dom.js b/src/core/dom.js index bee3d91c3..aade013cf 100644 --- a/src/core/dom.js +++ b/src/core/dom.js @@ -60,7 +60,7 @@ const toNodeArray = (nodes) => { * @returns {Array} - The DOM nodes found. */ const querySelectorAllAndMe = (el, selector) => { - if (!el) { + if (!el || !el.querySelectorAll) { return []; } diff --git a/src/core/dom.test.js b/src/core/dom.test.js index f8159f38e..ef6f58e8b 100644 --- a/src/core/dom.test.js +++ b/src/core/dom.test.js @@ -174,6 +174,15 @@ describe("core.dom tests", () => { done(); }); + + it("return empty list, if the element is not a real element.", (done) => { + const res = dom.querySelectorAllAndMe("text", ".selector"); + expect(Array.isArray(res)).toBe(true); + expect(res.length).toBe(0); + + done(); + }); + }); describe("wrap tests", () => { From 3369d3a3287d9bed0110b754cc8e853e03ed1c80 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 3 Jan 2024 11:41:07 +0100 Subject: [PATCH 2/9] pat-inject: Improve code for updating the history object. --- src/pat/inject/inject.js | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/pat/inject/inject.js b/src/pat/inject/inject.js index 4aea4b3f5..0bc5f301e 100644 --- a/src/pat/inject/inject.js +++ b/src/pat/inject/inject.js @@ -468,22 +468,20 @@ const inject = { _update_history(cfg, trigger, title) { // History support. if subform is submitted, append form params - const glue = cfg.url.indexOf("?") > -1 ? "&" : "?"; - if (cfg.history === "record" && "pushState" in history) { - if (cfg.params) { - history.pushState( - { url: cfg.url + glue + cfg.params }, - "", - cfg.url + glue + cfg.params - ); - } else { - history.pushState({ url: cfg.url }, "", cfg.url); - } - // Also inject title element if we have one - if (title) - this._inject(trigger, title, $("title"), { - action: "element", - }); + if (cfg.history !== "record" || !history?.pushState) { + return; + } + let url = cfg.url; + const glue = url.indexOf("?") > -1 ? "&" : "?"; + if (cfg.params) { + url = `${url}${glue}${cfg.params}`; + } + history.pushState({ url: url }, "", url); + // Also inject title element if we have one + if (title) { + this._inject(trigger, title, $("title"), { + action: "element", + }); } }, From 5c3c1aa0868299fc5b6031291965f127e2e37fe2 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Fri, 5 Jan 2024 00:58:12 +0100 Subject: [PATCH 3/9] pat-inject: Minor code optimization. --- src/pat/inject/inject.js | 127 +++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 60 deletions(-) diff --git a/src/pat/inject/inject.js b/src/pat/inject/inject.js index 0bc5f301e..91881eab6 100644 --- a/src/pat/inject/inject.js +++ b/src/pat/inject/inject.js @@ -38,9 +38,9 @@ parser.addArgument("class"); // Add a class to the injected content. parser.addArgument("history", "none", ["none", "record"]); parser.addArgument("push-marker"); parser.addArgument("scroll"); -// XXX: this should not be here but the parser would bail on -// unknown parameters and expand/collapsible need to pass the url -// to us + +// Note: this should not be here but the parser would bail on unknown +// parameters and expand/collapsible need to pass the url to us. parser.addArgument("url"); const inject = { @@ -56,12 +56,15 @@ const inject = { // from pat-inject. Waiting a tick in pat-inject solves this - // pat-validation's event handlers are initialized first. await utils.timeout(1); + + const el = utils.jqToNode($el); + const cfgs = this.extractConfig($el, opts); if (cfgs.some((e) => e.history === "record") && !("pushState" in history)) { // if the injection shall add a history entry and HTML5 pushState // is missing, then don't initialize the injection. log.warn("HTML5 pushState is missing, aborting"); - return $el; + return; } $el.data("pat-inject", cfgs); @@ -70,18 +73,19 @@ const inject = { // exists in the page, we do not activate the injection // but instead just change the anchors href. - // XXX: This is used in only one project for linked - // fullcalendars, it's sanity is wonky and we should - // probably solve it differently. - if ($el.is("a") && $(cfgs[0].nextHref).length > 0) { + // Note: This is used in only one project for linked fullcalendars, + // it's sanity is wonky and we should probably solve it differently. + if (el.nodeName === "A" && $(cfgs[0].nextHref).length > 0) { log.debug( "Skipping as next href is anchor, which already exists", cfgs[0].nextHref ); - // XXX: reconsider how the injection enters exhausted state - return $el.attr({ - href: (window.location.href.split("#")[0] || "") + cfgs[0].nextHref, - }); + // TODO: reconsider how the injection enters exhausted state + el.setAttribute( + "href", + (window.location.href.split("#")[0] || "") + cfgs[0].nextHref + ); + return $el; } } if (cfgs[0].pushMarker) { @@ -89,7 +93,7 @@ const inject = { log.debug("received push message: " + data); if (data == cfgs[0].pushMarker) { log.debug("re-injecting " + data); - this.onTrigger({ currentTarget: $el[0] }); + this.onTrigger({ currentTarget: el }); } }); } @@ -104,10 +108,10 @@ const inject = { } }); // setup event handlers - if ($el[0]?.tagName === "FORM") { - log.debug("Initializing form with injection on", $el[0]); + if (el?.tagName === "FORM") { + log.debug("Initializing form with injection on", el); events.add_event_listener( - $el[0], + el, "submit", "pat-inject--form-submit", (e) => { @@ -123,7 +127,7 @@ const inject = { this.onTrigger(e); } ); - } else if ($el.is(".pat-subform")) { + } else if (el?.matches(".pat-subform")) { log.debug("Initializing subform with injection"); } else { $el.on("click.pat-inject", this.onTrigger.bind(this)); @@ -131,22 +135,24 @@ const inject = { break; case "autoload": if (!cfgs[0].delay) { - this.onTrigger({ currentTarget: $el[0] }); + this.onTrigger({ currentTarget: el }); } else { // generate UID const uid = Math.random().toString(36); - $el.attr("data-pat-inject-uid", uid); + el.setAttribute("data-pat-inject-uid", uid); // function to trigger the autoload and mark as triggered const delayed_trigger = (uid_) => { // Check if the element has been removed from the dom - const still_there = $( - "[data-pat-inject-uid='" + uid_ + "']" + const still_there = document.querySelector( + `[data-pat-inject-uid="${uid_}"]` ); - if (still_there.length == 0) return false; + if (!still_there) { + return false; + } $el.data("pat-inject-autoloaded", true); - this.onTrigger({ currentTarget: $el[0] }); + this.onTrigger({ currentTarget: el }); return true; }; window.setTimeout( @@ -164,7 +170,7 @@ const inject = { } } - log.debug("initialised:", $el); + log.debug("initialised:", el); return $el; }, @@ -183,10 +189,11 @@ const inject = { // We want an AJAX request instead. e.preventDefault && e.preventDefault(); - const $el = $(e.currentTarget); + const el = e.currentTarget; + const $el = $(el); let cfgs = $el.data("pat-inject"); - if ($el[0].tagName === "FORM" && e.type === "submit") { - const form = $el[0]; + if (el.tagName === "FORM" && e.type === "submit") { + const form = el; const submitter = e.submitter; // Do not submit invalid forms, if validation is active. @@ -239,19 +246,20 @@ const inject = { this.execute(cfgs, $el); }, - extractConfig($el, opts) { - opts = $.extend({}, opts); + extractConfig($el, options = {}) { + const el = utils.jqToNode($el); + options = Object.assign({}, options); // copy - const cfgs = parser.parse($el, opts, true); + const cfgs = parser.parse($el, options, true); cfgs.forEach((cfg) => { cfg.$context = $el; - // opts and cfg have priority, fall back to href/action + // options and cfg have priority, fall back to href/action cfg.url = - opts.url || + options.url || cfg.url || - $el.attr("href") || - $el.attr("action") || - $el.parents("form").attr("action") || + el?.getAttribute("href") || + el?.getAttribute("action") || + el?.closest("form")?.getAttribute("action") || ""; // separate selector from url @@ -431,8 +439,8 @@ const inject = { createTarget(selector) { /* create a target that matches the selector * - * XXX: so far we only support #target and create a div with - * that id appended to the body. + * Note: so far we only support #target and create a div with that id + * appended to the body. */ if (selector.slice(0, 1) !== "#") { log.error("only id supported for non-existing target"); @@ -489,6 +497,7 @@ const inject = { /* Set a class on the injected elements and fire the * patterns-injected event. */ + const el = utils.jqToNode($el); $injected .filter((idx, el_) => { return el_.nodeType !== TEXT_NODE; @@ -501,14 +510,14 @@ const inject = { // The event handler should check whether the // injected element and the triggered element are // the same. - $injected.parent().trigger("patterns-injected", [cfg, $el[0], $injected[0]]); + $injected.parent().trigger("patterns-injected", [cfg, el, $injected[0]]); } else { $injected.each((idx, el_) => { // patterns-injected event will be triggered for each injected (non-text) element. if (el_.nodeType !== TEXT_NODE) { $(el_) .addClass(cfg["class"]) - .trigger("patterns-injected", [cfg, $el[0], el_]); + .trigger("patterns-injected", [cfg, el, el_]); } }); } @@ -534,7 +543,7 @@ const inject = { } } - $el[0].dispatchEvent( + el.dispatchEvent( new Event("pat-inject-success", { bubbles: true, cancelable: true }) ); }, @@ -567,7 +576,7 @@ const inject = { } cfgs.forEach((cfg, idx1) => { const perform_inject = () => { - if (cfg.target !== "none") + if (cfg.target !== "none") { cfg.$target.each((idx2, target) => { this._performInjection( target, @@ -578,6 +587,7 @@ const inject = { title ); }); + } }; if (cfg.processDelay) { setTimeout(() => perform_inject(), cfg.processDelay); @@ -674,6 +684,7 @@ const inject = { * Either by making an ajax request or by spoofing an ajax * request when the content is readily available in the current page. */ + const el = utils.jqToNode($el); // get a kinda deep copy, we scribble on it cfgs = cfgs.map((cfg) => $.extend({}, cfg)); if (!this.verifyConfig(cfgs)) { @@ -691,7 +702,7 @@ const inject = { for (const cfg of cfgs) { // Add a execute class on the pat-inject element. if (cfg?.executingClass) { - $el[0].classList.add(cfg.executingClass); + el.classList.add(cfg.executingClass); } // Add a loading class to the target. // Can be used for loading-spinners. @@ -707,12 +718,12 @@ const inject = { // is called before this one, even for non-async local injects. await utils.timeout(1); // Remove the close-panel event listener. - events.remove_event_listener($el[0], "pat-inject--close-panel"); + events.remove_event_listener(el, "pat-inject--close-panel"); // Only close the panel if a close-panel event was catched previously. if (do_close_panel) { do_close_panel = false; // Re-trigger close-panel event if it was caught while injection was in progress. - $el[0].dispatchEvent( + el.dispatchEvent( new Event("close-panel", { bubbles: true, cancelable: true }) ); } @@ -724,16 +735,11 @@ const inject = { // Prevent closing the panel while injection is in progress. let do_close_panel = false; - events.add_event_listener( - $el[0], - "close-panel", - "pat-inject--close-panel", - (e) => { - e.stopPropagation(); - e.stopImmediatePropagation(); - do_close_panel = true; - } - ); + events.add_event_listener(el, "close-panel", "pat-inject--close-panel", (e) => { + e.stopPropagation(); + e.stopImmediatePropagation(); + do_close_panel = true; + }); if (cfgs[0].url.length) { ajax.request($el, { @@ -964,7 +970,7 @@ const inject = { return false; } - const el = $el[0]; + const el = utils.jqToNode($el); // delay: default is 200ms to allow scrolling over and past autoload-visible elements without loading them. const delay = cfgs[0].delay || 200; @@ -1011,18 +1017,20 @@ const inject = { }, _initIdleTrigger($el, delay) { - // XXX TODO: handle item removed from DOM + // TODO: handle item removed from DOM const timeout = parseInt(delay, 10); let timer; + const el = utils.jqToNode($el); + const onTimeout = () => { - this.onTrigger({ currentTarget: $el[0] }); + this.onTrigger({ currentTarget: el }); unsub(); clearTimeout(timer); }; const onInteraction = utils.debounce(() => { - if (!document.body.contains($el[0])) { + if (!document.body.contains(el)) { unsub(); return; } @@ -1052,7 +1060,6 @@ const inject = { ); }, - // XXX: simple so far to see what the team thinks of the idea registerTypeHandler(type, handler) { this.handlers[type] = handler; }, @@ -1083,7 +1090,7 @@ $(document).on("patterns-injected.inject", async (ev, cfg, trigger, injected) => * Remove the "loading-class" classes from all injection targets and * then scan the injected content for new patterns. */ - if (cfg && cfg.skipPatInjectHandler) { + if (cfg?.skipPatInjectHandler) { // Allow skipping this handler but still have other handlers in other // patterns listen to ``patterns-injected``. return; From fa074698aeaf9ad06dcbb8c80647ebb33c1ffe89 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Sun, 12 Jan 2025 20:44:41 +0100 Subject: [PATCH 4/9] pat-inject: Minor code optimization: Consistently use nodeName over tagName. nodeName returns for Elements the same value but also works on document fragments and other type of nodes. --- src/pat/inject/inject.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pat/inject/inject.js b/src/pat/inject/inject.js index 91881eab6..18dd8cb69 100644 --- a/src/pat/inject/inject.js +++ b/src/pat/inject/inject.js @@ -108,7 +108,7 @@ const inject = { } }); // setup event handlers - if (el?.tagName === "FORM") { + if (el?.nodeName === "FORM") { log.debug("Initializing form with injection on", el); events.add_event_listener( el, @@ -192,7 +192,7 @@ const inject = { const el = e.currentTarget; const $el = $(el); let cfgs = $el.data("pat-inject"); - if (el.tagName === "FORM" && e.type === "submit") { + if (el.nodeName === "FORM" && e.type === "submit") { const form = el; const submitter = e.submitter; @@ -570,7 +570,7 @@ const inject = { sources$ && sources$[sources$.length - 1] && sources$[sources$.length - 1][0] && - sources$[sources$.length - 1][0].nodeName == "TITLE" + sources$[sources$.length - 1][0].nodeName === "TITLE" ) { title = sources$[sources$.length - 1]; } @@ -882,7 +882,7 @@ const inject = { .map(([tag, attr]) => `${tag}[${attr}]`) .join(", "); for (const el_ of page.querySelectorAll(rebase_selector)) { - const attr = this._rebaseAttrs[el_.tagName.toLowerCase()]; + const attr = this._rebaseAttrs[el_.nodeName.toLowerCase()]; let value = el_.getAttribute(attr); if ( From d5f69c2542cbc2c3f2c2ccec967050ae427d0f00 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Sun, 12 Jan 2025 21:24:16 +0100 Subject: [PATCH 5/9] pat-inject: Minor code optimization: Rework most forEach to for loops. --- src/pat/inject/inject.js | 54 ++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/pat/inject/inject.js b/src/pat/inject/inject.js index 18dd8cb69..86cdff461 100644 --- a/src/pat/inject/inject.js +++ b/src/pat/inject/inject.js @@ -102,11 +102,11 @@ const inject = { } else { switch (cfgs[0].trigger) { case "default": - cfgs.forEach((cfg) => { + for (const cfg of cfgs) { if (cfg.delay) { cfg.processDelay = cfg.delay; } - }); + } // setup event handlers if (el?.nodeName === "FORM") { log.debug("Initializing form with injection on", el); @@ -218,6 +218,7 @@ const inject = { cfgs = this.extractConfig($(cfg_node), opts); } + // store the params of the form in the config, to be used by history for (const cfg of cfgs) { cfg.params = $.param($el.serializeArray()); } @@ -230,13 +231,13 @@ const inject = { submitSubform($sub) { /* This method is called from pat-subform */ - const $el = $sub.parents("form"); + const $el = $($sub[0].closest("form")); const cfgs = $sub.data("pat-inject"); // store the params of the subform in the config, to be used by history - $(cfgs).each((i, v) => { - v.params = $.param($sub.serializeArray()); - }); + for (const cfg of cfgs) { + cfg.params = $.param($sub.serializeArray()); + } try { $el.trigger("patterns-inject-triggered"); @@ -251,7 +252,7 @@ const inject = { options = Object.assign({}, options); // copy const cfgs = parser.parse($el, options, true); - cfgs.forEach((cfg) => { + for (const cfg of cfgs) { cfg.$context = $el; // options and cfg have priority, fall back to href/action cfg.url = @@ -283,7 +284,7 @@ const inject = { } } cfg.processDelay = 0; - }); + } return cfgs; }, @@ -577,7 +578,7 @@ const inject = { cfgs.forEach((cfg, idx1) => { const perform_inject = () => { if (cfg.target !== "none") { - cfg.$target.each((idx2, target) => { + for (const target of cfg.$target) { this._performInjection( target, $el, @@ -586,7 +587,7 @@ const inject = { ev.target, title ); - }); + } } }; if (cfg.processDelay) { @@ -654,13 +655,13 @@ const inject = { } // clean up - cfgs.forEach((cfg) => { + for (const cfg of cfgs) { if ("$injected" in cfg) { cfg.$injected.remove(); } cfg.$target.removeClass(cfg.loadingClass); $el.removeClass(cfg.executingClass); - }); + } $el.off("pat-ajax-success.pat-inject"); $el.off("pat-ajax-error.pat-inject"); @@ -1039,25 +1040,36 @@ const inject = { }, timeout); const unsub = () => { - ["scroll", "resize"].forEach((e) => - window.removeEventListener(e, onInteraction) - ); - [ + for (const event of ["scroll", "resize"]) { + window.removeEventListener(event, onInteraction); + } + for (const event of [ "click", "keypress", "keyup", "mousemove", "touchstart", "touchend", - ].forEach((e) => document.removeEventListener(e, onInteraction)); + ]) { + document.removeEventListener(event, onInteraction); + } }; onInteraction(); - ["scroll", "resize"].forEach((e) => window.addEventListener(e, onInteraction)); - ["click", "keypress", "keyup", "mousemove", "touchstart", "touchend"].forEach( - (e) => document.addEventListener(e, onInteraction) - ); + for (const event of ["scroll", "resize"]) { + window.addEventListener(event, onInteraction); + } + for (const event of [ + "click", + "keypress", + "keyup", + "mousemove", + "touchstart", + "touchend", + ]) { + document.addEventListener(event, onInteraction); + } }, registerTypeHandler(type, handler) { From 9a55aa312b33baf5385cd3adabc127e3e89acb65 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Sun, 12 Jan 2025 22:13:51 +0100 Subject: [PATCH 6/9] pat-inject: Code modernization: Remove dependency on jquery-ext. --- src/pat/inject/inject.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pat/inject/inject.js b/src/pat/inject/inject.js index 86cdff461..69fefdbe8 100644 --- a/src/pat/inject/inject.js +++ b/src/pat/inject/inject.js @@ -1,4 +1,3 @@ -import "../../core/jquery-ext"; // for findInclusive import "../../core/polyfills"; // SubmitEvent.submitter for Safari < 15.4 and jsDOM import $ from "jquery"; import ajax from "../ajax/ajax"; @@ -461,9 +460,11 @@ const inject = { } let $src; $src = $source.safeClone(); - $src.findInclusive("img").on("load", (e) => { - $(e.currentTarget).trigger("pat-inject-content-loaded"); - }); + for (const img of dom.querySelectorAllAndMe($src[0], "img")) { + $(img).on("load", (e) => { + $(e.currentTarget).trigger("pat-inject-content-loaded"); + }); + } const $injected = cfg.$injected || $src; // Now the injection actually happens. @@ -529,7 +530,7 @@ const inject = { // 2) getting the element to scroll to (if not "top") const scroll_target = ["top", "target"].includes(cfg.scroll) ? cfg.$target[0] - : $injected.findInclusive(cfg.scroll)[0]; + : dom.querySelectorAllAndMe($injected[0], cfg.scroll); const scroll_container = dom.find_scroll_container( scroll_target, From cdc346ad2c66e7704bc2606eac1fada70b2df19b Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Sun, 12 Jan 2025 22:18:44 +0100 Subject: [PATCH 7/9] pat-inject: Minor code optimization. --- src/pat/inject/inject.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pat/inject/inject.js b/src/pat/inject/inject.js index 69fefdbe8..bca030987 100644 --- a/src/pat/inject/inject.js +++ b/src/pat/inject/inject.js @@ -458,8 +458,7 @@ const inject = { if (cfg.sourceMod === "content") { $source = $source.contents(); } - let $src; - $src = $source.safeClone(); + const $src = $source.safeClone(); for (const img of dom.querySelectorAllAndMe($src[0], "img")) { $(img).on("load", (e) => { $(e.currentTarget).trigger("pat-inject-content-loaded"); From b0f94fb058a7067e19bc9c3101a11dfa49ff8dd2 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 17 May 2023 00:11:41 +0200 Subject: [PATCH 8/9] maint(pat inject): Modernize some parts of pat inject. --- src/pat/inject/inject.js | 91 +++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 38 deletions(-) diff --git a/src/pat/inject/inject.js b/src/pat/inject/inject.js index bca030987..f775f6f8d 100644 --- a/src/pat/inject/inject.js +++ b/src/pat/inject/inject.js @@ -347,7 +347,6 @@ const inject = { return false; } cfg.$target = this.createTarget(cfg.target); - cfg.$injected = cfg.$target; } return true; }, @@ -455,23 +454,36 @@ const inject = { /* Called after the XHR has succeeded and we have a new $source * element to inject. */ - if (cfg.sourceMod === "content") { - $source = $source.contents(); - } - const $src = $source.safeClone(); - for (const img of dom.querySelectorAllAndMe($src[0], "img")) { - $(img).on("load", (e) => { - $(e.currentTarget).trigger("pat-inject-content-loaded"); - }); + const wrapper = document.createElement("div"); + if ($source.length > 0) { + if (cfg.sourceMod === "content") { + wrapper.innerHTML = $source[0].innerHTML; + } else { + wrapper.innerHTML = $source[0].outerHTML; + } + + for (const img of wrapper.querySelectorAll("img")) { + events.add_event_listener( + img, + "load", + "inject_img_load", + (e) => { + $(e.currentTarget).trigger("pat-inject-content-loaded"); + }, + { once: true } + ); + } } - const $injected = cfg.$injected || $src; + // Copy, because after insertion wrapper.children is empty. + const source_nodes = [...wrapper.childNodes]; + // Now the injection actually happens. - if (this._inject(trigger, $src, $(target), cfg)) { + if (this._inject(trigger, source_nodes, target, cfg)) { // Update history this._update_history(cfg, trigger, title); // Post-injection - this._afterInjection($el, $injected, cfg); + this._afterInjection($el, $(source_nodes), cfg); } }, @@ -488,9 +500,12 @@ const inject = { history.pushState({ url: url }, "", url); // Also inject title element if we have one if (title) { - this._inject(trigger, title, $("title"), { - action: "element", - }); + const title_el = document.querySelector("title"); + if (title_el) { + this._inject(trigger, title, title_el, { + action: "element", + }); + } } }, @@ -761,45 +776,45 @@ const inject = { } }, - _inject(trigger, $source, $target, cfg) { - // action to jquery method mapping, except for "content" - // and "element" - const method = { - contentbefore: "prepend", - contentafter: "append", - elementbefore: "before", - elementafter: "after", - }[cfg.action]; - + _inject(trigger, source, target, cfg) { if (cfg.source === "none") { - $target.replaceWith(""); + // Special case. Clear the target after ajax call. + target.replaceWith(""); return true; } - if ($source.length === 0) { - log.warn("Aborting injection, source not found:", $source); + if (source.length === 0) { + log.warn("Aborting injection, source not found:", source); $(trigger).trigger("pat-inject-missingSource", { url: cfg.url, selector: cfg.source, }); return false; } - if (cfg.target === "none") + if (cfg.target === "none") { // Special case. Don't do anything, we don't want any result return true; - if ($target.length === 0) { - log.warn("Aborting injection, target not found:", $target); + } + if (!target) { + log.warn("Aborting injection, target not found:", target); $(trigger).trigger("pat-inject-missingTarget", { selector: cfg.target, }); return false; } - if (cfg.action === "content") { - $target.empty().append($source); - } else if (cfg.action === "element") { - $target.replaceWith($source); - } else { - $target[method]($source); - } + + // cfg.action to DOM method mapping + const method = { + content: "replaceChildren", + contentafter: "append", + contentbefore: "prepend", + element: "replaceWith", + elementafter: "after", + elementbefore: "before", + }[cfg.action]; + + // Inject the content HERE! + target[method](...source); + return true; }, From 8a3af538654c7b6919d5e0b84b22a0603fa7a0b1 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Sun, 12 Jan 2025 23:41:53 +0100 Subject: [PATCH 9/9] maint(pat-inject): Use create_uuid for generating the temporary autoload uuid and remove it again afterwards. --- src/pat/inject/inject.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/pat/inject/inject.js b/src/pat/inject/inject.js index f775f6f8d..77ac9c274 100644 --- a/src/pat/inject/inject.js +++ b/src/pat/inject/inject.js @@ -6,6 +6,7 @@ import events from "../../core/events"; import logging from "../../core/logging"; import Parser from "../../core/parser"; import registry from "../../core/registry"; +import create_uuid from "../../core/uuid"; import utils from "../../core/utils"; const log = logging.getLogger("pat.inject"); @@ -136,15 +137,15 @@ const inject = { if (!cfgs[0].delay) { this.onTrigger({ currentTarget: el }); } else { - // generate UID - const uid = Math.random().toString(36); - el.setAttribute("data-pat-inject-uid", uid); + // generate UUID + const uuid = create_uuid(); + el.setAttribute("data-pat-inject-uuid", uuid); // function to trigger the autoload and mark as triggered - const delayed_trigger = (uid_) => { + const delayed_trigger = (uuid_) => { // Check if the element has been removed from the dom const still_there = document.querySelector( - `[data-pat-inject-uid="${uid_}"]` + `[data-pat-inject-uuid="${uuid_}"]` ); if (!still_there) { return false; @@ -152,10 +153,12 @@ const inject = { $el.data("pat-inject-autoloaded", true); this.onTrigger({ currentTarget: el }); + // Cleanup again. + still_there.removeAttribute("data-pat-inject-uuid"); return true; }; window.setTimeout( - delayed_trigger.bind(null, uid), + delayed_trigger.bind(null, uuid), cfgs[0].delay ); }