InDesign plugin Close event

Subject: Issue with Close Event Handling in React Plugin

Hello Adobe Support Team,

I hope this message finds you well. I am currently working on a React plugin for Adobe InDesign using the UXP framework. While attempting to implement a close event handler for the plugin, I have encountered some challenges that I’d like to bring to your attention.

My objective is to gracefully close the plugin when the user initiates a close action. However, despite implementing the recommended event handling code, I’m unable to achieve the desired behavior. The plugin’s close event doesn’t seem to be firing as expected, preventing me from performing necessary cleanup operations and closing the plugin reliably.

I have thoroughly reviewed the Adobe UXP documentation and followed best practices for event handling, including attaching the afterClose event to the app object. Despite these efforts, the issue persists.

I have also tried alternative approaches, such as using the uxp API to directly close the plugin’s panel. Unfortunately, this approach has not yielded the desired results either.

I am reaching out to you for guidance and assistance in resolving this matter. Could you kindly provide insights, code examples, or any potential workarounds to help me successfully implement the close event handling for my React plugin?

Your assistance is greatly appreciated, as it is crucial for the functionality and user experience of the plugin.

Thank you in advance for your time and support.

@team_creativebuffer I believe by Plugin close, you mean close of the panel(window created for plugin),
before jumping into more details, this is provided on UXP which also requires some implementation from the Indesign, which is not done yet, you can expect this to work in the later releases

Coming into more detail, Developers often confuse the terms plugin and panel in UXP,
What you are referring to above is a Panel and not Plugin,

Plugin creation and destruction are managed by the application i.e. Indesign in this case.
Invoking and closing of the entrypoints like Panel/Commands are based on the user actions

Plugin is created during the App launch and destroyed during the App Quit. UXP does have lifecycle events for these which can be referred here

Panel opening/invocation & closing/destroy are based on the User interaction like opening the panel from the Plugins Panel. Opening/Invocation of Panel entrypoint would result in Panel lifecycle events Create and Show more details refer here

Similarly, Panel closing would result in Hide/Destroy events.

Plugin Events(Create/Destroy) should be working as there is no implementation required from the host, but as mentioned these would come during the launch/Quit of the App respectively.

Panel events would be more on the Panel, like open and close, etc, but these need some work on Indesign and which is not done yet, so these wouldn’t work yet.

3 Likes

@vinayKumarG
can you please help me that what is incorrect here

const _id = Symbol(“_id”);
const _root = Symbol(“_root”);
const _attachment = Symbol(“_attachment”);
const _Component = Symbol(“_Component”);
const _menuItems = Symbol(“_menuItems”);
export class PanelController {

constructor(Component, { id, menuItems } = {}) {
    this[_id] = null;
    this[_root] = null;
    this[_attachment] = null;
    this[_Component] = null;
    this[_menuItems] = [];

    this[_Component] = Component;
    this[_id] = id;
    this[_menuItems] = menuItems || [];
    this.menuItems = this[_menuItems].map(menuItem => ({
        id: menuItem.id,
        label: menuItem.label,
        enabled: menuItem.enabled || true,
        checked: menuItem.checked || false
    }));

    [ "create", "show", "hide", "destroy", "invokeMenu" ].forEach(fn => this[fn] = this[fn].bind(this));
}

create() {
    this[_root] = document.createElement("div");
    this[_root].style.height = "100vh";
    this[_root].style.overflow = "auto";
    this[_root].style.padding = "8px";

    ReactDOM.render(this[_Component]({panel: this}), this[_root]);

    return this[_root];
}

show(event)  {
    if (!this[_root]) this.create();
    this[_attachment] = event;
    this[_attachment].appendChild(this[_root]);
}

hide() {
    if (this[_attachment] && this[_root]) {
        this[_attachment].removeChild(this[_root]);
        this[_attachment] = null;
    }
}

destroy() { 
    entrypoints.plugin.destroy()
}

invokeMenu(id) {
    const menuItem = this[_menuItems].find(c => c.id === id);
    if (menuItem) {
        const handler = menuItem.oninvoke;
        if (handler) {
            handler();
        }
    }
}

}

