Inject a custom Javascript function into the page

I am trying to add a status pane to my Panel web page which should show the status of the Python program which has been starting the server. I am starting the panel server from within my Python program like this:

    server = pn.serve(
        {"/": create_application},
        port=args.web_port,
        address=args.web_host,
        show=not args.headless,
        threaded=False,    # access to the tornado server does not work with threaded=True
        start=False,
        admin=args.debug,
        websocket_origin=[f"localhost:{args.web_port}",f"127.0.0.1:{args.web_port}"],
    )
    class PingHandler(RequestHandler):
        def get(self):
            self.write(b"pong")
    server._tornado.add_handlers(r".*", [(r"/ping", PingHandler)])
    server.start()
    # with server start, the python process does not continue to run, we need to block the main thread
    # and keep the server running
    IOLoop.current().start()

Part of creating the app is this code:

    status_pane = pn.pane.HTML("<div id='status-pane'><b>✅ Server connected</b></div>",
                                styles={"color": "green"},
                                sizing_mode="fixed",
                                name="Server Status")
    JS_MONITOR_CODE = """
    async function pingServer() {
        const statusPane = document.getElementById("status-pane");
        console.log("Status pane element in JS: ", statusPane);

        if (!statusPane) {
            console.error("Status pane element not found, stopping ping.");
            if (window.pingIntervalId) {
                clearInterval(window.pingIntervalId);
            }
            return;
        }

        try {
            const res = await fetch("/ping");
            if (!res.ok) throw new Error("Server responded with an error status.");
            statusPane.innerHTML = "<b>✅ Server connected</b>";
            statusPane.style.color = "green";
        } catch (e) {
            statusPane.innerHTML = "<b>❌ Server disconnected</b>";
            statusPane.style.color = "red";
            console.error("Ping failed:", e);
        }
    }

    // Ensure the interval is started only once
    if (!window.pingIntervalId) {
        window.pingIntervalId = setInterval(pingServer, 2000);
        console.log("JS: Ping interval started.");
        pingServer(); // Initial call
    }
    """

However, I could not find any way to inject that Javscript function into the page in a way that actually makes it work. If I just include the Javascript in a HTML pane, the getElementById never succeeds and always returns null.
I fear I am pretty ignorant when it comes to Javascript so I may be missing something trivial.

Is there a recommended/best way to make this work reliably?

OK, when looking closer at the HTML panel creates, the real issue seems to be that the HTML for the panel showing the status is deeply nested within several shadow roots and the javascript I created assumes the id of the div is directly visible when really it is hidden deeply within the shadow roots. Not sure what the best way to actually deal with this is then.

Maybe take a look at how it was done in Sync scrollbars for wide Tabulators

1 Like

Thanks - yes, in the meantime I came up with exactly the same strategy: use a function that recursively traverses the whole tree to find the element with the id we are looking for and then cache that element in some variable for re-use.
The other crucial issue is that the initial run must happen after Panel has actually created all the elements which happens AFTER the initial loading of the page, so the DOMContentLoaded listener cannot be used: when that one is triggered the element we are looking for has not yet been created by Panel!
Instead I am using requestAnimationFrame(initPinging); to initialize the whole process: keep checking for the element until it is found, every time re-running requestAnimationFrame(…). Once it is found, set the Interval to run the actual polling function.
All this is quite involved for such a simple functionality, but it seems there is no way around this with Panel as Panel does not offer any mechanism to achieve the same in a simpler way.