From 453b77559d29b32a157a78bdcc55e4abdb82ff4a Mon Sep 17 00:00:00 2001 From: AnnMarueW Date: Sun, 1 Sep 2024 11:28:17 -0700 Subject: [PATCH 1/2] fix for #291 --- pr-examples/pr-291.py | 130 +++++++++++++++++++++++++++++++++++++++ src/ts/utils/combobox.ts | 31 +++++++--- 2 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 pr-examples/pr-291.py diff --git a/pr-examples/pr-291.py b/pr-examples/pr-291.py new file mode 100644 index 00000000..25ad0eb6 --- /dev/null +++ b/pr-examples/pr-291.py @@ -0,0 +1,130 @@ +import dash_mantine_components as dmc +import dash +from dash import dcc, Input, Output, callback + + +dash._dash_renderer._set_react_version("18.2.0") +app = dash.Dash(__name__, prevent_initial_callbacks=True) + +all_types = {L: [f"{L}{i}" for i in range(5)] for L in ["A", "B", "C", "D"]} + +all_types_label_value = { + L: [{"label": f"Label {L}{i}", "value": f"{L}{i}"} for i in range(5)] + for L in ["A", "B", "C", "D"] +} + + +all_types_list = [f"{L}{i}" for L in ["A", "B", "C", "D"] for i in range(5)] + + +app.layout = dmc.MantineProvider( + dmc.Container( + [ + dcc.Markdown( + """ + In Issue #291 the values were not updated correctly when data is updated in a callback. + + This PR verifies that #291 is fixed for all valid formats of the `data` prop + + The `data` in dropdowns can be either: + + 1. an array of strings - use when label and value are same. + 2. an array of dicts with label and value properties. + 3. an array of dict with group and items as keys where items are one of the previous two types. + + """ + ), + dmc.Text("STEP 1", my=10), + dmc.MultiSelect( + w="47%", + searchable=True, + hidePickedOptions=True, + clearable=True, + label="Select Category", + id="n_search_in_category", + data=["A", "B", "C", "D"], + value=[], + ), + dmc.Text("STEP 2 Verify that all the dropdowns below update the values correctly regardless of the format of the `data`.", my=20), + dmc.MultiSelect( + w="47%", + searchable=True, + hidePickedOptions=True, + clearable=True, + label="1. Data is array of strings", + id="n_search_in_types", + value=[], + mb=60, + ), + dmc.MultiSelect( + w="47%", + searchable=True, + hidePickedOptions=True, + clearable=True, + label="2. Data is dict with labels and values", + id="n_search_in_types_dict", + value=[], + mb=60, + ), + dmc.MultiSelect( + w="47%", + searchable=True, + hidePickedOptions=True, + clearable=True, + label="3a. Data is dict with group and items keys, where items is array of strings", + id="n_search_in_types_groups", + value=[], + mb=60, + ), + dmc.MultiSelect( + w="47%", + searchable=True, + hidePickedOptions=True, + clearable=True, + label="3b. Data is dict with group and items keys, where items is a dict with labels and values", + id="n_search_in_types_groups_3b", + value=[], + mb=60, + ), + ], mb=200 + ) +) + + +# 1. Data is array of strings +@callback( + Output("n_search_in_types", "data"), + Input("n_search_in_category", "value"), +) +def add_types_in_search(category_id): + return [i for i in all_types_list if i[0] in category_id] + + +# 2. Data is dict with labels and values +@callback( + Output("n_search_in_types_dict", "data"), + Input("n_search_in_category", "value"), +) +def add_types_in_search(category_id): + return [item for L in category_id for item in all_types_label_value[L]] + + +# 3a. Data is dict with group and items keys, where items are an array +@callback( + Output("n_search_in_types_groups", "data"), Input("n_search_in_category", "value") +) +def add_types_in_search(category_id): + return [{"group": L, "items": all_types[L]} for L in category_id] + + +# 3b. Data is dict with group and items keys, where items is a dict with label and value +@callback( + Output("n_search_in_types_groups_3b", "data"), + Input("n_search_in_category", "value"), +) +def add_types_in_search(category_id): + return [{"group": L, "items": all_types_label_value[L]} for L in category_id] + + +if __name__ == "__main__": + app.run(debug=True) diff --git a/src/ts/utils/combobox.ts b/src/ts/utils/combobox.ts index dede8ead..b72fdb50 100644 --- a/src/ts/utils/combobox.ts +++ b/src/ts/utils/combobox.ts @@ -1,16 +1,33 @@ export const filterSelected = (options, values) => { if (options.length === 0 || values.length === 0) return []; - if (typeof options[0] === "string") { - return values.filter((value) => options.includes(value)); - } else if (typeof options[0] === "object") { - const optionValues = options.map((option) => option.value); - return values.filter((value) => optionValues.includes(value)); - } + const extractValues = (optionList) => { + let extractedValues = []; - return []; + for (const option of optionList) { + if (typeof option === "string") { + // Case 1: option is a string + extractedValues.push(option); + } else if ('value' in option && 'label' in option) { + // Case 2: option is an object with label and value + extractedValues.push(option.value); + } else if ('group' in option && 'items' in option) { + // Case 3: option is a group with nested items, recursively extract values + extractedValues = extractedValues.concat(extractValues(option.items)); + } + } + + return extractedValues; + }; + + // Extract all valid option values + const optionValues = extractValues(options); + + // Return filtered values based on extracted option values + return values.filter((value) => optionValues.includes(value)); }; + export const isInOption = (options, value) => { if (options.length === 0) return false; From 41ad0c704fd96d37ba2409b98a78631b6dd2dd44 Mon Sep 17 00:00:00 2001 From: AnnMarueW Date: Fri, 6 Sep 2024 01:20:24 -0700 Subject: [PATCH 2/2] closes #304 checks for null value --- pr-examples/pr-291.py | 18 +++++++++++++----- src/ts/utils/combobox.ts | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pr-examples/pr-291.py b/pr-examples/pr-291.py index 25ad0eb6..017314b2 100644 --- a/pr-examples/pr-291.py +++ b/pr-examples/pr-291.py @@ -43,7 +43,7 @@ label="Select Category", id="n_search_in_category", data=["A", "B", "C", "D"], - value=[], + # value=[], Checking for value is none closes PR ), dmc.Text("STEP 2 Verify that all the dropdowns below update the values correctly regardless of the format of the `data`.", my=20), dmc.MultiSelect( @@ -53,7 +53,7 @@ clearable=True, label="1. Data is array of strings", id="n_search_in_types", - value=[], + # value=[], mb=60, ), dmc.MultiSelect( @@ -63,7 +63,7 @@ clearable=True, label="2. Data is dict with labels and values", id="n_search_in_types_dict", - value=[], + # value=[], mb=60, ), dmc.MultiSelect( @@ -73,7 +73,7 @@ clearable=True, label="3a. Data is dict with group and items keys, where items is array of strings", id="n_search_in_types_groups", - value=[], + # value=[], mb=60, ), dmc.MultiSelect( @@ -83,7 +83,7 @@ clearable=True, label="3b. Data is dict with group and items keys, where items is a dict with labels and values", id="n_search_in_types_groups_3b", - value=[], + # value=[], mb=60, ), ], mb=200 @@ -97,6 +97,8 @@ Input("n_search_in_category", "value"), ) def add_types_in_search(category_id): + if category_id is None: + return dash.no_update return [i for i in all_types_list if i[0] in category_id] @@ -106,6 +108,8 @@ def add_types_in_search(category_id): Input("n_search_in_category", "value"), ) def add_types_in_search(category_id): + if category_id is None: + return dash.no_update return [item for L in category_id for item in all_types_label_value[L]] @@ -114,6 +118,8 @@ def add_types_in_search(category_id): Output("n_search_in_types_groups", "data"), Input("n_search_in_category", "value") ) def add_types_in_search(category_id): + if category_id is None: + return dash.no_update return [{"group": L, "items": all_types[L]} for L in category_id] @@ -123,6 +129,8 @@ def add_types_in_search(category_id): Input("n_search_in_category", "value"), ) def add_types_in_search(category_id): + if category_id is None: + return dash.no_update return [{"group": L, "items": all_types_label_value[L]} for L in category_id] diff --git a/src/ts/utils/combobox.ts b/src/ts/utils/combobox.ts index b72fdb50..7078c07a 100644 --- a/src/ts/utils/combobox.ts +++ b/src/ts/utils/combobox.ts @@ -1,5 +1,5 @@ export const filterSelected = (options, values) => { - if (options.length === 0 || values.length === 0) return []; + if (!options || !values || options.length === 0 || values.length === 0) return []; const extractValues = (optionList) => { let extractedValues = [];