From d460309f894c17af2421ae7cb3b3022c982cc7c2 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 25 May 2023 13:47:44 +0100 Subject: [PATCH] DEV: Move modal hide/show logic into a service (#21748) For now, state is still stored in the modal controller. Eventually the controller will be replaced with a component, and the state will be stored in the service. (extracted from https://github.com/discourse/discourse/pull/21304) --- .../discourse/app/lib/show-modal.js | 80 +--------- .../discourse/app/routes/application.js | 46 +----- .../discourse/app/services/modal.js | 137 ++++++++++++++++++ 3 files changed, 143 insertions(+), 120 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/services/modal.js diff --git a/app/assets/javascripts/discourse/app/lib/show-modal.js b/app/assets/javascripts/discourse/app/lib/show-modal.js index ab3a3534345..a0d93958b70 100644 --- a/app/assets/javascripts/discourse/app/lib/show-modal.js +++ b/app/assets/javascripts/discourse/app/lib/show-modal.js @@ -1,5 +1,3 @@ -import I18n from "I18n"; -import { dasherize } from "@ember/string"; import { getOwner } from "discourse-common/lib/get-owner"; /** @@ -26,80 +24,6 @@ export default function showModal(name, opts) { return; } - // We use the container here because modals are like singletons - // in Discourse. Only one can be shown with a particular state. - const route = container.lookup("route:application"); - const modalController = route.controllerFor("modal"); - - modalController.set( - "modalClass", - opts.modalClass || `${dasherize(name).toLowerCase()}-modal` - ); - - const controllerName = opts.admin ? `modals/${name}` : name; - modalController.set("name", controllerName); - - let controller = container.lookup("controller:" + controllerName); - const templateName = opts.templateName || dasherize(name); - - const renderArgs = { into: "modal", outlet: "modalBody" }; - if (controller) { - renderArgs.controller = controllerName; - } else { - // use a basic controller - renderArgs.controller = "basic-modal-body"; - controller = container.lookup(`controller:${renderArgs.controller}`); - } - - if (opts.addModalBodyView) { - renderArgs.view = "modal-body"; - } - - const modalName = `modal/${templateName}`; - const fullName = opts.admin ? `admin/templates/${modalName}` : modalName; - route.render(fullName, renderArgs); - if (opts.title) { - modalController.set("title", I18n.t(opts.title)); - } else if (opts.titleTranslated) { - modalController.set("title", opts.titleTranslated); - } else { - modalController.set("title", null); - } - - if (opts.titleAriaElementId) { - modalController.set("titleAriaElementId", opts.titleAriaElementId); - } - - if (opts.panels) { - modalController.setProperties({ - panels: opts.panels, - selectedPanel: opts.panels[0], - }); - - if (controller.actions.onSelectPanel) { - modalController.set( - "onSelectPanel", - controller.actions.onSelectPanel.bind(controller) - ); - } - - modalController.set( - "modalClass", - `${modalController.get("modalClass")} has-tabs` - ); - } else { - modalController.setProperties({ panels: [], selectedPanel: null }); - } - - controller.set("modal", modalController); - const model = opts.model; - if (model) { - controller.set("model", model); - } - if (controller.onShow) { - controller.onShow(); - } - controller.set("flashMessage", null); - - return controller; + const modalService = container.lookup("service:modal"); + return modalService.show(name, opts); } diff --git a/app/assets/javascripts/discourse/app/routes/application.js b/app/assets/javascripts/discourse/app/routes/application.js index 8495d9648a6..e162cbaf230 100644 --- a/app/assets/javascripts/discourse/app/routes/application.js +++ b/app/assets/javascripts/discourse/app/routes/application.js @@ -40,6 +40,7 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, { documentTitle: service(), dialog: service(), composer: service(), + modal: service(), actions: { toggleAnonymous() { @@ -152,46 +153,7 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, { // Close the current modal, and destroy its state. closeModal(initiatedBy) { - const route = getOwner(this).lookup("route:application"); - let modalController = route.controllerFor("modal"); - const controllerName = modalController.get("name"); - - if (controllerName) { - const controller = getOwner(this).lookup( - `controller:${controllerName}` - ); - if (controller && controller.beforeClose) { - if (false === controller.beforeClose()) { - return; - } - } - } - - this.render("hide-modal", { into: "modal", outlet: "modalBody" }); - $(".d-modal.fixed-modal").modal("hide"); - - if (controllerName) { - const controller = getOwner(this).lookup( - `controller:${controllerName}` - ); - - if (controller) { - this.appEvents.trigger("modal:closed", { - name: controllerName, - controller, - }); - - if (controller.onClose) { - controller.onClose({ - initiatedByCloseButton: initiatedBy === "initiatedByCloseButton", - initiatedByClickOut: initiatedBy === "initiatedByClickOut", - initiatedByESC: initiatedBy === "initiatedByESC", - }); - } - } - modalController.set("name", null); - } - modalController.hidden = true; + return this.modal.close(initiatedBy); }, /** @@ -200,11 +162,11 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, { user clicks "No", reopenModal. If user clicks "Yes", be sure to call closeModal. **/ hideModal() { - $(".d-modal.fixed-modal").modal("hide"); + return this.modal.hide(); }, reopenModal() { - $(".d-modal.fixed-modal").modal("show"); + return this.modal.reopen(); }, editCategory(category) { diff --git a/app/assets/javascripts/discourse/app/services/modal.js b/app/assets/javascripts/discourse/app/services/modal.js new file mode 100644 index 00000000000..4cf00307f5e --- /dev/null +++ b/app/assets/javascripts/discourse/app/services/modal.js @@ -0,0 +1,137 @@ +import Service, { inject as service } from "@ember/service"; +import { getOwner } from "@ember/application"; +import I18n from "I18n"; +import { dasherize } from "@ember/string"; +import { disableImplicitInjections } from "discourse/lib/implicit-injections"; + +@disableImplicitInjections +export default class ModalService extends Service { + @service appEvents; + + show(name, opts = {}) { + const container = getOwner(this); + const route = container.lookup("route:application"); + const modalController = route.controllerFor("modal"); + + modalController.set( + "modalClass", + opts.modalClass || `${dasherize(name).toLowerCase()}-modal` + ); + + const controllerName = opts.admin ? `modals/${name}` : name; + modalController.set("name", controllerName); + + let controller = container.lookup("controller:" + controllerName); + const templateName = opts.templateName || dasherize(name); + + const renderArgs = { into: "modal", outlet: "modalBody" }; + if (controller) { + renderArgs.controller = controllerName; + } else { + // use a basic controller + renderArgs.controller = "basic-modal-body"; + controller = container.lookup(`controller:${renderArgs.controller}`); + } + + if (opts.addModalBodyView) { + renderArgs.view = "modal-body"; + } + + const modalName = `modal/${templateName}`; + const fullName = opts.admin ? `admin/templates/${modalName}` : modalName; + route.render(fullName, renderArgs); + if (opts.title) { + modalController.set("title", I18n.t(opts.title)); + } else if (opts.titleTranslated) { + modalController.set("title", opts.titleTranslated); + } else { + modalController.set("title", null); + } + + if (opts.titleAriaElementId) { + modalController.set("titleAriaElementId", opts.titleAriaElementId); + } + + if (opts.panels) { + modalController.setProperties({ + panels: opts.panels, + selectedPanel: opts.panels[0], + }); + + if (controller.actions.onSelectPanel) { + modalController.set( + "onSelectPanel", + controller.actions.onSelectPanel.bind(controller) + ); + } + + modalController.set( + "modalClass", + `${modalController.get("modalClass")} has-tabs` + ); + } else { + modalController.setProperties({ panels: [], selectedPanel: null }); + } + + controller.set("modal", modalController); + const model = opts.model; + if (model) { + controller.set("model", model); + } + if (controller.onShow) { + controller.onShow(); + } + controller.set("flashMessage", null); + + return controller; + } + + close(initiatedBy) { + const route = getOwner(this).lookup("route:application"); + let modalController = route.controllerFor("modal"); + const controllerName = modalController.get("name"); + + if (controllerName) { + const controller = getOwner(this).lookup(`controller:${controllerName}`); + if (controller && controller.beforeClose) { + if (false === controller.beforeClose()) { + return; + } + } + } + + getOwner(this) + .lookup("route:application") + .render("hide-modal", { into: "modal", outlet: "modalBody" }); + $(".d-modal.fixed-modal").modal("hide"); + + if (controllerName) { + const controller = getOwner(this).lookup(`controller:${controllerName}`); + + if (controller) { + this.appEvents.trigger("modal:closed", { + name: controllerName, + controller, + }); + + if (controller.onClose) { + controller.onClose({ + initiatedByCloseButton: initiatedBy === "initiatedByCloseButton", + initiatedByClickOut: initiatedBy === "initiatedByClickOut", + initiatedByESC: initiatedBy === "initiatedByESC", + }); + } + } + modalController.set("name", null); + } + modalController.hidden = true; + } + + hide() { + $(".d-modal.fixed-modal").modal("hide"); + } + + reopen() { + $(".d-modal.fixed-modal").modal("show"); + } +}