-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Save state in url #188
Comments
It's not possible right now, but this would be a great feature. IIRC, this function that I wrote last year gets most of the way there: Once the state is in the URL, you could probably restore the state in a callback with the new query paramater support in the |
The way I understand it, callbacks are executed exclusively in the browser. Meaning, the page needs to be "reloaded" in order to get back to that state. So that when one first opens the page, the content isn't there yet. Regarding that it would be nice if the backend could access the query. If that's not possible, how could we trigger the callback? |
@chriddyp Do you have any thoughts on reloading the page right at the start given a JSON-dumped store? Client-sideThe Server-sideAnother (perhaps terrible/infeasible) idea is to intercept the default layout and update all of the relevant pieces of state. e.g., if you knew that the 'values' in a slider were
|
Sketch of a working solution that uses a backend database to store the redux store in this gist. We're currently using this as of this morning to allow for PDF printing of the current page on an external puppeteer service based on https://github.com/alvarcarto/url-to-pdf-api. Storing the state purely in the URL does not work for a relatively large app, so we had to set up a database to hold the store and then use an id to retrieve it.
$.post('/my/dash/post/endpoint/', {store: serialize(window.store.getState())});` This endpoint could either take the
def serve_layout(self):
external_store_id = request.cookies.get('externalStoreId', None)
if external_store_id is None: # We're good, just do the default layout
return super().serve_layout()
else:
def _conn_str_pg() -> str:
return 'my postgresql connection string here'
store_data = None
# SQL injection checks should go here
# store_data is a PostgreSQL JSON field
query_str = "SELECT store_data FROM my_store_table WHERE store_id='{store_id}';".format(store_id=external_store_id)
with psycopg2.connect(_conn_str_pg()) as conn:
with conn.cursor() as curs:
try:
curs.execute(query_str)
except Exception as e:
print('ERROR ON', query_str)
conn.rollback()
raise e
res = curs.fetchone()
store_data = res[0]
assert isinstance(store_data, dict), 'store must be dictionary!'
if store_data is not None:
# Update the layout with the values in store_data instead of the initial layout
layout = self._update_layout_value(
layout=self._layout_value(),
store=store_data)
else:
raise ValueError('store data not found for {} {}'.format(APP_NAME, external_store_id))
# Return the updated layout response
return flask.Response(
json.dumps(layout, cls=plotly.utils.PlotlyJSONEncoder), mimetype='application/json')
def _update_layout_value(layout, store: dict):
assert isinstance(store, dict)
for k, v in store.items():
try:
component_id, component_prop = k.split('.')
except ValueError as e:
raise ValueError(k, e)
try:
my_object = layout[component_id]
except KeyError:
print(component_id, 'not found') # For when component ids are not yet in the layout
continue
setattr(my_object, component_prop, v)
return layout
# Add a cookie when the user hits `?restoreFromExternalStore` that makes `_dash-layout` serve the right layout
@app.server.after_request
def update_store_cookie(response):
# If we are requesting an external store id
if request.args.get('restoreFromExternalStore', None) is not None:
response.set_cookie('externalStoreId', request.args.get('restoreFromExternalStore'))
# If we have gotten the layout, let's wipe the unnecessary store id
if request.path == '{}_dash-layout'.format(app.config['routes_pathname_prefix']):
response.set_cookie('externalStoreId', '', expires=0)
return response For the print operation (with additional credential steps excluded), the flow can be:
This feels well beyond what Dash should be doing. It's also probably not great to use the cookies for the But:
|
I had another thought, what about: (frontend) writing the state in the url of all inputs when an input is changed? advantages:
cons:
I guess the implementation should be fairly simple as only the initialisation and the change of values of an input need to be modified (and some details ofc). This change is probably also downwards compatible. |
We have the same need (being able to share states via URL between people), and decided to try an implementation while staying in Python land. Here is a gist that shows how to save the value of the components as query string parameters:
You will notice a little trick with the Location component to avoid infinite callback loop:
I also noticed that the Only primitive values where taken into account, such as numbers or strings. For more complex values like lists we would need to recreate the corresponding Python object using for example Another solution is to encode the state as a json string, base64 encode it, and put the base64 string as a query string parameter, which also has the benefit of keeping the URL shorter: import json
from base64 import b64encode
encoded = b64encode(json.dumps({'dropdown': ['MTL', 'NYC', 'SF'], 'slider': 2}).encode()) When reading the URL, use Also to generate the callback that updates the URL, we can either iterate through Here is a little demo of the flow: In the end it could work as a simple workaround for small use cases. Again as said above it would be a great feature to have built-in Dash itself! |
We are also looking for this feature for collaboration purposes. I am eager to push for dash at my firm and this could be a massive selling point if i can get one of these hacks to work, even better if its on the official Dash roadmap! |
I'd love to see this on the roadmap! |
@jtpio Thank you so much, your gist helped me greatly. I have one suggestion related to the following:
If you have a list of strings as one of your incoming url parameter's then the value from parse_qsl will look something like
|
I implemented the base64+json trick, so if someone is also in need of setting arbitrary attributes or assigning lists (such in multi item dropdowns): (also works nicely with tabs btw)
|
I am trying to extend @oegesam's implementation for element where we might be interested in multiple Inputs. For example I am failing at the very stupid step of writing the " Here a working example, where I cheat by just using the first element of the list in "
That's the end result I'd like to be using:
|
I have slightly updated the update_url_state method to work with the DatePickerRange:
|
How would this be possible if you had multiple apps? I have 0.0.0.0:8000/dash1 and 0.0.0.0:8000/dash2. I can get it to work for one dash but not for the other. I tried including that code in both dash1 script and dash2 script but I run into error saying that duplicate callbacks aren't allowed. So I tried including it in index.py and changed component_ids.items() to dash1.component_ids.items()+dash2.component_ids.items() in the inputs for the callback, but this didn't work either. Does anyone else have any idea? |
Here's my take on saving state to the URL, "v4" in this thread: https://gist.github.com/eddy-geek/73c8f73c089b0f998a49541b15a694b1 TL;DR: Encoded URL looks like: ?picker::start_date="2019-03-12"&picker::end_date="2019-03-19"&dropdown="LA"&input="foo"&slider=6 ...which is displayed properly by FF and Chrome (Chrome refuses to display single-quotes). Background: I wanted the benefits of @Thiebout's aproach (support DatePickerRange, lists, etc.) but still wanted the URL as readable as @jtpio. Using literal_eval as @sjtrny mentioned. Sample URLs to compare the different approaches:
I'm not sure this kind of approach is solid enough for inclusion "as-is" in Dash core:
. |
Here is my solution based on @eddy-geek. This link: https://gist.github.com/fzyzcjy/0322eebd54d4889b03e0c3ea9fd9e965 Features:
|
…ash-4.17.21 Bump lodash from 4.17.19 to 4.17.21
…17.21 Bump lodash from 4.17.19 to 4.17.21
Hi - this issue has been sitting for a while, so as part of our effort to tidy up our public repositories I'm going to close it. If it's still a concern, we'd be grateful if you could open a new issue (with a short reproducible example if appropriate) so that we can add it to our stack. Cheers - @gvwilson |
Is there a way to save the state in the url? So that I can bookmark or share certain states?
Btw, f*cking nice framework! Love it!
The text was updated successfully, but these errors were encountered: