From 1c6401a8b38326adf1356588f4638bb43f36fc62 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Sun, 16 Nov 2025 12:18:54 +0100 Subject: [PATCH] Convert page_publication_fields into custom element MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Much better to have a self contained html element that handles it’s own inner state instead of having to init the addEventListener for a custom dialog event which we have to set up on each page where the page configure dialog might be used. Signed-off-by: Thomas von Deyen --- app/javascript/alchemy_admin.js | 2 - .../alchemy_admin/components/index.js | 1 + .../page_publication_fields.js | 17 +-- .../admin/pages/_publication_fields.html.erb | 54 ++++---- app/views/alchemy/admin/pages/_table.html.erb | 7 - app/views/alchemy/admin/pages/edit.html.erb | 1 - .../page_publication_fields.spec.js | 125 ++++++++++++++++++ 7 files changed, 163 insertions(+), 44 deletions(-) rename app/javascript/alchemy_admin/{ => components}/page_publication_fields.js (56%) create mode 100644 spec/javascript/alchemy_admin/components/page_publication_fields.spec.js diff --git a/app/javascript/alchemy_admin.js b/app/javascript/alchemy_admin.js index aaa3212e18..d12d859245 100644 --- a/app/javascript/alchemy_admin.js +++ b/app/javascript/alchemy_admin.js @@ -17,7 +17,6 @@ import { LinkDialog } from "alchemy_admin/link_dialog" import pleaseWaitOverlay from "alchemy_admin/please_wait_overlay" import Sitemap from "alchemy_admin/sitemap" import Spinner from "alchemy_admin/spinner" -import PagePublicationFields from "alchemy_admin/page_publication_fields" import { reloadPreview } from "alchemy_admin/components/preview_window" import { openConfirmDialog } from "alchemy_admin/confirm_dialog" @@ -47,7 +46,6 @@ Object.assign(Alchemy, { pleaseWaitOverlay, Sitemap, Spinner, - PagePublicationFields, reloadPreview }) diff --git a/app/javascript/alchemy_admin/components/index.js b/app/javascript/alchemy_admin/components/index.js index a7239c14f0..49421aaf49 100644 --- a/app/javascript/alchemy_admin/components/index.js +++ b/app/javascript/alchemy_admin/components/index.js @@ -23,6 +23,7 @@ import "alchemy_admin/components/link_buttons" import "alchemy_admin/components/node_select" import "alchemy_admin/components/uploader" import "alchemy_admin/components/overlay" +import "alchemy_admin/components/page_publication_fields" import "alchemy_admin/components/page_select" import "alchemy_admin/components/picture_thumbnail" import "alchemy_admin/components/preview_window" diff --git a/app/javascript/alchemy_admin/page_publication_fields.js b/app/javascript/alchemy_admin/components/page_publication_fields.js similarity index 56% rename from app/javascript/alchemy_admin/page_publication_fields.js rename to app/javascript/alchemy_admin/components/page_publication_fields.js index 4c8eaee035..8eec40f48a 100644 --- a/app/javascript/alchemy_admin/page_publication_fields.js +++ b/app/javascript/alchemy_admin/components/page_publication_fields.js @@ -1,13 +1,12 @@ // Handles the page publication date fields -export default function () { - document.addEventListener("DialogReady.Alchemy", function (evt) { - const dialog = evt.detail.body - const public_on_field = dialog.querySelector("#page_public_on") - const public_until_field = dialog.querySelector("#page_public_until") - const publication_date_fields = dialog.querySelector( +export class PagePublicationFields extends HTMLElement { + connectedCallback() { + const public_on_field = this.querySelector("#page_public_on") + const public_until_field = this.querySelector("#page_public_until") + const publication_date_fields = this.querySelector( ".page-publication-date-fields" ) - const public_field = dialog.querySelector("#page_public") + const public_field = this.querySelector("#page_public") if (!public_field) return @@ -24,5 +23,7 @@ export default function () { } public_until_field.value = "" }) - }) + } } + +customElements.define("alchemy-page-publication-fields", PagePublicationFields) diff --git a/app/views/alchemy/admin/pages/_publication_fields.html.erb b/app/views/alchemy/admin/pages/_publication_fields.html.erb index 86ca9185e7..fa5f8262c6 100644 --- a/app/views/alchemy/admin/pages/_publication_fields.html.erb +++ b/app/views/alchemy/admin/pages/_publication_fields.html.erb @@ -1,30 +1,32 @@ -<% checkbox = check_box_tag :page_public, nil, @page.public?, name: nil, disabled: @page.attribute_fixed?(:public_on) %> + + <% checkbox = check_box_tag :page_public, nil, @page.public?, name: nil, disabled: @page.attribute_fixed?(:public_on) %> - + <% end %> + -<%= content_tag :div, class: [ - @page.public_on.present? || @page.public_until.present? ? nil : 'hidden', - 'page-publication-date-fields', - 'input-row' -] do %> -
- - <%= alchemy_datepicker @page, :public_on, type: :datetime, - disabled: @page.attribute_fixed?(:public_on) %> -
-
- - <%= alchemy_datepicker @page, :public_until, type: :datetime, - disabled: @page.attribute_fixed?(:public_until) %> -
-<% end %> + <%= content_tag :div, class: [ + @page.public_on.present? || @page.public_until.present? ? nil : 'hidden', + 'page-publication-date-fields', + 'input-row' + ] do %> +
+ + <%= alchemy_datepicker @page, :public_on, type: :datetime, + disabled: @page.attribute_fixed?(:public_on) %> +
+
+ + <%= alchemy_datepicker @page, :public_until, type: :datetime, + disabled: @page.attribute_fixed?(:public_until) %> +
+ <% end %> +
diff --git a/app/views/alchemy/admin/pages/_table.html.erb b/app/views/alchemy/admin/pages/_table.html.erb index 3bc461c101..c8bdb5f0a3 100644 --- a/app/views/alchemy/admin/pages/_table.html.erb +++ b/app/views/alchemy/admin/pages/_table.html.erb @@ -89,10 +89,3 @@ <% end %> <% table.delete_button tooltip: Alchemy.t(:delete_page), confirm_message: Alchemy.t(:confirm_to_delete_page) %> <% end %> - - - diff --git a/app/views/alchemy/admin/pages/edit.html.erb b/app/views/alchemy/admin/pages/edit.html.erb index a487baf697..03018a0c88 100644 --- a/app/views/alchemy/admin/pages/edit.html.erb +++ b/app/views/alchemy/admin/pages/edit.html.erb @@ -151,7 +151,6 @@ if (!not_dirty) Alchemy.pleaseWaitOverlay(false); return not_dirty; }); - Alchemy.PagePublicationFields(); Alchemy.PageLeaveObserver(); }); diff --git a/spec/javascript/alchemy_admin/components/page_publication_fields.spec.js b/spec/javascript/alchemy_admin/components/page_publication_fields.spec.js new file mode 100644 index 0000000000..a86e073572 --- /dev/null +++ b/spec/javascript/alchemy_admin/components/page_publication_fields.spec.js @@ -0,0 +1,125 @@ +import { vi } from "vitest" +import "alchemy_admin/components/page_publication_fields" +import { renderComponent } from "./component.helper" + +describe("alchemy-page-publication-fields", () => { + /** + * @type {HTMLElement | undefined} + */ + let component = undefined + let publicCheckbox = undefined + let publicOnField = undefined + let publicUntilField = undefined + let publicationDateFields = undefined + + beforeEach(() => { + const html = ` + + + + + ` + component = renderComponent("alchemy-page-publication-fields", html) + publicCheckbox = component.querySelector("#page_public") + publicOnField = component.querySelector("#page_public_on") + publicUntilField = component.querySelector("#page_public_until") + publicationDateFields = component.querySelector( + ".page-publication-date-fields" + ) + + // Mock flatpickr instance on the public_on field + publicOnField._flatpickr = { + setDate: vi.fn() + } + }) + + describe("when public checkbox is checked", () => { + beforeEach(() => { + publicCheckbox.checked = true + publicCheckbox.dispatchEvent(new Event("click", { bubbles: true })) + }) + + it("shows the publication date fields", () => { + expect(publicationDateFields.classList.contains("hidden")).toBeFalsy() + }) + + it("sets the public_on date to now", () => { + expect(publicOnField._flatpickr.setDate).toHaveBeenCalled() + const calledWith = publicOnField._flatpickr.setDate.mock.calls[0][0] + expect(calledWith).toBeInstanceOf(Date) + }) + + it("clears the public_until field", () => { + publicUntilField.value = "2025-12-31" + publicCheckbox.dispatchEvent(new Event("click", { bubbles: true })) + expect(publicUntilField.value).toEqual("") + }) + }) + + describe("when public checkbox is unchecked", () => { + beforeEach(() => { + publicCheckbox.checked = false + publicationDateFields.classList.remove("hidden") + publicOnField.value = "2025-01-01" + publicUntilField.value = "2025-12-31" + publicCheckbox.dispatchEvent(new Event("click", { bubbles: true })) + }) + + it("hides the publication date fields", () => { + expect(publicationDateFields.classList.contains("hidden")).toBeTruthy() + }) + + it("clears the public_on field value", () => { + expect(publicOnField.value).toEqual("") + }) + + it("clears the public_until field value", () => { + expect(publicUntilField.value).toEqual("") + }) + + it("does not call flatpickr setDate", () => { + expect(publicOnField._flatpickr.setDate).not.toHaveBeenCalled() + }) + }) + + describe("when public checkbox is missing", () => { + it("does not throw an error", () => { + const html = ` + + + + ` + expect(() => { + renderComponent("alchemy-page-publication-fields", html) + }).not.toThrow() + }) + }) + + describe("toggling multiple times", () => { + it("handles repeated toggling correctly", () => { + // First check + publicCheckbox.checked = true + publicCheckbox.dispatchEvent(new Event("click", { bubbles: true })) + expect(publicationDateFields.classList.contains("hidden")).toBeFalsy() + expect(publicOnField._flatpickr.setDate).toHaveBeenCalledTimes(1) + + // Uncheck + publicCheckbox.checked = false + publicCheckbox.dispatchEvent(new Event("click", { bubbles: true })) + expect(publicationDateFields.classList.contains("hidden")).toBeTruthy() + expect(publicOnField.value).toEqual("") + + // Check again + publicCheckbox.checked = true + publicCheckbox.dispatchEvent(new Event("click", { bubbles: true })) + expect(publicationDateFields.classList.contains("hidden")).toBeFalsy() + expect(publicOnField._flatpickr.setDate).toHaveBeenCalledTimes(2) + }) + }) +})