diff --git a/src/pat/modal/modal.js b/src/pat/modal/modal.js index fc82bb17e..821f58e5e 100644 --- a/src/pat/modal/modal.js +++ b/src/pat/modal/modal.js @@ -67,7 +67,7 @@ export default Base.extend({ inject.init(this.$el, opts); }, - _init_div1() { + async _init_div1() { const $header = $("
"); if (this.options.closing.indexOf("close-button") !== -1) { $( @@ -101,13 +101,20 @@ export default Base.extend({ if (document.activeElement) { document.activeElement.focus(); } - this._init_handlers(); + this.resize(); this.setPosition(); + $("body").addClass("modal-active"); this.el.dispatchEvent( new Event("pat-modal-ready", { bubbles: true, cancelable: true }) ); + + // Wait a bit to let any pattern initializations settle before initializing handlers. + await utils.timeout(2); + this._init_handlers(); + const modal_observer = new MutationObserver(this._init_handlers.bind(this)); + modal_observer.observe(this.el, { childList: true, subtree: true }); }, _init_handlers() { @@ -225,9 +232,10 @@ export default Base.extend({ $("body").removeClass("modal-panel"); }, - destroy_inject() { - const form = this.el.querySelector("form.pat-inject"); - if (form) { + destroy_inject(e) { + const button = e.target; + const form = button.form; + if (form && form.classList.contains("pat-inject")) { // if the modal contains a for mwith pat-inject, wait for injection // to be finished and then destroy the modal. const destroy_handler = () => { @@ -241,8 +249,7 @@ export default Base.extend({ destroy_handler.bind(this) ); } else { - // if working without injection, destroy after waiting a tick to let - // eventually registered on-submit handlers kick in first. + // if working without form injection, just destroy. this.destroy(); } }, diff --git a/src/pat/modal/modal.test.js b/src/pat/modal/modal.test.js index 481032975..ab8bf5ac4 100644 --- a/src/pat/modal/modal.test.js +++ b/src/pat/modal/modal.test.js @@ -196,6 +196,7 @@ describe("pat-modal", function () { `; registry.scan(document.body); await utils.timeout(1); // wait a tick for async to settle. + await utils.timeout(2); // wait a tick for async to settle. document.querySelector("button.close-panel[type=submit]").click(); await utils.timeout(1); // wait a tick for pat-inject to settle. @@ -206,6 +207,65 @@ describe("pat-modal", function () { expect(document.querySelector("#target2").textContent).toBe("there"); }); + it("Submit form, do injection and close overlay with multiple forms.", async function () { + await import("../inject/inject"); + const registry = (await import("../../core/registry")).default; + + jest.spyOn($, "ajax").mockImplementation(() => deferred); + answer( + `
hello.
there
` + ); + + document.body.innerHTML = ` +
+
+ +
+
+ +
+
+
+
+ `; + registry.scan(document.body); + await utils.timeout(1); // wait a tick for async to settle. + await utils.timeout(2); // wait a tick for async to settle. + + document.querySelector(".form2 button.close-panel[type=submit]").click(); + await utils.timeout(1); // wait a tick for pat-inject to settle. + await utils.timeout(1); // wait a tick for pat-modal destroy to settle. + + expect(document.querySelector(".pat-modal")).toBe(null); + expect(document.querySelector("#target").textContent).toBe("hello."); + }); + + it("Initialize modal also when modal contents change.", async function () { + document.body.innerHTML = ` +
+
+ `; + const instance = new pattern(document.querySelector(".pat-modal")); + const spy_init_handlers = jest.spyOn(instance, "_init_handlers"); + expect(spy_init_handlers).toHaveBeenCalledTimes(0); + + // first call is invoked after some ticks to allow any other + // patterns to modify the dom before the handlers are initialized. + await utils.timeout(2); // wait for init to happen. + expect(spy_init_handlers).toHaveBeenCalledTimes(1); + + // Provoke a DOM subtree change and the MutationObserver to kick in + document.querySelector(".pat-modal").innerHTML = "
"; + await utils.timeout(1); // wait a tick for async to settle. + expect(spy_init_handlers).toHaveBeenCalledTimes(2); + }); + it("Ensure destroy callback isn't called multiple times.", async function () { document.body.innerHTML = `
@@ -215,12 +275,13 @@ describe("pat-modal", function () { const instance = new pattern(document.querySelector(".pat-modal")); await utils.timeout(1); // wait a tick for async to settle. + await utils.timeout(2); // wait a tick for async to settle. const spy_destroy = jest.spyOn(instance, "destroy"); // ``destroy`` was already initialized with instantiating the pattern above. // Call init again for new instantiation. - instance.init($(".pat-modal")); + await instance.init($(".pat-modal")); document.querySelector("#close-modal").click(); await utils.timeout(1); // wait a tick for pat-modal destroy to settle. @@ -247,13 +308,14 @@ describe("pat-modal", function () { pattern_inject.init($(".pat-inject")); const instance = new pattern(document.querySelector(".pat-modal")); await utils.timeout(1); // wait a tick for async to settle. + await utils.timeout(2); // wait a tick for async to settle. const spy_destroy = jest.spyOn(instance, "destroy"); const spy_destroy_inject = jest.spyOn(instance, "destroy_inject"); // ``destroy`` was already initialized with instantiating the pattern above. // Call init again for new instantiation. - instance.init($(".pat-modal")); + await instance.init($(".pat-modal")); document.querySelector("#close-modal").click(); await utils.timeout(1); // wait a tick for pat-inject to settle. @@ -266,6 +328,7 @@ describe("pat-modal", function () { pattern_inject.init($(".pat-inject")); new pattern(document.querySelector(".pat-modal")); await utils.timeout(1); // wait a tick for async to settle. + await utils.timeout(2); // wait a tick for async to settle. document.querySelector("#close-modal").click(); await utils.timeout(1); // wait a tick for pat-inject to settle. // Previous mocks still active.