From 66bb5d362a8f9b668a3796c87fd1856087338f15 Mon Sep 17 00:00:00 2001 From: Chris P Date: Thu, 23 Jul 2020 09:59:19 -0700 Subject: [PATCH 01/17] don't override document.title if set to None This allows users to override `document.title` during runtime with a clientside callback or a special component (https://github.com/plotly/dash-core-components/issues/833) --- dash-renderer/src/components/core/DocumentTitle.react.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/dash-renderer/src/components/core/DocumentTitle.react.js b/dash-renderer/src/components/core/DocumentTitle.react.js index 3bd03aba57..62753b89e4 100644 --- a/dash-renderer/src/components/core/DocumentTitle.react.js +++ b/dash-renderer/src/components/core/DocumentTitle.react.js @@ -7,7 +7,6 @@ class DocumentTitle extends Component { super(props); const {update_title} = props.config; this.state = { - initialTitle: document.title, update_title, }; } @@ -15,8 +14,6 @@ class DocumentTitle extends Component { UNSAFE_componentWillReceiveProps(props) { if (this.state.update_title && props.isLoading) { document.title = this.state.update_title; - } else { - document.title = this.state.initialTitle; } } From 2dcc00a63b847a920f1b4173f84d36f8e813e731 Mon Sep 17 00:00:00 2001 From: Chris Parmer Date: Thu, 23 Jul 2020 18:32:16 -0700 Subject: [PATCH 02/17] add clientside page title test --- .../renderer/test_loading_states.py | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/tests/integration/renderer/test_loading_states.py b/tests/integration/renderer/test_loading_states.py index b946d72e33..d58453382f 100644 --- a/tests/integration/renderer/test_loading_states.py +++ b/tests/integration/renderer/test_loading_states.py @@ -172,15 +172,21 @@ def find_text(spec): @pytest.mark.parametrize( - "kwargs, expected_update_title", + "kwargs, expected_update_title, clientside_title", [ - ({}, "Updating..."), - ({"update_title": None}, "Dash"), - ({"update_title": ""}, "Dash"), - ({"update_title": "Hello World"}, "Hello World"), - ] + ({}, "Updating...", False), + ({"update_title": None}, "Dash", False), + ({"update_title": ""}, "Dash", False), + ({"update_title": "Hello World"}, "Hello World", False), + ({}, "Updating...", "Page 0"), + ({"update_title": None}, "Page 0", True), + ({"update_title": ""}, "Page 0", True), + ({"update_title": "Hello World"}, "Hello World", True), + ], ) -def test_rdls003_update_title(dash_duo, kwargs, expected_update_title): +def test_rdls003_update_title( + dash_duo, kwargs, expected_update_title, clientside_title +): app = dash.Dash("Dash", **kwargs) lock = Lock() @@ -189,13 +195,23 @@ def test_rdls003_update_title(dash_duo, kwargs, expected_update_title): html.H3("Press button see document title updating"), html.Div(id="output"), html.Button("Update", id="button", n_clicks=0), + html.Button("Update Page", id="page", n_clicks=0), + html.Div(id="dummy"), ] ) + if clientside_title: + app.clientside_callback( + """ + function(n_clicks) { + document.title = 'Page ' + n_clicks; + return null; // dummy output + } + """, + Output("dummy", "children"), + [Input("page", "n_clicks")], + ) - @app.callback( - Output("output", "children"), - [Input("button", "n_clicks")] - ) + @app.callback(Output("output", "children"), [Input("button", "n_clicks")]) def update(n): with lock: return n @@ -212,3 +228,10 @@ def update(n): dash_duo.find_element("#button").click() # check for update-title while processing callback until(lambda: dash_duo.driver.title == expected_update_title, timeout=1) + + if kwargs.get("update_page", False) is None and clientside_title: + dash_duo.find_element("#page").click() + until(lambda: dash_duo.driver.title == "Page 1", timeout=1) + dash_duo.find_element("#button").click() + dash_duo.wait_for_text_to_equal("output", "2") + until(lambda: dash_duo.driver.title == "Page 1", timeout=1) From e190cc941b89275677f0b3b995d21d5220e0209b Mon Sep 17 00:00:00 2001 From: Chris P Date: Thu, 23 Jul 2020 19:27:21 -0700 Subject: [PATCH 03/17] fix logic --- .../components/core/DocumentTitle.react.js | 10 +++- .../renderer/test_loading_states.py | 53 ++++++++++++++----- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/dash-renderer/src/components/core/DocumentTitle.react.js b/dash-renderer/src/components/core/DocumentTitle.react.js index 62753b89e4..936c959380 100644 --- a/dash-renderer/src/components/core/DocumentTitle.react.js +++ b/dash-renderer/src/components/core/DocumentTitle.react.js @@ -7,13 +7,19 @@ class DocumentTitle extends Component { super(props); const {update_title} = props.config; this.state = { + title: document.title, update_title, }; } UNSAFE_componentWillReceiveProps(props) { - if (this.state.update_title && props.isLoading) { - document.title = this.state.update_title; + if (props.isLoading) { + this.setState({title: document.title}); + if (this.state.update_title) { + document.title = this.state.update_title; + } + } else { + document.title = this.state.title; } } diff --git a/tests/integration/renderer/test_loading_states.py b/tests/integration/renderer/test_loading_states.py index b946d72e33..b6777aa360 100644 --- a/tests/integration/renderer/test_loading_states.py +++ b/tests/integration/renderer/test_loading_states.py @@ -172,15 +172,21 @@ def find_text(spec): @pytest.mark.parametrize( - "kwargs, expected_update_title", + "kwargs, expected_update_title, clientside_title", [ - ({}, "Updating..."), - ({"update_title": None}, "Dash"), - ({"update_title": ""}, "Dash"), - ({"update_title": "Hello World"}, "Hello World"), - ] + ({}, "Updating...", False), + ({"update_title": None}, "Dash", False), # fail + ({"update_title": ""}, "Dash", False), # fail + ({"update_title": "Hello World"}, "Hello World", False), + ({}, "Updating...", True), # fail + ({"update_title": None}, "Dash", True), # fail + ({"update_title": ""}, "Dash", True), # fail + ({"update_title": "Hello World"}, "Hello World", True), # fail + ], ) -def test_rdls003_update_title(dash_duo, kwargs, expected_update_title): +def test_rdls003_update_title( + dash_duo, kwargs, expected_update_title, clientside_title +): app = dash.Dash("Dash", **kwargs) lock = Lock() @@ -189,13 +195,23 @@ def test_rdls003_update_title(dash_duo, kwargs, expected_update_title): html.H3("Press button see document title updating"), html.Div(id="output"), html.Button("Update", id="button", n_clicks=0), + html.Button("Update Page", id="page", n_clicks=0), + html.Div(id="dummy"), ] ) + if clientside_title: + app.clientside_callback( + """ + function(n_clicks) { + document.title = 'Page ' + n_clicks; + return n_clicks; + } + """, + Output("dummy", "children"), + [Input("page", "n_clicks")], + ) - @app.callback( - Output("output", "children"), - [Input("button", "n_clicks")] - ) + @app.callback(Output("output", "children"), [Input("button", "n_clicks")]) def update(n): with lock: return n @@ -206,9 +222,22 @@ def update(n): until(lambda: dash_duo.driver.title == expected_update_title, timeout=1) # check for original title after loading - until(lambda: dash_duo.driver.title == "Dash", timeout=1) + until(lambda: dash_duo.driver.title == "Page 0" if clientside_title else "Dash", timeout=1) with lock: dash_duo.find_element("#button").click() # check for update-title while processing callback until(lambda: dash_duo.driver.title == expected_update_title, timeout=1) + + dash_duo.find_element("#page").click() + dash_duo.wait_for_text_to_equal("dummy", "1") + if clientside_title: + until(lambda: dash_duo.driver.title == "Page 1", timeout=1) + else: + until(lambda: dash_duo.driver.title == "Dash", timeout=1) + + dash_duo.find_element("#button").click() + if clientside_title: + until(lambda: dash_duo.driver.title == "Page 2", timeout=1) + else: + until(lambda: dash_duo.driver.title == "Dash", timeout=1) From 104510e2dd2e154c2780fa911e26c18f1489b55b Mon Sep 17 00:00:00 2001 From: Chris P Date: Thu, 23 Jul 2020 19:31:14 -0700 Subject: [PATCH 04/17] rm comments --- tests/integration/renderer/test_loading_states.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/integration/renderer/test_loading_states.py b/tests/integration/renderer/test_loading_states.py index b6777aa360..4c19ffee56 100644 --- a/tests/integration/renderer/test_loading_states.py +++ b/tests/integration/renderer/test_loading_states.py @@ -175,13 +175,13 @@ def find_text(spec): "kwargs, expected_update_title, clientside_title", [ ({}, "Updating...", False), - ({"update_title": None}, "Dash", False), # fail - ({"update_title": ""}, "Dash", False), # fail + ({"update_title": None}, "Dash", False), + ({"update_title": ""}, "Dash", False), ({"update_title": "Hello World"}, "Hello World", False), - ({}, "Updating...", True), # fail - ({"update_title": None}, "Dash", True), # fail - ({"update_title": ""}, "Dash", True), # fail - ({"update_title": "Hello World"}, "Hello World", True), # fail + ({}, "Updating...", True), + ({"update_title": None}, "Dash", True), + ({"update_title": ""}, "Dash", True), + ({"update_title": "Hello World"}, "Hello World", True), ], ) def test_rdls003_update_title( From 84266d70c4c503c67644c0412031df469dd95013 Mon Sep 17 00:00:00 2001 From: Chris P Date: Mon, 27 Jul 2020 06:53:55 -0700 Subject: [PATCH 05/17] add title to dash config & docstrings --- dash/dash.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/dash/dash.py b/dash/dash.py index 3bcf626cd5..296e563ebe 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -227,6 +227,15 @@ class Dash(object): with a ``plug`` method, taking a single argument: this app, which will be called after the Flask server is attached. :type plugins: list of objects + + :param title: Default ``Dash``. Configures the document.title + (the text that appears in a browser tab). + + :param update_title: Default ``Updating...``. Configures the document.title + (the text that appears in a browser tab) text when a callback is being run. + Set to None or '' if you don't want the document.title to change or if you + want to control the document.title through a separate component or + clientside callback. """ def __init__( @@ -252,6 +261,7 @@ def __init__( prevent_initial_callbacks=False, show_undo_redo=False, plugins=None, + title='Dash', update_title="Updating...", **obsolete ): @@ -321,6 +331,9 @@ def __init__( "via the Dash constructor" ) + # keep title as a class property for backwards compatability + self.title = title + # list of dependencies - this one is used by the back end for dispatching self.callback_map = {} # same deps as a list to catch duplicate outputs, and to send to the front end @@ -725,7 +738,6 @@ def index(self, *args, **kwargs): # pylint: disable=unused-argument config = self._generate_config_html() metas = self._generate_meta_html() renderer = self._generate_renderer() - title = getattr(self, "title", "Dash") if self._favicon: favicon_mod_time = os.path.getmtime( From 5da53ce1875982078fa8f7983088388281421064 Mon Sep 17 00:00:00 2001 From: Chris P Date: Mon, 27 Jul 2020 06:54:13 -0700 Subject: [PATCH 06/17] update logic as per https://github.com/plotly/dash/pull/1343#discussion_r460071792 --- .../src/components/core/DocumentTitle.react.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dash-renderer/src/components/core/DocumentTitle.react.js b/dash-renderer/src/components/core/DocumentTitle.react.js index 936c959380..bfea61831e 100644 --- a/dash-renderer/src/components/core/DocumentTitle.react.js +++ b/dash-renderer/src/components/core/DocumentTitle.react.js @@ -13,13 +13,21 @@ class DocumentTitle extends Component { } UNSAFE_componentWillReceiveProps(props) { + if (!this.state.update_title) { + // Let callbacks or other components have full control over title + return; + } if (props.isLoading) { this.setState({title: document.title}); if (this.state.update_title) { document.title = this.state.update_title; } } else { - document.title = this.state.title; + if (document.title === this.state.update_title) { + document.title = this.state.title; + } else { + this.setState({title: document.title}); + } } } From 219a50bb7240ba751deea90ef145661825d7366c Mon Sep 17 00:00:00 2001 From: Chris P Date: Mon, 27 Jul 2020 06:54:36 -0700 Subject: [PATCH 07/17] pin isort to fix pylint tests --- requires-dev.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requires-dev.txt b/requires-dev.txt index e27402850d..86dbb809c1 100644 --- a/requires-dev.txt +++ b/requires-dev.txt @@ -1,5 +1,6 @@ dash_flow_example==0.0.5 dash-dangerously-set-inner-html +isort==4.3.21 mock==4.0.1;python_version>="3.0" mock==3.0.5;python_version=="2.7" flake8==3.7.9 @@ -10,4 +11,4 @@ astroid==2.2.5;python_version=="3.7" black==19.10b0;python_version>="3.0" virtualenv==20.0.10;python_version=="2.7" fire==0.2.1 -coloredlogs==14.0 \ No newline at end of file +coloredlogs==14.0 From e2a29119c0e46948b5026acaa339081c1fb41490 Mon Sep 17 00:00:00 2001 From: Chris P Date: Mon, 27 Jul 2020 06:55:01 -0700 Subject: [PATCH 08/17] add chained callback test case --- .../renderer/test_loading_states.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/integration/renderer/test_loading_states.py b/tests/integration/renderer/test_loading_states.py index 4c19ffee56..a0f13a5471 100644 --- a/tests/integration/renderer/test_loading_states.py +++ b/tests/integration/renderer/test_loading_states.py @@ -241,3 +241,59 @@ def update(n): until(lambda: dash_duo.driver.title == "Page 2", timeout=1) else: until(lambda: dash_duo.driver.title == "Dash", timeout=1) + + +@pytest.mark.parametrize( + "update_title", + [ + (None,), + ('Custom Update Title',), + ], +) +def test_rdls004_update_title_chained_callbacks(dash_duo, update_title): + initial_title = 'Initial Title' + app = dash.Dash("Dash", title=initial_title, update_title=update_title) + lock = Lock() + + app.layout = html.Div( + children=[ + html.Button(id="page-title", n_clicks=0), + html.Div(id="page-output"), + html.Div(id="final-output") + ] + ) + if clientside_title: + app.clientside_callback( + """ + function(n_clicks) { + if (n_clicks > 0) { + document.title = 'Page ' + n_clicks; + } + return n_clicks; + } + """, + Output("page-output", "children"), + [Input("page-title", "n_clicks")], + ) + + @app.callback( + Output("final-output", "children"), + [Input("page-output", "children")]) + def update(n): + with lock: + return n + + # check for original title after loading + dash_duo.wait_for_text_to_equal("final-output", "0") + until(lambda: dash_duo.driver.title == initial_title, timeout=1) + + with lock: + dash_duo.find_element("#page-title").click() + # check for update-title while processing the serverside callback + if update_title: + until(lambda: dash_duo.driver.title == update_title, timeout=1) + else: + until(lambda: dash_duo.driver.title == 'Page 1', timeout=1) + + dash_duo.wait_for_text_to_equal("final-output", "1") + until(lambda: dash_duo.driver.title == 'Page 1', timeout=1) From 8e80ad9df32ce3a2e6e18463a5e41b9f8113026a Mon Sep 17 00:00:00 2001 From: Chris P Date: Mon, 27 Jul 2020 06:58:46 -0700 Subject: [PATCH 09/17] get title from config --- dash/dash.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dash/dash.py b/dash/dash.py index 296e563ebe..ba6b0c841d 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -738,6 +738,7 @@ def index(self, *args, **kwargs): # pylint: disable=unused-argument config = self._generate_config_html() metas = self._generate_meta_html() renderer = self._generate_renderer() + title = self.config.title if self._favicon: favicon_mod_time = os.path.getmtime( From 5f36766ec4bdcbb6e00c25915fd49b42a5dd8d53 Mon Sep 17 00:00:00 2001 From: Chris P Date: Mon, 27 Jul 2020 07:01:21 -0700 Subject: [PATCH 10/17] threading things through --- dash/dash.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dash/dash.py b/dash/dash.py index ba6b0c841d..b7f43fb8c2 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -310,6 +310,7 @@ def __init__( ), prevent_initial_callbacks=prevent_initial_callbacks, show_undo_redo=show_undo_redo, + title=title, update_title=update_title, ) self.config.set_read_only( From 864b1fbf629c93b75b23d4ca60e8839ae8803d2b Mon Sep 17 00:00:00 2001 From: Chris P Date: Mon, 27 Jul 2020 07:05:26 -0700 Subject: [PATCH 11/17] rm undefined variable --- .../renderer/test_loading_states.py | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/integration/renderer/test_loading_states.py b/tests/integration/renderer/test_loading_states.py index a0f13a5471..7723bf6044 100644 --- a/tests/integration/renderer/test_loading_states.py +++ b/tests/integration/renderer/test_loading_states.py @@ -262,19 +262,18 @@ def test_rdls004_update_title_chained_callbacks(dash_duo, update_title): html.Div(id="final-output") ] ) - if clientside_title: - app.clientside_callback( - """ - function(n_clicks) { - if (n_clicks > 0) { - document.title = 'Page ' + n_clicks; - } - return n_clicks; + app.clientside_callback( + """ + function(n_clicks) { + if (n_clicks > 0) { + document.title = 'Page ' + n_clicks; } - """, - Output("page-output", "children"), - [Input("page-title", "n_clicks")], - ) + return n_clicks; + } + """, + Output("page-output", "children"), + [Input("page-title", "n_clicks")], + ) @app.callback( Output("final-output", "children"), From 09014a4eecd4f61d12e33419e6da2e6b4256cbfd Mon Sep 17 00:00:00 2001 From: Chris P Date: Mon, 27 Jul 2020 07:10:12 -0700 Subject: [PATCH 12/17] id selector --- tests/integration/renderer/test_loading_states.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/renderer/test_loading_states.py b/tests/integration/renderer/test_loading_states.py index 7723bf6044..52c1e276f6 100644 --- a/tests/integration/renderer/test_loading_states.py +++ b/tests/integration/renderer/test_loading_states.py @@ -230,7 +230,7 @@ def update(n): until(lambda: dash_duo.driver.title == expected_update_title, timeout=1) dash_duo.find_element("#page").click() - dash_duo.wait_for_text_to_equal("dummy", "1") + dash_duo.wait_for_text_to_equal("#dummy", "1") if clientside_title: until(lambda: dash_duo.driver.title == "Page 1", timeout=1) else: @@ -283,7 +283,7 @@ def update(n): return n # check for original title after loading - dash_duo.wait_for_text_to_equal("final-output", "0") + dash_duo.wait_for_text_to_equal("#final-output", "0") until(lambda: dash_duo.driver.title == initial_title, timeout=1) with lock: @@ -294,5 +294,5 @@ def update(n): else: until(lambda: dash_duo.driver.title == 'Page 1', timeout=1) - dash_duo.wait_for_text_to_equal("final-output", "1") + dash_duo.wait_for_text_to_equal("#final-output", "1") until(lambda: dash_duo.driver.title == 'Page 1', timeout=1) From 3a61fd498796a4acb15210b95f6a642f4704a486 Mon Sep 17 00:00:00 2001 From: Chris P Date: Mon, 27 Jul 2020 07:21:34 -0700 Subject: [PATCH 13/17] fix a couple tests --- tests/integration/renderer/test_loading_states.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/integration/renderer/test_loading_states.py b/tests/integration/renderer/test_loading_states.py index 52c1e276f6..1be05ca1da 100644 --- a/tests/integration/renderer/test_loading_states.py +++ b/tests/integration/renderer/test_loading_states.py @@ -204,7 +204,7 @@ def test_rdls003_update_title( """ function(n_clicks) { document.title = 'Page ' + n_clicks; - return n_clicks; + return 'Page ' + n_clicks; } """, Output("dummy", "children"), @@ -229,16 +229,16 @@ def update(n): # check for update-title while processing callback until(lambda: dash_duo.driver.title == expected_update_title, timeout=1) - dash_duo.find_element("#page").click() - dash_duo.wait_for_text_to_equal("#dummy", "1") if clientside_title: + dash_duo.find_element("#page").click() + dash_duo.wait_for_text_to_equal("#dummy", "Page 1") until(lambda: dash_duo.driver.title == "Page 1", timeout=1) - else: - until(lambda: dash_duo.driver.title == "Dash", timeout=1) + # verify that when a separate callback runs, the page title gets restored dash_duo.find_element("#button").click() + dash_duo.wait_for_text_to_equal("#output", "2") if clientside_title: - until(lambda: dash_duo.driver.title == "Page 2", timeout=1) + until(lambda: dash_duo.driver.title == "Page 1", timeout=1) else: until(lambda: dash_duo.driver.title == "Dash", timeout=1) From 710269d85360bb363b4a85cf1d293414a579ef47 Mon Sep 17 00:00:00 2001 From: Chris P Date: Mon, 27 Jul 2020 08:05:22 -0700 Subject: [PATCH 14/17] fix tests --- .../integration/renderer/test_loading_states.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/integration/renderer/test_loading_states.py b/tests/integration/renderer/test_loading_states.py index 1be05ca1da..3f3fb4dab5 100644 --- a/tests/integration/renderer/test_loading_states.py +++ b/tests/integration/renderer/test_loading_states.py @@ -219,7 +219,9 @@ def update(n): with lock: dash_duo.start_server(app) # check for update-title during startup - until(lambda: dash_duo.driver.title == expected_update_title, timeout=1) + # the clientside callback isn't blocking so it may update the title + if not clientside_title: + until(lambda: dash_duo.driver.title == expected_update_title, timeout=1) # check for original title after loading until(lambda: dash_duo.driver.title == "Page 0" if clientside_title else "Dash", timeout=1) @@ -227,7 +229,10 @@ def update(n): with lock: dash_duo.find_element("#button").click() # check for update-title while processing callback - until(lambda: dash_duo.driver.title == expected_update_title, timeout=1) + if clientside_title and not kwargs.get('update_title', True): + until(lambda: dash_duo.driver.title == 'Page 0', timeout=1) + else: + until(lambda: dash_duo.driver.title == expected_update_title, timeout=1) if clientside_title: dash_duo.find_element("#page").click() @@ -246,8 +251,8 @@ def update(n): @pytest.mark.parametrize( "update_title", [ - (None,), - ('Custom Update Title',), + None, + 'Custom Update Title', ], ) def test_rdls004_update_title_chained_callbacks(dash_duo, update_title): @@ -257,7 +262,7 @@ def test_rdls004_update_title_chained_callbacks(dash_duo, update_title): app.layout = html.Div( children=[ - html.Button(id="page-title", n_clicks=0), + html.Button(id="page-title", n_clicks=0, children='Page Title'), html.Div(id="page-output"), html.Div(id="final-output") ] @@ -283,6 +288,7 @@ def update(n): return n # check for original title after loading + dash_duo.start_server(app) dash_duo.wait_for_text_to_equal("#final-output", "0") until(lambda: dash_duo.driver.title == initial_title, timeout=1) From 057dca74695543db95f642bd4e9aa2c2ca4ef161 Mon Sep 17 00:00:00 2001 From: Chris P Date: Mon, 27 Jul 2020 08:06:48 -0700 Subject: [PATCH 15/17] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ada9aeee03..612ad09039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## Unreleased ### Added +- [#1343](https://github.com/plotly/dash/pull/1343) Add `title` parameter to set the +document title. This is the recommended alternative to setting app.title or overriding +the index HTML. - [#1315](https://github.com/plotly/dash/pull/1315) Add `update_title` parameter to set or disable the "Updating...." document title during updates. Closes [#856](https://github.com/plotly/dash/issues/856) and [#732](https://github.com/plotly/dash/issues/732) ## [1.13.4] - 2020-06-25 From 91a4aa52dc54610b7b42d2f3510ab3928ffa0a07 Mon Sep 17 00:00:00 2001 From: Chris P Date: Mon, 27 Jul 2020 08:22:49 -0700 Subject: [PATCH 16/17] keep app.title backwards compatibility and add test --- dash/dash.py | 4 +++- tests/unit/test_configs.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/dash/dash.py b/dash/dash.py index b7f43fb8c2..70df787d20 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -739,7 +739,9 @@ def index(self, *args, **kwargs): # pylint: disable=unused-argument config = self._generate_config_html() metas = self._generate_meta_html() renderer = self._generate_renderer() - title = self.config.title + + # use self.title instead of app.config.title for backwards compatibility + title = self.title if self._favicon: favicon_mod_time = os.path.getmtime( diff --git a/tests/unit/test_configs.py b/tests/unit/test_configs.py index 9a7c8b5c01..a507efd57e 100644 --- a/tests/unit/test_configs.py +++ b/tests/unit/test_configs.py @@ -314,3 +314,13 @@ def test_proxy_failure(mocker, empty_environ): ) assert "port: 8055 is incompatible with the proxy" in excinfo.exconly() assert "you must use port: 8155" in excinfo.exconly() + + +def test_title(): + app = Dash() + assert 'Dash' in app.index() + app = Dash() + app.title = 'Hello World' + assert 'Hello World' in app.index() + app = Dash(title='Custom Title') + assert 'Custom Title' in app.index() From d587495b67e23dcefbca78d0c4f5ca8b221df069 Mon Sep 17 00:00:00 2001 From: Chris P Date: Mon, 27 Jul 2020 08:26:32 -0700 Subject: [PATCH 17/17] trim trailing whitespace --- dash/dash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/dash.py b/dash/dash.py index 70df787d20..0785bafe7f 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -741,7 +741,7 @@ def index(self, *args, **kwargs): # pylint: disable=unused-argument renderer = self._generate_renderer() # use self.title instead of app.config.title for backwards compatibility - title = self.title + title = self.title if self._favicon: favicon_mod_time = os.path.getmtime(