How to move / detach single pane from a dashboard into a separate browser window?

Hi there,

I have a panel dashboard, which works fine so far.
Now I’d like to move one of the components into a separate browser window, so I can move it around or maximize it on a 2nd screen. Apart from this changed placement, it should still behave as before, i.e. as a part of the dashboard, communicating with the other parts, etc.

Thanks for any pointers in a useful direction!

This works, but don’t use panel serve to launch. Just run it as a script. The Javascript tied to the open_input_window button might get stopped by a pop-up blocker.

import panel as pn
import param

class AppClass(param.Parameterized):
    text_input = param.String(default="input_string", doc="A string")
    output = pn.pane.Markdown('test')
    open_input_window = param.Event()

    @param.depends('text_input', on_init = True, watch = True)
    def markdown(self):
        self.output.object = self.text_input

app = AppClass()

button =   pn.widgets.Button.from_param(app.param['open_input_window'])

button.jscallback(clicks="""
x = window.open("2_input_window", '_blank', 'left=500,height=400,width=400')
""", args={})

pn.serve({
    '1_start_here': pn.Row(app.output, button),
    '2_input_window': pn.Row(app.param['text_input'])
})
1 Like

Hi @ansgar-t

I have an example implementation below. The key idea is to save the component in pn.state.cache under a unique_id and then use that unique_id when opening in another window.

script.py

import panel as pn
import uuid

pn.extension(template="fast")

def get_object_id():
    return pn.state.session_args.get("id", [b""])[0].decode(encoding="utf8")

def serve_no_object_id_app():
    unique_id = str(uuid.uuid4()) 

    js_pane = pn.pane.HTML(visible=False, height=0, width=0, sizing_mode="fixed", margin=0)
    open_button = pn.widgets.Button(name = "Open the component below in another window", button_type="primary")
    
    component = pn.widgets.TextInput(name="Some component")
    
    @pn.depends(open_button, watch=True)
    def open_new_window(_):
        pn.state.cache[unique_id]=component
        js_pane.object=f"""<script>
window.open("?id={unique_id}", '_blank', 'left=500,height=400,width=400')
</script>
"""
        js_pane.object = ""
    

    pn.Column("# Main Page", open_button, component, js_pane).servable()

def serve_object_id_app(object_id):
    component = pn.state.cache.get(object_id, pn.pane.Markdown("Not found"))
    pn.Column(f"# Page {object_id}", component).servable()

object_id = get_object_id()

if not object_id:
    serve_no_object_id_app()
else:
    serve_object_id_app(object_id)
panel serve script.py

There are some open questions though? How to delete these components. It might not matter for small use cases. But if you are sharing with many users over a longer period of time these objects will take up more and more memory.

3 Likes

Is there some reason the documented pn.serve functionality doesn’t work?

1 Like

Depends on what you want to achieve. The main issue is that all users will share the same app because it’s not instantiated inside a function provided to pn.serve

Ahh. Good point. I didn’t think about that.

1 Like

Thanks, guys! Will try it out soon!