Sync widgets with URL does not respect browser url go back/forward
Hi,
I am trying to update my widgets with the url search query params but seems like using the pn.state.location.sync does not work when I press the go back or forward arrows in the browser url tab.
Minimal reproducer:
widget = pn.widgets.FloatSlider(name='Slider', start=0, end=10)
widget2 = pn.widgets.TextInput(name='Text')
widget3 = pn.widgets.RangeSlider(name='RangeSlider', start=0, end=10)
if pn.state.location:
pn.state.location.sync(widget, {'value': 'slider_value'})
pn.state.location.sync(widget2, {'value': 'text_value'})
pn.state.location.sync(widget3, {'value': 'range_value'})
pn.Column(widget, widget2, widget3).servable()
Can you suggest some solution here or add this support?
I am using the latest panel 1.5.x version
Hi, any updates here?
I am able to reproduce this as well.
Reposting the solution here - thanks to @pierrotsmnrd for coming up with it originally:
The difficulty of this issue is that when the value of a synced widget changes, the GET parameter in the URL for the widget is changed, which triggers inevitably a new entry in the browser's navigation history. The only solution is to interact with the browser's navigation history to result in a more user-friendly solution.
Here is a solution that works, but unfortunately only available for Chrome and Edge :
import panel as pn widget = pn.widgets.FloatSlider(name='Slider', start=0, end=10) widget2 = pn.widgets.TextInput(name='Text') widget3 = pn.widgets.RangeSlider(name='RangeSlider', start=0, end=10) if pn.state.location: pn.state.location.sync(widget, {'value': 'slider_value'}) pn.state.location.sync(widget2, {'value': 'text_value'}) pn.state.location.sync(widget3, {'value': 'range_value'}) fix = pn.pane.HTML(""" <script> const navigationEntryOnLoad = window.navigation.currentEntry; window.navigation.addEventListener("navigate", e => { const isBackward = event.navigationType === 'traverse' && event.destination.index < window.navigation.currentEntry.index; if (isBackward) { // Find the index of navigationEntryOnLoad const indexOnLoad = window.navigation.entries().findIndex(entry => entry.id === navigationEntryOnLoad.id); // Check if there is a previous entry before the current one if (indexOnLoad > 0) { const firstPreviousEntry = window.navigation.entries()[indexOnLoad -1]; window.navigation.traverseTo(firstPreviousEntry.key); return; } } }); </script> """) pn.Column(widget, widget2, widget3, fix).servable() # panel serve --autoreload script.pyThis is not an ideal solution, and only works to go back in the navigation history - going forward results in iterating through each change in the GET parameters.
@philippjfr Not sure if there's something else we can do here, but I'd be grateful for your opinion about this if you have a minute; if this is the best solution for now, maybe we can close this out?
Reposting a different solution from @dalthviz which should work cross-platform:
fix = pn.pane.HTML(""" <script> window.addEventListener('popstate', function(event) { window.location.replace(window.location.href); }); </script> """)So updating the example above you would have:
import panel as pn widget = pn.widgets.FloatSlider(name='Slider', start=0, end=10) widget2 = pn.widgets.TextInput(name='Text') widget3 = pn.widgets.RangeSlider(name='RangeSlider', start=0, end=10) if pn.state.location: pn.state.location.sync(widget, {'value': 'slider_value'}) pn.state.location.sync(widget2, {'value': 'text_value'}) pn.state.location.sync(widget3, {'value': 'range_value'}) fix = pn.pane.HTML(""" <script> window.addEventListener('popstate', function(event) { window.location.replace(window.location.href); }); </script> """) pn.Column(widget, widget2, widget3, fix).servable()Checking that on Firefox I can get the following behavior:
From the docs, it seems like most of the browser should support the popstate usage but probably testing the specific browser used by users that still are reporting the issue could be useful 🤔
