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