From c4413e773bd2f014e98956d8732d17309d820da3 Mon Sep 17 00:00:00 2001 From: Sascha Karnatz <122262394+sascha-karnatz@users.noreply.github.com> Date: Tue, 7 May 2024 19:55:06 +0200 Subject: [PATCH] WIP: Remote Partial Component Try to extract the logic to render the content of the dialog and the error handing into a separate component. --- .../alchemy_admin/components/index.js | 1 + .../components/remote_partial.js | 47 ++++++++++++++++++ app/javascript/alchemy_admin/dialog.js | 49 ++++++------------- .../admin/page_editing_feature_spec.rb | 2 +- 4 files changed, 64 insertions(+), 35 deletions(-) create mode 100644 app/javascript/alchemy_admin/components/remote_partial.js diff --git a/app/javascript/alchemy_admin/components/index.js b/app/javascript/alchemy_admin/components/index.js index eff07702e2..34616a3fb0 100644 --- a/app/javascript/alchemy_admin/components/index.js +++ b/app/javascript/alchemy_admin/components/index.js @@ -18,6 +18,7 @@ import "alchemy_admin/components/uploader" import "alchemy_admin/components/overlay" import "alchemy_admin/components/page_select" import "alchemy_admin/components/preview_window" +import "alchemy_admin/components/remote_partial" import "alchemy_admin/components/select" import "alchemy_admin/components/spinner" import "alchemy_admin/components/tags_autocomplete" diff --git a/app/javascript/alchemy_admin/components/remote_partial.js b/app/javascript/alchemy_admin/components/remote_partial.js new file mode 100644 index 0000000000..e2f5b8f526 --- /dev/null +++ b/app/javascript/alchemy_admin/components/remote_partial.js @@ -0,0 +1,47 @@ +class RemotePartial extends HTMLElement { + constructor() { + super() + this.addEventListener("ajax:success", this) + this.addEventListener("ajax:error", this) + } + + handleEvent(event) { + switch (event.type) { + case "ajax:success": + this.#updateContent(event.detail[2]) + break + case "ajax:error": + break + } + } + + connectedCallback() { + this.innerHTML = `` + this.#loadContent().then((content) => (this.innerHTML = content)) + } + + /** + * load content of the given url + * @returns {Promise} + */ + async #loadContent() { + const response = await fetch(this.url, { + headers: { "X-Requested-With": "XMLHttpRequest" } + }) + return await response.text() + } + + #updateContent(xhr) { + const isTextResponse = xhr.getResponseHeader("Content-Type").match(/html/) + + if (isTextResponse) { + this.innerHTML = xhr.responseText + } + } + + get url() { + return this.getAttribute("url") + } +} + +customElements.define("alchemy-remote-partial", RemotePartial) diff --git a/app/javascript/alchemy_admin/dialog.js b/app/javascript/alchemy_admin/dialog.js index e2d5922e2a..e4e04afd31 100644 --- a/app/javascript/alchemy_admin/dialog.js +++ b/app/javascript/alchemy_admin/dialog.js @@ -23,26 +23,20 @@ export class Dialog { this.#build() // Show the dialog with the spinner after a small delay. // in most cases the content of the dialog is already available and the spinner is not flashing - setTimeout(() => this.#openDialog, 300) + this.#openDialog() + this.#select2Handling() - this.#loadContent().then((content) => { - // create the dialog markup and show the dialog - this.#dialogComponent.innerHTML = content - this.#select2Handling() - this.#openDialog() + // bind the current class instance to the DOM - element + // this should be an intermediate solution + // the main goal, is to close the dialog with the turbo:submit-end - event + this.#dialogComponent.dialogClassInstance = this - // bind the current class instance to the DOM - element - // this should be an intermediate solution - // the main goal, is to close the dialog with the turbo:submit-end - event - this.#dialogComponent.dialogClassInstance = this - - // the dialog is closing with the overlay, esc - key, or close - button - // the reject - callback will be fired, because the user decided to close the - // dialog without saving anything - this.#dialogComponent.addEventListener("sl-request-close", () => { - this.#removeDialog() - this.#onReject() - }) + // the dialog is closing with the overlay, esc - key, or close - button + // the reject - callback will be fired, because the user decided to close the + // dialog without saving anything + this.#dialogComponent.addEventListener("sl-after-hide", () => { + this.#removeDialog() + this.#onReject() }) return new Promise((resolve, reject) => { @@ -69,24 +63,13 @@ export class Dialog { }) } - /** - * load content of the given url - * @returns {Promise} - */ - async #loadContent() { - const response = await fetch(this.url, { - headers: { "X-Requested-With": "XMLHttpRequest" } - }) - return await response.text() - } - /** * create and append the dialog container to the DOM */ #build() { this.#dialogComponent = createHtmlElement(` - + `) document.body.append(this.#dialogComponent) @@ -107,10 +90,8 @@ export class Dialog { * remove the dialog from dom */ #removeDialog() { - this.#dialogComponent.addEventListener("sl-after-hide", () => { - this.#dialogComponent.remove() - this.#isOpen = false - }) + this.#dialogComponent.remove() + this.#isOpen = false } /** diff --git a/spec/features/admin/page_editing_feature_spec.rb b/spec/features/admin/page_editing_feature_spec.rb index a431f6e748..3bee15af5f 100644 --- a/spec/features/admin/page_editing_feature_spec.rb +++ b/spec/features/admin/page_editing_feature_spec.rb @@ -239,7 +239,7 @@ let!(:new_parent) { create(:alchemy_page) } it "can change page parent" do - within(".simple_form:first-child") do + within(".simple_form.edit_page") do expect(page).to have_css("#s2id_page_parent_id") select2_search(new_parent.name, from: "Parent") find(".edit_page .submit button").click