diff --git a/CHANGELOG.md b/CHANGELOG.md index bacc69fb2b..e87327bba5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). -## UNRELEASED +## Unreleased + +### Added + +- [#2261](https://github.com/plotly/dash/pull/2261) Added new `placeholder_text` property to `filterOptions` for DataTable which allows overriding the default filter field placeholder. ### Fixed diff --git a/components/dash-table/src/dash-table/components/Filter/Column.tsx b/components/dash-table/src/dash-table/components/Filter/Column.tsx index 10b7dc09c8..d7dac1cca9 100644 --- a/components/dash-table/src/dash-table/components/Filter/Column.tsx +++ b/components/dash-table/src/dash-table/components/Filter/Column.tsx @@ -70,7 +70,7 @@ export default class ColumnFilter extends PureComponent< e.stopPropagation(); }} value={value} - placeholder={'filter data...'} + placeholder={filterOptions.placeholder_text} stopPropagation={true} submit={this.submit} /> diff --git a/components/dash-table/src/dash-table/components/Table/props.ts b/components/dash-table/src/dash-table/components/Table/props.ts index 93e48b6821..8ef35a09ac 100644 --- a/components/dash-table/src/dash-table/components/Table/props.ts +++ b/components/dash-table/src/dash-table/components/Table/props.ts @@ -56,6 +56,7 @@ export enum TableAction { export interface IFilterOptions { case?: FilterCase; + placeholder_text?: string; } export interface IDerivedData { diff --git a/components/dash-table/src/dash-table/dash/DataTable.js b/components/dash-table/src/dash-table/dash/DataTable.js index beca21fc1d..7be47a8639 100644 --- a/components/dash-table/src/dash-table/dash/DataTable.js +++ b/components/dash-table/src/dash-table/dash/DataTable.js @@ -313,13 +313,18 @@ export const propTypes = { * There are two `filter_options` props in the table. * This is the column-level filter_options prop and there is * also the table-level `filter_options` prop. - * These props determine whether the applicable filter relational - * operators will default to `sensitive` or `insensitive` comparison. * If the column-level `filter_options` prop is set it overrides * the table-level `filter_options` prop for that column. */ filter_options: PropTypes.shape({ - case: PropTypes.oneOf(['sensitive', 'insensitive']) + /** + * (default: 'sensitive') Determine whether the applicable filter relational operators will default to `sensitive` or `insensitive` comparison. + */ + case: PropTypes.oneOf(['sensitive', 'insensitive']), + /** + * (default: 'filter data...') The filter cell placeholder text. + */ + placeholder_text: PropTypes.string }), /** @@ -776,14 +781,20 @@ export const propTypes = { * There are two `filter_options` props in the table. * This is the table-level filter_options prop and there is * also the column-level `filter_options` prop. - * These props determine whether the applicable filter relational - * operators will default to `sensitive` or `insensitive` comparison. * If the column-level `filter_options` prop is set it overrides * the table-level `filter_options` prop for that column. */ filter_options: PropTypes.shape({ - case: PropTypes.oneOf(['sensitive', 'insensitive']) + /** + * (default: 'sensitive') Determine whether the applicable filter relational operators will default to `sensitive` or `insensitive` comparison. + */ + case: PropTypes.oneOf(['sensitive', 'insensitive']), + /** + * (default: 'filter data...') The filter cell placeholder text. + */ + placeholder_text: PropTypes.string }), + /** * The `sort_action` property enables data to be * sorted on a per-column basis. diff --git a/components/dash-table/src/dash-table/dash/Sanitizer.ts b/components/dash-table/src/dash-table/dash/Sanitizer.ts index e5a50cb433..8ab843dcfa 100644 --- a/components/dash-table/src/dash-table/dash/Sanitizer.ts +++ b/components/dash-table/src/dash-table/dash/Sanitizer.ts @@ -37,9 +37,11 @@ const D3_DEFAULT_LOCALE: INumberLocale = { const DEFAULT_NULLY = ''; const DEFAULT_SPECIFIER = ''; const NULL_SELECTED_CELLS: SelectedCells = []; +const DEFAULT_FILTER_PLACEHOLDER_TEXT = 'filter data...'; const DEFAULT_FILTER_OPTIONS = { - case: FilterCase.Sensitive + case: FilterCase.Sensitive, + placeholder_text: DEFAULT_FILTER_PLACEHOLDER_TEXT }; const data2number = (data?: any) => +data || 0; diff --git a/components/dash-table/tests/selenium/conftest.py b/components/dash-table/tests/selenium/conftest.py index a171c681cc..9b017d3713 100644 --- a/components/dash-table/tests/selenium/conftest.py +++ b/components/dash-table/tests/selenium/conftest.py @@ -297,6 +297,13 @@ def filter_value(self, value=None): self.filter_clear() self.mixin.driver.switch_to.active_element.send_keys(value + Keys.ENTER) + def filter_placeholder(self): + return ( + self.filter() + .find_element(By.CSS_SELECTOR, "input") + .get_attribute("placeholder") + ) + class DataTableRowFacade(object): @preconditions(_validate_id, _validate_mixin, _validate_row, _validate_state) diff --git a/components/dash-table/tests/selenium/test_filter.py b/components/dash-table/tests/selenium/test_filter.py index 4f1f579e3f..ac6ec1eb48 100644 --- a/components/dash-table/tests/selenium/test_filter.py +++ b/components/dash-table/tests/selenium/test_filter.py @@ -81,7 +81,7 @@ def test_filt001_basic(test, props, expect): @pytest.mark.parametrize( - "filter_options,column_filter_options", + "filter_case_options,column_case_filter_options", [ ("sensitive", None), ("sensitive", None), @@ -91,7 +91,7 @@ def test_filt001_basic(test, props, expect): ("insensitive", "sensitive"), ], ) -def test_filt002_sensitivity(test, filter_options, column_filter_options): +def test_filt002_sensitivity(test, filter_case_options, column_case_filter_options): props = dict( id="table", data=[dict(a="abc", b="abc", c="abc"), dict(a="ABC", b="ABC", c="ABC")], @@ -99,37 +99,39 @@ def test_filt002_sensitivity(test, filter_options, column_filter_options): dict( id="a", name="a", - filter_options=dict(case=column_filter_options) - if column_filter_options is not None + filter_options=dict(case=column_case_filter_options) + if column_case_filter_options is not None else None, type="any", ), dict( id="b", name="b", - filter_options=dict(case=column_filter_options) - if column_filter_options is not None + filter_options=dict(case=column_case_filter_options) + if column_case_filter_options is not None else None, type="text", ), dict( id="c", name="c", - filter_options=dict(case=column_filter_options) - if column_filter_options is not None + filter_options=dict(case=column_case_filter_options) + if column_case_filter_options is not None else None, type="numeric", ), ], filter_action="native", - filter_options=dict(case=filter_options) - if filter_options is not None + filter_options=dict(case=filter_case_options) + if filter_case_options is not None else None, style_cell=dict(width=100, min_width=100, max_width=100), ) sensitivity = ( - filter_options if column_filter_options is None else column_filter_options + filter_case_options + if column_case_filter_options is None + else column_case_filter_options ) test.start_server(get_app(props)) @@ -197,7 +199,7 @@ def test_filt002_sensitivity(test, filter_options, column_filter_options): @pytest.mark.parametrize( - "filter_options,column_filter_options", + "filter_case_options,column_case_filter_options", [ ("sensitive", None), ("sensitive", None), @@ -207,7 +209,11 @@ def test_filt002_sensitivity(test, filter_options, column_filter_options): ("insensitive", "sensitive"), ], ) -def test_filt003_sensitivity(test, filter_options, column_filter_options): +def test_filt003_sensitivity(test, filter_case_options, column_case_filter_options): + column_b_filter_option = dict(placeholder_text="some descriptive text") + if column_case_filter_options is not None: + column_b_filter_option["case"] = column_case_filter_options + props = dict( id="table", data=[dict(a="abc", b="abc", c="abc"), dict(a="ABC", b="ABC", c="ABC")], @@ -215,43 +221,46 @@ def test_filt003_sensitivity(test, filter_options, column_filter_options): dict( id="a", name="a", - filter_options=dict(case=column_filter_options) - if column_filter_options is not None + filter_options=dict(case=column_case_filter_options) + if column_case_filter_options is not None else None, type="any", ), dict( id="b", name="b", - filter_options=dict(case=column_filter_options) - if column_filter_options is not None - else None, + filter_options=column_b_filter_option, type="text", ), dict( id="c", name="c", - filter_options=dict(case=column_filter_options) - if column_filter_options is not None + filter_options=dict(case=column_case_filter_options) + if column_case_filter_options is not None else None, type="numeric", ), ], filter_action="native", - filter_options=dict(case=filter_options) - if filter_options is not None + filter_options=dict(case=filter_case_options) + if filter_case_options is not None else None, style_cell=dict(width=100, min_width=100, max_width=100), ) sensitivity = ( - filter_options if column_filter_options is None else column_filter_options + filter_case_options + if column_case_filter_options is None + else column_case_filter_options ) test.start_server(get_app(props)) target = test.table("table") + target.column("a").filter_placeholder() == "filter data..." + target.column("b").filter_placeholder() == "some descriptive text" + target.column("a").filter_value("contains A") if sensitivity == "sensitive": assert target.cell(0, "a").get_text() == "ABC" @@ -301,3 +310,42 @@ def test_filt003_sensitivity(test, filter_options, column_filter_options): else: assert target.cell(0, "c").get_text() == "abc" assert target.cell(1, "c").get_text() == "ABC" + + +@pytest.mark.parametrize( + "column_placeholder_setting,table_placeholder_setting,expected_placeholder", + [ + ("abc", None, "abc"), + (None, "def", "def"), + ("gah", "ijk", "gah"), + ("", None, ""), + (None, None, "filter data..."), + ], +) +def test_filt004_placeholder( + test, column_placeholder_setting, table_placeholder_setting, expected_placeholder +): + column_filter_setting = dict(case="sensitive") + if column_placeholder_setting is not None: + column_filter_setting["placeholder_text"] = column_placeholder_setting + + props = dict( + id="table", + data=[], + columns=[ + dict( + id="a", + name="a", + type="any", + filter_options=column_filter_setting, + ), + ], + filter_action="native", + filter_options=dict(placeholder_text=table_placeholder_setting) + if table_placeholder_setting is not None + else None, + ) + + test.start_server(get_app(props)) + target = test.table("table") + assert target.column("a").filter_placeholder() == expected_placeholder