Skip to content

Instantly share code, notes, and snippets.

@jtpio
Created January 24, 2018 23:17
Show Gist options
  • Save jtpio/1aeb0d850dcd537a5b244bcf5aeaa75b to your computer and use it in GitHub Desktop.
Save jtpio/1aeb0d850dcd537a5b244bcf5aeaa75b to your computer and use it in GitHub Desktop.
Dash URL State example
import dash
import dash_core_components as dcc
import dash_html_components as html
from urllib.parse import urlparse, parse_qsl, urlencode
from dash.dependencies import Input, Output
app = dash.Dash()
app.config.suppress_callback_exceptions = True
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-layout')
])
def apply_default_value(params):
def wrapper(func):
def apply_value(*args, **kwargs):
if 'id' in kwargs and kwargs['id'] in params:
kwargs['value'] = params[kwargs['id']]
return func(*args, **kwargs)
return apply_value
return wrapper
def build_layout(params):
layout = [
html.H2('URL State demo', id='state'),
apply_default_value(params)(dcc.Dropdown)(
id='dropdown',
options=[{'label': i, 'value': i} for i in ['LA', 'NYC', 'MTL']],
value='LA'
),
apply_default_value(params)(dcc.Input)(
id='input',
placeholder='Enter a value...',
value=''
),
apply_default_value(params)(dcc.Slider)(
id='slider',
min=0,
max=9,
marks={i: 'Label {}'.format(i) for i in range(10)},
value=5,
),
html.Br(),
]
return layout
def parse_state(url):
parse_result = urlparse(url)
params = parse_qsl(parse_result.query)
state = dict(params)
return state
@app.callback(Output('page-layout', 'children'),
inputs=[Input('url', 'href')])
def page_load(href):
if not href:
return []
state = parse_state(href)
return build_layout(state)
component_ids = [
'dropdown',
'input',
'slider'
]
@app.callback(Output('url', 'search'),
inputs=[Input(i, 'value') for i in component_ids])
def update_url_state(*values):
state = urlencode(dict(zip(component_ids, values)))
return f'?{state}'
if __name__ == '__main__':
app.run_server(debug=True)
dash==0.20.0
dash-renderer==0.11.2
dash-html-components==0.8.0
dash-core-components==0.18.0
@GGPay
Copy link

GGPay commented Apr 5, 2018

Hi Jeremy,
Thank you so much for share your solution. It works as a charm. How to make that code works with dcc.DatePickerSingle where is no parameter value? I can't resolve the problem with building the url - it always say ?date=None&dropdown=LA and insert the date into DatePicker field. Any help would be much appreciate it.
Thanks

@jtpio
Copy link
Author

jtpio commented May 2, 2018

Hi @GGPay,

Have you tried using the date parameter for dcc.DatePickerSingle?
Something like this:

Input('date-picker-id', 'date')

For dcc.DatePickerRange, you can use start_date and end_date.

@oegesam
Copy link

oegesam commented Nov 23, 2018

Hi, I changed the code a bit to work with arbitrary attributes (not just 'value'), allow for saving lists of items as well (e.g. for multi dropdowns) and save the whole thing in a base64 encoded json:

(Also nicely saves the particular tab that you're on, when working with tabs)

def apply_default_value(params):
    def wrapper(func):
        def apply_value(*args, **kwargs):
            if 'id' in kwargs and kwargs['id'] in params:
                kwargs[params[kwargs['id']][0]] = params[kwargs['id']][1]
                #kwargs['value'] = params[kwargs['id']][1]
            return func(*args, **kwargs)
        return apply_value
    return wrapper

def parse_state(url):
    parse_result = urlparse(url)
    query_string = parse_qsl(parse_result.query)
    if query_string:
        encoded_state = query_string[0][1]
        state = dict(json.loads(urlsafe_b64decode(encoded_state)))
    else:
        state = dict()
    return state

@app.callback(Output('page-layout', 'children'),
              inputs=[Input('url', 'href')])
def page_load(href):
    if not href:
        return []
    state = parse_state(href)
    return build_layout(state)

component_ids = {
    'tabs' : 'value',
    'tab1_multidropdown' : 'value',
    'tab2_datepicker' : 'start_date'
}

@app.callback(Output('url', 'search'),
              inputs=[Input(id, param) for id, param in component_ids.items()])
def update_url_state(*values):
    state = dict(zip(list(component_ids.keys()), list(zip(component_ids.values(), values))))
    encoded = urlsafe_b64encode(json.dumps(state).encode())
    params = urlencode(dict(params=encoded))
    return f'?{params}'

@eddy-geek
Copy link

eddy-geek commented Mar 22, 2020

The original gist does not work anymore with the stricter type enforcement of recent Dash:

The low-footprint fix I tried is to try to coerce the vale into its existing type (won't work if it's init with None):

def apply_default_value(params):
    def wrapper(func):
        def apply_value(*args, **kwargs):
            if 'id' in kwargs and kwargs['id'] in params:
                hval = kwargs['value']  # hardcoded value
                sval = params[kwargs['id']]  # saved value
                ctor = None if hval is None or isinstance(hval, str) else type(hval)
                kwargs['value'] = ctor(sval) if ctor else sval
            return func(*args, **kwargs)
        return apply_value
    return wrapper

It works but it's brittle (so I moved to another approach: plotly/dash#188 (comment) )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment