Skip to content

Commit 72b8a91

Browse files
authored
Merge pull request #2068 from AnnMarieW/dcc-location-callback-nav
dcc location callback nav
2 parents ced1b2e + 6ba4f9d commit 72b8a91

File tree

4 files changed

+70
-4
lines changed

4 files changed

+70
-4
lines changed

Diff for: CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ This project adheres to [Semantic Versioning](https://semver.org/).
66

77
## Added
88

9+
-### Added
10+
11+
- [#2068](https://github.com/plotly/dash/pull/2068) Added `refresh="callback-nav"` in `dcc.Location`. This allows for navigation without refreshing the page when url is updated in a callback.
912
- [#2417](https://github.com/plotly/dash/pull/2417) Add wait_timeout property to customize the behavior of the default wait timeout used for by wait_for_page, fix [#1595](https://github.com/plotly/dash/issues/1595)
1013
- [#2417](https://github.com/plotly/dash/pull/2417) Add the element target text for wait_for_text* error message, fix [#945](https://github.com/plotly/dash/issues/945)
1114
- [#2425](https://github.com/plotly/dash/pull/2425) Add `add_log_handler=True` to Dash init, if you don't want a log stream handler at all.

Diff for: components/dash-core-components/src/components/Location.react.js

+16-3
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default class Location extends Component {
4343
propsToSet[fieldName] = window.location[fieldName];
4444
} else if (propVal !== window.location[fieldName]) {
4545
// Prop has changed?
46-
if (refresh) {
46+
if (refresh === true) {
4747
// Refresh the page?
4848
window.location[fieldName] = propVal;
4949
} else if (this.props[fieldName] !== propVal) {
@@ -71,6 +71,9 @@ export default class Location extends Component {
7171
// Special case -- overrides everything!
7272
if (hrefUpdated) {
7373
window.history.pushState({}, '', href);
74+
if (refresh === 'callback-nav') {
75+
window.dispatchEvent(new CustomEvent('_dashprivate_pushstate'));
76+
}
7477
} else if (pathnameUpdated || hashUpdated || searchUpdated) {
7578
// Otherwise, we can mash everything together
7679
const searchVal = type(search) !== 'Undefined' ? search : '';
@@ -80,6 +83,9 @@ export default class Location extends Component {
8083
'',
8184
`${pathname}${searchVal}${hashVal}`
8285
);
86+
if (refresh === 'callback-nav') {
87+
window.dispatchEvent(new CustomEvent('_dashprivate_pushstate'));
88+
}
8389
}
8490
}
8591

@@ -141,8 +147,15 @@ Location.propTypes = {
141147
/** href in window.location - e.g., "/my/full/pathname?myargument=1#myhash" */
142148
href: PropTypes.string,
143149

144-
/** Refresh the page when the location is updated? */
145-
refresh: PropTypes.bool,
150+
/**
151+
* If True, refresh the page when the location is updated.
152+
* If 'callback-nav' it will navigate to the new page when the location is updated in
153+
* a callback without refreshing the page.
154+
*/
155+
refresh: PropTypes.oneOfType([
156+
PropTypes.oneOf(['callback-nav']),
157+
PropTypes.bool,
158+
]),
146159

147160
/**
148161
* Dash-assigned callback that gets fired when the value changes.

Diff for: components/dash-core-components/tests/integration/location/test_location_callback.py

+50
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,53 @@ def check_path_parts(pathname, search, _hash):
136136
check_path_parts("/test/pathname/a", "?queryA=valueA", "")
137137

138138
assert dash_dcc.get_logs() == []
139+
140+
141+
def test_loca003_location_callback(dash_dcc):
142+
app = Dash(__name__)
143+
app.layout = html.Div(
144+
[
145+
dcc.Location(id="url", refresh=False),
146+
dcc.Location(id="callback-url", refresh="callback-nav"),
147+
html.Button(id="callback-btn", n_clicks=0),
148+
html.Div(id="content"),
149+
]
150+
)
151+
152+
@app.callback(Output("content", "children"), [Input("url", "pathname")])
153+
def display_page(pathname):
154+
if pathname is None or pathname == "/page-1":
155+
return html.Div("1", id="div1")
156+
elif pathname == "/":
157+
return html.Div("base", id="div0")
158+
else:
159+
return "404"
160+
161+
@app.callback(
162+
Output("callback-url", "pathname"),
163+
Input("callback-btn", "n_clicks"),
164+
)
165+
def update_location(n):
166+
if n > 0:
167+
return "/page-1"
168+
169+
dash_dcc.start_server(app)
170+
dash_dcc.driver.execute_script(
171+
"""
172+
window.addEventListener('_dashprivate_pushstate', function() {
173+
window._test_link_event_counter = (window._test_link_event_counter || 0) + 1;
174+
});
175+
176+
window.addEventListener('_dashprivate_historychange', function() {
177+
window._test_history_event_counter = (window._test_history_event_counter || 0) + 1;
178+
});
179+
"""
180+
)
181+
182+
dash_dcc.wait_for_element_by_id("div0")
183+
184+
dash_dcc.find_element("#callback-btn").click()
185+
186+
dash_dcc.wait_for_element_by_id("div1")
187+
188+
assert dash_dcc.get_logs() == []

Diff for: dash/dash.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@
119119
try:
120120
page_container = html.Div(
121121
[
122-
dcc.Location(id=_ID_LOCATION),
122+
dcc.Location(id=_ID_LOCATION, refresh="callback-nav"),
123123
html.Div(id=_ID_CONTENT, disable_n_clicks=True),
124124
dcc.Store(id=_ID_STORE),
125125
html.Div(id=_ID_DUMMY, disable_n_clicks=True),

0 commit comments

Comments
 (0)