In JS there are several mechanisms to communicate between different browser windows such as postMessage, localstorage, broadcastchannel etc.
My question is, is there any way to communicate such info to the Python code driving a Panel app?
The scenario is that I have a web application that allows you to design a simulation model, of which the results are plotted in a Panel app. So only the data visualization is in Panel, the editor is a React thing.
Now what I want to be able to do is interactively select what to plot in the editor. So currently I just have a list of things that can be plotted in the Panel app, but this will get unwieldy in more complex designs. So I want to implement a “probe” tool in the editor that allows you to select things to plot.
One way to do it is just write the probed locations to the database along with the model itself, but that seems quite inefficient. If you could just write a few lines of custom JS to communicate between the editor and the Panel backend, that seems much nicer.
It looks like this example is polling the frontend. I was hoping there would be a way to do it in an event driven way.
Maybe if the template registers an event handler that sets data.value I can link that on the Python side?
I’m just trying to do
_template = """<div>
<script>
var ch = new BroadcastChannel("probe")
ch.addEventListener("message", function(msg) {
model.value = msg.data;
})
</script></div>"""
But there are toooooo many levels of magic going on. Like, obviously model is not defined here so I put the code in _scripts instead but it wont just insert the script in my random JS code. Seems like they can only live in stuff like onclick parameters? Same problem with calling Python code, too much magic.
So now I’m like ok what the hell I’ll make a hidden input field and just set its value, so I give it an ID, and then PANEL CHANGES THE ID!! So much for that idea…
class BroadcastChannel(pn.reactive.ReactiveHTML):
value = param.String(default=None, allow_None=True)
_template = """<div>
<input id="hiddenmsg" type="hidden" value="${value}"></input>
<script>
var inp = document.currentScript.previousSibling;
var name = "${name}".trim()
var ch = new BroadcastChannel(name)
ch.addEventListener("message", function(msg) {
inp.value = msg.data;
inp.dispatchEvent(new Event('change'));
})
</script>
</div>"""
_dom_events = {'hiddenmsg': ['change']}
Feels like half of the examples in the docs just don’t work as you’d expect but with some random trial and error this seems to do it. I have NO IDEA what kind of magic is going on here.
But you don’t have to use javascript to create a broadcast channel. You might also just create a Parameterized class you store in the shared pn.state.cache.
import panel as pn
import param
pn.extension(sizing_mode="stretch_width", template="fast")
class BroadCastChannel(param.Parameterized):
value = param.String()
@classmethod
def create_channel(cls, name):
if not name in cls._channels:
cls._channels[name]=BroadCastChannel(name=name)
return cls._channels[name]
@classmethod
@property
def _channels(cls):
if not "channels" in pn.state.cache:
pn.state.cache["channels"]={}
return pn.state.cache["channels"]
channel = BroadCastChannel.create_channel("test")
pn.Param(channel).servable()