From 9ab9351a430896d787c3cf3f254b0c93aef0d748 Mon Sep 17 00:00:00 2001 From: pipeline Date: Thu, 6 Oct 2022 16:04:16 +0200 Subject: [PATCH 1/4] :sparkles: Custom filter placeholder text for data table --- .../dash-table/components/Filter/Column.tsx | 2 +- .../src/dash-table/components/Table/props.ts | 1 + .../src/dash-table/dash/DataTable.js | 6 ++- .../src/dash-table/dash/Sanitizer.ts | 4 +- .../dash-table/tests/selenium/conftest.py | 7 ++++ .../dash-table/tests/selenium/test_filter.py | 37 +++++++++++++++++++ 6 files changed, 53 insertions(+), 4 deletions(-) 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..d05fcb2f0a 100644 --- a/components/dash-table/src/dash-table/dash/DataTable.js +++ b/components/dash-table/src/dash-table/dash/DataTable.js @@ -319,7 +319,8 @@ export const propTypes = { * the table-level `filter_options` prop for that column. */ filter_options: PropTypes.shape({ - case: PropTypes.oneOf(['sensitive', 'insensitive']) + case: PropTypes.oneOf(['sensitive', 'insensitive']), + placeholder_text: PropTypes.string }), /** @@ -782,7 +783,8 @@ export const propTypes = { * the table-level `filter_options` prop for that column. */ filter_options: PropTypes.shape({ - case: PropTypes.oneOf(['sensitive', 'insensitive']) + case: PropTypes.oneOf(['sensitive', 'insensitive']), + placeholder_text: PropTypes.string }), /** * The `sort_action` property enables data to be diff --git a/components/dash-table/src/dash-table/dash/Sanitizer.ts b/components/dash-table/src/dash-table/dash/Sanitizer.ts index e5a50cb433..d4eacb4606 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..c2af3c54f2 100644 --- a/components/dash-table/tests/selenium/test_filter.py +++ b/components/dash-table/tests/selenium/test_filter.py @@ -301,3 +301,40 @@ 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_filt003_placeholder( + test, column_placeholder_setting, table_placeholder_setting, expected_placeholder +): + props = dict( + id="table", + data=[], + columns=[ + dict( + id="a", + name="a", + filter_options=dict(placeholder_text=column_placeholder_setting) + if column_placeholder_setting is not None + else None, + type="any", + ), + ], + 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 From ccba7e19aeefc00f54de8626360dbf9f1d25ef2e Mon Sep 17 00:00:00 2001 From: pipeline Date: Thu, 6 Oct 2022 17:50:42 +0200 Subject: [PATCH 2/4] :memo: Updated docs --- .../src/dash-table/dash/DataTable.js | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/components/dash-table/src/dash-table/dash/DataTable.js b/components/dash-table/src/dash-table/dash/DataTable.js index d05fcb2f0a..05f1c11a7f 100644 --- a/components/dash-table/src/dash-table/dash/DataTable.js +++ b/components/dash-table/src/dash-table/dash/DataTable.js @@ -1,7 +1,7 @@ import * as R from 'ramda'; -import React, {Component, Suspense} from 'react'; +import React, { Component, Suspense } from 'react'; import PropTypes from 'prop-types'; -import {asyncDecorator} from '@plotly/dash-component-plugins'; +import { asyncDecorator } from '@plotly/dash-component-plugins'; import LazyLoader from 'dash-table/LazyLoader'; @@ -313,13 +313,17 @@ 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({ + /** + * (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 }), @@ -777,15 +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({ + /** + * (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. From 393194abe2325f089d61b2d8ef76ba6baf6d8e11 Mon Sep 17 00:00:00 2001 From: pipeline Date: Fri, 7 Oct 2022 16:32:19 +0200 Subject: [PATCH 3/4] :white_check_mark: Update test and fix linting --- .../src/dash-table/dash/DataTable.js | 4 +- .../src/dash-table/dash/Sanitizer.ts | 2 +- .../dash-table/tests/selenium/test_filter.py | 66 +++++++++++-------- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/components/dash-table/src/dash-table/dash/DataTable.js b/components/dash-table/src/dash-table/dash/DataTable.js index 05f1c11a7f..7be47a8639 100644 --- a/components/dash-table/src/dash-table/dash/DataTable.js +++ b/components/dash-table/src/dash-table/dash/DataTable.js @@ -1,7 +1,7 @@ import * as R from 'ramda'; -import React, { Component, Suspense } from 'react'; +import React, {Component, Suspense} from 'react'; import PropTypes from 'prop-types'; -import { asyncDecorator } from '@plotly/dash-component-plugins'; +import {asyncDecorator} from '@plotly/dash-component-plugins'; import LazyLoader from 'dash-table/LazyLoader'; diff --git a/components/dash-table/src/dash-table/dash/Sanitizer.ts b/components/dash-table/src/dash-table/dash/Sanitizer.ts index d4eacb4606..8ab843dcfa 100644 --- a/components/dash-table/src/dash-table/dash/Sanitizer.ts +++ b/components/dash-table/src/dash-table/dash/Sanitizer.ts @@ -37,7 +37,7 @@ 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_PLACEHOLDER_TEXT = 'filter data...'; const DEFAULT_FILTER_OPTIONS = { case: FilterCase.Sensitive, diff --git a/components/dash-table/tests/selenium/test_filter.py b/components/dash-table/tests/selenium/test_filter.py index c2af3c54f2..ab1de6c69e 100644 --- a/components/dash-table/tests/selenium/test_filter.py +++ b/components/dash-table/tests/selenium/test_filter.py @@ -1,6 +1,7 @@ import dash from dash.dash_table import DataTable +from pyparsing import col import pytest @@ -81,7 +82,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 +92,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 +100,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 +200,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 +210,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 +222,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" @@ -313,9 +323,13 @@ def test_filt003_sensitivity(test, filter_options, column_filter_options): (None, None, "filter data..."), ], ) -def test_filt003_placeholder( +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=[], @@ -323,10 +337,8 @@ def test_filt003_placeholder( dict( id="a", name="a", - filter_options=dict(placeholder_text=column_placeholder_setting) - if column_placeholder_setting is not None - else None, type="any", + filter_options=column_filter_setting, ), ], filter_action="native", From d36bfe382759f7245a7a411735d8637b2f618333 Mon Sep 17 00:00:00 2001 From: pipeline Date: Thu, 13 Oct 2022 11:23:05 +0200 Subject: [PATCH 4/4] :rotating_light: Fix linter error, add changelog entry --- CHANGELOG.md | 6 ++++++ components/dash-table/tests/selenium/test_filter.py | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7db41c1467..6a63f43c3e 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 + +- [#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. + ## [2.6.2] - 2022-09-23 ### Fixed diff --git a/components/dash-table/tests/selenium/test_filter.py b/components/dash-table/tests/selenium/test_filter.py index ab1de6c69e..ac6ec1eb48 100644 --- a/components/dash-table/tests/selenium/test_filter.py +++ b/components/dash-table/tests/selenium/test_filter.py @@ -1,7 +1,6 @@ import dash from dash.dash_table import DataTable -from pyparsing import col import pytest