From 246a7338cc8af53d00af93794ab2ad785b62266d Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Thu, 26 Dec 2024 15:47:34 +0100 Subject: [PATCH] phx-trigger-action: check for cloned tree (#3601) Fixes https://github.com/phoenixframework/phoenix_live_view/issues/3591. --- assets/js/phoenix_live_view/dom.js | 2 +- assets/js/phoenix_live_view/dom_patch.js | 13 ++++++++++++- assets/test/dom_test.js | 6 ++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/assets/js/phoenix_live_view/dom.js b/assets/js/phoenix_live_view/dom.js index 478a844581..731d33ae99 100644 --- a/assets/js/phoenix_live_view/dom.js +++ b/assets/js/phoenix_live_view/dom.js @@ -467,7 +467,7 @@ let DOM = { isTextualInput(el){ return FOCUSABLE_INPUTS.indexOf(el.type) >= 0 }, isNowTriggerFormExternal(el, phxTriggerExternal){ - return el.getAttribute && el.getAttribute(phxTriggerExternal) !== null + return el.getAttribute && el.getAttribute(phxTriggerExternal) !== null && document.body.contains(el) }, cleanChildNodes(container, phxUpdate){ diff --git a/assets/js/phoenix_live_view/dom_patch.js b/assets/js/phoenix_live_view/dom_patch.js index bced1943d0..664e9c8367 100644 --- a/assets/js/phoenix_live_view/dom_patch.js +++ b/assets/js/phoenix_live_view/dom_patch.js @@ -30,6 +30,7 @@ export default class DOMPatch { let focused = liveSocket.getActiveElement() let {selectionStart, selectionEnd} = focused && DOM.hasSelectionRange(focused) ? focused : {} let phxUpdate = liveSocket.binding(PHX_UPDATE) + let externalFormTriggered = null morphdom(container, clonedTree, { childrenOnly: false, @@ -42,9 +43,19 @@ export default class DOMPatch { DOM.mergeFocusedInput(fromEl, toEl) return false } + if(DOM.isNowTriggerFormExternal(toEl, liveSocket.binding(PHX_TRIGGER_ACTION))){ + externalFormTriggered = toEl + } } }) + if(externalFormTriggered){ + liveSocket.unload() + // use prototype's submit in case there's a form control with name or id of "submit" + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit + Object.getPrototypeOf(externalFormTriggered).submit.call(externalFormTriggered) + } + liveSocket.silenceEvents(() => DOM.restoreFocus(focused, selectionStart, selectionEnd)) } @@ -228,7 +239,7 @@ export default class DOMPatch { return false } if(fromEl.type === "number" && (fromEl.validity && fromEl.validity.badInput)){ return false } - // If the element has PHX_REF_SRC, it is loading or locked and awaiting an ack. + // If the element has PHX_REF_SRC, it is loading or locked and awaiting an ack. // If it's locked, we clone the fromEl tree and instruct morphdom to use // the cloned tree as the source of the morph for this branch from here on out. // We keep a reference to the cloned tree in the element's private data, and diff --git a/assets/test/dom_test.js b/assets/test/dom_test.js index 732ef85c1f..137dca013a 100644 --- a/assets/test/dom_test.js +++ b/assets/test/dom_test.js @@ -181,9 +181,15 @@ describe("DOM", () => { test("isNowTriggerFormExternal", () => { let form form = tag("form", {"phx-trigger-external": ""}, "") + document.body.appendChild(form) expect(DOM.isNowTriggerFormExternal(form, "phx-trigger-external")).toBe(true) form = tag("form", {}, "") + document.body.appendChild(form) + expect(DOM.isNowTriggerFormExternal(form, "phx-trigger-external")).toBe(false) + + // not in the DOM -> false + form = tag("form", {"phx-trigger-external": ""}, "") expect(DOM.isNowTriggerFormExternal(form, "phx-trigger-external")).toBe(false) })