Also refer this

import React from “react”;

import { PanelController } from “./controllers/PanelController.jsx”;
import { Demos } from “./panels/Demos.jsx”;
import “bootstrap/dist/css/bootstrap.min.css”;
import { entrypoints, MenuItemType } from “uxp”;
const { app } = window.require(“indesign”);

const demosController = new PanelController(() => , {
id: “demos”,
});

entrypoints.setup({
plugin: {
create(plugin) {
/* optional */ console.log(“created”, plugin);

},
destroy() {
  entrypoints.plugin.destroy()
},

},
panels: {
demos: demosController,
},
});

Your usage in the above example is not correct, destroy() { entrypoints.plugin.destroy() },

Below is a sample in vanilla javascript, just ensure that you have a panel entrypoint with the id demoPanel in manifest

const entrypoints = require("uxp").entrypoints;

entrypoints.setup({
    plugin: {
        create() {
            console.log("Plugin: create called");
        },
        destroy() {
            console.log("Plugin: destroy called");
        }
    },
    panels: {
        demoPanel: {
            create(rootNode) {
                console.log("Panel: create called");
                rootNode.appendChild(addDomElement("Create Call"));
            },
            show(rootNode, data) {
                console.log("Panel: show called");
                rootNode.appendChild(addDomElement("show Call"));
            },
            hide(rootNode, data) {
                console.log("Panel: hide called");
                rootNode.appendChild(addDomElement("hide Call"));
            },
            destroy(rootNode) {
                console.log("Panel: destroy called");
                rootNode.appendChild(addDomElement("destroy Call"));
            }
        },
    },
});

function addDomElement(text = "This is a panel") {
    let mainContainer = document.createElement("div");
    mainContainer.className = "test";
    let innerHtml = `<p> ${text} </>`;
    mainContainer.innerHTML = innerHtml;
    return mainContainer;
}

1 Like

also, one another important thing is that, ensure that

entrypoints.setup({})

is the first call from the plugin, don’t call any other function before this

1 Like

I have tried this but not working can you please help with some example in react

These are the code of panel controller

import ReactDOM from “react-dom”;

const _id = Symbol(“_id”);
const _root = Symbol(“_root”);
const _attachment = Symbol(“_attachment”);
const _Component = Symbol(“_Component”);
const _menuItems = Symbol(“_menuItems”);
import { entrypoints, MenuItemType } from “uxp”;

export class PanelController {

constructor(Component, { id, menuItems } = {}) {
    this[_id] = null;
    this[_root] = null;
    this[_attachment] = null;
    this[_Component] = null;
    this[_menuItems] = [];

    this[_Component] = Component;
    this[_id] = id;
    this[_menuItems] = menuItems || [];
    this.menuItems = this[_menuItems].map(menuItem => ({
        id: menuItem.id,
        label: menuItem.label,
        enabled: menuItem.enabled || true,
        checked: menuItem.checked || false
    }));

    [ "create", "show", "hide", "destroy", "invokeMenu" ].forEach(fn => this[fn] = this[fn].bind(this));
}

create() {
    this[_root] = document.createElement("div");
    this[_root].style.height = "100vh";
    this[_root].style.overflow = "auto";
    this[_root].style.padding = "8px";

    ReactDOM.render(this[_Component]({panel: this}), this[_root]);

    return this[_root];
}

show(event)  {
    if (!this[_root]) this.create();
    this[_attachment] = event;
    this[_attachment].appendChild(this[_root]);
}

hide() {
    if (this[_attachment] && this[_root]) {
        this[_attachment].removeChild(this[_root]);
        this[_attachment] = null;
    }
    console.log("Hide");
}

destroy() {
    console.log("Distroyed");
 }

invokeMenu(id) {
    const menuItem = this[_menuItems].find(c => c.id === id);
    if (menuItem) {
        const handler = menuItem.oninvoke;
        if (handler) {
            handler();
        }
    }
}

}

