From 3532997f15db12697d0757ef627508e5a8acd995 Mon Sep 17 00:00:00 2001 From: AnnMarieW Date: Sat, 18 Nov 2023 08:47:54 -0700 Subject: [PATCH 1/7] Added triggered_id to clientside callbacks --- dash/dash-renderer/src/actions/callbacks.ts | 13 ++++++ .../clientside/assets/clientside.js | 6 +++ .../integration/clientside/test_clientside.py | 42 +++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/dash/dash-renderer/src/actions/callbacks.ts b/dash/dash-renderer/src/actions/callbacks.ts index d936d6f915..2d91ba7956 100644 --- a/dash/dash-renderer/src/actions/callbacks.ts +++ b/dash/dash-renderer/src/actions/callbacks.ts @@ -262,6 +262,9 @@ async function handleClientside( prop_id: prop_id, value: inputDict[prop_id] })); + dc.callback_context.triggered_id = getTriggeredId( + payload.changedPropIds + ); dc.callback_context.inputs_list = inputs; dc.callback_context.inputs = inputDict; dc.callback_context.states_list = state; @@ -576,6 +579,16 @@ function inputsToDict(inputs_list: any) { return inputs; } +function getTriggeredId(triggered: string[]): string | object { + // for regular callbacks, takes the first triggered prop_id, e.g. "btn.n_clicks" and returns "btn" + // for pattern matching callback, e.g. '{"index":0, "type":"btn"}' and returns {index:0, type: "btn"}' + let componentId = triggered[0].split('.')[0]; + if (componentId.startsWith('{')) { + componentId = JSON.parse(componentId); + } + return componentId; +} + export function executeCallback( cb: IPrioritizedCallback, config: any, diff --git a/tests/integration/clientside/assets/clientside.js b/tests/integration/clientside/assets/clientside.js index 6f851d1f6b..c84e369da6 100644 --- a/tests/integration/clientside/assets/clientside.js +++ b/tests/integration/clientside/assets/clientside.js @@ -63,6 +63,12 @@ window.dash_clientside.clientside = { return triggered.map(t => `${t.prop_id} = ${t.value}`).join(', '); }, + triggered_id_to_str: function(n_clicks0, n_clicks1) { + const triggered = dash_clientside.callback_context.triggered_id; + const triggered_id = typeof triggered === "string" ? triggered : triggered.btn1 + return triggered_id + }, + inputs_to_str: function(n_clicks0, n_clicks1) { const inputs = dash_clientside.callback_context.inputs; const keys = Object.keys(inputs); diff --git a/tests/integration/clientside/test_clientside.py b/tests/integration/clientside/test_clientside.py index 150da399e4..3ce38ecfda 100644 --- a/tests/integration/clientside/test_clientside.py +++ b/tests/integration/clientside/test_clientside.py @@ -829,3 +829,45 @@ def test_clsd019_clientside_inline_promise(dash_duo): dash_duo.start_server(app) dash_duo.wait_for_text_to_equal("#output-div", "initial-inline") + + +def test_clsd020_clientside_callback_context_triggered_id(dash_duo): + app = Dash(__name__, assets_folder="assets") + + app.layout = html.Div( + [ + html.Button("btn0", id="btn0"), + html.Button("btn1:0", id={"btn1": 0}), + html.Button("btn1:1", id={"btn1": 1}), + html.Button("btn1:2", id={"btn1": 2}), + html.Div(id="output-clientside", style={"font-family": "monospace"}), + ] + ) + + app.clientside_callback( + ClientsideFunction(namespace="clientside", function_name="triggered_id_to_str"), + Output("output-clientside", "children"), + [Input("btn0", "n_clicks"), Input({"btn1": ALL}, "n_clicks")], + ) + + dash_duo.start_server(app) + + dash_duo.wait_for_text_to_equal("#output-clientside", "") + + dash_duo.find_element("#btn0").click() + + dash_duo.wait_for_text_to_equal( + "#output-clientside", + "btn0", + ) + + dash_duo.find_element("button[id*='btn1\":0']").click() + + dash_duo.wait_for_text_to_equal("#output-clientside", '0') + + dash_duo.find_element("button[id*='btn1\":2']").click() + + dash_duo.wait_for_text_to_equal( + "#output-clientside", + '2' + ) From 250e1b56236e3c43f187dc2c85eb4179ee60a508 Mon Sep 17 00:00:00 2001 From: AnnMarieW Date: Sat, 18 Nov 2023 09:04:03 -0700 Subject: [PATCH 2/7] added changelog for 2695 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2b1ca20df..089b5398c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). + +## [UNRELEASED] +### Added +- [#2695](https://github.com/plotly/dash/pull/2695) Adds `triggered_id` to `dash_clientside.callback_context`. fixes [#2692](https://github.com/plotly/dash/issues/2692) + + ## [2.14.1] - 2023-10-26 ## Fixed From 2521ee815da265cbdd4d8314d1223474acce0deb Mon Sep 17 00:00:00 2001 From: AnnMarieW Date: Sat, 18 Nov 2023 09:37:29 -0700 Subject: [PATCH 3/7] lint --- tests/integration/clientside/test_clientside.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/integration/clientside/test_clientside.py b/tests/integration/clientside/test_clientside.py index 3ce38ecfda..405acf0268 100644 --- a/tests/integration/clientside/test_clientside.py +++ b/tests/integration/clientside/test_clientside.py @@ -863,11 +863,8 @@ def test_clsd020_clientside_callback_context_triggered_id(dash_duo): dash_duo.find_element("button[id*='btn1\":0']").click() - dash_duo.wait_for_text_to_equal("#output-clientside", '0') + dash_duo.wait_for_text_to_equal("#output-clientside", "0") dash_duo.find_element("button[id*='btn1\":2']").click() - dash_duo.wait_for_text_to_equal( - "#output-clientside", - '2' - ) + dash_duo.wait_for_text_to_equal("#output-clientside", "2") From e53291be2f6f4919330388cdb68a7bbd3cc05152 Mon Sep 17 00:00:00 2001 From: AnnMarieW Date: Sat, 18 Nov 2023 13:07:47 -0700 Subject: [PATCH 4/7] fix for tests --- dash/dash-renderer/src/actions/callbacks.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dash/dash-renderer/src/actions/callbacks.ts b/dash/dash-renderer/src/actions/callbacks.ts index 2d91ba7956..1dd7e05d58 100644 --- a/dash/dash-renderer/src/actions/callbacks.ts +++ b/dash/dash-renderer/src/actions/callbacks.ts @@ -582,11 +582,13 @@ function inputsToDict(inputs_list: any) { function getTriggeredId(triggered: string[]): string | object { // for regular callbacks, takes the first triggered prop_id, e.g. "btn.n_clicks" and returns "btn" // for pattern matching callback, e.g. '{"index":0, "type":"btn"}' and returns {index:0, type: "btn"}' - let componentId = triggered[0].split('.')[0]; - if (componentId.startsWith('{')) { - componentId = JSON.parse(componentId); + if (triggered && triggered.length) { + let componentId = triggered[0].split('.')[0]; + if (componentId.startsWith('{')) { + componentId = JSON.parse(componentId); + } + return componentId; } - return componentId; } export function executeCallback( From 81da7bfbf9a729a8dd23fdf77cbf1ab5160b1e0b Mon Sep 17 00:00:00 2001 From: AnnMarieW Date: Sat, 18 Nov 2023 13:45:02 -0700 Subject: [PATCH 5/7] rerun tests --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 089b5398c1..e9d719448c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## [UNRELEASED] ### Added -- [#2695](https://github.com/plotly/dash/pull/2695) Adds `triggered_id` to `dash_clientside.callback_context`. fixes [#2692](https://github.com/plotly/dash/issues/2692) +- [#2695](https://github.com/plotly/dash/pull/2695) Adds `triggered_id` to `dash_clientside.callback_context`. Fixes [#2692](https://github.com/plotly/dash/issues/2692) ## [2.14.1] - 2023-10-26 From 09fb52b5a40b87ed7f00328e93f0ec76e54c5f2a Mon Sep 17 00:00:00 2001 From: Ann Marie Ward <72614349+AnnMarieW@users.noreply.github.com> Date: Wed, 22 Nov 2023 05:52:48 -0800 Subject: [PATCH 6/7] Update callbacks.ts --- dash/dash-renderer/src/actions/callbacks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/dash-renderer/src/actions/callbacks.ts b/dash/dash-renderer/src/actions/callbacks.ts index 1dd7e05d58..3390869dee 100644 --- a/dash/dash-renderer/src/actions/callbacks.ts +++ b/dash/dash-renderer/src/actions/callbacks.ts @@ -579,7 +579,7 @@ function inputsToDict(inputs_list: any) { return inputs; } -function getTriggeredId(triggered: string[]): string | object { +function getTriggeredId(triggered: string[]): string | object | undefined { // for regular callbacks, takes the first triggered prop_id, e.g. "btn.n_clicks" and returns "btn" // for pattern matching callback, e.g. '{"index":0, "type":"btn"}' and returns {index:0, type: "btn"}' if (triggered && triggered.length) { From 81fd84811de89a944435c4c17617a90a148a6de6 Mon Sep 17 00:00:00 2001 From: Ann Marie Ward <72614349+AnnMarieW@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:31:55 -0800 Subject: [PATCH 7/7] rerun tests --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a5d6f1848..f987977a8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). ## [UNRELEASED] + ### Added - [#2695](https://github.com/plotly/dash/pull/2695) Adds `triggered_id` to `dash_clientside.callback_context`. Fixes [#2692](https://github.com/plotly/dash/issues/2692)