and i have done entrypoints like this So, please check it once
entrypoints.setup({
plugin: {
create(plugin) {
/* optional */ console.log(“created”, plugin);
app.addEventListener(“afterClose”, closePlugin);
// app.addEventListener(“afterOpen”, onDocumentOpen);
},
destroy() {},
},
panels: {
demos: demosController,
},
});

@team_creativebuffer can you give details on what’s not working …?

As mentioned in my initial reply, currently only plugin create/destroy would work as expected and Panel events create/show & hide/destroy would not work completely as expected, partially they work(as there is some implementation pending)

so in the code sample shared above, you have an event listener to invoke closePlugin on afterClose event on the plugin, I hope you have closePlugin defined.

is your console.log(“created”, plugin); not getting executed on the load of the Plugin …?

entrypoints.setup({
    plugin: {
        create(plugin) {
            /* optional */ console.log(“created”, plugin);
            app.addEventListener(“afterClose”, closePlugin);
            // app.addEventListener(“afterOpen”, onDocumentOpen);
        },
        destroy() {},
    },
    panels: {
        demos: demosController,
    },
});

@vinayKumarG
is your console.log(“created”, plugin); not getting executed on the load of the Plugin …? - it logs created- undefined

I have panel constructor like this can you please help me what next to be implemented or give some sample

import ReactDOM from “react-dom”;

const _id = Symbol(“_id”);
const _root = Symbol(“_root”);
const _attachment = Symbol(“_attachment”);
const _Component = Symbol(“_Component”);
const _menuItems = Symbol(“_menuItems”);

export class PanelController {

constructor(Component, { id, menuItems } = {}) {
    this[_id] = null;
    this[_root] = null;
    this[_attachment] = null;
    this[_Component] = null;
    this[_menuItems] = [];

    this[_Component] = Component;
    this[_id] = id;
    this[_menuItems] = menuItems || [];
    this.menuItems = this[_menuItems].map(menuItem => ({
        id: menuItem.id,
        label: menuItem.label,
        enabled: menuItem.enabled || true,
        checked: menuItem.checked || false
    }));

    [ "create", "show", "hide", "destroy", "invokeMenu" ].forEach(fn => this[fn] = this[fn].bind(this));
}

create() {
    this[_root] = document.createElement("div");
    this[_root].style.height = "100vh";
    this[_root].style.overflow = "auto";
    this[_root].style.padding = "8px";

    ReactDOM.render(this[_Component]({panel: this}), this[_root]);

    return this[_root];
}

show(event)  {
    if (!this[_root]) this.create();
    this[_attachment] = event;
    this[_attachment].appendChild(this[_root]);
}

hide() {
    if (this[_attachment] && this[_root]) {
        this[_attachment].removeChild(this[_root]);
        this[_attachment] = null;
    }
    console.log("Hide");
}

destroy() {
    console.log("Distroyed");
 }

invokeMenu(id) {
    const menuItem = this[_menuItems].find(c => c.id === id);
    if (menuItem) {
        const handler = menuItem.oninvoke;
        if (handler) {
            handler();
        }
    }
}

}

@team_creativebuffer can you give details on what you are trying to achieve…?

if you are looking for a way to close the panel (Plugin as you mentioned), it’s not supported yet.

if it is something else, i can try to provide a sample

1 Like

@vinayKumarG
Yes I am looking to close the Panel of the Plugin. Thank you for your Support.

The whole morning I tried to find a way to handle the user closing my panel, but unsuccessful. Panel hide or destroy as given here stills seems to be un-implemented (as of ID 19.4.0).
Also things like document.onvisibilitychange or even document.onvisibilitychange or document.hidden are all undefined.
Can we expect something to deal with the user closing our panels anytime soon?

Yes, I would also really need it, for example to remove EventListeners. I think I once heard that this is being worked on. Hopefully with the next update.

1 Like